Contribuyentes
Dar de alta a quien emite, dejarlo listo para facturar y darlo de baja.
Un contribuyente es la persona o la empresa a cuyo nombre se emite: su
cédula, su certificado de firma, sus credenciales de Hacienda, sus sucursales y
sus terminales. Toda emisión lleva el issuerId de uno.
Una cuenta puede tener varios. Cuando tiene uno solo podés omitir el issuerId
y las rutas de configuración lo resuelven solas; en cuanto hay dos, omitirlo es
400 con issuer_id_required.
Todo lo de esta página —alta, certificado, credenciales, sucursales, terminales, régimen, marca, baja— también se hace desde el panel, en Contribuyentes → el que quieras configurar. Lo de abajo es para cuando lo vas a manejar por código, o para automatizar el alta de muchos.
Darlo de alta
curl -X POST https://api.timbre.cr/internal/v1/issuers \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{
"identification": "3101123456",
"tradeName": "Ferretería OSA",
"taxRegime": "normal",
"certificateBase64": "MIIK…",
"certificatePassword": "…"
}'El .p12 viaja en base64 y su PIN aparte. taxRegime es normal,
simplificado o zona_franca, y por defecto normal.
Responde 201 con el perfil: su id —el issuerId que vas a mandar de ahí en
adelante—, su active, y los metadatos que Timbre leyó del certificado
(subject, issuer, serialNumber, fingerprint, notBefore, notAfter).
La llave privada no vuelve nunca.
El certificado se valida al recibirlo, no al firmar el primer documento: un
.p12 que no abre con ese PIN es certificate_password_invalid, uno vencido es
certificate_expired, uno que todavía no entra en vigor es
certificate_not_yet_valid, y uno cuya cédula no es la que declaraste es
certificate_identification_mismatch. Todos son 400.
Leerlo de vuelta
curl https://api.timbre.cr/internal/v1/issuers \
-H "Authorization: Bearer timbre_sk_…"El mismo perfil que devolvió el alta, con sus metadatos de certificado. Sirve
para una cuenta con un solo contribuyente —si tiene varios, hay que decir
cuál con issuerId como parámetro de consulta, y omitirlo es
issuer_id_required. Para ver todos a la vez, sin el certificado, es
GET /list de la siguiente sección.
Un contribuyente desactivado se lee igual que uno activo: por eso active
importa más que si la ruta respondió. Que exista el perfil no dice que pueda
facturar.
Cuántos podés tener
La cuenta tiene un tope de contribuyentes activos. Al alcanzarlo, dar de
alta otro responde 409 con issuer_limit_reached.
curl https://api.timbre.cr/internal/v1/issuers/list \
-H "Authorization: Bearer timbre_sk_…"{
"issuers": [
{
"id": "…",
"identification": "3101123456",
"tradeName": "Ferretería OSA",
"taxRegime": "normal",
"active": true,
"hasTerminal": true,
"hasCredentials": true,
"credentialStatus": "accepted"
}
],
"maxIssuers": 25
}maxIssuers viaja en cada respuesta a propósito: el tope lo guarda Timbre y se
puede subir en cualquier momento, así que leelo de acá en vez de tenerlo escrito
en tu lado.
Acepta environment como parámetro de consulta —sandbox o production, y
sandbox si lo omitís— porque dos de los campos dependen del ambiente:
| Campo | Qué es |
|---|---|
active | Si el contribuyente puede emitir del todo |
hasTerminal | Si tiene al menos un terminal, en cualquier sucursal |
hasCredentials | Si hay credenciales guardadas para ese ambiente |
credentialStatus | Qué dijo Hacienda de esas credenciales |
Las sucursales y las terminales son compartidas entre ambientes; las credenciales no. Por eso un contribuyente listo en sandbox no está listo en producción por herencia: le faltan las credenciales del otro ambiente. Eso es Producción.
Credenciales de Hacienda
Son el usuario y la contraseña del ATV con los que se presenta el comprobante. Se guardan por ambiente.
curl -X PUT https://api.timbre.cr/internal/v1/issuers/environments/sandbox/credentials \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{
"haciendaUsername": "cpf-00-0000-0000",
"haciendaPassword": "…"
}'Guardar no es solo guardar: Timbre le pide un token al proveedor de identidad de
Hacienda con esas credenciales, sin caché, y te devuelve qué contestó en
verification.outcome.
outcome | Qué pasó |
|---|---|
accepted | Hacienda entregó un token |
rejected | Hacienda las rechazó con su propio invalid_grant |
unreachable | No hubo veredicto |
unreachable es un hecho sobre ese intento, no sobre las credenciales: se
guardaron igual y no queda archivado ningún veredicto. Cubre dos cosas: que no
hayamos podido preguntar —la red, el proveedor de identidad caído—, y que
Hacienda haya contestado algo que no es un juicio sobre estas credenciales, como
una mala configuración de nuestro lado.
Volvé a guardarlas: si era lo primero, se vuelve a preguntar y el veredicto
aparece. Si después de reintentar credentialStatus sigue en unverified, el
problema puede estar de nuestro lado y reintentar no lo va a mover; escribinos.
Los cuatro estados guardados
credentialStatus, en la lista, es lo que quedó archivado:
| Estado | Qué significa |
|---|---|
absent | No hay credenciales guardadas para ese ambiente |
unverified | Hay, pero Hacienda nunca dio un veredicto sobre ellas |
accepted | Este contribuyente puede presentar ahora mismo |
rejected | Hacienda las rechazó |
accepted no quiere decir «la contraseña se acaba de comprobar». Quiere
decir que se consiguió un token, y ese token puede venir de uno en caché o
renovado. Si necesitás fechar el veredicto, la lectura del ambiente —GET /internal/v1/issuers/environments/{environment}/credentials— lo trae con su
verifiedAt, o null si todavía no hay ninguno.
Solo rejected es un veredicto en contra. unverified es incertidumbre nuestra
—normalmente el proveedor de identidad no respondió—, no una falta del
contribuyente, y tratarlo como un rechazo deja sin facturar a alguien cuyas
credenciales están bien.
Sucursales y terminales
branchCode y terminalCode no se inventan en la emisión: se registran antes,
y el terminal es lo que provisiona la numeración. Sin al menos uno no hay
consecutivo, y sin consecutivo no hay documento.
curl -X POST https://api.timbre.cr/internal/v1/issuers/branches \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{ "code": "001", "name": "Casa matriz" }'La respuesta trae el id de la sucursal, que es lo que necesita el terminal:
curl -X POST https://api.timbre.cr/internal/v1/issuers/branches/{branchId}/terminals \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{ "code": "00001", "name": "Caja 1" }'Podés registrar cuantas quieras: GET /internal/v1/issuers/branches lista las
sucursales del contribuyente y GET /internal/v1/issuers/branches/{branchId}/terminals las cajas de una de ellas.
Repetir un código de sucursal en el mismo contribuyente es 409 con
duplicate_branch_code, y repetir uno de terminal en la misma sucursal es 409
con duplicate_terminal_code. El mismo código de terminal en otra sucursal sí
vale: la numeración es por sucursal y terminal, no por terminal a secas.
Rotar el certificado
Los certificados de Hacienda vencen. Cargá el nuevo y desde ese momento se firma con él; la cédula, el nombre comercial, las sucursales y la numeración no se tocan.
curl -X PUT https://api.timbre.cr/internal/v1/issuers/certificate \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{ "certificateBase64": "MIIK…", "certificatePassword": "…" }'El nuevo se valida igual que el primero, así que un .p12 equivocado se rechaza
con 400 y el anterior sigue firmando. Los documentos ya firmados no cambian:
lo que se presentó quedó firmado con el certificado de ese momento.
Si Timbre no logra leer el .p12 guardado, el perfil vuelve con
certificateUnreadable: true y sin metadatos de certificado. Todo lo que ese
contribuyente emita va a fallar al firmarse; cargá uno nuevo.
Cambiar de régimen
Entrar o salir del régimen simplificado es un evento normal, no una re-inscripción: no hace falta dar de alta al contribuyente de nuevo.
curl -X PUT https://api.timbre.cr/internal/v1/issuers/tax-regime \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{ "taxRegime": "simplificado" }'El cambio queda con su fecha (taxRegimeUpdatedAt), pero no es retroactivo:
los documentos ya emitidos conservan el régimen que tenían congelado en su
propia procedencia, aunque el contribuyente haya cambiado de régimen después.
Marca
El logo, el color de acento y los datos de contacto que aparecen en la representación en PDF son del contribuyente, no del proyecto.
curl -X PUT https://api.timbre.cr/internal/v1/issuers/branding \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{
"logoBase64": "iVBORw0KGgo…",
"accentColor": "#1B4D3E",
"contactEmail": "facturas@ferreteriaosa.cr"
}'El logo es PNG o JPEG, hasta 512 KB, en base64. Cada campo tiene tres estados:
omitilo y queda como estaba, mandalo y lo reemplaza, mandá null y lo borra
—así podés corregir un logo equivocado sin escribirle a nadie.
GET /internal/v1/issuers/branding nunca devuelve los bytes del logo, solo
logoSha256 y logoContentType: alcanza para saber si hay uno sin
descargarlo en cada carga de página.
Desactivar y reactivar
Cuando un cliente deja de facturar con vos, desactivalo: deja de poder emitir y libera su espacio en el tope de la cuenta.
curl -X PUT https://api.timbre.cr/internal/v1/issuers/{issuerId}/active \
-H "Authorization: Bearer timbre_sk_…" \
-H 'Content-Type: application/json' \
-d '{ "active": false }'Acá el issuerId va en la ruta y es obligatorio aunque la cuenta tenga uno
solo: esto cambia si alguien puede facturar o no, y no se hace sobre un
contribuyente adivinado.
No se borra nada. El perfil, las sucursales, las terminales y —sobre todo— la numeración quedan como estaban, porque el consecutivo fiscal no tiene huecos ni marcha atrás: al reactivarlo sigue donde lo dejó, no vuelve a 1.
Un contribuyente desactivado sigue apareciendo en la lista, con
active: false. Es la única forma de encontrarlo para traerlo de vuelta, y es lo
que hace que la lista cuadre con maxIssuers. Emitir a su nombre responde 404
con issuer_not_found.
Reactivar vuelve a mirar el tope. Si el espacio que este liberó ya lo ocupa
otro, la respuesta es 409 con issuer_limit_reached: desactivá uno que ya
no uses y volvé a intentar.
Cuándo puede emitir
Tres cosas:
Está activo
active: true. Vale para toda la cuenta, no por ambiente.Tiene al menos un terminal
En cualquiera de sus sucursales. Es lo que provisiona la numeración, y las sucursales y terminales son las mismas en los dos ambientes.
Tiene credenciales que Hacienda no rechazó
credentialStatusdistinto deabsenty derejecteden el ambiente desde el que vas a facturar. Es la única de las tres que no se hereda de un ambiente al otro.