API
Request fiscal numbering
Gets the fiscal identifiers the point of sale needs to print the receipt. It is synchronous: you call it after charging and before injecting the order.
POST
Fire resolves internally which fiscal provider matches the store’s country, asks it for the
numbering, and hands you the data ready to print.
Today the gateway numbers in Ecuador and Colombia. Every country that comes in adds its tab here — and
only that: the response’s shape does not change, because the block is open.
What to do when
Three steps, in this order. The third one is the one people forget.
The response has the same shape as an invoice’s. Three things change:
Three things before you integrate:
- There is no order id here. When you charge, the order does not exist in Fire yet. The
orderCodeis the only thing tying this request to the sale, so it must be the same string you later send when injecting. - The response does not say the document is authorized. It says there are numbers to print. The authority’s approval arrives later, asynchronously.
- You request an operation, not a document type.
INVOICEorCANCEL. Which fiscal instrument materializes it — invoice, credit note, cancellation event — is the country’s call, and not the point of sale’s business.
The full flow
1
You charge
The customer pays at the POS or the kiosk.
2
You request the numbering
You call this endpoint. Fire resolves the store, the issuer and the provider, and stores the
request before going out for the numbers.
3
You print
Depending on
printing.mode you print the fiscal receipt or a provisional ticket.4
You inject the order
With your usual body, adding nothing to it. Fire correlates the sale with its numbering
through the
orderCode.5
The authority approves
Minutes later. Fire receives the provider’s result and updates the order. If you want to see
it, query this same request.
Headers
string
required
Your Fire API key, with the Fiscal Gateway (numbering) permission.The account and the vendor are derived from the key, never from the body. That is why the
payload carries no
accountId or vendorId: a credential cannot lie about who it belongs to.string
required
A UUID identifying this attempt. Generate it once per sale and reuse it across the retries
of that same sale.
This is not what prevents a duplicate document — the
orderCode does that, being the
natural key on both ends: if you repeat the same orderCode, you get the same document back
even if you generate a new key.What this key adds is detecting that you reused it for a different sale: if the same key
arrives with a different body, Fire answers 409 instead of numbering. It guards against a
point-of-sale bug — not regenerating the key — that would otherwise go unnoticed.string
Optional. An identifier of your own for this operation — the one you already use in your
logs.Fire stores it with the request and returns it in
correlationId. It changes no behaviour: it
exists so that, when something fails, you can cross your records with ours without matching by
timestamp and orderCode.If you do not send it, correlationId comes back null.Body
It is a payload of its own, not the order injection one: only what numbering requires. The names match the ones you already use (store, device, orderCode) so you can build it by
trimming what you have, but do not send the full order body — no products, payments or
shippingMethod.
Five fields, and none of them is an authority code.
string
required
The sale’s code. It is the idempotency key, both Fire’s and the provider’s.It must be unique per account and country, and the same one you later send when injecting
the order. If two stores of the same account use the same
orderCode, Fire stops with 409
before issuing: without that stop, the second store would print the first one’s sequential.string
required
The order’s creation date and time.It has two uses, and they are worth keeping apart. It is the fallback for the issue date —
the primary source is the store’s open business day, because a sale made after midnight belongs
to the day that is still open, not to the clock’s — and it also travels to the fiscal
provider as the wall-clock time the sale happened, for regimes that require it on the receipt.With no timezone (
2026-08-12 17:26:09), it is read as UTC. It always leaves for the
provider normalized, with Z.string
default:"INVOICE"
What you are asking for. One of:
INVOICE— number the sale.CANCEL— void it.
To void, send the sale’s same
orderCode with operation: "CANCEL". Nothing else: no
fiscal keys, no original document number, no Fire identifiers.Fire finds the document to compensate through the natural key —country + orderCode + operation— and tells you which one it matched in document.compensates. Your point of sale
does not need to store anything of ours in order to void.object
required
The issuing store. From
code, Fire resolves the country, the issuer’s fiscal identity and the
establishment — do not send those yourself.object
required
The issuing device. It is the same block you already send when injecting orders — nothing
needs to be added to it.
Do not declare the point of emission. There used to be an
externalId the channel used
to report it; it was removed. The authority assigns it under the issuer’s tax id and the
point of sale does not speak that language: the provider now resolves it from the uid, just
as it does with store.code.It comes back in countryData.puntoEmision, with the value it was actually issued under.array
What was charged. It is the same
payments.totals you already send when injecting orders
— send it as is, whole.What is sent today, per country:
Send
taxes[] with amount, not a loose percentage. One element per tax, with name,
base, rate and amount — the same shape in every country.The amount has to come computed by you, who charged and printed it. Where the fiscal
identifier is a hash of the invoice —the Colombian CUFE— deriving it from the percentage
forces someone else to round, and if they round differently than the register, the identifier
stops matching the paper the customer is holding.Amounts travel unscaled. Send the number exactly as you charged it:
50000, 42016.81.
Do not multiply it by 10,000.That scale does exist, but it belongs to another path: the order’s events
(order.completed and the rest) carry the same amounts as an
integer in a string ×10,000, because that is how FIRE stores them. Not here.If you integrate both paths, that is the only conversion you have to make — and doing it
backwards means declaring ten thousand times the amount.Send the amount exactly as you charged and printed it. Fire neither rounds it nor
reformats it: the number in the request and the one on the receipt are the same.It matters because there are regimes where the fiscal identifier is a hash of the invoice
— the Colombian CUFE, for example. If the amount going into the hash is not the one printed,
the identifier does not correspond to the invoice the customer has in hand.
object
Who bought. It is the same block you already send when injecting orders: send it whole, as
is. Do not trim it, do not rename it, do not translate it.Fire reads from there whatever the country’s regime needs and discards the rest. That it is
the complete block and not a subset is deliberate: if each country demanded its own trim, the
point of sale would have to know which field each authority looks at — which is exactly what
this contract avoids.The remaining fields —
uid, email, phone, gender, birthdate, externalId— travel and
are not fiscalized. Fire does not forward them to the fiscal provider: they are not the
authority’s business.The values Fire expects in
govIdType:There is no guard today: whatever you send, the sale gets numbered. The field travels as
is to the fiscal provider, so a value outside this list does not break the numbering — it
reaches them, and they are the ones who have to recognize it.That is why it pays to stick to it: a
CEDULA where CI belongs, or two different spellings
for the final consumer, are documents that come out wrong without anything failing along the
way.Final consumer: send both fields, translating neither.You send the number, same as in any other sale. What you must not do is convert it into
what each regime requires: the DIAN’s generic NIT
222222222222 in Colombia, the absence of
a recipient in Brazil. That is resolved by the fiscal provider, who is the one certified
before the authority.It is deliberate: that rule changes per country and per resolution of the authority, and it
should not force you to deploy the point of sale when it changes.Fire does not touch it either. The client block travels as is to the provider: we do not
fill in the number, we do not normalize it and we do not validate it. What you send is what
they receive.object
Key-value of the sale: what changes on every transaction and some country requires.It is opaque to your integration: Fire does not interpret it, it carries it. Valid keys depend
on the store’s country, and an unknown key is rejected with
400 — a Fire error is preferable
to a made-up field travelling to the authority.In most cases it goes empty: what is constant about the store is not sent here, it is
configured once (see below).What you do not send: the store configuration
Everything that is constant about the store is configured once in the backoffice and travels on its own: the issuer’s fiscal identity, plus a key-value block per country for the attributes that country’s provider needs.That key-value lives in the store’s fiscal configuration, separated by country. It is the
reason this endpoint is the same everywhere: what is country-specific is administered, not
programmed and not sent on every sale.If your integration starts needing a new field per country, the answer is almost always to
configure it there — not to add it to the payload.
string
Only for
CANCEL, and only when automatic resolution is not enough: a cancellation referencing
a document from another order, or several invoices for the same one.In the normal case, do not send it. Fire finds the original through the natural key, so your
point of sale does not need to store any identifier of ours in order to void.Response
string
The request’s identifier in Fire. It is what you query the outcome with later.
string
Echo of the code you sent.
string
Echo of the
x-correlation-id header, or null if you did not send it. It is for traceability:
it takes no part in numbering or idempotency.boolean
true if this request already existed and was returned as-is, without numbering again.string
The status of the numbering: did I get numbers to print?Five possible values. The first four describe how the attempt ended; the fifth says there was no
attempt because this store does not number.
NOT_APPLICABLE is the only one that is not stored: it creates no fiscal request
(fiscalRequestId: null) and does not appear in the order’s events. It exists because the POS
always calls this endpoint — that is how it discovers whether the store numbers — and
answering with an error would make every sale of a gateway-less store look like a failure.string
The status of the document before the authority: did it approve it?
PENDING · AUTHORIZED · REJECTED · CANCELLEDIn this response it is always
PENDING: there are numbers, there is no verdict. Only the
provider’s result moves it, and that arrives later. Collapsing the two statuses into one is
the mistake that makes a POS believe a sale is authorized when it is merely numbered.string
Which environment Fire numbered in:
SANDBOX or PRODUCTION.It is ours, not the authority’s. It comes from the account’s fiscal configuration —which is per
vendor and per country, so the same account can have Ecuador in production and Colombia in
sandbox— and it is frozen on the request: if the configuration changes tomorrow, this value
still says what this sale was numbered with.null when requestStatus is NOT_APPLICABLE: nothing was numbered, so there was no
environment to number in.object
What you need in order to print, without knowing about countries.
null if nothing was
numbered.sequential and serie are no longer here. They are country-shaped pieces —in Ecuador
the serie is six digits split in half— and they live in countryData under the name their
authority gives them. Only what means the same everywhere stayed in document.object
The country’s identifiers, in its authority’s vocabulary and ready to print.It is an open map: the keys are defined by each country’s tax regime, not by this contract.
A new country comes in without the response’s shape changing.
The block changes entirely per country, and so do the labels. In Colombia
documentLabel is FACTURA ELECTRONICA DE VENTA and authorizationLabel is
VALIDACION PREVIA — those are the DIAN’s names, not a variant of the Ecuadorian text.ambiente arrives translated in both countries, and that is the point: the DIAN encodes
1 as production and the SRI encodes it as testing. Fire resolves it so no channel has to
carry that table.The values come translated, not as authority codes. The provider sends
ambiente: "2" —that is how the SRI defines it— and what arrives here is "PRODUCCION",
which is what the ticket says. Translating it on the channel’s side would mean every
integrator carries its own copy of the authority’s table, and the first one to copy it wrong
prints “PRUEBAS” on a production invoice.countryData by country
Today the gateway numbers in Ecuador and Colombia. Every country that comes in adds its tab here — and
only that: the response’s shape does not change, because the block is open.
- Ecuador (EC) · available
- Colombia (CO) · available
- Other countries · when they arrive
SRI receipts.
The number is composed by the provider, not by Fire. The format belongs to the regime
—fifteen digits in three segments, article 18 of the Reglamento de Comprobantes de Venta—
and it is known by whoever is certified before the SRI. If the regime changes the
convention, it changes there and Fire does not need to deploy.
document.documentNumber is an echo of this same value, so you do not have to reach
into the country block just to print. It is the same fact, not two.object
The issuing branch, for the receipt’s header.
object
The issuing legal entity: the receipt’s header and footer, already resolved.
store, company and document.compensates come in every response of this endpoint,
including NOT_APPLICABLE and the 400 for a store that cannot issue,
including the idempotent retry — the channel needs the header both the first time and when it
repeats after a network drop.Queries (GET by fiscalRequestId or orderCode) return them as null: they are
resolved at issue time and are not stored with the request. If your integration needs them to
reprint, use the printing data.object
Key → the exact string to encode, ready to render. For example
{ "qr": "1208202601…811" }.It is an open map because each country’s receipt does not always carry the same thing, and a
country may need more than one element. Iterate the keys that arrive, do not look for fixed
fields.Fire does not generate images: size and resolution depend on your printer, and only whoever
prints knows that.object
What you may print. It is a legal rule of the country, not a derivation of whether there is a
document: which is why Fire resolves it and not each channel.
object
What to do with the sale when numbering failed. The account decides, not you: it is
configured per vendor in the backoffice and Fire returns the decision already made, just like
printing.It travels always, including when numbering succeeded. Branch on the value, never on the
presence of the key.What this policy decides, and what it does not.It decides one single thing: whether the register gives the customer their money back
when the sale was charged and could not be numbered. Nothing else.It does not decide what you print — that is
printing, and it is a legal rule of the
country, not anybody’s preference. It does not block sales: by the time you request
numbering the customer has already paid, so there is no sale left to block. And it does
not depend on retryable: a failure that heals on its own is still a failure, and if the
account configured a refund, you refund.The account configures it, per country and per vendor, in the backoffice. You neither
derive it nor negotiate it: Fire returns it resolved, just like printing. If it is not
configured, if it carries a value we do not recognize, or if we could not read it,
CONTINUE applies — the default points that way on purpose, because a badly written
configuration must not be able to trigger refunds.Two cases ignore it entirely, however it is configured: when nothing failed
(GENERATED or NOT_APPLICABLE), and when what failed was a cancellation
(operation: "CANCEL") — there the order exists and its money was not refunded, so there is
nothing to give back.The other two fields are the decision’s receipt: configVersion says which configuration
decided it and resolvedFrom with what context. They let you reconstruct a refund from three
weeks ago even if the account is configured differently today.Where it is configured and what happens without it. The policy is set per account, country
and vendor. If the vendor has none, if it carries a value we do not recognize, or if we could
not read it,
CONTINUE applies — and that default points that way on purpose: a badly
written configuration cannot trigger refunds. You will see it as configVersion: null.What to do when REFUND arrives
Three steps, in this order. The third one is the one people forget.
1
Give the money back at the counter
The customer already paid. That refund is yours to make with your payment method — Fire
moves no money and does not know whether you refunded.
2
Do not inject the order
Do not send it to Create order. That sale did not happen:
injecting it would leave a charged order with no fiscal receipt, which is worse than not
having it.
3
Report it with Record lost sale
POST /orders/lost-sales, copying policy.numberingFailure.lostSaleReason into reason.
It is the only step that tells us.When what fails is a cancellation
Cancelling is two calls, in this order: first you request the credit note’s numbering here (operation: "CANCEL"), and only then you call
Cancel order.
If the credit note’s numbering fails, this endpoint answers just as always: never a 4xx.
PENDING and FAILED_RETRYABLE come back 202; FAILED_FINAL comes back 200. The body
carries the failure with the reason and the fiscalRequestId to escalate with.
And policy.numberingFailure.action always comes back CONTINUE, with
lostSaleReason: null, regardless of how the account is configured. It is not an arbitrary
exception: there is no charge to reverse here. The sale already happened, it is in orders and
it still stands — what is missing is the cancellation paperwork, not the money.
object
Why there is no document.
null when there is one.string
Our adapter identifier (
hio), not the provider’s name. It says which integration
numbered this sale. null on NOT_APPLICABLE: none was involved.object
Who numbered, on the provider’s side. It has a shape —all three fields are part of the
contract— and that is why it travels apart from the opaque bag.
null when nothing was numbered.object
The provider’s diagnostic bag, exactly as it arrived. Opaque: it has no guaranteed shape
and nobody should program against its keys — they change with no notice and without versioning
the contract. It is there to paste into a ticket, not to branch on.
null when the provider sent nothing.It is the same field that travels in the event, same name and same content. All three
provider fields —
providerCode, providerIdentity, providerMetadata— read the same here
and in fiscalRepresentation: what you learn at one end works at the other.Status codes
The call is synchronous, but it has a time budget. The customer is standing at the register:
Fire waits a few seconds for the provider and, if it does not answer, cuts and returns
202
instead of leaving the sale hanging.That 202 is not a promise that it will reach your POS later through another channel: it is
Fire saying “I have no numbers yet, print provisional and carry on”.To complete it, retry with the same orderCode. Fire resumes the request and asks the
provider again. It is rare, but it exists because the alternative — failing the sale — is worse.What to do with each response
What follows describes the
CONTINUE path, which is the default and what most accounts
run. If policy.numberingFailure.action says REFUND, the instruction is inverted: you do
not inject the order and do not retry numbering — you refund and
report the lost sale.Everything else about each status — what it means and whether the failure fixes itself —
holds in both cases.GENERATED — there is a receipt
GENERATED — there is a receipt
printing.mode: "FISCAL_DOCUMENT". Print the receipt with document.documentNumber and draw
the codes from graphic. Inject the order with the same orderCode.If printing.reason is ISSUED_OFFLINE, the receipt is valid but was issued under
contingency: print the legend that country requires.PENDING — unknown whether there is a receipt
PENDING — unknown whether there is a receipt
The provider did not answer: timeout or dropped connection. It may have numbered and
consumed a sequential without us finding out.Print the provisional ticket and inject the order. Then retry with the same
orderCode:
Fire resumes the request and asks the provider again, so if it numbered the first time, you
recover that document.FAILED_RETRYABLE — there is none, but there may be
FAILED_RETRYABLE — there is none, but there may be
The provider answered that it cannot right now. Unlike
PENDING, here we know for certain
that nothing was numbered.Print provisional, inject the order and retry with the same orderCode.FAILED_FINAL — there is none, and retrying will not help
FAILED_FINAL — there is none, and retrying will not help
Retrying with the same body will give you the same thing. Read
failure.message, which
carries the real reason, and failure.scope, which says whose problem it is:FUNCTIONAL— some value the tax authority does not accept. It is fixed in the sale or in the store’s configuration.TECHNICAL— the integration with the provider is broken. The sale is fine; what fails is the connection to whoever assigns the numbers. Nobody at the register can fix it.
fiscalRequestId. With CONTINUE, the sale ends up charged without a fiscal receipt — that also
travels in the order’s events, so you can compensate for it. With REFUND there is no
order and no event: the only trace is the lost-sale report.NOT_APPLICABLE — this store does not number
NOT_APPLICABLE — this store does not number
It is not an error. This vendor has no fiscal representation: there is nothing to number
and no request was created (
fiscalRequestId: null).Print your usual ticket and inject the order normally. It is the expected answer for
aggregators, countries with no fiscal gateway and businesses with numbering switched off.store and company come here too. Nothing was numbered, so you get the issuer’s
identity and not the fiscal apparel (countryLines and legends empty). It is the same
block as in any other response: there is no separate shape to learn for this case.4xx — the problem is in the request
4xx — the problem is in the request
In these cases no fiscal request was created: fix it and call again.If you get a
404 with a store-not-found message, check that your API key belongs to the
vendor that owns that store — the message includes which vendor it searched against.The 400 for a store that cannot issue carries the issuer’s identity in data. That is
the case of a store with no tax id or not enabled: the sale already happened and you still
have to print a provisional receipt, so the header travels with the error.Always call it. This is how you find out whether the store numbers.You do not need to sync configuration or decide per country: if that vendor has no fiscal
representation, the answer is
200 with requestStatus: "NOT_APPLICABLE" and
printing.mode: "NONE" — you print your ticket and carry on. It is not an error and no
request is created.It is the same branch you already have: you branch on printing.mode, not on the HTTP code.Voiding
You send the sale’s sameorderCode with operation: "CANCEL". Nothing else.
The credit note has its own numbering. It does not continue the invoices’: in the example,
the invoice is
005-004-000000068 and its cancellation 005-004-000000002. They are two
different runs under the same establishment and point of emission.If there is nothing to void
If the sale was never numbered —because the store does not invoice, or because numbering failed— the cancellation answers400:
When the void does not settle right away
Voiding has the same uncertain outcomes as invoicing, and it is worth saying out loud because it is easy to assume a void always closes.Cancelling the order and voiding the document are two different things. Your order can be
cancelled instantly while the fiscal void is still in flight. If you need fiscal certainty — a
book close, a reconciliation — query the document; the order status will not give it to you.
When the instrument is not a credit note
In Ecuador a cancellation produces a new document. In other countries it does not: in Brazil it is a cancellation event that generates no receipt, and there the response arrives withstatus: "CANCELLED" and document: null. It is not an error — it is the correct outcome of
that operation in that country.
That is why you request an operation and not a document type: the instrument is the tax regime’s
call.
Querying a request
There are two read endpoints, and they exist for when the normal path is not enough: you lost the synchronous response, or you want to see whether the authority already approved without waiting for the event. In day-to-day operation you should not need them — the data arrives through the order’s events.- By identifier —
GET /numbering/{fiscalRequestId}. - By order —
GET /numbering?orderCode=…. It returnsitems[], because an order may have two documents: the invoice and the cancellation compensating it. It is the one you use when you lose the response to a network drop — theorderCodeis all you have in hand.
documentStatus becomes AUTHORIZED.

