Guía de integración
De cero a una lectura completada, y de ahí a producción. Lo que aporta esta página es el orden y
las decisiones: qué se hace antes de qué, y qué elegir en cada bifurcación. El detalle de cada
endpoint —parámetros, esquemas y ejemplos campo a campo— está en la referencia de la API,
que se genera del contrato; el documento en bruto es /openapi.yaml. Lo que hace
cada pantalla de la consola está en el manual de la consola.
Los ejemplos son curl y se pegan tal cual. Antes, dos variables:
export LINCE_HOST="https://el-host-del-servicio" # el que te hayamos dado
export LINCE_KEY="sk_test_tu_key_de_prueba" # de test: nunca una live para explorarLas API keys y los dos modos
Una API key es una cadena con prefijo de modo: sk_live_… o sk_test_…. Tras el prefijo van 43
caracteres aleatorios. El servicio no guarda el secreto: solo su SHA-256 y los doce primeros
caracteres, que son los que ves luego en la lista de keys.
Los dos modos comparten código y no comparten nada más:
sk_test_ | sk_live_ | |
|---|---|---|
| Ejecuta el motor real | Sí, el mismo | Sí |
| Se factura | No | Sí |
| Qué lecturas ve | Solo las hechas con keys test | Solo las hechas con keys live |
| Cuota que consume | limits.test_quota_per_month | limits.monthly_quota del plan |
| Necesita suscripción o saldo | No | Sí |
«Solo ve las suyas» es literal: pedir con una key test una lectura hecha en live responde
404 resource_not_found, igual que si fuera de otra cuenta. No
existe una key que vea las dos cosas.
Dónde se crean. En la consola, en la pantalla de API keys, que llama a
POST /api/admin/api-keys con un nombre y un modo. El campo secret de esa respuesta es la única
vez que el secreto existe fuera de tu código: si se pierde, no se recupera; se emite otra key. Una
key live se puede crear sin suscripción activa, pero no admitirá lecturas hasta que la haya.
Qué puede hacer cada key. Una key lleva uno o más alcances: read (las llamadas GET: lecturas,
uso y cuenta), write (enviar facturas y dar feedback) y delete (borrar lecturas). Al crearla en la
consola salen los tres marcados; desmarca lo que esa integración no necesite. Una llamada que pide un
alcance que la key no tiene responde 403 insufficient_scope y no hace
nada. Los alcances no se cambian después: se emite otra key y se revoca la vieja.
El número de keys activas por cuenta tiene tope (limits.max_active_api_keys); por encima, la
creación responde 422 api_key_limit_reached. Las revocadas
no cuentan.
Cómo se rota sin cortar el servicio. En este orden, y nunca al revés:
- Emite la key nueva y guárdala donde guardes los secretos.
- Despliega con la nueva. Las dos keys funcionan a la vez: no hay que cambiar nada de golpe.
- Comprueba en la lista de keys que la vieja ya no se usa. Cada key lleva un
last_used_atque se actualiza como mucho una vez por minuto: es una pista, no una auditoría, pero basta para ver si algo que creías apagado la sigue usando. - Revócala con
DELETE /api/admin/api-keys/{id}. Revocar es idempotente y tiene efecto en la petición siguiente: a partir de ahí esa key responde401 invalid_api_key.
Autenticación
La key se envía en una de estas dos cabeceras:
Authorization: Bearer sk_test_tu_key_de_prueba
X-Api-Key: sk_test_tu_key_de_pruebaSi llegan las dos, prevalece X-Api-Key. Tiene un motivo: donde un IAP ocupa Authorization
con su propio token, es la única forma de que tu key llegue entera.
Sin key, con una desconocida o con una revocada:
401 invalid_api_key. Con la cuenta suspendida:
403 account_suspended: lo que ya está leído se puede seguir
consultando; lo que no se admite son lecturas nuevas.
Toda la API está bajo /api: las integraciones llaman a /api/v1/… (con API key) y la
administración de la cuenta a /api/admin/… (como persona, con un ID token de Identity Platform, no
con una key). Las rutas sin ese prefijo —/v1/… y /admin/…— siguen respondiendo, pero son un
alias transitorio que existe para no romper a quien ya las llamaba: no las uses en una integración
nueva.
La primera llamada, que no gasta ninguna lectura y confirma que la key vale:
curl -sS "$LINCE_HOST/api/v1/account" \
-H "Authorization: Bearer $LINCE_KEY" \
-H "Accept: application/json"Ahí dentro, en limits, están todos los topes de tu cuenta ya resueltos. Vuelve a esta llamada
cuando algo responda 429: los números de esta página son los del perfil, y los de tu cuenta son
los que devuelve el endpoint.
El ciclo de una lectura, de punta a punta
Una lectura funciona así: subes un fichero, el servicio lo encola, un worker lo lee y la lectura
termina en completed o en failed. Los cuatro estados son queued, processing, completed y
failed; los dos últimos son finales.
1. Subir
POST /api/v1/invoices con multipart/form-data. Solo PDF. Un fichero por lectura, de uno a N
ficheros por petición, y la respuesta es siempre una lista, también con un solo fichero:
curl -sS -X POST "$LINCE_HOST/api/v1/invoices" \
-H "Authorization: Bearer $LINCE_KEY" \
-H "Accept: application/json" \
-F "files[]=@factura-marzo.pdf" \
-F "wait=30" \
-F 'metadata={"batch":"lote-3"}'Campos del cuerpo de la petición, además de files[]:
| Campo | Qué es |
|---|---|
wait | Segundos (0-60) que la respuesta espera a que las lecturas terminen. Por defecto, 0 |
metadata | Un string JSON con un objeto plano, hasta 20 campos escalares. Se devuelve tal cual y filtra el listado |
origin | Etiqueta tuya para el origen de la petición (^[a-z0-9][a-z0-9._-]*$, hasta 64). Se devuelve en origin y filtra el listado |
metadata va como string porque multipart no transporta objetos: si mandas algo que no es un
objeto JSON, la petición entera responde
422 validation_failed con param: metadata.
Qué hacemos con el fichero. Se lee y se te devuelve la lectura. No entra en el conjunto con el que afinamos el motor, que es material propio reunido aparte y que no se alimenta del tráfico del servicio. La separación la sostiene un test que falla si alguien conecta las dos cosas; el detalle, en el manual.
Cuánto se guarda. El PDF y la lectura se quedan: hoy no hay borrado automático ni fecha de caducidad, y se conservan en la UE, cifrados en reposo. Es la política de hoy, no una promesa perpetua. Si necesitas que eliminemos algo, se hace a mano y no es inmediato — el detalle, en el manual.
2. Esperar
Respuesta con wait=30, cuando la lectura terminó dentro de esos segundos: 200 (recortada; el
objeto completo, campo a campo, está en la referencia).
{
"object": "list",
"data": [
{
"id": "rd_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd",
"object": "reading",
"status": "completed",
"mode": "test",
"api_key": { "id": "key_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd", "name": "produccion", "prefix": "sk_test_EJEM" },
"created_at": "2026-09-05T22:42:36Z",
"completed_at": "2026-09-05T22:42:36Z",
"duration_ms": 1,
"file": {
"name": "factura-marzo.pdf",
"bytes": 218,
"pages": 1,
"sha256": "c012d7f843bf9bfbe128cb3862eaf8ca85bf2b3f5634c1c347aab42646a83e2c",
"stored": true
},
"engine": { "version": "3.0", "model": "claude-sonnet-5" },
"review": { "needed": false, "mode": "flag", "threshold": 0.85, "confidence": 1, "resolution": null },
"invoice": {
"type": "electricity",
"bills": [
{
"id": "inv_01a073bc-ee9d-7355-864b-62037038536c",
"cups": "ES0031300666852008VE",
"rate": "2.0TD",
"initial_date": "2026-07-01",
"final_date": "2026-07-31",
"days_billed": 31,
"total_cost": 58.43,
"consumption": { "p1": 37, "p2": 41, "p3": 122, "p4": 0, "p5": 0, "p6": 0 }
}
]
},
"billing": { "price_eur": 0 },
"metadata": { "batch": "lote-3" },
"origin": null,
"error": null
}
]
}El ejemplo es una factura de electricidad. Cómo cambian rate, consumption y los precios en una
de gas o en una dual, más abajo.
Y si el tiempo se agota con alguna lectura todavía en curso, 202 con el mismo cuerpo y la lectura en el estado en que esté:
{
"object": "list",
"data": [
{
"id": "rd_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd",
"object": "reading",
"status": "queued",
"mode": "test",
"completed_at": null,
"duration_ms": null,
"engine": null,
"review": null,
"invoice": null,
"billing": { "price_eur": 0 },
"error": null
}
]
}Esa es la regla entera del código de estado: 200 si terminaron todas, 202 si alguna sigue en curso. Nunca es un error, y el 202 tampoco significa que algo haya ido mal.
3. Recoger
Con la lectura terminada, GET /api/v1/invoices/{id} devuelve el mismo objeto Reading, y
GET /api/v1/invoices la lista de la cuenta en el modo de la key, con filtros (status,
created_after, created_before, metadata[campo]=valor, origin, review_needed) y paginación
por cursor.
curl -sS "$LINCE_HOST/api/v1/invoices/rd_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd" \
-H "Authorization: Bearer $LINCE_KEY" \
-H "Accept: application/json"4. Borrar
Si has prometido a tus usuarios que su factura desaparece pasado un plazo, DELETE /api/v1/invoices/{id}
borra la lectura de verdad: las facturas con los datos del titular, el feedback, las entregas de webhook
y el PDF. Responde 204 y no se puede deshacer. Lo facturado se queda: el saldo y el uso diario no
cambian.
Una lectura que aún se está leyendo, o cuyo coste no se ha liquidado (unos minutos tras cerrarse),
responde 422 reading_not_settled sin borrar nada. Una que no existe o ya se borró responde 404: si
borras por calendario, cuenta un 404 como hecho.
Hace falta una key con el alcance delete. Cada borrado queda apuntado —qué key, qué lectura, cuándo y
con qué resultado, sin ningún dato del titular— y se ve en la pantalla de API keys de la consola.
curl -sS -X DELETE "$LINCE_HOST/api/v1/invoices/rd_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd" \
-H "Authorization: Bearer $LINCE_KEY"Las tres formas de recibir el resultado
Son tres, y la elección depende del volumen y de quién está esperando.
wait en la propia subida (0-60 s). La respuesta se queda abierta hasta que todas las lecturas
de esa petición terminan o se acaba el tiempo. Es una sola llamada y no hace falta guardar nada.
Úsalo cuando hay una persona delante esperando el resultado y son pocos ficheros. Ten en cuenta
que mantiene abierta la conexión —tuya y nuestra— todo ese rato: wait=60 sobre un lote grande es
un worker ocupado un minuto en cada lado.
Consultar el detalle en bucle (polling). Guarda el id que te devolvió el 202 y consulta
GET /api/v1/invoices/{id} hasta que status sea completed o failed. Ojo: ese endpoint
responde al momento con lo que hay, no mantiene la conexión esperando; lo único que espera es el
wait de la subida. Sirve para reanudar un trabajo que se cortó, o para vigilar unos pocos ids. Lo
que acota el ritmo es tu límite de peticiones por minuto (limits.rate_per_minute), así que
espacia las consultas y no consultes sin pausa entre llamadas.
Webhook. El servicio te hace un POST en cuanto la lectura termina. Es el recomendado para
volumen: ni conexiones abiertas ni polling. La URL y su secreto se fijan con
PUT /api/admin/webhook, y cada PUT con URL genera un secreto nuevo (whsec_…) que se enseña una
sola vez.
Lo imprescindible, con las instrucciones completas —verificación de firma en PHP y en Node incluida— en la referencia:
- El payload es el mismo objeto
Readingque devuelveGET /api/v1/invoices/{id}. No hay un formato aparte para el webhook. - Dos eventos y no más:
reading.completedyreading.failed. - Cabeceras:
X-Lince-Event,X-Lince-Delivery-Id,X-Lince-Signaturey unUser-Agentfijo,Lince-Webhooks/1.0. - La firma es
t=<unix>,v1=<HMAC-SHA256>sobre<t>.<raw body>con el secreto de la cuenta. Firma los bytes tal como llegaron: deserializar y volver a serializar los cambia y la firma deja de cuadrar. - Entregado = un
2xxen menos de 10 segundos. El cuerpo de tu respuesta se ignora. Responde rápido y haz el trabajo después. - Un
3xxes un intento fallido: las redirecciones no se siguen. Mover el endpoint con un301no funciona; hay que cambiar la URL conPUT /api/admin/webhook. - Cinco intentos, cuatro esperas: 1 min, 5 min, 30 min y 2 h. Tras el quinto, la entrega se marca como fallida y se abandona —algo más de dos horas y media después del primero—.
- Si tu receptor estuvo caído más tiempo, te las reenviamos. Escríbenos con los
X-Lince-Delivery-Idque te faltan o con la franja horaria en que estuvo caído, y las volvemos a mandar: con el mismo id, así que tu deduplicación sigue valiendo, firmadas con tu secreto actual y a la URL que tengas configurada en ese momento. No hay endpoint para pedirlo tú, y no hace falta esperar:GET /api/v1/invoiceste devuelve esas lecturas igual. - Deduplica por
X-Lince-Delivery-Id, que es el mismo en los cinco intentos. Una entrega puede repetirse porque tu2xxllegó tarde. - Se manda una entrega por lectura terminada, y nada más: lo que cambie después no emite un segundo evento.
Lo razonable es combinar: webhook para el caso normal y un barrido periódico con
GET /api/v1/invoices?status=… como red, que es además lo que te cubre sin esperar a nadie cuando
una entrega agota sus cinco intentos.
Idempotencia
Manda una cabecera Idempotency-Key en cada POST /api/v1/invoices:
curl -sS -X POST "$LINCE_HOST/api/v1/invoices" \
-H "Authorization: Bearer $LINCE_KEY" \
-H "Accept: application/json" \
-H "Idempotency-Key: lote-3-intento-1" \
-F "files[]=@factura-marzo.pdf" \
-F "wait=30"Las reglas:
- El ámbito es la API key, y el valor se recuerda durante 24 horas. Dos keys distintas pueden usar la misma cadena sin colisionar.
- Entre 1 y 255 caracteres.
- Repetirla con el mismo cuerpo devuelve las mismas lecturas, sin crear ninguna nueva y sin volver a cobrar.
- Repetirla con otro cuerpo responde
422 idempotency_key_reusedconparam: Idempotency-Key. - «El cuerpo» aquí son los SHA-256 de los ficheros en orden, la
metadatay elorigin. Cambiar solo eloriginya es otro cuerpo.
Por qué importa. Un timeout de red al subir un lote no te dice si la petición llegó. Sin la cabecera solo tienes dos malas opciones: no reintentar y perder el lote, o reintentar y crear —y pagar— las lecturas dos veces. Con ella, reintentar es seguro por definición, y el reintento se reconoce antes de consumir concurrencia, cuota o saldo. Usa un valor por intento lógico, no por petición HTTP: si reintentas el mismo lote, el valor es el mismo.
Un fallo de lectura no es un error HTTP
Es la distinción en la que más se equivoca una integración nueva.
Lo que va mal en la petición —falta files, el wait está fuera de rango, no hay saldo—
devuelve el objeto error que se describe abajo, y no se crea ninguna lectura. Lo que va mal en
un fichero no es un error HTTP: la petición fue perfectamente bien y esa lectura concreta se
crea directamente como failed, dentro de un 200, con su motivo en error:
{
"id": "rd_01a073bc-ee8e-70a3-ad0d-be4ebf7dd7fd",
"status": "failed",
"file": { "name": "nope.txt", "pages": null, "stored": false },
"engine": null,
"review": null,
"invoice": null,
"billing": { "price_eur": 0 },
"error": {
"type": "processing",
"code": "unsupported_file_type",
"message": "Only PDF files are accepted"
}
}En una petición de N ficheros la lista respeta el orden de la petición y mezcla sin problema
lecturas completed y failed. Recorre data[] y mira status y error.code; el código HTTP
por sí solo no te dice si una factura se leyó.
Los cuatro que son culpa del fichero, y ninguno tiene coste (billing.price_eur es 0 y no hay
cargo):
error.code | Qué pasó |
|---|---|
unsupported_file_type | No es un PDF |
file_too_large | Pesa más que limits.max_file_mb |
too_many_pages | Tiene más páginas que limits.max_pages |
unreadable_document | El PDF está dañado y no se abre, o se abre y no trae una factura legible |
Los tres primeros —y el cuarto, cuando el PDF ni siquiera se deja abrir— los decide una
comprobación barata antes de llegar al motor: esa lectura ya es final cuando se crea, pongas wait
o no. El unreadable_document que tarda es el otro caso, el del documento que se abrió y no tenía
dentro una factura. Hay además tres códigos que son nuestros, no tuyos:
model_unavailable,
engine_error y
never_started. Esos sí admiten volver a subir el mismo fichero.
El formato de error
Uno solo para toda la API. Cuando la petición no sale adelante, el cuerpo es siempre este:
{
"error": {
"type": "invalid_request",
"code": "validation_failed",
"message": "The files field is required.",
"param": "files",
"docs_url": "https://seleya.es/docs/errors#validation_failed",
"request_id": "req_01a073bc-eeaf-73b6-af43-1ae071158b3b"
}
}codees lo estable: la cadena a la que enganchar tuswitch. Elmessageva en inglés y es para máquinas y para logs; puede cambiar de redacción sin previo aviso.paramdice qué campo tiene la culpa en un 422 (files,files.2,wait,metadata,Idempotency-Key…), y esnullcuando no aplica.docs_urlapunta a la entrada concreta de la tabla de errores, no a la página en general. El host es el del servicio que te respondió.request_ides elreq_…de esa petición.
X-Request-Id llega en TODA respuesta, tenga éxito o no, y en un error es exactamente el mismo
valor que error.request_id. Lo emitimos nosotros siempre: si mandas tú la cabecera, se ignora. Y
lo lleva todo, incluidas las respuestas que nunca llegan a tu ruta —un 404 de una ruta que no
existe, o un 413 por un cuerpo descomunal—.
Qué mandar a soporte: el request_id. Con él encontramos la petición en los logs. Guárdalo en
tu propio log junto a cada llamada, también en las que salen bien: el día que haya que reconstruir
qué pasó, es el único dato que une tu traza con la nuestra.
Cuotas y límites
Lo primero: los límites de tu cuenta te los dice GET /api/v1/account en limits. Los de aquí
abajo son los valores de cada perfil, que es de donde salen si el plan o la cuenta no los
sobrescriben.
limits.* | Perfil default | Perfil marketer |
|---|---|---|
rate_per_minute | 30 | 120 |
max_files_per_request | 5 | 20 |
max_file_mb | 8 | 10 |
max_pages | 10 | 10 |
max_concurrent_readings | 5 | 20 |
test_quota_per_month | 50 | 200 |
max_active_api_keys | 10 | 20 |
La cuota mensual de las keys live (monthly_quota), las lecturas incluidas y los precios los pone
el plan. Aquí no se repiten a propósito, para que esta página no se quede desfasada el día que
cambien: el catálogo de planes públicos lo sirve GET /console/config (público, sin autenticación)
y lo enseña la pantalla de Facturación de la consola, y lo que aplica a tu cuenta está en
GET /api/v1/account.
La admisión es todo o nada
Antes de crear ninguna lectura, el POST comprueba en este orden concurrencia → cuota → tope
diario → saldo, contando los N ficheros de la petición. La primera protección que salta responde
por la petición entera: o entran las N lecturas o no entra ninguna. Nunca te vas a encontrar
medio lote creado.
Qué se reintenta y qué no
| Código | HTTP | ¿Reintentar? |
|---|---|---|
rate_limit_exceeded | 429 | Sí. Respeta Retry-After; la respuesta trae también X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset |
concurrency_exceeded | 429 | Sí, con backoff. Es transitorio: espera a que terminen las lecturas que ya tienes en curso. Ojo, no trae Retry-After: el backoff lo pones tú |
quota_exceeded | 429 | No dentro del mes. Se agotaron las lecturas contratadas: reintentar no las devuelve |
daily_limit_exceeded | 429 | No hoy. Se reinicia a medianoche en Europe/Madrid |
insufficient_credit | 402 | Nunca. Hace falta saldo; solo afecta a live |
payment_required | 402 | Nunca. La suscripción no está activa o el periodo de prueba terminó. Las keys test siguen funcionando |
account_suspended | 403 | Nunca |
validation_failed | 422 | Nunca. Arregla la petición; param dice dónde |
idempotency_key_reused | 422 | Nunca con ese cuerpo |
payload_too_large | 413 | Nunca. Parte el lote |
invalid_api_key | 401 | Nunca |
internal_error | 500 | Sí, con backoff. Es nuestro. Guarda el request_id |
La regla que resume la tabla: los 429 de rate limit (rate_limit_exceeded,
concurrency_exceeded) se reintentan con backoff; los 429 de presupuesto (quota_exceeded,
daily_limit_exceeded) y los 402 no se arreglan reintentando, por rápido o por despacio que lo
hagas. Un bucle que trate los cuatro 429 igual se pasará el resto del día reintentando en vano.
Dos avisos sobre los topes que más confunden. Pasarse de max_files_per_request no es un
código de cuota, es
422 validation_failed con param: files —un error de forma, no
de presupuesto—, y max_concurrent_readings cuenta lecturas en curso, no peticiones: veinte
ficheros en una sola petición ocupan veinte huecos.
La dirección llega en dos campos
Cada factura trae la dirección del suministro en dos campos que no se sustituyen:
supply_addresses lo que el motor leyó del papel, sin tocar. Si la factura imprime una errata, aquí está la errata.addresses la dirección normalizada por el servicio, con su procedencia (source) y su bloquegeocon coordenadas y precisión.
Para casar con tus datos, supply_address. Para dibujar un mapa o agrupar por municipio, address.
Las dos reglas que hay que saber antes de programar contra ellas: address.geo es siempre un
objeto —sin coordenadas, los campos van a null y fallback_reason explica por qué—, y una
lectura nunca falla porque no se haya podido geolocalizar.
El resto —qué campos trae cada uno, qué significa cada source, cada precision y cada
fallback_reason— está en la sección de dirección y geolocalización de la referencia, que
es donde se mantiene.
Gas y facturas duales
El motor lee facturas de electricidad, de gas y duales —un solo documento que factura
los dos suministros—. invoice.type dice cuál es, e invoice.bills trae una factura por
suministro: una en electricidad o gas, y dos en una dual, cada una con su propio invoice_type y
su propio CUPS, porque el punto de suministro de gas tiene el suyo. El ciclo de la lectura es el
mismo; lo que cambia es qué campos traen valor:
| Electricidad | Gas | |
|---|---|---|
rate | 2.0TD, 3.0TD o 6.1TD | RL.1 a RL.5 |
consumption | Por periodo: p1…p6 | Un solo total, en kWh |
| Término fijo | power_prices (potencia contratada por periodo) y costs.power | fix_term_prices (precio por día y días facturados) y costs.fix_term |
costs.tax_energy | Impuesto especial sobre la electricidad | Impuesto sobre hidrocarburos |
La misma lectura de antes si el PDF hubiera sido una factura de gas, recortada igual:
{
"invoice": {
"type": "gas",
"bills": [
{
"id": "inv_01a073bc-f1a2-7c3e-9d4b-5e6f7a8b9c0d",
"invoice_type": "gas",
"cups": "ES0230000000000001SR",
"rate": "RL.2",
"initial_date": "2026-07-01",
"final_date": "2026-07-31",
"days_billed": 31,
"total_cost": 35.2,
"consumption": { "total": 512 },
"costs": { "energy": 18.4, "fix_term": 9.3, "tax_energy": 1, "tax_iva": 6.15, "service": 0.35, "power": 0 },
"fix_term_prices": [{ "price": 0.3, "unit": "day", "duration": 31, "discount": 0 }],
"power_prices": []
}
]
}
}En una dual, type es "dual" y bills trae dos objetos como estos, el de electricidad y el de
gas. Si solo te interesa uno de los dos suministros, filtra por el invoice_type de cada factura,
no por invoice.type.
De test a live
Si has llegado hasta aquí con una key test, ya tienes la integración entera. Pasar a producción
no cambia una línea de tu código: cambia la key. Antes de hacerlo, repasa esto:
- La suscripción está activa. Una key
livesin suscripción activa, o con el periodo de prueba terminado, responde402 payment_requireden cada llamada. Latestseguiría funcionando, así que esto no se descubre probando en test. - Hay saldo. Cada lectura
livelo descuenta, y la admisión mira el saldo contra las N lecturas de la petición: si no llega, elPOSTresponde402 insufficient_credity no crea ninguna. - Los límites de
liveson otros.test_quota_per_monthno tiene nada que ver con elmonthly_quotadel plan. Vuelve a mirarGET /api/v1/accountcon la key nueva. - Las lecturas de test no cruzan. No esperes ver en
livenada de lo que probaste entest: son dos mundos separados, y el id de una lectura de test responde 404 a una key live. - El webhook es de la cuenta, no del modo. Lo que ya sabrás si has probado con él: las
lecturas
testtambién generan entregas. Si la URL apunta a tu entorno de pruebas, cámbiala antes, o las entregas de producción irán allí. - Estás guardando el
request_idde cada llamada en tu log. - Tus reintentos distinguen los 429 de rate limit de los de presupuesto, y mandan
Idempotency-Key.
Para vigilar el uso, GET /api/v1/usage da el uso diario de la cuenta en el modo de la key
—días cortados a medianoche en Europe/Madrid; por defecto, el mes en curso—.
Si algo no cuadra
Escríbenos con el request_id de la petición concreta. Con eso solemos tener bastante; sin eso,
casi nunca. El code del error y la hora aproximada (di en qué zona horaria) ayudan, pero el
request_id es lo que identifica tu petición entre todas.
Esta página se ha escrito y verificado contra el código del servicio. Los límites, códigos, tiempos y nombres de campo que aparecen aquí salen de ese código, no de una nota anterior: si alguno no se comporta como dice esta guía, es un fallo nuestro y queremos saberlo.