Enviar desde una plantilla
Crea un sobre a partir de una de tus plantillas y envíalo en una sola llamada; después obtén los enlaces de firma.
Este es el endpoint en torno al que se construyen la mayoría de las integraciones: toma una de tus plantillas, asigna personas reales a sus roles y envíala para firmar, todo en una sola llamada. En la API no hay paso de borrador.
Crear un sobre a partir de una plantilla y enviarlo
Requiere el scope envelopes:send. La creación y el envío ocurren juntos: si el envío falla por
cualquier motivo, el sobre se elimina y se devuelve un error, de modo que nunca queda un borrador
a medio crear. Cada envío correcto consume una de las solicitudes de firma por API de tu plan.
Endpoint
POST https://api2.bunnydoc.com/v1/envelopes/from-template
Cuerpo
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
template_id | string (uuid) | Sí | La plantilla a enviar. Obtenla de GET /v1/templates. |
recipients | array | Sí | De 1 a 50 destinatarios, uno por cada rol desbloqueado de la plantilla. |
sender_email | string | No | Enviar como este usuario. Debe ser un usuario activo de tu empresa con un asiento de firma electrónica. Por defecto, el propietario de la clave de API. |
title | string | No | Título del sobre, hasta 250 caracteres. Por defecto, el nombre de la plantilla. |
email_subject | string | No | Asunto de los correos de invitación (y de recordatorio). Hasta 300 caracteres. Admite placeholders. Por defecto, el asunto de la plantilla o New Document for Signature - {title}. |
email_message | string | No | Mensaje que ven los destinatarios. Hasta 10 000 caracteres. Texto plano o HTML limitado, y admite placeholders. Por defecto, el mensaje de la plantilla o el predeterminado de tu marca activa. |
Cada destinatario asigna una persona real a un rol de la plantilla:
| Campo del destinatario | Tipo | Obligatorio | Notas |
|---|---|---|---|
role | string | Sí | Debe coincidir con un rol desbloqueado de GET /v1/templates/{id}. Hasta 25 caracteres. |
name | string | Sí | Nombre completo del destinatario, hasta 120 caracteres. |
email | string | Sí | Correo del destinatario, hasta 150 caracteres. |
contact_number | string | No | Hasta 20 caracteres. |
Haz coincidir los roles con exactitud y omite los bloqueados
Cada role debe ser el nombre de un rol desbloqueado de la plantilla, y cada rol puede aparecer
una vez. Un rol que no existe, o un rol bloqueado, se rechaza con unknown_signer_role. Llama
siempre antes a GET /v1/templates/{id} para leer los roles
válidos.
No pongas al remitente en recipients
recipients es para las personas que reciben el documento. El remitente es quien resuelva
sender_email, y su parte de la plantilla —el rol Me— se añade y se completa por ti. Nunca pasas
un destinatario Me; si lo haces, su nombre y correo se ignoran en favor del remitente real.
Ejemplo de petición
curl -X POST https://api2.bunnydoc.com/v1/envelopes/from-template \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"template_id": "018f2c3d-4e5f-7a8b-9c0d-1e2f3a4b5c6d",
"title": "Mutual NDA — Acme Corp",
"sender_email": "sales@yourco.com",
"email_subject": "Please sign: Mutual NDA",
"email_message": "Hi Jane — please review and sign our mutual NDA. Thanks!",
"recipients": [
{ "role": "Client", "name": "Jane Doe", "email": "jane@acme.com" }
]
}'POST /v1/envelopes/from-template HTTP/1.1
Host: api2.bunnydoc.com
Authorization: Bearer <access_token>
Content-Type: application/json
{
"template_id": "018f2c3d-4e5f-7a8b-9c0d-1e2f3a4b5c6d",
"title": "Mutual NDA — Acme Corp",
"sender_email": "sales@yourco.com",
"email_subject": "Please sign: Mutual NDA",
"email_message": "Hi Jane — please review and sign our mutual NDA. Thanks!",
"recipients": [
{ "role": "Client", "name": "Jane Doe", "email": "jane@acme.com" }
]
}Respuesta
{
"envelope_id": "018f9a0b-1c2d-7e3f-8a4b-5c6d7e8f9a0b",
"status": "sent",
"title": "Mutual NDA — Acme Corp",
"sender": { "email": "sales@yourco.com" },
"recipients": [
{ "role": "Client", "name": "Jane Doe", "email": "jane@acme.com" }
],
"signing_urls": [
{
"recipient_id": "018f9a0b-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
"name": "Jane Doe",
"email": "jane@acme.com",
"recipient_type": "need-to-sign",
"signing_url": "https://sign.bunnydoc.com/s/eyJhbGciOi…"
}
],
"created_at": "2026-07-28T10:20:00.000Z"
}| Campo | Tipo | Descripción |
|---|---|---|
envelope_id | string | El id del nuevo sobre. |
status | string | Siempre sent cuando tiene éxito. |
sender | object | El correo del remitente resuelto. |
recipients | array | Los destinatarios que proporcionaste. |
signing_urls | array | null | Presente solo si el sobre comparte enlaces de firma con el remitente; en caso contrario, null. Ver más abajo. |
created_at | string | Marca de tiempo ISO 8601. |
¿De dónde salen la caducidad, los recordatorios y los campos?
La caducidad, los recordatorios, el orden de firma y la disposición de los campos se heredan de la plantilla. La API establece los destinatarios, el título, el remitente y —cuando los proporcionas— el asunto y el mensaje del correo; la plantilla (y el predeterminado de tu marca activa) aportan el resto.
Personalizar el correo de invitación
email_subject y email_message controlan el correo que recibe cada destinatario (y los
recordatorios que le siguen: los recordatorios reutilizan el asunto con el prefijo Reminder:).
Si los omites, se usan los valores de la plantilla.
Placeholders
Ambos campos admiten placeholders que se rellenan por destinatario en el momento del envío. Así una sola llamada puede producir un correo personalizado para cada firmante.
| Placeholder | Se convierte en |
|---|---|
{{recipient_name}} | El nombre completo del destinatario |
{{recipient_first_name}} | El nombre de pila del destinatario |
{{recipient_email}} | El correo del destinatario |
{{sender_name}} | El nombre completo del remitente |
{{sender_first_name}} | El nombre de pila del remitente |
{{sender_email}} | El correo del remitente |
{{company_name}} | El nombre de tu empresa |
{{envelope_title}} | El título del sobre |
{{expiry_date}} | La fecha de caducidad del sobre, formateada según su configuración regional |
{
"template_id": "018f2c3d-4e5f-7a8b-9c0d-1e2f3a4b5c6d",
"email_subject": "{{recipient_first_name}}, firma {{envelope_title}}",
"email_message": "<p>Hola {{recipient_first_name}}:</p><p>Revisa y firma. Caduca el {{expiry_date}}.</p><p>Gracias,<br/>{{sender_name}}</p>",
"recipients": [
{ "role": "Client", "name": "Jane Doe", "email": "jane@acme.com" },
{ "role": "Witness", "name": "Sam Ortiz", "email": "sam@acme.com" }
]
}Un placeholder desconocido se rechaza, no se ignora
Cualquier cosa dentro de {{ … }} que no esté en la lista anterior hace fallar la petición con
validation_failed: no enviaremos un correo que contenga un literal {{customer_ref}}. Comprueba
la ortografía contra la tabla; los nombres son exactos y en minúsculas.
Deliberadamente no hay placeholder para el enlace de firma ni para el código de acceso. El correo
ya lleva el botón de firma, y un código de acceso es un segundo factor que debe llegar al
destinatario por otra vía. Usa
GET /v1/envelopes/{id}/signing-links si tu
propio sistema necesita la URL.
Formato en email_message
Envía texto plano (se conservan los saltos de línea) o un subconjunto pequeño y seguro de HTML:
| Permitido | Etiquetas |
|---|---|
| Párrafos y saltos | <p>, <br> |
| Énfasis | <strong>, <b>, <em>, <i>, <u>, <s> |
| Listas | <ul>, <ol>, <li> |
| Enlaces | <a href>: solo https, http, mailto, tel |
Todo lo demás se elimina a la entrada, incluidos style, class, id, imágenes, tablas y
encabezados. El valor guardado es el ya depurado, así que lo que puedes guardar es exactamente lo
que recibe el destinatario.
Se rechazan los enlaces cuyo texto no coincide con su destino
<a href="https://evil.example">https://yourbank.example</a> falla con validation_failed. Si el
texto del enlace parece una dirección, debe ser la dirección a la que enlazas. Usa texto descriptivo
(<a href="https://acme.example/terms">nuestros términos</a>) y esto nunca te afectará.
El límite de 10 000 caracteres se cuenta después de depurar, así que algo de marcado suelto no te hará superarlo.
Errores
| Estado | error.code | Cuándo ocurre |
|---|---|---|
402 | quota_exceeded | Tu empresa no tiene solicitudes de firma por API restantes en este periodo. No se entregó nada. |
404 | template_not_found | No existe ninguna plantilla con ese id en tu empresa. |
422 | unknown_signer_role | Un rol de destinatario no coincide con la plantilla, o se proporcionó un rol bloqueado. |
422 | sender_not_found | sender_email no es un usuario activo con asiento de firma electrónica en tu empresa. (Sin sender_email, el propietario de la clave ha perdido su asiento.) |
422 | sender_fields_incomplete | El propio rol de remitente de la plantilla tiene campos obligatorios sin valor, así que el documento no puede salir. Arréglalo en la plantilla: consulta la referencia de errores. |
400 422 | validation_failed | El cuerpo está mal formado: falta un campo, rol duplicado, se supera un límite de longitud, un {{placeholder}} desconocido o un enlace engañoso. |
429 | rate_limited | Más de 100 envíos en 15 minutos para esta clave. |
5xx | send_failed | Se creó pero no se envió, así que se descartó. No se entregó nada; reintentar crea un sobre nuevo en lugar de duplicar uno. |
Obtener los enlaces de firma de un sobre enviado
Los enlaces de firma permiten que el sistema del remitente entregue a cada destinatario un enlace directo en lugar de esperar al correo. Están disponibles en cualquier momento tras el envío y también los devuelve la llamada de envío anterior.
Endpoint
GET https://api2.bunnydoc.com/v1/envelopes/{id}/signing-links
Ejemplo de petición
curl https://api2.bunnydoc.com/v1/envelopes/018f9a0b-1c2d-7e3f-8a4b-5c6d7e8f9a0b/signing-links \
-H "Authorization: Bearer <access_token>"GET /v1/envelopes/018f9a0b-1c2d-7e3f-8a4b-5c6d7e8f9a0b/signing-links HTTP/1.1
Host: api2.bunnydoc.com
Authorization: Bearer <access_token>Respuesta
{
"signing_urls": [
{
"recipient_id": "018f9a0b-2c3d-7e4f-8a5b-6c7d8e9f0a1b",
"name": "Jane Doe",
"email": "jane@acme.com",
"recipient_type": "need-to-sign",
"signing_url": "https://sign.bunnydoc.com/s/eyJhbGciOi…"
}
]
}signing_url es null para los destinatarios que no firman (por ejemplo, uno que "recibe una
copia").
Un enlace de firma es una credencial de portador
Cualquiera que tenga un enlace de firma puede firmar como ese destinatario. Entrégalos por canales seguros y trátalos como secretos: no los registres ni los incluyas en URLs que compartas.
Errores
| Estado | error.code | Cuándo ocurre |
|---|---|---|
403 | signing_links_disabled | Este sobre no tiene activada la opción de "compartir enlaces de firma con el remitente". |
404 | envelope_not_found | No existe ningún sobre con ese id en tu empresa. |
¿Quieres actualizaciones de estado sin sondear?
La API es de solo escritura para los sobres: no hay endpoint de estado por diseño. Para saber cuándo se ve, se firma o se completa un documento, suscríbete a los webhooks.