POST https://api.fire.rest/api/v1/fire/external/orders/ORD-77/confirm-payment
x-api-key: <your_api_key>
Content-Type: application/json
{
"payments": [
{
"uid": "9b1c...",
"total": "20",
"method": "CASH",
"currency_code": "BRL",
"details": {
"total_bill": 20,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648480",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:10:48.000Z" }
}
},
{
"uid": "7d3a...",
"total": "15.90",
"method": "CREDIT",
"currency_code": "BRL",
"details": {
"total_bill": 15.90,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648999",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:11:02.000Z" }
}
}
]
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "settled",
"settled": true,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 2,
"declinedCount": 0,
"amountMismatch": false,
"declines": [],
"flowsTriggered": 1
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "declined",
"settled": false,
"status": "OPEN",
"paymentStatus": "PENDING",
"completedAt": null,
"tenderCount": 0,
"declinedCount": 2,
"amountMismatch": false,
"declines": [
{ "sent": "INSUFFICIENT_FUNDS", "resolvedTo": "INSUFFICIENT_FUNDS", "group": "funds", "exceedsOrderTotal": false },
{ "sent": "51", "resolvedTo": null, "group": null, "exceedsOrderTotal": true }
],
"flowsTriggered": 0
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "duplicate",
"settled": false,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 0,
"declinedCount": 0,
"amountMismatch": false,
"flowsTriggered": 0
}
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "INCOMPLETE_CHARGE",
"message": "Approved tenders total 200000 does not reach the order total 359000 — send the complete charge in one call"
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "AMOUNT_EXCEEDS_TOTAL",
"message": "Approved tenders total 500000 exceeds the order total 359000"
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_ALREADY_SETTLED",
"message": "Order is already settled and does not accept new payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"completedAt": "2026-07-30T16:24:11.802Z"
}
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_NOT_OPEN",
"message": "Order is closed and does not accept payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"orderStatus": "CANCELLED",
"completedAt": null
}
}
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Datos de entrada inválidos",
"details": [
{
"code": "custom",
"path": ["payments", 1],
"message": "Duplicate transaction id \"BR-K000-POS-38-1785258648480\" within the same payment batch"
}
]
}
{
"success": false,
"error": "FORBIDDEN",
"message": "API key must be vendor-scoped (account + vendor binding) to access this endpoint"
}
{
"success": false,
"error": "NOT_FOUND",
"message": "InjectedOrder not found with ID ORD-77"
}
{
"success": false,
"error": "CONFLICT",
"message": "External order id ORD-77 matches 2 orders across vendors; cannot disambiguate"
}
API
Confirm payment
Records the payment for an open order and settles it. Accepts several tenders in one charge, and declined attempts too.
POST
/
api
/
v1
/
fire
/
external
/
orders
/
{orderId}
/
confirm-payment
POST https://api.fire.rest/api/v1/fire/external/orders/ORD-77/confirm-payment
x-api-key: <your_api_key>
Content-Type: application/json
{
"payments": [
{
"uid": "9b1c...",
"total": "20",
"method": "CASH",
"currency_code": "BRL",
"details": {
"total_bill": 20,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648480",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:10:48.000Z" }
}
},
{
"uid": "7d3a...",
"total": "15.90",
"method": "CREDIT",
"currency_code": "BRL",
"details": {
"total_bill": 15.90,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648999",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:11:02.000Z" }
}
}
]
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "settled",
"settled": true,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 2,
"declinedCount": 0,
"amountMismatch": false,
"declines": [],
"flowsTriggered": 1
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "declined",
"settled": false,
"status": "OPEN",
"paymentStatus": "PENDING",
"completedAt": null,
"tenderCount": 0,
"declinedCount": 2,
"amountMismatch": false,
"declines": [
{ "sent": "INSUFFICIENT_FUNDS", "resolvedTo": "INSUFFICIENT_FUNDS", "group": "funds", "exceedsOrderTotal": false },
{ "sent": "51", "resolvedTo": null, "group": null, "exceedsOrderTotal": true }
],
"flowsTriggered": 0
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "duplicate",
"settled": false,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 0,
"declinedCount": 0,
"amountMismatch": false,
"flowsTriggered": 0
}
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "INCOMPLETE_CHARGE",
"message": "Approved tenders total 200000 does not reach the order total 359000 — send the complete charge in one call"
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "AMOUNT_EXCEEDS_TOTAL",
"message": "Approved tenders total 500000 exceeds the order total 359000"
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_ALREADY_SETTLED",
"message": "Order is already settled and does not accept new payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"completedAt": "2026-07-30T16:24:11.802Z"
}
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_NOT_OPEN",
"message": "Order is closed and does not accept payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"orderStatus": "CANCELLED",
"completedAt": null
}
}
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Datos de entrada inválidos",
"details": [
{
"code": "custom",
"path": ["payments", 1],
"message": "Duplicate transaction id \"BR-K000-POS-38-1785258648480\" within the same payment batch"
}
]
}
{
"success": false,
"error": "FORBIDDEN",
"message": "API key must be vendor-scoped (account + vendor binding) to access this endpoint"
}
{
"success": false,
"error": "NOT_FOUND",
"message": "InjectedOrder not found with ID ORD-77"
}
{
"success": false,
"error": "CONFLICT",
"message": "External order id ORD-77 matches 2 orders across vendors; cannot disambiguate"
}
Closes the deferred payment cycle: the order was born open, the kitchen already worked, and the
payment arrives here. The order becomes
What separates a rejection from a retry is the
That last row is deliberate. If Fire answered with an error because an event could not
be queued, you would retry a charge that already went through. The money wins over the
notification.
Fields Fire does not know about are accepted and stored: you can send your full payment object
without trimming it. The currency of a declined tender never invalidates the batch, because it does
not add up.
COMPLETED and invoicing starts when the approved amount
reaches the order total exactly.
One charge with several tenders, in a single call. If you split the bill between cash and card,
send both pieces in the same call: the approved tenders must add up to the order total exactly.
A charge that does not add up is rejected in full and nothing is recorded — consolidate your
partials before sending them.Retrying is safe and expected: resend the whole batch and idempotency by
transaction_id takes
care of the rest.Need to correct the order before charging it —customer billing details, locator—? That goes
through Update order, not this endpoint. This one only records
the money; the difference between the two is explained there.
Authentication
string
required
Your Fire API key with the
orders:write scope. The key must be vendor-scoped — keys without
a vendorId are rejected with 403.Path parameters
string
required
Any of the order’s three public references:
This is the same set accepted by Get order and
Cancel order.
| reference | what it is |
|---|---|
orders.id | Fire’s internal UUID |
order_external | the id you assigned when creating the order |
metadata.order_id | a copy of the external id inside the order |
Body
object[]
required
The tenders you charged the order with. Between 1 and 20 pieces. Each object is stored exactly as
you sent it; only the fields Fire reads are listed below.
string
required
Outcome of the attempt. These count as charged:
APPROVED, AUTHORIZED, CAPTURED, PAID,
SUCCESS, SUCCEEDED (case-insensitive).Any other value is treated as a decline, even one we do not recognise. That is deliberate:
invoicing a charge that never happened cannot be undone, while failing to settle one that did is
fixed by sending it again.string
required
Transaction identifier. This is the idempotency key: resending the same one does not charge or
invoice twice. If your tender does not produce one, Fire falls back to
payments[].uid.number
required
Amount for this tender, decimal. Must be greater than zero. If it is absent, Fire falls back to
the top-level
payments[].total.string
required
Currency of the tender. All approved tenders must share the same one — including tenders sent
in earlier calls for the same order. If it is absent, Fire falls back to the top-level
payments[].currency_code.string
Reason for the decline, using a code from Decline reasons.
Only applies when
transaction_status is not approved.Your acquirer’s code (51, do_not_honor) must be translated on your side to the catalog’s:
Fire does not store each provider’s codes. If you send one that is not there it is accepted and
stored, but it comes back with resolvedTo: null and stays out of your grouped metrics.string
Payment method (
CASH, CREDIT, DEBIT, PIX…). Used for invoicing and metrics.Request
POST https://api.fire.rest/api/v1/fire/external/orders/ORD-77/confirm-payment
x-api-key: <your_api_key>
Content-Type: application/json
{
"payments": [
{
"uid": "9b1c...",
"total": "20",
"method": "CASH",
"currency_code": "BRL",
"details": {
"total_bill": 20,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648480",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:10:48.000Z" }
}
},
{
"uid": "7d3a...",
"total": "15.90",
"method": "CREDIT",
"currency_code": "BRL",
"details": {
"total_bill": 15.90,
"currency_code": "BRL",
"transaction_id": "BR-K000-POS-38-1785258648999",
"transaction_status": "APPROVED",
"transaction_date": { "date": "2026-07-28T17:11:02.000Z" }
}
}
]
}
What Fire does with it
Only approved tenders add up and settle. Declined ones are recorded for your metrics; they never add up and never block the charge.| situation | outcome | settles? |
|---|---|---|
| the approved sum equals the total | settled | yes — becomes COMPLETED and invoicing starts |
| no approved tender, only declines | declined | no — recorded, the order stays open |
| no new tender (you already sent them) | duplicate | no — idempotent retry |
| the approved sum does not equal the total | — | 400 — nothing is recorded |
| a new tender on a closed order (settled, cancelled or force-closed) | — | 409 — nothing is recorded |
All or nothing. A charge whose approved tenders do not add up to the order total is rejected in
full — not even a decline that travelled in the same batch is kept.The reason is not accounting, it is operational: money accepted on an order that does not settle
leaves it with cash inside and no way out. Fire does not process refunds and there is no way for you
to tell us you refunded. Consolidating partial charges is yours to do — and you are the only one who
can refund, so you carry that state anyway.Charging more than the total is rejected for a different reason: it would invoice an amount that
was not the one collected, and an invoice cannot be un-issued. Both messages give you the two
amounts so you can fix and resend.Declines are the exception: a batch with no approved tenders is recorded and the order stays
open. No money means no state to resolve, and that is where your decline metrics live.
What happened to each decline
Every declined tender comes back indeclines[], with what you sent and whether we found it in the
catalog.
"declines": [
{ "sent": "INSUFFICIENT_FUNDS", "resolvedTo": "INSUFFICIENT_FUNDS", "group": "funds", "exceedsOrderTotal": false },
{ "sent": "51", "resolvedTo": null, "group": null, "exceedsOrderTotal": true }
]
exceedsOrderTotal: true means the amount you attempted is larger than the order total. We do
not require each tender to equal the total —in a split charge, $20 on $35.90 is legitimate— but
exceeding it never is: it almost always means the wrong order is being charged. The decline is your
free warning, because if the next attempt is approved it would settle an amount that does not belong.
resolvedTo: null means that code does not exist in the catalog — in the example, the acquirer’s
raw code was sent instead of Fire’s. The decline was still recorded and the value is kept, but it
shows up in nothing grouped by reason.
Check this block on your first integration. A mistranslated code breaks nothing: the charge works,
the response is
200, and your declines land unclassified. You would find out months later with an
empty reasons dashboard.A closed order takes nothing else
An order takes tenders while it is open. Once closed it is closed, however it got there. A tender it has never seen is rejected and nothing is recorded — approved or declined alike. The error code tells you why it is closed, and the two call for different actions:| code | the order | what it means for you |
|---|---|---|
ORDER_ALREADY_SETTLED | already charged in full | the money is in. If you charged again, refund it |
ORDER_NOT_OPEN | closed without being charged (cancelled, force-closed) | this money does not belong to this order |
transaction_id, not the state of the order:
| you send | what it is | response |
|---|---|---|
a transaction_id Fire already has | a retry after a network timeout | 200, outcome: duplicate |
a transaction_id Fire has never seen | a report on a closed order | 409 |
A retry is never rejected, on purpose. You charged once and our answer never reached you; replying
with an error there would push the operator to run the card again — the double charge we are trying
to prevent. Send the same
transaction_id and you get the original outcome back.When the charge settles
Settling is what completes the order, so that is when Fire emitsorder.completed — the order was born OPEN and only
now has it finished. The response reports it as flowsTriggered.
flowsTriggered: 0 is not a payment error. It means one of three things:
| Case | Why |
|---|---|
| The charge did not settle | The order is still open; nothing completed. |
outcome: "duplicate" | The event already went out with the original call. |
| Enqueueing failed | The payment is recorded either way. The incident is kept on Fire’s side for an operator to replay. |
An order with deferred payment already issued its fiscal document when it opened, and
it passes through the fiscal step again on
order.completed. Fire detects the
existing document and skips the second issuance — you do not get two invoices.Idempotency
Each tender is identified by itstransaction_id within the order. Retry without fear:
- Resending the whole charge →
duplicate. Nothing is settled or invoiced again. - Resending after a timeout → resend the whole batch. An incomplete charge was never
recorded, and any tender that did land is ignored by its
transaction_id.
Validations
All return400 unless stated otherwise. Every rejection carries a code on top of the message:
branch on the code, not on the text — the message is written to be read and may be reworded, the code
is contract.
| rule | code |
|---|---|
payments with at least one tender | 400 |
| at most 20 tenders | 400 |
details.transaction_status present | 400 |
transaction_id or uid present | 400 |
no repeated transaction_id within the same batch | 400 |
| amount greater than zero on every tender | 400 |
| a single currency across approved tenders | 400 MIXED_CURRENCIES |
| the approved sum equals the order total exactly (all or nothing) | 400 INCOMPLETE_CHARGE |
| the approved sum does not exceed the order total | 400 AMOUNT_EXCEEDS_TOTAL |
| the order total is resolvable in the tender currency | 400 TOTAL_NOT_RESOLVABLE |
| valid API key | 401 |
orders:write scope and vendor-scoped key | 403 |
| the order exists within your vendor | 404 |
| the reference matches more than one order | 409 AMBIGUOUS_ORDER_REFERENCE |
| a new tender on an already settled order | 409 ORDER_ALREADY_SETTLED |
| a new tender on an order closed without being charged | 409 ORDER_NOT_OPEN |
Responses
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "settled",
"settled": true,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 2,
"declinedCount": 0,
"amountMismatch": false,
"declines": [],
"flowsTriggered": 1
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "declined",
"settled": false,
"status": "OPEN",
"paymentStatus": "PENDING",
"completedAt": null,
"tenderCount": 0,
"declinedCount": 2,
"amountMismatch": false,
"declines": [
{ "sent": "INSUFFICIENT_FUNDS", "resolvedTo": "INSUFFICIENT_FUNDS", "group": "funds", "exceedsOrderTotal": false },
{ "sent": "51", "resolvedTo": null, "group": null, "exceedsOrderTotal": true }
],
"flowsTriggered": 0
}
}
{
"success": true,
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"outcome": "duplicate",
"settled": false,
"status": "COMPLETED",
"paymentStatus": "SUCCEEDED",
"completedAt": "2026-07-28T17:11:02.000Z",
"tenderCount": 0,
"declinedCount": 0,
"amountMismatch": false,
"flowsTriggered": 0
}
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "INCOMPLETE_CHARGE",
"message": "Approved tenders total 200000 does not reach the order total 359000 — send the complete charge in one call"
}
{
"success": false,
"error": "BUSINESS_ERROR",
"code": "AMOUNT_EXCEEDS_TOTAL",
"message": "Approved tenders total 500000 exceeds the order total 359000"
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_ALREADY_SETTLED",
"message": "Order is already settled and does not accept new payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"completedAt": "2026-07-30T16:24:11.802Z"
}
}
{
"success": false,
"error": "CONFLICT",
"code": "ORDER_NOT_OPEN",
"message": "Order is closed and does not accept payment reports",
"data": {
"orderId": "7e2b8c10-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"orderCode": "OC-1024",
"orderStatus": "CANCELLED",
"completedAt": null
}
}
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Datos de entrada inválidos",
"details": [
{
"code": "custom",
"path": ["payments", 1],
"message": "Duplicate transaction id \"BR-K000-POS-38-1785258648480\" within the same payment batch"
}
]
}
{
"success": false,
"error": "FORBIDDEN",
"message": "API key must be vendor-scoped (account + vendor binding) to access this endpoint"
}
{
"success": false,
"error": "NOT_FOUND",
"message": "InjectedOrder not found with ID ORD-77"
}
{
"success": false,
"error": "CONFLICT",
"message": "External order id ORD-77 matches 2 orders across vendors; cannot disambiguate"
}
Related
Decline reasons
Grouped catalog used to classify declined attempts.
Get order
Check the status and total before charging.

