Skip to main content
Without an idempotency key, a network failure or process crash mid-call leaves the outcome ambiguous. The number may have been reserved, the authorization request may have reached ARCA, or the response may have been lost in transit. Retrying blindly in this situation risks issuing the same invoice twice — two authorized vouchers for the same sale, both permanent in ARCA’s records. Idempotency keys solve this. Pair an idempotencyKey with a durable store, and every call to issue() writes the reservation before it ever contacts ARCA. On retry, issue() finds the existing reservation and consults the already-reserved number instead of requesting a new one. The sale gets exactly one invoice, regardless of how many times the code runs.
Use idempotency keys and a durable store in every production application. They are optional for initial exploration but should be the default from your first real deployment onward.

How it works

1

First call: reserve and authorize

issue() reads the next available voucher number, writes a reservation entry to your store (bound to the idempotency key), and then sends the authorization request to ARCA. Even if the process crashes after the reservation is written but before ARCA responds, the number is preserved.
2

Retry: look up the reservation

On the next call with the same key and the same input, issue() finds the stored reservation and consults the already-reserved number. It never reads a new number or sends a duplicate authorization request.
3

Outcome returned

The retry returns the same outcome shape (authorized, rejected, indeterminate, or conflict) based on what ARCA says about the reserved number. Your code can handle it identically to the first call.

Postgres example

Key rules

string
required
A stable, business-level identifier for the operation. Use the sale ID, order ID, or another identifier that is already unique in your system and stays the same across retries.
  • Use your existing business IDs. sale.id, order.id, payment.id — identifiers you already have and that remain constant for the lifetime of the operation.
  • Never use a fresh UUID per attempt. A new UUID on every retry defeats the purpose: each attempt looks like a new operation.
  • Never include PII. Do not put CUIT, DNI, email addresses, or any personal data in a key. Keys are stored in your persistence layer and may appear in logs.
  • Keep keys 1–255 characters long. Longer values throw ArcaInputError before any I/O.
  • Keys are scoped to CUIT + environment. The same key string in production and in the sandbox is safe — they do not collide.
  • Prefix by operation type when the same business object generates multiple documents. For example, use nc:${devolucion.id} for a credit note so it doesn’t collide with the original invoice’s key.

Changing the input for an existing key

If you call issue() with the same idempotency key but a different input, the SDK throws ARCA_INPUT_IDEMPOTENCY_MISMATCH before any network I/O. This is intentional: a key is permanently bound to its input once the first call writes the reservation. The only correct retry is with the identical input.

Using a key without a store

Passing an idempotencyKey without configuring a store on the client throws ArcaInputError before any I/O with ARCA. The store is not optional when keys are in use.

Available stores

Postgres

Production-ready. Pass any query function compatible with pg. Works with Vercel Postgres, Supabase, Neon, and plain node-postgres.

Redis

Production-ready. Works with ioredis and the redis npm package via the sendCommand adapter.

File

Durable on a single machine. Useful for small deployments and local testing against real ARCA credentials.

Memory

In-process only. Does not survive restarts. Use for unit tests and local exploration — never in production.
See Guides → Stores for setup instructions, schema migrations, and how to implement a custom store.

Consulting a reservation without issuing

recover(key, options) looks up an existing reservation and consults ARCA about the stored voucher number — without authorizing or reserving anything new.
If ARCA reports the reserved number as empty, recover() returns indeterminate with lookup.kind === "not_found". It does not authorize the voucher. To authorize, call issue() with the same key and the identical input. If no reservation exists for the key, recover() throws ArcaInputError.
recover() accepts representedTaxId, forceRefresh, and include as options, matching the same options available on issue().