# Ruby SDK

Install the fiscalrail gem and use its Ruby API client, response models and tax helpers.

The official [`fiscalrail` gem](https://rubygems.org/gems/fiscalrail) supports all 42 operations in the [API reference](/en/api/introduction). Version `0.4.0` requires Ruby 3.3 or later and works with or without Rails. [Source code and releases](https://github.com/fiscalrail/fiscalrail-ruby) are available on GitHub.

## Install and authenticate

Add the gem to your `Gemfile` and run `bundle install`:

```ruby
gem "fiscalrail", "~> 0.4.0"
```

Create a client with an explicit API key:

```ruby
require "fiscalrail"

client = FiscalRail::Client.new(api_key: ENV.fetch("FISCALRAIL_API_KEY"))
```

Your application reads the key from its environment or secret manager. The SDK does not read it automatically. Test and Live use the same base URL; the key selects the account environment.

Reuse the client for the lifetime of your application process and call `client.close` on shutdown. Its default HTTP adapter reuses a connection and serializes concurrent requests. Use separate clients when you need concurrent HTTP requests. `FiscalRail::Client.open(api_key: key) { |client| ... }` closes the connection when the block finishes. Custom `adapter:` instances remain owned by your application.

## Issue an invoice

Use `BigDecimal` or strings for monetary input and Spain's tax helpers for catalog-backed taxes:

```ruby
require "fiscalrail"

client = FiscalRail::Client.new(api_key: ENV.fetch("FISCALRAIL_API_KEY"))

invoice = client.invoices.issue(
  idempotency_key: "2f294ef2-9a60-4c7e-a573-5e18fa8348e2",
  lines: [{ description: "Consulting", quantity: 2, unit_price: BigDecimal("75.00"),
            taxes: [FiscalRail::TaxRegimes::ES::VAT.general] }]
)
```

Requests accept keyword arguments and nested hashes with symbol or string keys. Omitted fields stay omitted; explicit `nil`, `false` and empty arrays are preserved. Dates and times are serialized as ISO 8601 strings. Business validation stays on the API.

## Response models

Responses are generated, read-only objects under `FiscalRail::Models`. Dates become `Date`, timestamps become `Time` and decimal strings become `BigDecimal`:

```ruby
invoice.id
invoice.issue_date
invoice.totals.payable
invoice.request_id
invoice.idempotency_key
invoice.idempotent_replayed
invoice.to_h
```

Nested values are frozen. Unknown fields remain available through `extra_fields` and `response["field"]`; `to_h` returns fresh containers with string keys. Invalid response structure raises `FiscalRail::ResponseParseError` with the field path and request ID.

## Idempotency and retries

Invoice `issue` and `amend` generate an idempotency key when none is supplied. Retries within that call reuse it. For durable jobs, persist your own `idempotency_key:` before the first attempt and supply it on every retry after a process restart. A new call without a supplied key generates a new key. Never reuse a key for a different operation or payload.

The default is at most two retries for connection failures, timeouts, HTTP `408`, `429` and `5xx`, only for safe operations: reads, idempotency-protected issuance and amendments, PDF rendering, and destination enable/disable. Ordinary create, update and delete calls are not automatically retried. The client honors `Retry-After`, bounded to 30 seconds, and otherwise uses exponential backoff. Set `max_retries: 0` to disable retries.

## Errors

All SDK errors inherit from `FiscalRail::Error`. API errors expose `status_code`, `code`, `request_id`, `details` and `idempotency_key`:

```ruby
begin
  invoice = client.invoices.retrieve("inv_...")
rescue FiscalRail::APIConnectionError => error
  warn error.message
rescue FiscalRail::APIError => error
  warn "#{error.code}: HTTP #{error.status_code}, request #{error.request_id}"
end
```

Connection and timeout errors also preserve the idempotency key. Keep it when the result of an issuance or amendment is uncertain.

## Available resources

The client exposes `account`, `account_tax_regimes`, `balances`, `api_keys`, `customers`, `event_destinations`, `events`, `invoice_series`, `payment_instructions`, `invoices`, `invoice_pdfs`, `tax_ids` and `tax_regimes`. Each operation in the API reference includes a Ruby example. Invoices use `issue` and `amend`; issued invoices are never updated.

The singleton account routes require an SDK release generated from the current OpenAPI contract. Existing released SDK versions still use account IDs.

Retrieve account-specific state with `client.balances.retrieve()` and `client.account_tax_regimes.retrieve()`. The separate `tax_regimes` resource describes the public catalog.

## Pagination

`list` retrieves one page. Use `auto_paging_each` to fetch subsequent pages lazily while preserving filters:

```ruby
page = client.customers.list(country: "ES", limit: 25)
page.each { |customer| puts customer.name }

client.invoices.auto_paging_each(customer: "cus_...", page_size: 100) do |invoice|
  puts invoice.code
end
```

Pages expose `data` and `has_more`. For manual cursors, pass `starting_after:` or `ending_before:` to `list`. Tax regimes return a single list and do not expose automatic pagination.

## Invoice PDFs

`render` and `retrieve` return PDF metadata, including a customer-facing URL. To download the bytes directly:

```ruby
client.invoice_pdfs.render_content(invoice.id, locale: "en").write_to_file("invoice.pdf")
```

The `_content` methods return `FiscalRail::BinaryContent`. Downloads are buffered in memory. `locale:` becomes the `Accept-Language` header.

## Verify webhooks

Verify the unmodified body before processing an event. In a Rails controller:

```ruby
event = FiscalRail::Webhooks.construct_event(
  request.raw_post,
  request.headers["FiscalRail-Signature"],
  signing_secret
)
```

The helper verifies the HMAC in constant time, rejects timestamps more than five minutes in either direction and then parses the event into a hash with string keys. Verification failure raises `FiscalRail::WebhookSignatureError`. See [webhooks](/en/webhooks) for delivery and acknowledgement behavior.
