Registrar venta perdida
curl --request POST \
--url https://api.fire.rest/api/v1/fire/external/orders/lost-sales \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"reason": "<string>",
"detail": {},
"orderId": "<string>",
"store.code": "<string>"
}
'import requests
url = "https://api.fire.rest/api/v1/fire/external/orders/lost-sales"
payload = {
"reason": "<string>",
"detail": {},
"orderId": "<string>",
"store.code": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({reason: '<string>', detail: {}, orderId: '<string>', 'store.code': '<string>'})
};
fetch('https://api.fire.rest/api/v1/fire/external/orders/lost-sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fire.rest/api/v1/fire/external/orders/lost-sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => '<string>',
'detail' => [
],
'orderId' => '<string>',
'store.code' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fire.rest/api/v1/fire/external/orders/lost-sales"
payload := strings.NewReader("{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fire.rest/api/v1/fire/external/orders/lost-sales")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fire.rest/api/v1/fire/external/orders/lost-sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyAPI
Registrar venta perdida
Avisa que cobraste una venta en la caja y nunca llegó a ser orden en Fire.
POST
/
api
/
v1
/
fire
/
external
/
orders
/
lost-sales
Registrar venta perdida
curl --request POST \
--url https://api.fire.rest/api/v1/fire/external/orders/lost-sales \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"reason": "<string>",
"detail": {},
"orderId": "<string>",
"store.code": "<string>"
}
'import requests
url = "https://api.fire.rest/api/v1/fire/external/orders/lost-sales"
payload = {
"reason": "<string>",
"detail": {},
"orderId": "<string>",
"store.code": "<string>"
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({reason: '<string>', detail: {}, orderId: '<string>', 'store.code': '<string>'})
};
fetch('https://api.fire.rest/api/v1/fire/external/orders/lost-sales', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fire.rest/api/v1/fire/external/orders/lost-sales",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => '<string>',
'detail' => [
],
'orderId' => '<string>',
'store.code' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fire.rest/api/v1/fire/external/orders/lost-sales"
payload := strings.NewReader("{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fire.rest/api/v1/fire/external/orders/lost-sales")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fire.rest/api/v1/fire/external/orders/lost-sales")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"<string>\",\n \"detail\": {},\n \"orderId\": \"<string>\",\n \"store.code\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyUna venta perdida es dinero que se movió en el mostrador y no quedó registrado como venta. La
caja cobró, algo impidió que la orden se creara, y devolviste el cobro ahí mismo.
Todo lo demás del payload —
No lleva header
Sin esta llamada, esa venta no existe en ningún lado. No hay orden, no hay evento y —según la
causa— tampoco queda registro en el dominio que falló. El cierre de caja no lo puede explicar y
nadie se entera de que una tienda dejó de vender.
No es una anulación. Una anulación tiene orden, nota de crédito y evento
order.reversed.
Acá la venta nunca llegó a existir.No sos vos quien decide reportar. Fire lo decide y te lo dice en
policy.numberingFailure.action de Emitir comprobante:
REFUND significa devolver el cobro y reportar acá; CONTINUE, seguir normal y no reportar
nada. La regla la carga la cuenta por vendor, así que puede ser distinta entre dos tiendas
del mismo cliente — por eso se pregunta en cada venta y no se cachea.El cuerpo es el de la inyección
No armes un payload nuevo. Mandá exactamente el mismo JSON que le ibas a mandar a Crear orden, y agregale dos claves al mismo nivel:reason y
detail.
No es comodidad: adentro corre el mismo mapeo de la inyección, así que la venta perdida queda
guardada con la misma forma que una vendida — mismas líneas, mismos totales, mismos medios de pago.
Eso es lo que después permite cuadrar la caja del día sumando las dos.
string
requerido
Por qué la venta no llegó a ser orden.
No lo deduzcas: te lo devolvemos en
| valor | cuándo |
|---|---|
FISCAL_NUMBERING_FAILED | Pediste la numeración con Emitir comprobante y falló |
policy.numberingFailure.lostSaleReason de la respuesta
de numeración. Copialo. El día que agreguemos una causa, no tenés que tocar nada.Es un enum cerrado y no hay endpoint para consultarlo: la lista de arriba es el catálogo
completo, y el valor que te toca ya viene en la respuesta que te trajo hasta acá. Mientras sea
de este tamaño, una llamada extra para descubrirlo no te compra nada. Si crece, pasa a ser un
endpoint y lo vas a ver anunciado acá — el campo no cambia.Cualquier otro valor vuelve 400.object
Lo propio de la causa. Para
Es opcional a propósito: el peor caso —un error de configuración, que corta antes de crear la
solicitud fiscal— no tiene nada de esto, y exigirlo dejaría fuera justo al caso más difícil de
detectar.
FISCAL_NUMBERING_FAILED copiá de la respuesta de numeración:| de la respuesta del prekey | a detail |
|---|---|
failure.code | detail.code |
failure.message | detail.message |
fiscalRequestId | detail.fiscalRequestId |
failure (entero) | detail.failure |
policy.numberingFailure (entero) | detail.policy |
string
requerido
Del payload de inyección. Junto con el vendor es la llave que hace seguro reintentar.
string
requerido
Del payload de inyección. Sin tienda no se puede cuadrar la caja ni saber quién dejó de vender.
client, order.products, payments, createdAt, orderCode…—
viaja tal cual y lo interpretamos con el mismo mapeo de siempre.
Autenticación
string
requerido
Tu API key de Fire con scope
orders:write. La key debe ser vendor-scoped — las keys sin
vendorId se rechazan con 403.Reintentar es seguro
Es idempotente pororderId + vendor, la misma llave con la que Fire identifica una orden. Si
tu caja se queda sin red justo acá —un momento malo, con el cliente enfrente— acumulá y reenviá.
| respuesta | qué pasó |
|---|---|
201 | Se registró ahora |
200 | Ya estaba registrada. Tu reintento llegó bien y no se duplicó nada |
Idempotency-Key: no existen dos pérdidas distintas de la misma venta.
Ejemplo
Request
{
"reason": "FISCAL_NUMBERING_FAILED",
"detail": {
"code": "FISCAL_BUSINESS_RULE",
"message": "identidad fiscal no configurada: EC / la tienda K000 no tiene el punto de emisión \"8cd0b157\"",
"fiscalRequestId": "fr_01HZ8N4K2P",
"failure": { "code": "FISCAL_BUSINESS_RULE", "scope": "FUNCTIONAL" },
"policy": {
"action": "REFUND",
"lostSaleReason": "FISCAL_NUMBERING_FAILED",
"configVersion": "fnv1a:d096701f",
"resolvedFrom": { "retryable": "false", "operation": "INVOICE" }
}
},
"orderId": "EC-K000-POS-1-1787239618530373",
"orderCode": "EC-K000-POS-1-1787239618530373",
"createdAt": "2026-08-21T14:32:09.881Z",
"accountId": 51,
"account": "KFC Kioscos EC",
"selectedShippingMethod": "pickup",
"client": { "uid": "c-1", "name": "Consumidor", "lastName": "Final" },
"store": { "id": 1, "code": "K000", "vendorId": "51.1.10" },
"order": { "products": [ "…tus líneas, tal cual…" ] },
"payments": { "totals": [ "…" ], "paymentMethods": [ "…" ] }
}
201
{
"success": true,
"data": { "id": "3f2a8c11-9d54-4b7e-8f11-2c9b0e7a4d63", "alreadyRecorded": false }
}
Errores
| código | cuándo |
|---|---|
400 | Falta reason, orderId o store.code — o reason trae un valor que no está en la tabla |
401 | API key ausente o inválida |
403 | La key no tiene scope orders:write, o no es vendor-scoped |
404 | La tienda no existe bajo el vendor de tu key |
Un campo de más no se rechaza, y el día de negocio cerrado tampoco: el dinero ya se movió, y
ese mismo día cerrado puede ser la causa de la próxima pérdida. Preferimos guardar de más a
perder el rastro de una venta.

