Conectar el portal de tramites¶
Guia de punta a punta para el equipo que desarrolla el portal de tramites del municipio: que hay que tener antes de escribir la primera linea, cual es el flujo completo de un tramite y como se entera el portal del numero oficial de un documento firmado.
Si buscas el contrato campo por campo de cada endpoint, esta en Ciudadanos, Documentos, Expedientes y Webhook. Esta pagina es el hilo que los une.
Disponibilidad por ambiente
El 202, el GET /tad/documents/{id} y la Idempotency-Key estan disponibles en
DEV y en HML (homologacion); llegan a produccion con el proximo pase.
Detalle en API TAD Ciudadano. Confirma con el equipo GDI contra que
ambiente integras.
1. Antes de empezar¶
Cinco cosas que se piden una sola vez al administrador del municipio (BackOffice) y sin las cuales el portal no arranca:
| # | Que | Donde se obtiene | Sin esto |
|---|---|---|---|
| 1 | URL del Gateway de tu municipio | Administrador / equipo GDI | No hay a donde pegarle |
| 2 | API Key de tipo TAD (key_type='tad') |
BackOffice → /api-key |
401 en todo |
| 3 | URL de callback + secret del webhook, cargados en esa misma key | BackOffice → /api-key |
El portal nunca se entera del numero oficial |
| 4 | Tipos de documento con "Firmable por TAD" activo | BackOffice → Tipos de Documento | 400 Tipo de documento 'X' no habilitado para firma ciudadana (TAD) |
| 5 | Tipos de expediente con canal api o both |
BackOffice → Tipos de Expediente | El catalogo de expedientes vuelve vacio |
La API Key es del municipio entero
Identifica al municipio completo y no tiene usuario asociado: quien la tiene puede operar a nombre de cualquier ciudadano de la base. Vive en el backend del portal, nunca en el navegador ni en una app movil.
El webhook se configura del lado de GDI, no del tuyo
La URL de callback la carga el administrador en la API Key. Es el paso que mas se olvida: sin el, POST /tad/documents sigue devolviendo 202 con normalidad, la firma se hace bien... y el aviso con el numero no le llega a nadie. Verificalo con POST /tad/webhook/test antes de escribir el resto del portal.
Prueba de humo (5 minutos)¶
Antes de integrar nada, confirma que la key funciona y que tu receptor de webhooks esta vivo:
export GW="https://gateway.tu-muni.gob.ar"
export KEY="tu-api-key-tad"
# 1. La key es valida y hay tipos habilitados
curl -s "$GW/api/v1/tad/document-types" -H "X-API-Key: $KEY"
# 2. Tu receptor de webhooks recibe y valida la firma
curl -s -X POST "$GW/api/v1/tad/webhook/test" -H "X-API-Key: $KEY"
El segundo comando te devuelve que contesto tu propio servidor (delivered, status_code, error). Si ahi no da verde, no sigas: el resto del flujo depende de eso.
2. El flujo completo¶
PORTAL MUNICIPAL GDI
El vecino se registra
│
├─ 1. POST /tad/citizens ──────────────────────► ciudadano 'pendiente'
│
El municipio valida identidad
│
├─ 2. PATCH /tad/citizens/{id} {"estado":"validado"} ──► puede firmar
│
El vecino inicia un tramite
│
├─ 3. GET /tad/document-types ─────────────────► que puede firmar
│
├─ 4. POST /tad/documents ─────────────────────► 202 Accepted
│ │ (firma en cola)
│ ◄──── webhook documents.signed ───────────────────┘
│ (numero oficial + pdf_url)
│
├─ 5. POST /tad/cases ─────────────────────────► expediente + caratula
│ (numero al instante)
│
└─ 6. POST /tad/cases/{id}/propose ────────────► el municipio acepta
la vinculacion
Los pasos 1 y 2 se hacen una vez por vecino; del 3 al 6, una vez por tramite.
3. Paso a paso¶
Paso 1 y 2 — El ciudadano tiene que existir y estar validado¶
POST /tad/documents exige un ciudadano en estado validado. Un ciudadano recien creado nace pendiente y no puede firmar (403).
# Alta (upsert por country_id: si ya existe, actualiza en vez de duplicar)
curl -X POST "$GW/api/v1/tad/citizens" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"full_name": "Maria Portal", "country_id": "27333444556"}'
# Validacion (el unico campo que acepta el PATCH es `estado`)
curl -X PATCH "$GW/api/v1/tad/citizens/27333444556" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"estado": "validado"}'
Quien valida es el municipio, no GDI. GDI solo guarda el estado: el criterio (login con AFIP/ANSES, validacion presencial, mesa de entradas) lo define el portal. Decidilo antes de salir a produccion, porque es el paso que puede dejar tramites frenados esperando a una persona.
Detalle completo en Ciudadanos.
Paso 3 — Preguntar que se puede firmar¶
Nunca hardcodees acronimos: el administrador puede habilitar o deshabilitar tipos en cualquier momento.
De cada tipo te importan dos cosas: el acronym (es lo que manda el POST, no el id) y el has_fields:
has_fields: false→ mandas HTML libre encontent_html(o nada, y sale lareference).has_fields: true→ es un formulario controlado (FFCC): primero pedis los campos conGET /tad/document-types/{id}/fields(aca si, porid) y despues mandasform_data. Ver FFCC.- Si el tipo es Importado, mandas el PDF ya armado en
pdf_base64.
Paso 4 — Crear y firmar: la respuesta es 202, no el numero¶
curl -X POST "$GW/api/v1/tad/documents" \
-H "X-API-Key: $KEY" -H "X-Citizen-ID: 27333444556" \
-H "Content-Type: application/json" \
-d '{
"document_type_acronym": "PROV",
"reference": "Solicitud de poda de arbol",
"content_html": "<p>Solicito la poda del arbol frente a mi domicilio.</p>"
}'
{
"success": true,
"message": "Documento recibido — la firma se está procesando",
"document_id": "007a5613-f796-4280-8f3a-ddf60e6c6743",
"session_id": "9f2b1c40-5d3e-4a71-9c88-2b0a5e6d1f34",
"status": "queued",
"expires_at": "2026-07-24T18:41:09Z"
}
Que hacer con cada campo:
| Campo | Que es | Que hace el portal con esto |
|---|---|---|
document_id |
El documento, ya creado | Guardalo. Es la clave con la que vas a reconocer el webhook y con la que se propone la vinculacion al expediente |
session_id |
Esta corrida de firma | Guardalo para trazar el caso con soporte si algo no llega |
status |
queued |
La firma esta en cola |
expires_at |
Vencimiento de la sesion de firma (30 min por defecto) | Si pasado ese plazo no llego ningun webhook, es un caso a revisar |
El timeout del cliente: 60 s si integras contra produccion
En DEV y HML el 202 sale en 1 o 2 segundos, tambien en la primera llamada
del dia: el PDF lo arma el worker, no el pedido.
En produccion el alta todavia es sincronica y espera el PDF, la firma y la
numeracion completas: bajo carga pasa de 30 segundos. Ahi el timeout tiene que ser
≥ 60 s, porque si cortas vas a recibir un error de red por un alta que salio
bien — y en ese ambiente la Idempotency-Key todavia no se respeta, asi que
reintentar te deja dos documentos numerados. Ver la
tabla por ambiente.
Guardar el document_id no es opcional
Es el unico identificador que vincula tu solicitud con el webhook que va a llegar despues. Persistilo junto al tramite en la misma transaccion en la que registras el pedido del vecino, antes de contestarle nada al navegador. Si lo perdes, el documento se firma igual pero tu portal no tiene como saber de quien era.
Paso 5 — Saber el numero oficial¶
Esta es la parte que mas confunde al llegar de un modelo sincronico. El numero no viene en el 202. El sellado y la subida ocurren despues de contestarte, asi que GDI cierra el pedido y avisa cuando termina.
Cuanto tarda, en la practica: unos pocos segundos (medido en DEV contra almacenamiento real: el numero aparecio 4,5 s despues del alta). Pero se midieron picos de mas de un minuto en homologacion sin que nada estuviera roto: la cola drena, con picos.
No armes la experiencia del vecino asumiendo que el numero va a estar listo cuando termine de cargar la pagina siguiente: mostrale que el tramite fue recibido y actualizalo cuando llegue el aviso.
Camino oficial: el webhook documents.signed¶
{
"event": "documents.signed",
"document_id": "007a5613-f796-4280-8f3a-ddf60e6c6743",
"official_number": "PROV-2026-00003039-MDEV-TAD",
"pdf_url": "https://...presignado-600s...",
"status": "signed",
"sent_at": "2026-07-24T18:12:31.412Z"
}
Tu handler tiene que, en este orden:
- Validar la firma HMAC del header
X-GDI-Signature(como). Sin esto cualquiera que descubra tu URL te inyecta numeros falsos. - Responder
2xxrapido — encolar y procesar despues. GDI reintenta con backoff exponencial durante horas si no contestas. - Buscar el tramite por
document_idy guardar elofficial_number. - Descargar el PDF ya: el
pdf_urles un link presignado que vence a los 10 minutos. Si lo guardas en tu base para usarlo mañana, mañana da error. - Deduplicar: la entrega es al menos una vez. El mismo
documents.signedpuede llegar dos veces; si ya tenes numero para esedocument_id, ignoralo.
Y el par negativo, que hay que manejar si o si:
{
"event": "documents.signature_failed",
"document_id": "007a5613-...",
"status": "failed",
"failure_reason": "notary_business_error",
"sent_at": "2026-07-24T18:12:31.412Z"
}
El documento queda sin numerar. El portal puede volver a dar de alta el documento (un POST nuevo) — con una Idempotency-Key nueva, porque la anterior ya quedo consumida por el alta que si se acepto y te devolveria el mismo 202 sin crear nada. failure_reason es un codigo para soporte, no un texto para mostrarle al vecino; el detalle de cada valor esta en Documentos.
Un solo endpoint receptor para todos los eventos
Los tres eventos llegan a la misma URL de callback y todos traen el campo
event: ruteá por ahí. Ignora silenciosamente los que no reconozcas — es la forma
de que un evento nuevo en una version futura de GDI no te tire el handler.
El otro camino: preguntar¶
Con el document_id del 202, el portal puede preguntar en cualquier momento como viene esa firma:
{
"document_id": "007a5613-f796-4280-8f3a-ddf60e6c6743",
"status": "signed",
"official_number": "PROV-2026-00003039-MDEV-TAD",
"pdf_url": "https://...presignado...",
"signed_at": "2026-07-24T18:12:29Z",
"reference": "Solicitud de poda de arbol",
"document_type_acronym": "PROV",
"failure_reason": null
}
status es queued (esperar), signed (listo, con numero y PDF) o failed (con failure_reason: hay que volver a dar de alta el documento). Detalle en Documentos.
Para que sirve, en concreto:
- Desarrollo:
localhostno es alcanzable desde GDI, asi que sin esto habria que montar un tunel solo para ver si la firma salio. - Reconciliar: un trámite que quedo en
queuedpasado elexpires_at, o uno cuyo webhook se perdio porque tu servidor estuvo caido mas tiempo que los reintentos. - Respaldo del webhook, mientras el municipio termina de publicar el endpoint de callback.
Preguntar no reemplaza escuchar
El webhook avisa cuando pasa algo; esto contesta cuando preguntan. Un portal que sondee en loop cerrado se come el rate limit de la key (30 req/min) y se entera mas tarde que uno que escucha. Si vas a sondear igual, hacelo con backoff (2 s, 5 s, 15 s, 30 s) y solo sobre los documentos que todavia esperas.
Configura el webhook igual. Este endpoint es una red, no el piso.
Paso 6 — Expediente y vinculacion¶
A diferencia del documento, el expediente si devuelve el numero al instante (es sincronico):
curl -X POST "$GW/api/v1/tad/cases" \
-H "X-API-Key: $KEY" -H "X-Citizen-ID: 27333444556" \
-H "Content-Type: application/json" \
-d '{"case_template_id": "50c22aa9-41c9-47cd-92eb-b3696f50f33f", "reference": "Poda de arbol - Calle Falsa 123"}'
{
"case_id": "584ee1f9-...",
"case_number": "EE-2026-000227-MDF-INNO",
"official_number": "CAEX-2026-00003045-MDF-TAD"
}
El case_template_id es el UUID que devuelve GET /tad/case-templates, no un numero
correlativo: sacalo siempre del catalogo.
El expediente nace con su caratula y compartido con el ciudadano que lo inicio. Despues, con el documento ya firmado (paso 5), se propone la vinculacion: el municipio la acepta o la rechaza desde la aplicacion. Es asi a proposito — el portal propone, el municipio dispone.
Detalle en Expedientes.
4. Reintentos: mandá siempre una Idempotency-Key¶
Cada POST /tad/documents crea un documento nuevo. Si el portal reintenta por un timeout de red y la primera llamada en realidad si habia llegado, quedan dos documentos firmados y numerados para el mismo tramite. Un timeout no significa que el documento no se creo.
La solucion es un header:
curl -X POST "$GW/api/v1/tad/documents" -H "X-API-Key: $KEY" -H "X-Citizen-ID: 27333444556" -H "Idempotency-Key: tramite-4711" -H "Content-Type: application/json" -d '{ ... }'
Con la misma clave, un reintento devuelve la misma respuesta (202 + header Idempotent-Replay: true) en vez de crear un segundo documento.
Una clave por trámite, no por intento
Es el error que anula toda la proteccion: si generas un UUID nuevo en cada reintento, para GDI son solicitudes distintas y vas a tener duplicados igual. La clave se genera cuando el vecino aprieta el boton y se guarda junto al tramite; todos los reintentos de ese envio la reusan.
Reglas del resto:
- La clave vive 24 horas y su alcance es tu API Key. Maximo 255 caracteres.
- Misma clave con otro contenido →
409: es una clave reciclada por error. Cada solicitud distinta necesita la suya. - Reintento mientras el primero se procesa →
409: esperar unos segundos. - Si el alta falla (
400/403), la clave queda libre: se corrige el cuerpo y se reintenta con la misma. - No se deduplica por contenido: dos solicitudes identicas del mismo vecino pueden ser dos tramites reales. Sos vos el que sabe si son el mismo.
Y aunque uses la clave, sigue valiendo lo de siempre: no reintentes automaticamente un timeout sin mirar. Con Idempotency-Key el reintento es seguro; sin ella, un timeout es "no se que paso" y conviene marcarlo para revision — o preguntar con GET /tad/documents/{id}.
Contrato completo en Documentos.
5. Checklist antes de produccion¶
- La API Key vive solo en el backend; no aparece en el bundle del front ni en un repo.
- El webhook esta configurado en la key y
POST /tad/webhook/testdadelivered: true. - El handler valida la firma HMAC y rechaza timestamps fuera de la ventana de 5 minutos.
- El handler responde
2xxen menos de un par de segundos (encola y procesa aparte). - El handler deduplica por
document_id. - El handler rutea por
evente ignora los desconocidos. - Se maneja
documents.signature_failed, no solo el camino feliz. - El PDF se descarga al recibir el webhook (el link vence a los 10 minutos).
- El
document_idse persiste junto al tramite antes de contestarle al vecino. - El
POST /tad/documentsmandaIdempotency-Key, una por tramite (no por intento). - El re-alta despues de un
signature_failedusa unaIdempotency-Keynueva. - Si integras contra produccion: el timeout del cliente sobre
POST /tad/documentses ≥ 60 s (alli el alta es sincronica). - El
503se distingue por su mensaje: "servidor ocupado" se reintenta, migraciones pendientes se escala. - Se conoce el rate limit real de tu API Key (preguntarselo al administrador) y se maneja el
429. - Si hay carga inicial de padron, se contemplo el limite de 5/min del alta de ciudadanos.
- Esta definido quien y como pasa un ciudadano de
pendienteavalidado. - Hay un tablero o alerta para tramites que quedaron en
queuedpasado elexpires_at.
6. Sintomas y causas¶
| Sintoma | Causa mas probable |
|---|---|
401 en todo |
La key no es de tipo TAD (una key REST comun no sirve para /tad/*), o esta vencida/inactiva |
403 al crear un documento |
El ciudadano esta pendiente (falta el PATCH a validado) o bloqueado |
400 Tipo de documento 'X' no habilitado... |
Falta tildar "Firmable por TAD" en BackOffice, o el acronimo no existe (el mensaje es el mismo en ambos casos, a proposito) |
El 202 llega pero nunca el webhook |
La API Key no tiene webhook_url configurada. Confirmalo con POST /tad/webhook/test: si da 422, es eso. Mientras tanto, el estado se consulta con GET /tad/documents/{id} |
| El webhook llega y el PDF da error al descargarlo | El pdf_url vencio (10 min). Hay que descargarlo al recibirlo |
| Documentos duplicados | Reintento sin Idempotency-Key, o una clave nueva por intento. Ver Reintentos |
409 al crear un documento |
Reintento en curso con la misma Idempotency-Key, o clave reusada con otro contenido |
El POST /tad/documents tarda decenas de segundos o corta por timeout |
Estas contra un despliegue donde el alta todavia es sincronica y espera la firma completa. El alta igual salio: subi el timeout a ≥ 60 s y no reintentes a ciegas. En un despliegue con el alta asincronica el 202 sale en 1 o 2 segundos |
Rehiciste un documento tras un failed y no pasa nada |
Reusaste la Idempotency-Key: te devolvio el 202 viejo (Idempotent-Replay: true) sin crear nada. Va con clave nueva |
429 |
Rate limit. Fijate cual: el general de tu key (60/min por defecto), el de POST /tad/citizens (5/min), el de GET /tad/citizens/* (10/min) o el que va por IP (30/min) |
429 al cargar el padron |
Es el limite de 5/min del alta de ciudadanos. Coordinar la carga inicial con el equipo GDI |
503 con "servidor ocupado" |
Pico de carga momentaneo: reintentar con backoff, se resuelve solo |
503 por migraciones pendientes |
Infraestructura del tenant incompleta. No es un error del portal: avisar al equipo GDI |
| El catalogo de expedientes vuelve vacio | Ningun tipo de expediente tiene canal api o both |