Ir al contenido

Facturas

Escribir y leer son permisos independientes. Una tienda escribe y nunca lee; una gestoría lee y nunca escribe. Ninguno implica al otro.

Cuando alguien compra, envías lo que le has cobrado. Aparece en su cuenta como un gasto con sus líneas, sin que haga nada.

POST/public/v1/invoicesscope invoices:write

Registrar una factura

Idempotente sobre externalReference: reintentar la misma compra devuelve la misma factura en vez de crear una segunda. El comercio se toma de la identidad de partner del cliente que llama y no puede fijarse en el cuerpo. Los importes se envían en positivo; Costia guarda los gastos personales en negativo y hace el signo por su cuenta.

Parámetros

NombreTipoDescripción
localestringIdioma para todo lo que Costia resuelve por nombre, como las categorías.

Cuerpo

CampoTipoDescripción
externalReferenceobligatoriostringel identificador del pedido en el sistema del propio partner. Obligatorio, y es la clave de idempotencia: reintentar la misma compra no debe producir un segundo gasto.
invoiceNumberstringel número impreso en el documento, si lo hay. Se guarda para el archivo de la persona; Costia no deriva nada de él.
titleobligatoriostringlo que la persona ve como nombre del gasto. El comercio va aparte y sale del partner, así que esto queda libre para describir la compra y no la tienda.
issuedAtobligatoriostring (date)la fecha del documento, no la del envío. Una factura registrada tarde sigue perteneciendo al día en que se emitió, que es por el que se rige una declaración.
currencyCodeobligatoriostringISO 4217, tres letras.
totalobligatorionumberlo que pagó la persona, en positivo. Costia guarda los gastos personales en negativo y hace el signo por su cuenta.
totalTaxnumberel total de impuestos tal y como está impreso. Opcional: si falta, Costia suma las líneas, y si viene se respeta tal cual, porque un documento que redondea distinto sigue siendo el documento que la persona tiene en la mano.
defaultTaxRatenumberel tipo que se aplica a cualquier línea que no declare el suyo.
taxIncludedInPricesbooleansi los importes de línea ya llevan impuestos incluidos. Si es false, Costia añade las líneas de impuesto para que la factura cuadre.
notesstringcualquier cosa que la persona deba leer junto a la factura.
linesobligatorioPartnerInvoiceLine[]al menos una. Una factura sin líneas es un total sin nada que lo explique, que es justo lo que la persona abrió el gasto para ver.

Respuestas

CódigoSignificado
200Ese externalReference ya estaba registrado. Vuelve la misma factura con replayed=true, así que reintentar tras un tiempo de espera agotado es seguro.
201La factura se registró
400VALIDATION_ERROR cuando el cuerpo incumple una regla, con fieldErrors nombrando cada una; UNKNOWN_LINE_TYPE o UNKNOWN_QUANTITY_UNIT cuando una línea trae un valor fuera del conjunto aceptado.
401UNAUTHORIZED: el token no es uno emitido por Costia a un cliente registrado.
403ACCESS_DENIED cuando al token le falta invoices:write; NOT_A_PARTNER_CLIENT cuando el cliente no tiene identidad de partner; PARTNER_CANNOT_WRITE cuando el partner no tiene dominio verificado; PARTNER_DISABLED cuando está deshabilitado.

externalReference es tu clave de idempotencia. Manda el identificador del pedido en tu propio sistema. Si reintentas tras un tiempo de espera agotado, Costia devuelve la misma factura con replayed: true en vez de crear una segunda. Por eso es seguro reintentar, y por eso no debe ser un valor nuevo en cada intento.

El comercio sale de tu partner, no del cuerpo. No hay campo para ponerlo. Tu marca verificada es la que aparece, y es lo que hace que la palabra «verificado» signifique algo para quien lo lee.

Los importes van en positivo. Costia guarda los gastos personales en negativo y hace el signo por su cuenta.

La tienda de ejemplo lo hace así, y merece la pena copiar dos decisiones.

app/api/costia/invoice/route.js
// El navegador manda solo el identificador del pedido.
const { orderUuid } = await request.json();
// El pedido se relee del backend de la tienda, no se toma del carrito que
// mandó la página.
const order = await fetch(`${backend}/front/orders/${orderUuid}`, { headers });
// El token se obtiene en servidor, y se refresca solo si hace falta.
const { accessToken } = await auth.api.getAccessToken({
body: { providerId: 'costia' },
headers: request.headers,
});
await fetch('https://api.costia.app/public/v1/invoices', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(invoice),
});

El pedido se relee en servidor. Si aceptaras la cesta que manda el navegador, cualquiera podría escribir la factura que quisiera — en su propia cuenta, así que no es una brecha, pero convertiría en ficción cada gasto espejado. Y toda la gracia de que la tienda empuje sus datos es que son sus datos.

El token nunca sale al navegador. Un token con invoices:write es justo lo que no debe estar en una página.

Todas las de la cuenta, no solo las que escribiste tú. Es lo que hace útil este permiso para una gestoría.

GET/public/v1/invoicesscope invoices:read

Listar las facturas de la persona

Todas las facturas de la cuenta, no solo las que escribió este cliente. Ordenadas por fecha descendente con un desempate estable, así que recorrer un ejercicio entero no repite ni se salta filas.

Parámetros

NombreTipoDescripción
dateFromstring (date)Fecha de factura más antigua que incluir, inclusive.
dateTostring (date)Fecha de factura más reciente que incluir, inclusive.
categoryUuidsstring (uuid)[]Limitar a estas categorías. Sus uuid salen de GET /public/v1/categories.
pageinteger (int32)Número de página, empezando en cero.
sizeinteger (int32)Tamaño de página. Los valores mayores se recortan a 200 en vez de rechazarse, así que recorrer un ejercicio no puede convertirse en una sola llamada enorme.
localestringIdioma de los nombres de categoría y de origen.

Respuestas

CódigoSignificado
200Una página de facturas
400VALIDATION_ERROR: page debe ser cero o mayor y size debe ser al menos uno.
401UNAUTHORIZED
403ACCESS_DENIED: al token le falta invoices:read.

El orden es por fecha descendente con un desempate estable, así que recorrer un ejercicio entero no repite ni se salta filas aunque entren facturas nuevas mientras paginas.

GET/public/v1/invoices/{uuid}scope invoices:read

Obtener una factura

La factura completa con sus líneas y el desglose de impuestos, que es lo que necesita una declaración y lo que el listado no lleva.

Parámetros

NombreTipoDescripción
uuidobligatoriostring (uuid)El uuid de la factura, tal y como se devolvió al registrarla.
localestringIdioma de los nombres de categoría y de origen.

Respuestas

CódigoSignificado
200La factura
400VALIDATION_ERROR: uuid no es un UUID válido.
401UNAUTHORIZED
403ACCESS_DENIED: al token le falta invoices:read.
404RESOURCE_NOT_FOUND. Una factura de otra cuenta devuelve esto y no un 403 — decir que existe ya sería decir algo sobre esa cuenta.

La factura completa trae sus líneas y el desglose por tipo de impuesto, que es lo que necesita una declaración y lo que el listado no lleva.