---
title: "Migración v2 → v3 — API v3 de AllSign"
type: how-to
canonical_url: "https://allsign.io/developers/docs/migration"
api_version: "2026-07-11"
---

# Migrar de v2 a v3

La v3 es un **re-ordenamiento, no una reescritura**. Los mismos recursos y la misma lógica de negocio, con un contrato consistente encima. **Tu misma API key funciona** en ambas. La v2 sigue **viva y congelada** en `/v2`: migra a tu ritmo, endpoint por endpoint.

## De un vistazo

Nueve cambios, todos mecánicos. Ninguno cambia *qué* hace la API, solo *cómo* la lees:

| Área | v2 | v3 |
| --- | --- | --- |
| [Base URL](#base-url) | `/v2` | `/v3` (v2 sigue viva) |
| [Casing](#casing) | snake/camel mezclado | `camelCase` en todo |
| [IDs](#ids) | UUID crudos | Prefijados opacos (`doc_`, `tmpl_`…) |
| [Errores](#errores) | `{error:{code:E1xxx}}` | RFC 9457 `problem+json` |
| [Paginación](#paginacion) | Varias formas | Un envelope cursor unificado |
| [Estados](#estados) | `MAYÚSCULAS_ES` | `lowercase_en` |
| [Headers](#headers) | Solo `X-RateLimit-*` | Request-id, versión, rate limit IETF… |
| [Status codes](#status-codes) | Algunos mal | Corregidos (404 no 500…) |
| [Webhooks](#webhooks) | `snake_case` + `X-AllSign-Signature` | Cohorte v3: camelCase + Standard Webhooks (los clásicos **no cambian**) |

## Base URL

Cambia el prefijo de la ruta de `/v2` a `/v3`. Eso es todo lo obligatorio para empezar: `https://api.allsign.io/v3/...`. La **misma API key** (`allsign_live_sk_` / `allsign_test_sk_`) autentica en ambas versiones — no generes keys nuevas. La v2 **no se apaga**: está congelada (sin cambios de comportamiento) y puedes migrar un endpoint a la vez.

## Casing

La v2 mezclaba `snake_case` y `camelCase` según el endpoint. La v3 es **`camelCase` en todo** — request y response, sin excepciones. Renombra tus claves: `created_at` → `createdAt`, `signer_status` → `signerStatus`, `guest_link` → `guestLink`.

## IDs

Los UUID crudos se vuelven **identificadores opacos con prefijo**. Son cadenas *opacas*: no parsees su interior, solo guárdalas y reenvíalas. Un id malformado responde `400` `INVALID_ID` (antes de tocar la base de datos).

| Prefijo | Recurso |
| --- | --- |
| `doc_` | Documento |
| `sgr_` | Firmante (signer) |
| `fie_` | Campo (field) |
| `rol_` | Rol de un documento |
| `tmpl_` | Plantilla |
| `tv_` | Versión de plantilla |
| `fld_` | Carpeta (folder) |
| `ses_` | Sesión de firma |
| `bat_` | Lote de envío masivo (bulk send) |
| `sset_` | *Signer set* |
| `cst_` | Constancia NOM-151 |
| `usr_` | Usuario |
| `ten_` | Tenant |
| `org_` | Organización |
| `whe_` | Webhook endpoint |
| `whd_` | Entrega de webhook |
| `evt_` | Evento |
| `whsec_` | Secreto de firma de webhook |

## Errores

El *envelope* propietario de v2 desaparece. La v3 usa **RFC 9457 `application/problem+json`**, con `code` en `UPPER_SNAKE_CASE` (estable) en vez del `E1xxx` numérico. Programa contra `code`, nunca contra el texto.

**Antes (v2)**

```
{
  "error": {
    "code": "E1300",
    "message": "document not found"
  }
}
```

**Ahora (v3)**

```
{
  "type": "https://allsign.io/developers/docs/errors#DOCUMENT_NOT_FOUND",
  "title": "Document not found",
  "status": 404,
  "detail": "No document exists with id doc_...",
  "code": "DOCUMENT_NOT_FOUND",
  "requestId": "req_..."
}
```

La validación semántica responde `422` con un arreglo `errors[]`: un objeto por campo con `field` y su `pointer` (JSON Pointer).

```
{
  "type": "https://allsign.io/developers/docs/errors#VALIDATION_ERROR",
  "title": "Validation failed",
  "status": 422,
  "code": "VALIDATION_ERROR",
  "errors": [
    { "field": "signers[0].email", "pointer": "/signers/0/email",
      "code": "INVALID_VALUE", "detail": "not a valid email address" }
  ]
}
```

Catálogo completo de códigos en [Errores](https://allsign.io/developers/docs/errors).

## Paginación

Las varias formas de paginar de v2 se unifican en **un solo envelope basado en cursor**: los recursos vienen en `data`, con `hasMore` y `nextCursor`. Para la siguiente página, reenvía `nextCursor` como `startingAfter` hasta que `hasMore` sea `false`.

```
GET /v3/documents?limit=20&startingAfter=djF8Y3JlYXRlZEF0fC4uLg

{
  "object": "list",
  "data": [ { "id": "doc_..." }, { "id": "doc_..." } ],
  "hasMore": true,
  "nextCursor": "djF8Y3JlYXRlZEF0fC4uLg",
  "limit": 20
}
```

## Estados

Los estados pasan de `MAYÚSCULAS_ES` (español) a `lowercase_en` (inglés). Actualiza tus comparaciones y tus *mappings* de UI:

| v2 | v3 |
| --- | --- |
| `ESPERANDO_FIRMAS` | `awaiting_signatures` |
| `TODOS_FIRMARON` | `completed` |
| `SELLOS_PDF` / `RECOLECTANDO_FIRMANTES` | `draft` |
| `ANULADO` | `voided` |

Recuerda que `DocumentStatus` y `SignerStatus` son `x-extensible-enum`: maneja valores nuevos sin romperte (ver [Versionado](https://allsign.io/developers/docs/versioning)).

## Headers

La v3 agrega headers nuevos (en v2 solo existían los `X-RateLimit-*`). Vale la pena instrumentarlos:

| Header | Para qué |
| --- | --- |
| `AllSign-Request-Id` | Correlación: cítalo al reportar un problema (también en `requestId` del error). |
| `AllSign-Version` | La versión fechada activa (ver [Versionado](https://allsign.io/developers/docs/versioning)). |
| `RateLimit-*` | Límite, restante y reinicio (ver [Rate limits](https://allsign.io/developers/docs/rate-limits)). |
| `Idempotency-Replayed` | `true` cuando una respuesta salió del caché de idempotencia, no de una ejecución nueva. |

**Los `X-*` de v2 no viajan en v3.** Una respuesta v3 hecha con tu API key trae `AllSign-Request-Id` y `RateLimit-*`; ya no trae `X-Allsign-Request-Id` ni el trío `X-RateLimit-*`. Si tu cliente v2 leía esos headers, cámbialo al migrar. Los topes propios, como el de `POST /v3/constancias`, también responden con `RateLimit-*` y su `429` es problem+json `RATE_LIMITED` con `Retry-After`. Solo dos `429` conservan algún `X-RateLimit-*`: el del tope de invitaciones de `POST /v3/documents/{id}/send` (`X-RateLimit-Reason: invite-fanout`) y el tope diario por IP de una petición sin key válida, cuyo `X-DailyRateLimit-Reset` es un **epoch** (ver [Rate limits](https://allsign.io/developers/docs/rate-limits)). `RateLimit-Reset` es un **delta en segundos**: para saber cuánto esperar, lee **solo** `Retry-After` y los headers nuevos.

## Status codes

Se corrigieron códigos que en v2 mentían. Si tu cliente v2 trataba un `500` como "reintenta", revisa estos:

-   `templateId` malformado → **`400 INVALID_ID`** (antes `500`).
-   `GET /v3/users/me` **nunca** responde `403`: si tu key es válida, te ves a ti mismo.
-   Acceso *cross-tenant* (un id que no es tuyo) → **`404`**, no `403`: no revelamos que el recurso existe.

## Webhooks

Migrar tus *llamadas* a `/v3` **no cambia tus webhooks**. El formato de cada endpoint de webhook lo decide su propia versión de contrato (`apiVersion`), y hoy hay dos cohortes:

-   **Tus destinos existentes son la cohorte clásica (`v2legacy`) y así se quedan:** cuerpo `snake_case` congelado byte a byte, cabeceras `X-AllSign-Event` / `X-AllSign-Event-Id` / `X-AllSign-Timestamp`, y (con HMAC habilitado) la firma `X-AllSign-Signature` = HMAC-SHA256 en **hex** de `"{timestamp}.{body}"`, con los bytes literales del secreto como llave.
-   **Un endpoint creado con `POST /v3/webhooks` nace en la cohorte v3** (versión fechada `2026-07-11`): sobre `camelCase` y firma [Standard Webhooks](https://allsign.io/developers/docs/webhooks#firma-standard-webhooks) (`webhook-id` / `webhook-timestamp` / `webhook-signature`) — la fórmula *y la llave* de verificación cambian, así que tu verificador clásico no sirve tal cual.
-   **Uno creado desde el dashboard hereda el formato de tu cuenta:** si ya tienes destinos clásicos, nace `v2legacy` (para no romper el handler que ya tienes); una cuenta que estrena integración nace en v3.
-   **Cambiar un endpoint de cohorte** hoy solo se puede desde el dashboard — `PATCH /v3/webhooks/{id}` no expone `apiVersion`.
-   Algunos eventos **cambian de nombre** entre cohortes (p.ej. `signature.reminder_sent` → `signer.reminder_sent`), y ambas cohortes traen la cabecera `AllSign-Livemode` — para la clásica es la única señal de entorno.

El detalle completo (tabla lado a lado, nacimiento y reintentos por cohorte) está en [Webhooks → Las dos cohortes](https://allsign.io/developers/docs/webhooks#las-dos-cohortes).

## Checklist

Diez pasos para migrar sin sorpresas:

1.  Cambia la base URL de `/v2` a `/v3` (la misma API key sigue funcionando).
2.  Pasa todas tus claves a `camelCase` (request y response).
3.  Trata los IDs como cadenas opacas con prefijo; no parsees su interior.
4.  Reescribe el manejo de errores a `problem+json`; programa contra `code`.
5.  Lee `errors[]` (con `pointer`) en los `422` de validación.
6.  Adopta el envelope cursor: `data` + `hasMore` + `nextCursor`.
7.  Remapea los estados a `lowercase_en` y tolera valores nuevos en los enums extensibles.
8.  Instrumenta los headers nuevos (`AllSign-Request-Id`, `RateLimit-*`, `Idempotency-Replayed`) y deja de leer los `X-*` legacy (`X-RateLimit-Reset` es epoch, no delta).
9.  Ajusta tu manejo de status codes (404 en vez de 500; `/users/me` nunca 403; cross-tenant → 404).
10.  Decide la cohorte de tus [webhooks](#webhooks): los destinos clásicos siguen en `v2legacy`; un endpoint nuevo por API nace v3 y se verifica con Standard Webhooks.
