Endpoint /sign-pdf¶
El endpoint principal de Notary. Firma documentos PDF con firma PAdES digital o visual, usando layout automatico de 2 columnas.
Request¶
Parametros (Form Data)¶
| Parametro | Tipo | Requerido | Descripcion |
|---|---|---|---|
pdf_file |
File | Si | PDF formato A4, max 10 MB |
name |
string | Si | Nombre del firmante (1-100 chars) |
seal |
string | Si | Cargo del firmante (1-50 chars) |
department |
string | Si | Departamento (1-100 chars) |
entity |
string | Si | Entidad (1-100 chars) |
document_number |
string | No | Numero de documento para estampado (max 40 chars) |
city |
string | Condicional | Ciudad para estampado. Requerido si document_number presente |
stamp_position |
string | No | 'first' (default) o 'last' |
tenant_id |
string | No | ID del tenant (para logging; el certificado llega via cert_file) |
use_pades |
string | No | 'true' (default) o 'false' |
cert_file |
File | No | Archivo .p12 del certificado (enviado por el Backend desde R2) |
cert_password |
string | No | Password del .p12 (enviado por el Backend) |
expected_sha256 |
string | No | Hash SHA-256 hex del PDF esperado. Si se envia, se verifica integridad antes de firmar |
Campos condicionales
cityes obligatorio cuando se enviadocument_number- Para firma PAdES, el Backend debe enviar
cert_file+cert_password(el certificado viene desde R2, no se busca localmente) - Si
expected_sha256no coincide con el hash real del PDF recibido, se rechaza con400 PDF_INTEGRITY_FAILED
Response¶
Exito (200):
Content-Type: application/pdf
Content-Disposition: attachment; filename={document_number}.pdf
X-Signature-Type: pades | visual
El body es el PDF firmado en bytes.
Header X-Signature-Type:
pades: Se aplico firma PAdES criptograficavisual: Se aplico firma visual (sin componente criptografico)
Nomenclatura del archivo¶
- Si se envio
document_number: el archivo se llama{document_number}.pdf - Si no se envio: se mantiene el nombre original del archivo subido
Ejemplo con cURL¶
Firma PAdES¶
curl -X POST "http://localhost:8001/sign-pdf" \
-H "X-API-Key: your-api-key-here" \
-F "pdf_file=@documento.pdf" \
-F "name=Juan Perez" \
-F "seal=Director" \
-F "department=Hacienda" \
-F "entity=Municipalidad del Futuro" \
-F "tenant_id=200_muni" \
-F "document_number=IF-2025-00001234-MUNI" \
-F "city=Ciudad Autonoma" \
--output firmado.pdf
Firma visual (sin certificado)¶
curl -X POST "http://localhost:8001/sign-pdf" \
-H "X-API-Key: your-api-key-here" \
-F "pdf_file=@documento.pdf" \
-F "name=Maria Lopez" \
-F "seal=Secretaria" \
-F "department=Administracion" \
-F "entity=Municipalidad Demo" \
-F "use_pades=false" \
--output firmado_visual.pdf
Proceso interno paso a paso¶
flowchart TD
A[Recibir request] --> B[Validar tenant_id si presente]
B --> C[Validar PDF: formato + tamano]
C --> SHA{expected_sha256 presente?}
SHA -->|Si| SHAV[Calcular SHA-256 del PDF recibido]
SHAV --> SHAC{Hash coincide?}
SHAC -->|No| SHAERR[Error 400 PDF_INTEGRITY_FAILED]
SHAC -->|Si| D
SHA -->|No| D[Validar parametros firma]
D --> E{document_number?}
E -->|Si| F[Validar stamp params]
E -->|No| G[Determinar modo firma]
F --> G
G --> H{cert_file + cert_password + use_pades?}
H -->|Si| J[load_certificate_from_bytes /dev/shm]
H -->|No + FALLBACK=true| K[Modo visual]
H -->|No + FALLBACK=false| L[Error 400 CERTIFICATE_NOT_PROVIDED]
J --> M[Contar firmas existentes]
K --> M
M --> N[Calcular posicion X,Y]
N --> O{document_number + city?}
O -->|Si| P[stamp_document async - asyncio.to_thread]
O -->|No| Q{PAdES?}
P --> Q
Q -->|Si| R[sign_pdf_combined - TSA con reintentos + circuit breaker]
Q -->|No| S[sign_pdf_document async - asyncio.to_thread]
R -->|TSA caido| TSA503[Error 503 TSA_UNAVAILABLE]
R --> T[Response PDF - header X-Signature-Type]
S --> T
Codigos de error¶
| Codigo | Error Code | Causa |
|---|---|---|
| 400 | FILE_TOO_LARGE |
PDF excede 10 MB |
| 400 | INVALID_PDF_FILE |
No se puede leer el archivo |
| 400 | INVALID_PDF_FORMAT |
No comienza con %PDF |
| 400 | PDF_INTEGRITY_FAILED |
Hash SHA-256 del PDF no coincide con expected_sha256 |
| 400 | INVALID_PARAMETERS |
Parametros de firma invalidos |
| 400 | INVALID_STAMP_PARAMETERS |
document_number sin city |
| 400 | INVALID_STAMP_POSITION |
stamp_position no es first ni last |
| 400 | INVALID_TENANT_ID |
tenant_id con caracteres no permitidos |
| 400 | CERTIFICATE_NOT_PROVIDED |
No llego cert_file y FALLBACK_TO_VISUAL=false |
| 400 | CERTIFICATE_LOAD_ERROR |
Error al cargar certificado desde cert_file |
| 400 | LayoutError |
No se encontro end-text o pagina llena (FULLPAGE) |
| 401 | INVALID_API_KEY |
API Key invalida o faltante |
| 500 | PADES_ERROR |
Error en firma PAdES |
| 503 | TSA_UNAVAILABLE |
Circuit breaker abierto: TSA no disponible |
| 500 | - | Error interno del servidor |
Otros endpoints¶
GET /health¶
Health check sin autenticacion. Unico endpoint con IP publica en Fly.io.
{
"status": "healthy",
"service": "Notary",
"version": "2.1.0",
"signature_system": { "type": "digital_signature", "version": "2.0" },
"pades_system": { "type": "PAdES-B-T", "library": "pyHanko" },
"available_certificates": ["200_muni"],
"fallback_to_visual": false
}
POST /sign-pdf/verify¶
Verifica las firmas PAdES de un PDF. Requiere API Key. Limite 50 MB.
{
"ok": true,
"failure_reason": null,
"signature_count": 2,
"signature_visible": true,
"modification_level": "LTA_UPDATES",
"signatures": [
{ "intact": true, "valid": true, "modification_level": "LTA_UPDATES", "signature_visible": true }
]
}
Si no hay firmas:
POST /stamp-number¶
Estampa numero/fecha en el PDF y devuelve el PDF estampado en base64 mas las coordenadas donde el cliente de firma (FirmadorGDI) debe insertar la firma criptografica. Requiere API Key.
Parametros (Form Data):
| Parametro | Requerido | Descripcion |
|---|---|---|
pdf_file |
Si | PDF a estampar |
document_number |
No | Numero de documento. Si se omite, solo calcula posicion |
city |
Condicional | Requerido si document_number presente |
stamp_position |
No | 'first' (default) o 'last' |
existing_count |
No | Override manual de firmas existentes (omitir para auto-deteccion) |
Respuesta:
{
"stamped_pdf_b64": "JVBERi0x...",
"sig_llx": 50.0,
"sig_lly": 192.0,
"sig_urx": 250.0,
"sig_ury": 272.0
}
GET /certificate/{tenant_id}¶
Informacion de un certificado especifico. Requiere API Key.
{
"exists": true,
"tenant_id": "200_muni",
"subject": "<Name(CN=GESTION DOCUMENTAL INTELIGENTE,O=Municipalidad del Futuro)>",
"issuer": "...",
"not_valid_before": "2025-01-01T00:00:00+00:00",
"not_valid_after": "2026-01-01T00:00:00+00:00",
"is_valid": true,
"validation_message": "Certificado valido",
"serial_number": "..."
}
GET /certificates¶
Lista todos los certificados disponibles. Requiere API Key.