API de facturación
Trae el uso y el coste de tu propio workspace a tus propios sistemas, sin abrir la consola. Una clave de facturación es una de solo lectura credencial limitada a un solo workspace - solo puede leer datos de facturación, nada más.
Una clave de facturación puede leer cada solicitud registrada en su workspace, incluidas las claves que desde entonces se han eliminado o revocado, pero no puede llamar a ningún modelo, ni crear, modificar o eliminar ninguna clave API. Usarla en cualquier otro sitio devuelve 401 wrong_key_kind.
Crear una clave de facturación
- Abre Consola → Claves de la API de facturación (solo propietario del workspace). Ponle un nombre, restríngela opcionalmente a una lista blanca de IP o define una caducidad, y luego copia la clave - empieza por
sk-syn-bill-y solo se muestra una vez. - Llama a los endpoints de abajo usándola como token Bearer. Revócala en cualquier momento desde la misma página - revocarla la invalida de inmediato y no afecta a nada más en el workspace.
- Una clave está vinculada a quien la creó: si deja de ser el propietario del workspace, deja de funcionar y devuelve
403 key_owner_not_authorized.
Autenticación
Cada llamada necesita Authorization: Bearer sk-syn-bill-... contra la URL base de abajo. Una clave de facturación solo funciona en estos endpoints, y estos endpoints solo aceptan una clave de facturación - cualquier otra combinación devuelve 401 wrong_key_kind.
Authorization: Bearer sk-syn-bill-... URL base: https://synthorai.io/api/v1/billing
El alcance es todo el workspace: cada clave de inferencia que contiene, incluidas las eliminadas - sus filas históricas siguen siendo consultables. api_key_id y model solo pueden acotar el resultado, nunca ampliarlo; cada consulta queda anclada al propio workspace de la clave.
Cada consulta se ejecuta en la réplica de lectura, nunca en la primaria. Un proceso sin réplica configurada responde 503 replica_unavailable en lugar de recurrir silenciosamente a ella.
Campos de tokens: siguen la convención de OpenAI - prompt_tokens es el total de tokens de entrada y ya incluye cada lectura y escritura de caché indicadas abajo, y completion_tokens ya incluye reasoning_tokens. Ninguno de los dos se suma a los totales - son un desglose, no tokens adicionales.
La parte de lectura de caché de prompt_tokens es cached_tokens; la parte de escritura de caché es cache_write_tokens, que se divide además en las filas de /records en cache_write_5m_tokens y cache_write_1h_tokens según el TTL.
Campos monetarios: cost_usd es el importe realmente cobrado. byok_list_price_usd es lo que habría costado una solicitud BYOK al precio de catálogo - 0 para una solicitud no BYOK. No existe list_price_usd en ningún lugar de esta API.
Aquí "error" significa cualquier solicitud que llegó a la API y fue rechazada - un 4xx como 402 (saldo insuficiente), 403 o 429, así como un fallo en el proveedor. Solo se excluyen los rechazos de nuestra propia infraestructura interna, igual que en la consola. Los rechazos se registran como mucho una vez por clave, código de estado y minuto en cada servidor, así que los recuentos de 402, 403 y 429 son un mínimo; las solicitudes correctas y los cargos están completos.
GET /usage - uso agregado
GET /usage
Filas preagregadas por hora o por día, pensadas para facturación y paneles. Salen del agregado horario más las solicitudes escritas desde su última ingesta, así que los buckets recientes no van un ciclo de ingesta entero por detrás.
| Parámetro | Tipo | Descripción |
|---|---|---|
start* | string | Inicio del rango: una marca de tiempo RFC 3339 o una fecha como 2026-09-01. |
end | string | Fin del rango. Por defecto, ahora. |
granularity | string | Tamaño del bucket: hour o day. Por defecto, day. |
group_by | string | Dimensiones para desglosar las filas: api_key, model, o ambas (cualquier subconjunto). Por defecto, ambas. |
api_key_id | integer | Filtra por uno o más id de clave API. Repetible; solo acota, nunca amplía. |
model | string | Filtra por uno o más id de modelo. Repetible. |
limit | integer | Filas por página. Por defecto 1000, con un tope de 5000. |
cursor | string | Cursor de paginación opaco, tomado del meta.next_cursor de la página anterior. |
En cada fila, api_key_id y api_key_name solo aparecen cuando group_by incluye api_key, y model solo cuando incluye model. avg_latency_ms puede ser null cuando un bucket no tiene muestras de latencia.
Ejemplo de solicitud
curl "https://synthorai.io/api/v1/billing/usage?start=2026-09-01&end=2026-09-24&granularity=day" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{
"data": [
{
"bucket_start": "2026-09-23T00:00:00Z",
"bucket_end": "2026-09-24T00:00:00Z",
"api_key_id": 42,
"api_key_name": "prod-backend",
"model": "claude-sonnet-5",
"requests": 1280,
"success_requests": 1265,
"error_requests": 15,
"errors_4xx": 12,
"errors_429": 2,
"errors_5xx": 1,
"prompt_tokens": 512000,
"completion_tokens": 98000,
"cached_tokens": 210000,
"cache_write_tokens": 8000,
"reasoning_tokens": 4000,
"cost_usd": 4.1732,
"byok_list_price_usd": 0,
"byok_requests": 0,
"avg_latency_ms": 812,
"final": true
}
],
"meta": {
"generated_at": "2026-09-24T08:00:00Z",
"data_complete_until": "2026-09-24T06:00:00Z",
"timezone": "UTC",
"currency": "USD",
"next_cursor": null,
"has_more": false
}
} GET /records - filas por solicitud
GET /records
Una fila por solicitud, pensada para la sincronización incremental en tu propia base de datos. Sincroniza por cursor, nunca por tiempo, y elimina duplicados de las filas recibidas por record_id - consulta más abajo "Evitar huecos de tiempo" para saber por qué.
| Parámetro | Tipo | Descripción |
|---|---|---|
cursor | string | Cursor de sincronización opaco (cadena), tomado del meta.next_cursor de la llamada anterior. Indica exactamente uno de cursor o start; no tiene tope de rango. |
start | string | Inicio del rango (marca de tiempo RFC 3339 o fecha). Indica exactamente uno de cursor o start; un rango start/end está limitado a 31 días, mientras que cursor no tiene límite de rango. |
end | string | Fin del rango. Por defecto, ahora. |
api_key_id | integer | Filtra por uno o más id de clave API. Repetible; solo acota, nunca amplía. |
model | string | Filtra por uno o más id de modelo. Repetible. |
limit | integer | Filas por página. Por defecto 500, con un tope de 1000. |
Ejemplo de solicitud
curl "https://synthorai.io/api/v1/billing/records?start=2026-09-24T00:00:00Z&limit=500" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{
"data": [
{
"record_id": "rec_8f2a91c4",
"request_id": "req_9f3c2b1a",
"recorded_at": "2026-09-24T05:58:31Z",
"completed_at": "2026-09-24T05:58:29Z",
"api_key_id": 42,
"api_key_name": "prod-backend",
"model": "claude-sonnet-5",
"status": "success",
"status_code": 200,
"prompt_tokens": 812,
"completion_tokens": 194,
"cached_tokens": 512,
"cache_write_tokens": 64,
"cache_write_5m_tokens": 64,
"cache_write_1h_tokens": 0,
"cost_usd": 0.00412,
"byok_list_price_usd": 0,
"is_byok": false,
"is_stream": true,
"duration_ms": 1340,
"ttft_ms": 210
}
],
"meta": {
"next_cursor": "eyJvIjoiODgxNDA5MCJ9",
"has_more": false,
"window_final": true,
"safe_until": "2026-09-24T05:58:31Z",
"latest_recorded_at": "2026-09-24T05:58:31Z",
"data_complete_until": "2026-09-24T05:58:00Z"
}
} | Parámetro | Descripción |
|---|---|
next_cursor | Cursor para la siguiente llamada. Siempre presente. |
has_more | Indica si volver a llamar ahora mismo con este cursor devolvería más filas. |
window_final | true solo si indicaste un end y este es anterior o igual a safe_until - entonces el rango solicitado está garantizado como completo. Ausente o false en caso contrario. |
safe_until | El recorded_at más reciente que un llamador puede considerar definitivo; las filas más recientes que eso aún se retienen. |
latest_recorded_at | El recorded_at más reciente entre todas las filas, definitivas o no. |
data_complete_until | El mismo valor que devuelve /freshness - hasta dónde está completo el rollup por hora, para cotejarlo con /usage. |
Base temporal: /usage agrupa por completed_at (cuando la solicitud terminó). El filtro start/end de /records se basa en recorded_at (cuando se escribió la fila, lo cual puede quedar rezagado respecto a la finalización bajo carga). Para conciliar /records con /usage, agrupa tú mismo las filas sincronizadas por completed_at, no por recorded_at.
GET /records/export - exportación en streaming
GET /records/export
Los mismos filtros que /records, transmitidos como JSON delimitado por saltos de línea (application/x-ndjson) - pensado para un gran backfill en lugar de una lista paginada. Solo se puede transmitir una exportación por workspace a la vez; una segunda recibe 429 too_many_concurrent_requests.
Ejemplo de solicitud
curl -N "https://synthorai.io/api/v1/billing/records/export?cursor=eyJvIjoiODgxNDAzMiJ9" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{"record_id":"rec_8f2a91c4","request_id":"req_9f3c2b1a","model":"claude-sonnet-5","status":"success","cost_usd":0.00412}
{"record_id":"rec_8f2a91c5","request_id":"req_9f3c2b1b","model":"claude-sonnet-5","status":"success","cost_usd":0.00398}
{"_meta":{"next_cursor":"eyJvIjoiODgxNDA5MSJ9","rows":2,"complete":true,"window_final":true,"generated_at":"2026-09-24T06:00:02Z"}} La última línea del flujo es un _meta objeto:
| Parámetro | Descripción |
|---|---|
next_cursor | Cursor para la siguiente llamada de exportación. |
rows | Número de registros escritos en este flujo. |
complete | false significa que el flujo se detuvo antes de agotar el rango - reanuda con cursor=next_cursor. |
window_final | Mismo significado que en /records: true solo si el end de la exportación es anterior o igual a safe_until. |
generated_at | Momento en que se escribió esta línea. |
stopped_because | Aparece cuando complete es false: row_cap, scan_cap (el flujo recorrió su rango máximo de id sin llenarse; habitual con un filtro estrecho), time_cap, query_error o window_not_final. |
GET /summary - estadísticas preliminares y anomalías
GET /summary
Totales, los modelos y claves principales por coste, y una tasa de error para el rango, más una anomalies lista (high_error_rate, rate_limited, data_not_final, rollup_delayed), cada una con una gravedad info o warning y un mensaje legible. Los números de un rango no finalizado todavía pueden cambiar - ver más abajo.
| Parámetro | Tipo | Descripción |
|---|---|---|
start | string | Inicio del rango. Por defecto, 30 días antes de end. |
end | string | Fin del rango. Por defecto, ahora. |
api_key_id | integer | Filtra por uno o más id de clave API. Repetible; solo acota, nunca amplía. |
model | string | Filtra por uno o más id de modelo. Repetible. |
models_count y keys_count dan los totales detrás de top_models y top_keys, que listan como máximo los 20 principales por coste. El rango por defecto son los 30 días antes de end cuando se omite start .
Ejemplo de solicitud
curl "https://synthorai.io/api/v1/billing/summary?start=2026-09-17&end=2026-09-24" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{
"data": {
"totals": { "requests": 48210, "cost_usd": 132.55, "error_rate": 0.021 },
"models_count": 6,
"keys_count": 3,
"top_models": [ { "model": "claude-sonnet-5", "cost_usd": 88.10, "byok_list_price_usd": 0 } ],
"top_keys": [ { "api_key_id": 42, "api_key_name": "prod-backend", "cost_usd": 61.30, "byok_list_price_usd": 0 } ],
"anomalies": [
{ "type": "data_not_final", "severity": "info", "message": "The last 2 hours of this range are not yet final." }
]
},
"meta": {
"generated_at": "2026-09-24T06:00:02Z",
"data_complete_until": "2026-09-24T04:00:00Z"
}
} GET /freshness - grado de integridad de los datos
GET /freshness
Devuelve data_complete_until, latest_recorded_at, safe_until y pipeline_lag_seconds - consúltalo antes de tratar un rango recién obtenido como definitivo. safe_until es el recorded_at más reciente que un llamador puede considerar completo; filas más recientes pueden estar aún llegando.
Ejemplo de solicitud
curl "https://synthorai.io/api/v1/billing/freshness" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{
"data": {
"data_complete_until": "2026-09-24T04:00:00Z",
"latest_recorded_at": "2026-09-24T05:58:31Z",
"safe_until": "2026-09-24T05:58:31Z",
"pipeline_lag_seconds": 47
},
"meta": { "generated_at": "2026-09-24T06:00:02Z" }
} GET /balance - lo que el workspace puede gastar ahora
GET /balance
Devuelve el saldo disponible actual del workspace, la misma cifra que muestra la consola. Pensado para consultas periódicas: cada 30 a 60 segundos es suficiente. Tiene su propio límite de tasa, así que consultarlo no consume el presupuesto de los endpoints de datos, y responde aunque el saldo sea cero.
Ejemplo de solicitud
curl "https://synthorai.io/api/v1/billing/balance" \
-H "Authorization: Bearer sk-syn-bill-..." Ejemplo de respuesta
{
"data": {
"available_usd": 1284.37,
"debt_usd": 0,
"source": "live",
"voucher": null,
"scheduled_credits": [
{ "amount_usd": 250, "release_at": "2026-10-01T00:00:00Z" }
],
"scheduled_credit_total_usd": 250
},
"meta": { "generated_at": "2026-09-24T06:00:02Z", "currency": "USD" }
} | Parámetro | Descripción |
|---|---|
available_usd | El saldo general contra el que se cobran las solicitudes en este momento. Nunca por debajo de 0. Puede subir brevemente al liquidarse solicitudes en curso; para el gasto, usa /usage o /records. |
debt_usd | El saldo pendiente: el consumo por encima de lo acreditado. 0 si no hay nada pendiente. Una recarga lo paga primero; solo el resto pasa a available_usd. |
source | live: leído del registro en tiempo real. delayed: no se pudo acceder al registro y el valor viene de una copia en base de datos que puede ir unos 30 segundos por detrás. |
voucher | Crédito promocional solo para generación de imágenes (applies_to), en los patrones de modelo listados y hasta expires_at; las demás solicitudes usan solo available_usd, y este crédito no forma parte de él. null si no hay; active es false una vez vencido. |
scheduled_credits | Créditos ya acordados que llegan por tramos (amount_usd, release_at); cada tramo se suma al saldo unos 10 minutos después de release_at como mucho, no antes. scheduled_credit_total_usd es su suma. |
Errores
Cada error es {"error": {"type", "message", "hint"}}:
{
"error": {
"type": "range_too_large",
"message": "the requested range exceeds this endpoint's cap",
"hint": "narrow start/end to at most 31 days, or sync /records with a cursor instead"
}
} | Tipo | Estado HTTP | Significado |
|---|---|---|
invalid_parameter | 400 | Falta un parámetro de consulta, está mal formado o está fuera de rango. |
range_too_large | 400 | El rango temporal o la ventana de id solicitados son mayores de lo que permite el endpoint; el hint indica el límite correspondiente. |
start_too_recent | 400 | Una ventana start/end empieza después de safe_until, donde su límite inicial aún no es estable. Reintenta tras el tiempo de la cabecera Retry-After o sincroniza con un cursor. |
wrong_key_kind | 401 | La credencial no es una clave de facturación, o se usó una clave de facturación fuera de estos endpoints. |
authentication_error | 401 | La clave falta, no es válida, ha caducado, o está bloqueada por su lista blanca de IP. |
key_owner_not_authorized | 403 | El creador de esta clave ya no es el propietario del workspace. Las claves de facturación son exclusivas del propietario y dejan de funcionar en el momento en que cambia la propiedad; el nuevo propietario debe crear la suya. |
rate_limited | 429 | Más de 60 solicitudes por minuto en este workspace. Vuelve a intentarlo después del tiempo indicado en la cabecera Retry-After. |
too_many_concurrent_requests | 429 | Más de 3 solicitudes simultáneas en este workspace, u otra exportación ya en curso. |
replica_unavailable | 503 | Este proceso no tiene ninguna réplica de lectura configurada; la API nunca recurre a la primaria. |
query_timeout | 503 | La consulta superó el tiempo de espera de 10 segundos o el plazo de 15 segundos. Acota el rango e inténtalo de nuevo. |
internal_error | 500 | Un error inesperado del servidor. Vuelve a intentarlo y notifícalo si persiste. |
Límites de tasa y topes
| Protección | Valor |
|---|---|
| Límite de tasa | 60 solicitudes por minuto por workspace, una ventana deslizante compartida en todas las instancias. |
| Concurrencia | 3 solicitudes simultáneas por workspace en cada servidor, y un flujo de exportación por workspace en cada servidor. |
| Consulta del saldo | /balance: 60 solicitudes por minuto y 2 simultáneas por workspace, contadas aparte de los demás endpoints. |
| Topes de rango | /usage: 31 días con granularidad hour, 366 días con day. /summary: hasta 366 días. /records y la exportación: hasta 31 días. |
| Topes de página | /usage devuelve hasta 5000 filas por página, /records hasta 1000. La exportación transmite páginas de 2000 y se detiene en 1 000 000 de filas - reanudable con cursor. También se detiene tras recorrer 2 000 000 de id de registro (stopped_because=scan_cap) y ajusta su ritmo a la base de datos; reanuda con cursor. |
| Caché | Cada respuesta lleva Cache-Control: no-store. |
Nunca se devuelve: channel_id, upstream_request_id, usage_raw, other, content, ip_address, user_agent, cost_detail.
Evitar huecos de tiempo
Las solicitudes se escriben en el registro de forma asíncrona tras finalizar (normalmente segundos, más tiempo si hay acumulación), y el rollup por hora que leen /usage y /summary se ingiere por lotes. Las horas más recientes siempre están un poco incompletas. /usage y /summary también suman las solicitudes escritas desde la última ingesta (meta.live_tail es true); si el agregado va demasiado atrasado para ello, meta.live_tail es false y los buckets recientes están incompletos.
data_complete_until es el más temprano de estos dos puntos: la marca de agua del rollup por hora menos su retraso de pipeline actual, o la solicitud más antigua que aún no se ha incorporado al rollup - menos un margen de seguridad de 30 minutos, redondeado hacia abajo a la hora.
Un bucket final es estable, con una excepción: el trabajo de reconciliación diaria todavía puede corregir una hora dentro de las 48 horas si detecta una desviación. /records es siempre la fuente de verdad para las cifras exactas de una solicitud.
Cada fila de /usage lleva un indicador final : true , válido en cuanto su bucket termina antes o en data_complete_until (valor de /freshness). Un bucket final ya no cambia nunca.
- Agregados: haz upsert de cada fila en tu propio almacén por (bucket_start, api_key_id, model), y vuelve a obtener cualquier bucket mientras siga siendo final: false. Nunca calcules un delta restando un total antiguo de uno nuevo.
- Filas en bruto: sincroniza por cursor, nunca por tiempo. Conserva next_cursor y reanuda desde ahí en la siguiente llamada, y elimina duplicados de las filas recibidas por record_id (un id opaco estable por fila) en caso de que una página repetida vuelva a entregar la misma fila. Las filas de menos de unos 5 minutos se retienen - safe_until es la hora de la fila más recientemente registrada menos 5 minutos - de modo que una fila que aún se está confirmando nunca pueda colarse antes de una posición de cursor ya entregada, y nada cae en un hueco. Para contar solicitudes, cuenta los request_id distintos.
- Ventana acotada: si consultas un rango start/end en lugar de sincronizar por cursor, confía en que está completo solo cuando window_final sea true (solo es true si indicaste un end y este es anterior o igual a safe_until). Si window_final es false, vuelve a llamar más tarde con el mismo rango, o cambia a sincronizar por cursor. Una ventana debe empezar como muy tarde en safe_until; un inicio posterior devuelve 400 start_too_recent con una cabecera Retry-After.
Integración recomendada
- Facturación nocturna: llama a /usage una vez al día con granularity=day, y vuelve a obtener cualquier bucket que tu última ejecución todavía viera como final: false.
- Detalle por solicitud: llama a /records cada hora con cursor fijado al último next_cursor que guardaste, sigue paginando mientras has_more sea true, y elimina duplicados de las filas recibidas por record_id.
- Paneles: llama a /summary para el rango visible en lugar de agregar /records tú mismo - ya incluye la lista de anomalías.
¿Necesitas generar o gestionar claves API normales por programación en lugar de leer datos de facturación? Consulta Claves de provisioning.