API: Certificados
Subida, listado y eliminación del certificado digital .p12 con el que Notta firma tus DTE, cifrado con envelope encryption por organización
El recurso /certificates administra los certificados digitales .p12 de tu organización. El .p12 se cifra con envelope encryption (DEK por org, KEK maestra) antes de persistirse; el password se guarda dentro de ese mismo blob cifrado, nunca en claro, y se descifra en memoria cada vez que hay que usarlo.
Base URL: https://app.notta.cl/api/v1.
Autenticación: API key Bearer o sesión del dashboard
Los cuatro endpoints de /certificates aceptan las dos credenciales: Authorization: Bearer ntt_cert_… o la cookie de sesión del dashboard. Las tres mutaciones (POST /certificates, POST /certificates/:id/activate y DELETE /certificates/:id) exigen además el scope certificates:write en la key y rol owner/admin: una key con solo dte:write recibe 403 forbidden, y un emitter o viewer también. Sin organización responden 412 org.required.
Endpoints
POST /certificates
Sube un certificado .p12 (multipart/form-data): lo cifra, calcula su fingerprint SHA-256 y devuelve el id que después va en el header X-Cert-Id al emitir.
| Campo (form) | Tipo | Requerido | Descripción |
|---|---|---|---|
file | archivo | sí | El .p12 emitido por tu PSC (Acepta, eCertChile, etc.). |
password | string | sí | Password del .p12; se guarda cifrado junto al archivo y se descifra en memoria al usarlo. |
label | string | sí | Nombre legible, p. ej. "Producción". |
rut_owner | string | no | RUT del titular del certificado. |
subject_cn | string | no | CN del subject; default: el label. |
issuer_cn | string | no | CN del emisor del certificado. |
Respuesta 201 Created:
La deduplicación es por contenido: el mismo .p12 subido dos veces para la misma org responde 409 cert.duplicate.
Errores posibles:
| Código | HTTP | next_action |
|---|---|---|
cert.invalid_request (falta file, password o label) | 400 | — |
cert.duplicate | 409 | — |
org.required | 412 | — |
cert.insert_failed | 500 | — |
db.unavailable · cert.kms_unavailable | 503 | — |
GET /certificates
Lista los certificados vigentes (no eliminados) de tu organización.
Respuesta 200 OK:
Errores posibles:
| Código | HTTP | next_action |
|---|---|---|
org.required | 412 | — |
db.unavailable | 503 | — |
POST /certificates/:id/activate
Fija cuál de tus certificados firma por defecto. Es el que Notta usa cuando emites sin mandar X-Cert-Id. Sirve para rotar el certificado sin tocar tu código: subes el nuevo, lo activas, y las emisiones siguientes salen firmadas con él.
Respuesta 200 OK:
Un certificado vencido no puede quedar como predeterminado: responde 409 cert.expired. Un certificado de otra organización responde 404, no 403: no se filtra si existe.
Errores posibles:
| Código | HTTP | next_action |
|---|---|---|
cert.not_found | 404 | — |
cert.expired | 409 | — |
org.required | 412 | — |
db.unavailable | 503 | — |
DELETE /certificates/:id
Elimina (soft delete) un certificado de tu organización; deja de aparecer en el listado y de servir para firmar.
Respuesta 200 OK:
No se puede borrar el ÚNICO certificado de la organización: sin ninguno cargado, Notta no
puede timbrar, emitir ni enviar documentos al SII, y el dashboard devuelve al asistente de
puesta en marcha. Ese intento responde 409 cert.last_remaining.
Para rotar el certificado, el orden es al revés del que uno suele intentar: sube primero
el reemplazo con POST /certificates y recién después elimina el anterior. Entre los dos
pasos la organización nunca se queda sin firma, y con más de un certificado vigente eliges
cuál firma con POST /certificates/:id/activate.
Errores posibles:
| Código | HTTP | next_action |
|---|---|---|
cert.not_found | 404 | — |
cert.last_remaining (es el único certificado de la org) | 409 | upload_certificate |
org.required | 412 | — |
db.unavailable | 503 | — |
Próximos pasos
- Onboarding y certificación SII: de dónde sale el
.p12y cómo encaja en el camino a producción. - API: Folios (CAF), el siguiente upload: los folios autorizados por el SII.
- Autenticación: API keys, scopes y entornos para el resto de la API.
Última actualización