API REST v1 · OpenAPI 3.1
Referência da API
Todos os endpoints, objetos e webhooks do ODSign, com exemplos em cURL, PHP, Node.js e Python e um console para testar as chamadas aqui mesmo, com o seu token.
https://app.odsign.com.br/api/v1
Versão 1.0.0
Visão geral
API REST do ODSign: crie envelopes com documentos e destinatários, escolha como cada pessoa confirma a
assinatura, envie, acompanhe, anule e baixe o documento assinado, a trilha de assinaturas e as provas
de integridade. Os eventos chegam ao seu sistema pelos webhooks de saída (seção webhooks).
- Autenticação: token pessoal no cabeçalho
Authorization: Bearer ods_..., criado por um administrador em Configurações › Conta › Tokens da API. - Formato: JSON em UTF-8 (envie
Accept: application/json); a criação de envelope émultipart/form-data(arquivos); downloads devolvem o próprio arquivo. - Limite de uso: 120 requisições por minuto por usuário (cabeçalhos
X-RateLimit-*). - Não há ambiente de testes separado: as chamadas agem na conta real. Para testar sem enviar
nada, crie rascunhos (
sendausente ou0): eles não saem para ninguém e não contam no limite mensal de envelopes.
Guias, exemplos completos e boas práticas: https://docs.odsign.com.br/integracoes.
Autenticação
Token pessoal criado por um administrador em Configurações › Conta › Tokens da API
(exibido uma única vez). Age em nome do usuário que o criou e com o perfil dele: administrador
alcança todos os envelopes da conta; os demais perfis, só os que enviaram. Usuário bloqueado ou
excluído e conta suspensa invalidam o token na hora (401).
Envie o token em todas as chamadas, precedido de Bearer e um espaço. Ele começa com ods_ e é exibido uma única vez, na criação. Veja Criar e usar um token de API.
curl "https://app.odsign.com.br/api/v1/envelopes?per_page=1" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"Resposta 200 com uma lista em data: o token funciona. 401 com {"message": "Não autenticado."}: token ausente, digitado errado, de usuário bloqueado ou de conta suspensa.
Testar no navegador
Informe o seu token abaixo e use o botão Testar esta chamada de cada endpoint. A requisição sai do seu navegador direto para https://app.odsign.com.br, com o token no cabeçalho Authorization; esta página não envia o token para nenhum outro lugar nem o guarda no servidor.
Atenção: não há ambiente de testes separado. As chamadas agem na conta real do token: enviar, reenviar e anular avisam pessoas de verdade e o envio conta no limite mensal do plano. Para experimentar, crie rascunhos (
send=0, o padrão do console), que não saem para ninguém e não contam no limite, e use o seu próprio e-mail como destinatário.
Dica: crie um token só para testes (por exemplo, "Testes da API") e, se ele for exposto, peça a revogação ao suporte. Prefira um navegador seu, sem extensões desconhecidas.
Nenhum token informado.
Erros
A API usa os códigos HTTP de sempre: 2xx deu certo, 4xx há algo a corrigir na chamada e 5xx é falha nossa (tente de novo em instantes). Todo erro tem corpo JSON com message; os de validação (422) trazem também errors, com a lista de problemas por campo. Decida pelo código e pelas chaves de errors, não pelo texto: as mensagens podem ser melhoradas a qualquer momento.
| Código | Quando acontece |
|---|---|
| 200 201 | Deu certo. 201 na criação do envelope. |
| 401 | Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa. |
| 403 | O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio). |
| 404 | Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído. |
| 422 | Dados inválidos ( |
| 429 | Limite de 120 requisições por minuto atingido. Espere |
| 405 | Método errado para o caminho (por exemplo, DELETE). O cabeçalho Allow lista os aceitos. |
| 500 503 | Falha interna ou armazenamento de documentos indisponível. Tente de novo com espera progressiva. |
{
"message": "Não autenticado."
}{
"message": "Este token não tem acesso a este envelope: só o remetente e os administradores da conta o gerenciam."
}{
"message": "Envelope não encontrado."
}{
"message": "Escolha a forma de autenticação de Maria Souza.",
"errors": {
"envelope": [
"Escolha a forma de autenticação de Maria Souza."
]
}
}{
"message": "Limite de requisições atingido. Tente de novo em 42 s."
}Limites de uso
Cada usuário pode fazer até 120 requisições por minuto (contadas por usuário dono do token, somando todos os tokens dele). Acima disso, a API responde 429 e o cabeçalho Retry-After diz quantos segundos esperar.
Os limites do plano também valem pela API: cada envelope enviado conta na cota mensal de envelopes da conta, e os convites e códigos por WhatsApp ou SMS contam nas cotas de mensagens. Rascunhos não contam.
Para acompanhar o andamento, use os webhooks em vez de consultar a API repetidamente.
| Cabeçalho | Conteúdo |
|---|---|
X-RateLimit-Limit | requisições permitidas por minuto (120) |
X-RateLimit-Remaining | quantas ainda cabem na janela atual |
Retry-After | só no 429: segundos até liberar |
X-RateLimit-Reset | só no 429: momento da liberação (Unix, em segundos) |
Formatos e paginação
| Dado | Formato |
|---|---|
| Identificador do envelope | UUID, por exemplo 01a1cc92-f28f-73b8-8dcc-f15232d31a3f. Destinatários, documentos e eventos usam números inteiros. |
| Datas | ISO 8601 em UTC, por exemplo 2026-11-11T14:30:00+00:00. Campos de data sem valor vêm como null. |
| Texto | UTF-8. Respostas sempre em JSON (envie Accept: application/json), exceto os downloads. |
| Booleanos no multipart | 1 ou 0 (não true/false). No JSON das respostas, true/false. |
| Celular | Na entrada, com DDD (11987654321) ou com + e o código do país; na saída, só dígitos com o código do país (5511987654321). |
| Coordenadas dos campos | Pontos PDF (72 por polegada), origem no canto superior esquerdo da página. A4 = 595 × 842. |
| Paginação | page e per_page (até 100) na query; a resposta traz meta.page, meta.per_page, meta.total e meta.last_page. |
| Compatibilidade | Na v1, novos campos e novos valores podem aparecer a qualquer momento: ignore o que não conhecer. Remoções e mudanças de significado só numa nova versão (/api/v2), com aviso prévio. |
Envelopes
Criar, listar e detalhar envelopes (documentos + destinatários).
GET/envelopes
Listar envelopes
Lista os envelopes do mais recentemente alterado para o mais antigo. Com token de administrador,
traz os envelopes da conta toda; com os demais perfis, só os que o usuário enviou. Modelos e
envelopes eliminados não aparecem. Use meta.last_page para percorrer as páginas.
Para sincronizar o seu sistema, combine from_date com os webhooks: o webhook avisa na hora e a
listagem confere de tempos em tempos o que possa ter se perdido.
Parâmetros
statusstring · queryUm ou mais estados separados por vírgula (
created,sent,delivered,correct,completed,declined,voided,expired). Estado desconhecido não dá erro: só não traz nada.from_datestring (date) · querySó envelopes alterados a partir desta data (
AAAA-MM-DD) ou data e hora (ISO 8601).per_pageinteger · queryItens por página, de 1 a 100 (valores fora da faixa são ajustados).
pageinteger · queryNúmero da página, a partir de 1.
Respostas
200
Página de envelopes.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
422
Dados inválidos (
errorstraz a lista por campo) ou pendências que impedem a operação (errors.envelope).429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl "https://app.odsign.com.br/api/v1/envelopes?status=sent%2Cdelivered&per_page=25" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->get('envelopes', [
'query' => ['status' => 'sent,delivered', 'per_page' => 25],
]);
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$body = json_decode((string) $response->getBody(), true);
foreach ($body['data'] as $envelope) {
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
}
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes?status=sent%2Cdelivered&per_page=25`, { method: 'GET', headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.get(
f"{BASE}/envelopes",
headers=HEADERS,
params={"status": "sent,delivered", "per_page": 25},
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 200 · exemplo
{
"data": [
{
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "delivered",
"status_label": "Em curso",
"recipients": "Maria Souza, João Lima",
"progress": 0,
"waiting_for": "Maria Souza",
"sender": "Ana Prado",
"sender_id": 14,
"sender_avatar_url": null,
"folder_id": null,
"updated_at": "2026-11-11T13:40:02+00:00",
"expires_at": "2027-03-11T13:05:12+00:00",
"deleted": false
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"last_page": 1
}
}
POST/envelopes
Criar envelope (e enviar)
Cria o envelope com os documentos e os destinatários. Com send=1, envia em seguida: cada
destinatário da vez recebe o convite pela forma de envio (channel) e confirma a assinatura pela
forma de autenticação (auth_method). Sem send, o envelope fica como rascunho: não sai
para ninguém, não conta no limite mensal e pode ser enviado depois com POST /envelopes/{id}/send
(ou completado no app, em Envelopes › Rascunhos).
Forma de autenticação. Com auth_method, a assinatura vai para a trilha de assinaturas
anexada ao fim do PDF assinado e não é preciso posicionar campos. Sem auth_method, cada
signatário (signer ou in_person_signer) precisa de pelo menos um campo signature em
fields (fluxo por campos).
Pendências no envio. Se send=1 e algo impedir o envio (signatário sem forma de
autenticação nem campo, token pelo celular sem phone, forma indisponível no plano, limite
mensal atingido), a resposta é 422 com as pendências em errors.envelope e o envelope fica
salvo como rascunho (não crie de novo: corrija no app ou anule e recrie).
Envie booleanos como 1 ou 0 (multipart). O usuário do token precisa poder enviar
envelopes (perfil Administrador ou Remetente, com o envio liberado no plano).
Efeito real: Com send=1, envia convites reais (e-mail, WhatsApp ou SMS) e conta no limite mensal de envelopes do plano.
Corpo multipart/form-data
subjectstringobrigatórioAssunto do envelope (vai no convite).
messagestringMensagem para todos os destinatários.
signing_orderinteger1segue a ordem derouting_order(um de cada vez);0envia a todos ao mesmo tempo. Valores:0,1.files[]array de string (binary)obrigatórioDe 1 a 10 arquivos PDF, JPG, PNG, DOC ou DOCX, até 25 MB cada. Imagens viram páginas em PDF; Word é convertido para PDF quando a conversão está disponível.
recipients[i][...]listaobrigatórioDe 1 a 50 destinatários, como
recipients[0][name],recipients[0][email]...recipients[i][name]stringobrigatóriorecipients[i][email]string (email)obrigatórioObrigatório na API, mesmo para quem recebe pelo celular.
recipients[i][type]string (enum)Papel:
signer= assinatura obrigatória;in_person_signer= assinatura presencial;cc= apenas cópia (recebe o documento na conclusão);viewer= apenas visualização (precisa abrir, não assina).recipients[i][routing_order]integerOrdem de assinatura (padrão: a posição na lista). Vale com
signing_order=1.recipients[i][phone]stringCelular com DDD (
11987654321); fora do Brasil,+e o código do país.recipients[i][channel]string (enum)Forma de envio do convite e dos lembretes. Com
whatsappousms(exigephone), a mensagem sai pelo número da plataforma e conta na cota do plano.recipients[i][auth_method]string (enum)Forma de autenticação de quem assina:
link= só o acesso pelo link pessoal e a confirmação na tela;signature= assinatura manuscrita desenhada na tela;email= token de 6 dígitos enviado ao e-mail na hora de assinar (exigechannel=email);sms= token de 6 dígitos por SMS (exigephone);whatsapp= token de 6 dígitos pelo WhatsApp (exigephone);document= fotos da frente e do verso de um documento oficial com foto, pela câmera do navegador (conforme o plano).recipients[i][whatsapp_auth]integerOpção antiga: código pelo WhatsApp para abrir (precisa de
phone). Valores:0,1.recipients[i][sms_auth]integerOpção antiga: código por SMS para abrir (precisa de
phone). Valores:0,1.fields[i][...]listaCampos posicionados (opcional com
auth_method), comofields[0][type]...fields[i][recipient_index]integerobrigatórioPosição do destinatário em
recipients, a partir de 0.fields[i][document_index]integerPosição do arquivo em
files[], a partir de 0.fields[i][type]stringobrigatórioTipo do campo. Tamanhos padrão (pontos):
signature120×40,initials60×36,date_signed110×18,name150×18,email170×18,company150×18,title120×18,text140×20,number90×20,checkbox14×14,note160×40. Valores:signature,initials,date_signed,name,email,company,title,text,number,checkbox,note.fields[i][page]integerobrigatóriofields[i][x]numberobrigatórioDistância da borda esquerda da página, em pontos.
fields[i][y]numberobrigatórioDistância da borda superior da página, em pontos.
fields[i][width]numberfields[i][height]numberfields[i][required]integerValores:
0,1.sendinteger1envia logo depois de criar;0deixa como rascunho. Valores:0,1.
Respostas
201
Envelope criado (e enviado, com
send=1).401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
422
Dados inválidos (
errorstraz a lista por campo) ou pendências que impedem a operação (errors.envelope).429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl -X POST "https://app.odsign.com.br/api/v1/envelopes" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json" \
-F "subject=Contrato de prestação de serviços" \
-F "message=Olá! Segue o contrato para assinatura." \
-F "recipients[0][name]=Maria Souza" \
-F "recipients[0][email]=maria@exemplo.com.br" \
-F "recipients[0][auth_method]=email" \
-F "send=1" \
-F "files[]=@contrato.pdf"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->post('envelopes', [
'multipart' => [
['name' => 'subject', 'contents' => 'Contrato de prestação de serviços'],
['name' => 'message', 'contents' => 'Olá! Segue o contrato para assinatura.'],
['name' => 'recipients[0][name]', 'contents' => 'Maria Souza'],
['name' => 'recipients[0][email]', 'contents' => 'maria@exemplo.com.br'],
['name' => 'recipients[0][auth_method]', 'contents' => 'email'],
['name' => 'send', 'contents' => '1'],
['name' => 'files[]', 'contents' => fopen('contrato.pdf', 'r'), 'filename' => 'contrato.pdf'],
],
]);
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$envelope = json_decode((string) $response->getBody(), true)['data'];
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
import { readFile } from 'node:fs/promises';
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const form = new FormData();
form.append('subject', 'Contrato de prestação de serviços');
form.append('message', 'Olá! Segue o contrato para assinatura.');
form.append('recipients[0][name]', 'Maria Souza');
form.append('recipients[0][email]', 'maria@exemplo.com.br');
form.append('recipients[0][auth_method]', 'email');
form.append('send', '1');
form.append('files[]', new Blob([await readFile('contrato.pdf')], { type: 'application/pdf' }), 'contrato.pdf');
const res = await fetch(`${BASE}/envelopes`, { method: 'POST', headers, body: form });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.post(
f"{BASE}/envelopes",
headers=HEADERS,
data={
"subject": "Contrato de prestação de serviços",
"message": "Olá! Segue o contrato para assinatura.",
"recipients[0][name]": "Maria Souza",
"recipients[0][email]": "maria@exemplo.com.br",
"recipients[0][auth_method]": "email",
"send": "1",
},
files=[("files[]", ("contrato.pdf", open("contrato.pdf", "rb"), "application/pdf"))],
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 201 · exemplo
{
"data": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"is_template": false,
"template_name": null,
"template_description": null,
"subject": "Contrato de prestação de serviços",
"message": "Olá! Segue o contrato para assinatura.",
"status": "sent",
"status_label": "Em curso",
"editable": false,
"signing_order_enabled": false,
"current_routing_order": 1,
"reminder_enabled": true,
"reminder_delay_days": 1,
"reminder_frequency_days": 2,
"expire_enabled": true,
"expire_after_days": 120,
"expire_warn_days": 3,
"allow_reassign": false,
"category": null,
"sender": {
"name": "Ana Prado",
"email": "ana@suaempresa.com.br"
},
"folder": null,
"void_reason": null,
"original_sha256": "6298f5337e18894ea1b110664fce01b34bc90a3760673afa99d288f1e6859b3e",
"final_sha256": null,
"has_final": false,
"has_certificate": false,
"proofs": {
"timestamp": null,
"seal": null,
"package": false
},
"created_at": "2026-11-11T13:05:10+00:00",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"voided_at": null,
"declined_at": null,
"expires_at": "2027-03-11T13:05:12+00:00",
"updated_at": "2026-11-11T13:40:02+00:00",
"deleted_at": null,
"documents": [
{
"id": 812,
"name": "contrato.pdf",
"order": 1,
"pages": 3,
"page_sizes": [
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
}
],
"size": 184233,
"url": "https://app.odsign.com.br/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documentos/812"
}
],
"recipients": [
{
"id": 2051,
"type": "signer",
"type_label": "Assinatura obrigatória",
"role_name": null,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"routing_order": 1,
"status": "sent",
"status_label": "Enviado",
"has_access_code": false,
"phone": null,
"channel": "email",
"whatsapp_auth": false,
"sms_auth": false,
"auth_method": "email",
"auth_label": "Token por e-mail",
"private_message": null,
"color_index": 0,
"sent_at": "2026-11-11T13:05:12+00:00",
"delivered_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}
}
GET/envelopes/{id}
Detalhar envelope
Devolve o envelope com documentos, destinatários (situação, forma de envio e de autenticação,
datas de envio, visualização e assinatura), campos, hashes e provas de integridade. As fotos de
documento pessoal nunca saem pela API. O url de cada documento é o do app (exige login): para
baixar pela API, use GET /envelopes/{id}/documents/{kind}.
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.
Respostas
200
Envelope.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->get('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f');
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$envelope = json_decode((string) $response->getBody(), true)['data'];
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f`, { method: 'GET', headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.get(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
headers=HEADERS,
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 200 · exemplo
{
"data": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"is_template": false,
"template_name": null,
"template_description": null,
"subject": "Contrato de prestação de serviços",
"message": "Olá! Segue o contrato para assinatura.",
"status": "delivered",
"status_label": "Em curso",
"editable": false,
"signing_order_enabled": false,
"current_routing_order": 1,
"reminder_enabled": true,
"reminder_delay_days": 1,
"reminder_frequency_days": 2,
"expire_enabled": true,
"expire_after_days": 120,
"expire_warn_days": 3,
"allow_reassign": false,
"category": null,
"sender": {
"name": "Ana Prado",
"email": "ana@suaempresa.com.br"
},
"folder": null,
"void_reason": null,
"original_sha256": "6298f5337e18894ea1b110664fce01b34bc90a3760673afa99d288f1e6859b3e",
"final_sha256": null,
"has_final": false,
"has_certificate": false,
"proofs": {
"timestamp": null,
"seal": null,
"package": false
},
"created_at": "2026-11-11T13:05:10+00:00",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"voided_at": null,
"declined_at": null,
"expires_at": "2027-03-11T13:05:12+00:00",
"updated_at": "2026-11-11T13:40:02+00:00",
"deleted_at": null,
"documents": [
{
"id": 812,
"name": "contrato.pdf",
"order": 1,
"pages": 3,
"page_sizes": [
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
}
],
"size": 184233,
"url": "https://app.odsign.com.br/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documentos/812"
}
],
"recipients": [
{
"id": 2051,
"type": "signer",
"type_label": "Assinatura obrigatória",
"role_name": null,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"routing_order": 1,
"status": "delivered",
"status_label": "Visualizado",
"has_access_code": false,
"phone": null,
"channel": "email",
"whatsapp_auth": false,
"sms_auth": false,
"auth_method": "email",
"auth_label": "Token por e-mail",
"private_message": null,
"color_index": 0,
"sent_at": "2026-11-11T13:05:12+00:00",
"delivered_at": "2026-11-11T13:40:02+00:00",
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}
}
Envio
Enviar um rascunho, reenviar o convite e anular um envelope em curso.
POST/envelopes/{id}/send
Enviar rascunho
Envia um envelope em rascunho (created). Devolve o envelope atualizado. Se houver pendências,
responde 422 com a lista em errors.envelope (por exemplo, "Este envelope já foi enviado." ou
o aviso de limite mensal de envelopes do plano atingido).
Efeito real: Envia convites reais e conta no limite mensal de envelopes do plano.
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.
Respostas
200
Envelope enviado.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
422
Dados inválidos (
errorstraz a lista por campo) ou pendências que impedem a operação (errors.envelope).429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl -X POST "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/send" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->post('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/send');
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$envelope = json_decode((string) $response->getBody(), true)['data'];
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/send`, { method: 'POST', headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.post(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/send",
headers=HEADERS,
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 200 · exemplo
{
"data": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"is_template": false,
"template_name": null,
"template_description": null,
"subject": "Contrato de prestação de serviços",
"message": "Olá! Segue o contrato para assinatura.",
"status": "sent",
"status_label": "Em curso",
"editable": false,
"signing_order_enabled": false,
"current_routing_order": 1,
"reminder_enabled": true,
"reminder_delay_days": 1,
"reminder_frequency_days": 2,
"expire_enabled": true,
"expire_after_days": 120,
"expire_warn_days": 3,
"allow_reassign": false,
"category": null,
"sender": {
"name": "Ana Prado",
"email": "ana@suaempresa.com.br"
},
"folder": null,
"void_reason": null,
"original_sha256": "6298f5337e18894ea1b110664fce01b34bc90a3760673afa99d288f1e6859b3e",
"final_sha256": null,
"has_final": false,
"has_certificate": false,
"proofs": {
"timestamp": null,
"seal": null,
"package": false
},
"created_at": "2026-11-11T13:05:10+00:00",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"voided_at": null,
"declined_at": null,
"expires_at": "2027-03-11T13:05:12+00:00",
"updated_at": "2026-11-11T13:40:02+00:00",
"deleted_at": null,
"documents": [
{
"id": 812,
"name": "contrato.pdf",
"order": 1,
"pages": 3,
"page_sizes": [
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
}
],
"size": 184233,
"url": "https://app.odsign.com.br/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documentos/812"
}
],
"recipients": [
{
"id": 2051,
"type": "signer",
"type_label": "Assinatura obrigatória",
"role_name": null,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"routing_order": 1,
"status": "sent",
"status_label": "Enviado",
"has_access_code": false,
"phone": null,
"channel": "email",
"whatsapp_auth": false,
"sms_auth": false,
"auth_method": "email",
"auth_label": "Token por e-mail",
"private_message": null,
"color_index": 0,
"sent_at": "2026-11-11T13:05:12+00:00",
"delivered_at": null,
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}
}
POST/envelopes/{id}/resend
Reenviar o convite
Reenvia o convite aos destinatários da vez que ainda não concluíram, pela mesma forma de envio.
Cada reenvio gera um novo link pessoal: o link do convite anterior deixa de funcionar. Só
vale para envelopes em curso (sent ou delivered); em correção, envie as correções pelo app.
Efeito real: Reenvia convites reais e troca o link pessoal de quem está na vez.
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.
Respostas
200
Convite reenviado.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
422
Dados inválidos (
errorstraz a lista por campo) ou pendências que impedem a operação (errors.envelope).429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl -X POST "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/resend" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->post('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/resend');
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
echo json_decode((string) $response->getBody(), true)['message'];
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/resend`, { method: 'POST', headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.post(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/resend",
headers=HEADERS,
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["message"])
Resposta 200 · exemplo
{
"message": "Reenviado."
}
POST/envelopes/{id}/void
Anular envelope
Anula um envelope em curso. Os destinatários já avisados recebem a notificação da anulação, com o motivo, e os links pessoais param de funcionar. Não dá para desfazer: para corrigir algo, anule e crie outro envelope.
Efeito real: Anula o envelope de verdade e avisa os destinatários (não dá para desfazer).
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.
Corpo application/json
reasonstringobrigatórioMotivo, mostrado aos destinatários e na trilha de auditoria.
Respostas
200
Envelope anulado.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
422
Dados inválidos (
errorstraz a lista por campo) ou pendências que impedem a operação (errors.envelope).429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl -X POST "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/void" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"reason":"Valores revisados; um novo contrato será enviado."}'
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->post('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/void', [
'json' => ['reason' => 'Valores revisados; um novo contrato será enviado.'],
]);
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$envelope = json_decode((string) $response->getBody(), true)['data'];
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/void`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({"reason":"Valores revisados; um novo contrato será enviado."}) });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.post(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/void",
headers=HEADERS,
json={"reason": "Valores revisados; um novo contrato será enviado."},
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 200 · exemplo
{
"data": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"is_template": false,
"template_name": null,
"template_description": null,
"subject": "Contrato de prestação de serviços",
"message": "Olá! Segue o contrato para assinatura.",
"status": "voided",
"status_label": "Anulado",
"editable": false,
"signing_order_enabled": false,
"current_routing_order": 1,
"reminder_enabled": true,
"reminder_delay_days": 1,
"reminder_frequency_days": 2,
"expire_enabled": true,
"expire_after_days": 120,
"expire_warn_days": 3,
"allow_reassign": false,
"category": null,
"sender": {
"name": "Ana Prado",
"email": "ana@suaempresa.com.br"
},
"folder": null,
"void_reason": "Valores revisados; um novo contrato será enviado.",
"original_sha256": "6298f5337e18894ea1b110664fce01b34bc90a3760673afa99d288f1e6859b3e",
"final_sha256": null,
"has_final": false,
"has_certificate": false,
"proofs": {
"timestamp": null,
"seal": null,
"package": false
},
"created_at": "2026-11-11T13:05:10+00:00",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"voided_at": "2026-11-11T15:20:03+00:00",
"declined_at": null,
"expires_at": "2027-03-11T13:05:12+00:00",
"updated_at": "2026-11-11T15:20:03+00:00",
"deleted_at": null,
"documents": [
{
"id": 812,
"name": "contrato.pdf",
"order": 1,
"pages": 3,
"page_sizes": [
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
},
{
"w": 595.28,
"h": 841.89
}
],
"size": 184233,
"url": "https://app.odsign.com.br/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documentos/812"
}
],
"recipients": [
{
"id": 2051,
"type": "signer",
"type_label": "Assinatura obrigatória",
"role_name": null,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"routing_order": 1,
"status": "delivered",
"status_label": "Visualizado",
"has_access_code": false,
"phone": null,
"channel": "email",
"whatsapp_auth": false,
"sms_auth": false,
"auth_method": "email",
"auth_label": "Token por e-mail",
"private_message": null,
"color_index": 0,
"sent_at": "2026-11-11T13:05:12+00:00",
"delivered_at": "2026-11-11T13:40:02+00:00",
"signed_at": null,
"declined_at": null,
"decline_reason": null
}
],
"fields": []
}
}
Auditoria
Trilha de auditoria: cada ação registrada no envelope, com data, IP e origem.
GET/envelopes/{id}/audit-events
Trilha de auditoria
Lista os eventos do envelope em ordem cronológica, os mesmos do histórico do app e do log da
trilha de assinaturas: criação, envio, convites, aberturas, aceite do termo, códigos, assinaturas,
recusa, anulação, conclusão, downloads e provas. source indica a origem (web = app ou link
de assinatura, api = esta API, sistema = tarefas automáticas, como lembretes e expiração).
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.
Respostas
200
Eventos da trilha.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/audit-events" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-H "Accept: application/json"
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->get('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/audit-events');
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
$envelope = json_decode((string) $response->getBody(), true)['data'];
echo $envelope['id'], ' ', $envelope['status'], PHP_EOL;
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/audit-events`, { method: 'GET', headers });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data ?? body.message);
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.get(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/audit-events",
headers=HEADERS,
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
print(resp.json()["data"])
Resposta 200 · exemplo
{
"data": [
{
"id": 40201,
"action": "created",
"action_label": "Registrado",
"description": "O envelope foi criado por Ana Prado",
"actor_name": "Ana Prado",
"actor_email": "ana@suaempresa.com.br",
"source": "api",
"ip": "200.150.10.20",
"envelope_status": "created",
"document_sha256": null,
"created_at": "2026-11-11T13:05:10+00:00"
},
{
"id": 40202,
"action": "sent",
"action_label": "Convite enviado",
"description": "O envelope foi enviado",
"actor_name": "Ana Prado",
"actor_email": "ana@suaempresa.com.br",
"source": "api",
"ip": "200.150.10.20",
"envelope_status": "sent",
"document_sha256": "6298f5337e18894ea1b110664fce01b34bc90a3760673afa99d288f1e6859b3e",
"created_at": "2026-11-11T13:05:12+00:00"
},
{
"id": 40203,
"action": "sent",
"action_label": "Convite enviado",
"description": "Convite enviado a Maria Souza [maria@exemplo.com.br]",
"actor_name": "Ana Prado",
"actor_email": "ana@suaempresa.com.br",
"source": "api",
"ip": "200.150.10.20",
"envelope_status": "sent",
"document_sha256": null,
"created_at": "2026-11-11T13:05:12+00:00"
},
{
"id": 40207,
"action": "opened",
"action_label": "Aberto",
"description": "Maria Souza abriu o envelope",
"actor_name": "Maria Souza",
"actor_email": "maria@exemplo.com.br",
"source": "web",
"ip": "177.32.4.18",
"envelope_status": "delivered",
"document_sha256": null,
"created_at": "2026-11-11T13:40:02+00:00"
}
]
}
Documentos
Downloads do envelope concluído: documento assinado, original, trilha completa e provas.
GET/envelopes/{id}/documents/{kind}
Baixar documento
Baixa um arquivo do envelope concluído. A resposta é o próprio arquivo, com o nome em
Content-Disposition. Antes da conclusão, todos os tipos respondem 404 ("Documento ainda não
concluído."); o carimbo do tempo responde 404 enquanto não for emitido.
kind |
Arquivo |
|---|---|
signed |
Documento assinado (PDF): os documentos com a trilha de assinaturas no fim. É o arquivo do final_sha256 |
original |
Documento original (PDF): os documentos como foram enviados, antes das assinaturas |
package |
Trilha completa (ZIP): documento assinado, original, comprovante de conclusão, carimbo do tempo .tsr (quando emitido) e LEIA-ME.txt |
final |
o contrato final exatamente como gravado na conclusão (bytes do final_sha256) |
certificate |
comprovante de conclusão (PDF), selado com o selo eletrônico ICP-Brasil da plataforma quando proofs.seal.issued |
timestamp |
carimbo do tempo RFC 3161 da FreeTSA sobre o contrato final (.tsr) |
Confira a integridade calculando o SHA-256 do arquivo signed (ou final) e comparando com
final_sha256.
Parâmetros
idstring (uuid) · caminhoobrigatórioIdentificador do envelope (UUID), o
iddevolvido na criação.kindstring · caminhoobrigatórioTipo do arquivo. Valores:
signed,original,package,final,certificate,timestamp.
Respostas
200
O arquivo.
401
Token ausente, inválido, de usuário bloqueado ou excluído, ou de conta suspensa.
403
O token é válido, mas o usuário não pode fazer esta operação (envelope de outro remetente sem ser administrador, ou perfil sem envio).
404
Envelope inexistente ou de outra conta, endpoint errado, tipo de documento inválido ou documento ainda não concluído.
429
Limite de 120 requisições por minuto atingido. Espere
Retry-Aftersegundos.
curl "https://app.odsign.com.br/api/v1/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documents/signed" \
-H "Authorization: Bearer $ODSIGN_TOKEN" \
-o contrato-assinado.pdf
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client([
'base_uri' => 'https://app.odsign.com.br/api/v1/',
'headers' => [
'Authorization' => 'Bearer '.getenv('ODSIGN_TOKEN'),
'Accept' => 'application/json',
],
'http_errors' => false, // 4xx/5xx não lançam exceção: confira o status
]);
$response = $client->get('envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documents/signed', [
'sink' => 'contrato-assinado.pdf', // grava o arquivo direto no disco
]);
if ($response->getStatusCode() >= 400) {
exit('Erro '.$response->getStatusCode().': '.$response->getBody());
}
// Content-Disposition traz o nome original do arquivo.
echo $response->getHeaderLine('Content-Disposition');
// Node.js 18 ou mais novo (fetch e FormData nativos), como módulo ES (.mjs).
import { writeFile } from 'node:fs/promises';
const BASE = 'https://app.odsign.com.br/api/v1';
const headers = { Authorization: `Bearer ${process.env.ODSIGN_TOKEN}`, Accept: 'application/json' };
const res = await fetch(`${BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documents/signed`, { method: 'GET', headers });
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).message}`);
await writeFile('contrato-assinado.pdf', Buffer.from(await res.arrayBuffer()));
# pip install requests
import os
import requests
BASE = "https://app.odsign.com.br/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ODSIGN_TOKEN']}", "Accept": "application/json"}
resp = requests.get(
f"{BASE}/envelopes/01a1cc92-f28f-73b8-8dcc-f15232d31a3f/documents/signed",
headers=HEADERS,
timeout=60,
)
resp.raise_for_status() # 4xx/5xx: requests.HTTPError (o corpo traz "message" e "errors")
with open("contrato-assinado.pdf", "wb") as f:
f.write(resp.content)
Objetos
Campos de cada objeto devolvido pela API. Campos sem valor vêm como null; novos campos podem aparecer (ignore o que não conhecer).
Envelope
O envelope completo: documentos, destinatários, campos, hashes e provas.
idstring (uuid)Identificador do envelope.
is_templatebooleanSempre
falsena API (modelos não são expostos).template_namestring ou nulltemplate_descriptionstring ou nullsubjectstringAssunto (vai no convite).
messagestring ou nullMensagem para todos os destinatários.
statusstring (enum)Estado do envelope:
created= rascunho;sent= em curso, ninguém abriu ainda;delivered= em curso, alguém já abriu;correct= em correção pelo remetente;completed= concluído;declined= recusado;voided= anulado;expired= expirado.status_labelstringeditablebooleantruesó no rascunho.signing_order_enabledbooleanSegue a ordem de assinatura (
routing_order).current_routing_orderintegerOrdem que está na vez.
reminder_enabledbooleanLembretes automáticos (padrão da conta em Configurações › Conta).
reminder_delay_daysintegerreminder_frequency_daysintegerexpire_enabledbooleanValidade (padrão da conta).
expire_after_daysintegerexpire_warn_daysintegerallow_reassignbooleancategorystring ou nullsenderobjectfolderstring ou nullNome da pasta no app.
void_reasonstring ou nulloriginal_sha256string ou nullSHA-256 dos documentos como foram enviados (definido no envio).
final_sha256string ou nullSHA-256 do documento assinado (definido na conclusão).
has_finalbooleanO documento assinado está pronto para baixar.
has_certificatebooleanproofsProofscreated_atstring (date-time)sent_atstring (date-time) ou nullPrimeiro envio.
completed_atstring (date-time) ou nullConclusão.
voided_atstring (date-time) ou nullAnulação.
declined_atstring (date-time) ou nullRecusa.
expires_atstring (date-time) ou nullExpiração prevista.
updated_atstring (date-time)deleted_atstring (date-time) ou nullSempre
nullna API.documentsarray de Documentrecipientsarray de Recipientfieldsarray de FieldCampos posicionados (vazio quando todos assinam pela forma de autenticação).
EnvelopeSummary
Item da listagem de envelopes.
Item da listagem (resumo).
idstring (uuid)subjectstringstatusstring (enum)Estado do envelope:
created= rascunho;sent= em curso, ninguém abriu ainda;delivered= em curso, alguém já abriu;correct= em correção pelo remetente;completed= concluído;declined= recusado;voided= anulado;expired= expirado.status_labelstringNome do estado no app (Rascunho, Em curso, Concluído...).
recipientsstringNomes dos destinatários, separados por vírgula.
progressintegerPorcentagem de quem precisa agir e já concluiu.
waiting_forstring ou nullNome de quem está na vez (ou
me, quando é o próprio dono do token);nullfora de curso.senderstring ou nullNome do remetente.
sender_idinteger ou nullsender_avatar_urlstring ou nullfolder_idinteger ou nullPasta do app (organização interna).
updated_atstring (date-time)expires_atstring (date-time) ou nullQuando expira, se a validade estiver ligada.
deletedbooleanSempre
falsena API (eliminados não são listados).
Recipient
Cada pessoa do envelope, com a forma de envio e a de autenticação.
idintegerIdentificador do destinatário (o mesmo dos webhooks).
typestring (enum)Papel:
signer= assinatura obrigatória;in_person_signer= assinatura presencial;cc= apenas cópia (recebe o documento na conclusão);viewer= apenas visualização (precisa abrir, não assina).type_labelstringrole_namestring ou nullSó em envelopes criados de modelo, no app.
namestringemailstring (email) ou nullrouting_orderintegerOrdem de assinatura (vale com
signing_order_enabled).statusstring (enum)Estado do destinatário:
created= aguardando a vez;sent= convite enviado;delivered= abriu o documento;signed= assinou (ou visualizou, se viewer);completed= cópia entregue na conclusão (cc);declined= recusou.status_labelstringhas_access_codebooleanExige código de acesso combinado (definido no app).
phonestring ou nullCelular só com dígitos e o código do país (5511987654321).
channelstring (enum)Forma de envio do convite e dos lembretes. Com
whatsappousms(exigephone), a mensagem sai pelo número da plataforma e conta na cota do plano.whatsapp_authbooleanCódigo pelo WhatsApp para abrir o documento (opção antiga; prefira
auth_method).sms_authbooleanCódigo por SMS para abrir o documento (opção antiga; prefira
auth_method).auth_methodstring (enum) ou nullnullpara quem não assina (cc, viewer) e para quem assina pelos campos. Forma de autenticação de quem assina:link= só o acesso pelo link pessoal e a confirmação na tela;signature= assinatura manuscrita desenhada na tela;email= token de 6 dígitos enviado ao e-mail na hora de assinar (exigechannel=email);sms= token de 6 dígitos por SMS (exigephone);whatsapp= token de 6 dígitos pelo WhatsApp (exigephone);document= fotos da frente e do verso de um documento oficial com foto, pela câmera do navegador (conforme o plano).auth_labelstring ou nullNome da forma de autenticação (Link pessoal, Assinatura manuscrita, Token por e-mail, Token por SMS, Token pelo WhatsApp, Documento pessoal).
private_messagestring ou nullcolor_indexinteger ou nullCor do destinatário no app.
sent_atstring (date-time) ou nullConvite enviado.
delivered_atstring (date-time) ou nullPrimeira abertura.
signed_atstring (date-time) ou nullAssinatura (ou visualização registrada).
declined_atstring (date-time) ou nullRecusa.
decline_reasonstring ou null
Document
Cada arquivo do envelope (convertido para PDF).
idintegernamestringNome do arquivo enviado.
orderintegerPosição no envelope, a partir de 1.
pagesintegerpage_sizesarray de objectTamanho de cada página em pontos PDF (A4 = 595 × 842).
sizeintegerTamanho do PDF em bytes.
urlstringEndereço no app (exige login). Para baixar pela API, use os downloads.
Field
Campo posicionado no documento (fluxo por campos).
Campo posicionado no documento. Coordenadas em pontos PDF (72 por polegada), origem no canto superior esquerdo da página.
idintegerdocument_idintegerrecipient_idintegertypestringValores:
signature,initials,date_signed,name,email,company,title,text,number,checkbox,dropdown,radio,approve,decline,note,attachment.pageintegerxnumberynumberwidthnumberheightnumberrequiredbooleanread_onlybooleanlabelstring ou nulltooltipstring ou nulldefault_valuestring ou nulloptionsarray de string ou nullvalidationstring ou nullmax_lengthinteger ou nullfontstring ou nullfont_sizeinteger ou nullfont_colorstring ou null
Proofs
Provas de integridade do envelope concluído.
Provas de integridade do envelope concluído. timestamp e seal são null quando a integração não estava ativa na conclusão.
Proof
Uma prova (carimbo do tempo ou selo).
statusstringpending= na fila (novas tentativas automáticas);issued= emitida;failed= não emitida. Valores:pending,issued,failed.issuedbooleanEmitida e verificada.
issued_atstring (date-time) ou nullHora da emissão (genTime do carimbo ou hora do selo).
verified_atstring (date-time) ou nullHora da verificação.
attemptsintegerlast_attempt_atstring (date-time) ou nullÚltima tentativa.
next_attempt_atstring (date-time) ou nullPróxima tentativa (pendente).
last_errorstring ou nullsha256string ou nullSHA-256 do
.tsrou do comprovante selado.serialstring ou nullpolicystring ou nullauthoritystring ou nullQuem emitiu (TSA ou titular do certificado do selo).
cnpjstring ou nullCNPJ do titular do selo.
issuer_namestring ou nullAutoridade certificadora do selo.
developmentbooleantruesó fora de produção (credencial de desenvolvimento).
AuditEvent
Cada ação registrada na trilha de auditoria.
idintegeractionstringAção registrada (veja
action_label). Valores:created,sent,resent,reminder_sent,opened,consented,viewed,signed,completed,declined,voided,corrected,expired,deleted,restored,downloaded,copy_sent,access_code_failed,access_code_verified,whatsapp_sent,whatsapp_code_sent,whatsapp_verified,whatsapp_code_failed,sms_sent,sms_code_sent,sms_verified,sms_code_failed,email_code_sent,email_code_failed,signature_token_verified,identity_document_captured,identity_document_viewed,timestamped,sealed,document_sealed.action_labelstringdescriptionstringFrase completa, como aparece no histórico.
actor_namestringQuem fez (usuário, destinatário ou "Sistema").
actor_emailstring ou nullsourcestringValores:
web,api,sistema.ipstring ou nullenvelope_statusstring (enum)Estado do envelope:
created= rascunho;sent= em curso, ninguém abriu ainda;delivered= em curso, alguém já abriu;correct= em correção pelo remetente;completed= concluído;declined= recusado;voided= anulado;expired= expirado.document_sha256string ou nullHash relacionado ao evento (envio, conclusão, foto do documento pessoal).
created_atstring (date-time)
PageMeta
Paginação da listagem.
pageintegerPágina atual.
per_pageintegertotalintegerTotal de envelopes no filtro.
last_pageintegerÚltima página (1 quando não há resultados).
WebhookEvent
Corpo de cada entrega de webhook.
Corpo de cada entrega de webhook (POST, JSON). Assinado em X-Signature = HMAC-SHA256 do corpo bruto com o segredo whsec_..., em hexadecimal minúsculo.
eventstringValores:
envelope.sent,envelope.delivered,envelope.completed,envelope.declined,envelope.voided,recipient.sent,recipient.delivered,recipient.signed,recipient.declined.sent_atstring (date-time)Quando o evento foi gerado.
envelopeobjectrecipientobject ou nullnullnos eventosenvelope.*.
Webhooks
Os webhooks de saída avisam o seu sistema quando algo acontece com um envelope: o ODSign faz um POST com um JSON para o endereço cadastrado por um administrador em Configurações › Webhooks (conforme o plano), assinado com o segredo whsec_... exibido na criação.
- Cabeçalhos:
Content-Type: application/json,X-Event(nome do evento) eX-Signature(HMAC-SHA256 do corpo bruto com o segredo completo, em hexadecimal minúsculo). - Sucesso: qualquer resposta
2xx, dentro do tempo limite (padrão de 10 s). Responda logo e processe depois, em segundo plano. - Novas tentativas: até 3 tentativas no total: na hora, cerca de 10 s depois e cerca de 1 min depois. Redirecionamentos (
3xx) não são seguidos e contam como falha. - Repetição e ordem: a mesma entrega pode chegar mais de uma vez e a ordem de chegada não é garantida. Use
event,envelope.iderecipient.idpara ignorar repetições e consulteGET /envelopes/{id}antes de decisões importantes. - Endereços: só endereços públicos
httpouhttps(prefirahttps);localhost, IPs privados e URLs com usuário e senha são recusados. - Teste: o botão Testar do webhook envia
{"event":"test.ping","sent_at":"..."}, assinado da mesma forma.
Guias: Webhooks de saída e Validar a assinatura dos webhooks.
POST /webhooks/odsign HTTP/1.1
Content-Type: application/json
X-Event: recipient.signed
X-Signature: 5f0c1f8e0d2b9a7c3e4d6f8a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4{
"event": "recipient.signed",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "delivered",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": {
"id": 2051,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"status": "signed",
"routing_order": 1
}
}Eventos
Em um envelope com um signatário, a sequência típica é envelope.sent → recipient.sent → recipient.delivered → envelope.delivered → recipient.signed → envelope.completed. Nos eventos envelope.*, recipient vem como null. Reenvios, lembretes, expiração e emissão das provas não geram eventos.
envelope.sentO envelope foi enviado (também quando as correções são enviadas).
{
"event": "envelope.sent",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "sent",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": null
}envelope.deliveredO primeiro destinatário abriu o envelope.
{
"event": "envelope.delivered",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "delivered",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": null
}envelope.completedTodos concluíram: o documento assinado (com a trilha de assinaturas) está pronto para baixar.
{
"event": "envelope.completed",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "completed",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": "2026-11-11T13:42:11+00:00",
"final_sha256": "b7d4c0e1a9f35a2c8e6d1f0b3a7c9e2d4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c"
},
"recipient": null
}envelope.declinedUm destinatário recusou e o envelope foi encerrado.
{
"event": "envelope.declined",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "declined",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": null
}envelope.voidedO remetente anulou o envelope.
{
"event": "envelope.voided",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "voided",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": null
}recipient.sentO convite saiu para um destinatário (com ordem de assinatura, quando chega a vez dele).
{
"event": "recipient.sent",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "sent",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": {
"id": 2051,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"status": "sent",
"routing_order": 1
}
}recipient.deliveredUm destinatário abriu o documento pela primeira vez.
{
"event": "recipient.delivered",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "delivered",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": {
"id": 2051,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"status": "delivered",
"routing_order": 1
}
}recipient.signedUm destinatário concluiu a parte dele (assinou ou, se viewer, visualizou).
{
"event": "recipient.signed",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "delivered",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": {
"id": 2051,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"status": "signed",
"routing_order": 1
}
}recipient.declinedUm destinatário recusou a assinatura.
{
"event": "recipient.declined",
"sent_at": "2026-11-11T13:42:11+00:00",
"envelope": {
"id": "01a1cc92-f28f-73b8-8dcc-f15232d31a3f",
"subject": "Contrato de prestação de serviços",
"status": "declined",
"sent_at": "2026-11-11T13:05:12+00:00",
"completed_at": null,
"final_sha256": null
},
"recipient": {
"id": 2051,
"name": "Maria Souza",
"email": "maria@exemplo.com.br",
"status": "declined",
"routing_order": 1
}
}Validar e simular um webhook
Confira a sua implementação sem depender de um evento real: cole o segredo e o corpo recebido e compare a assinatura calculada com o X-Signature que chegou. O cálculo acontece só no seu navegador (Web Crypto); nada é enviado.
Para simular uma entrega no seu ambiente local, informe também o endereço do seu receptor: a página monta o comando curl com o corpo e a assinatura certos.
Atenção: calcule sempre sobre o corpo bruto, byte a byte. Decodificar e codificar o JSON de novo muda espaços, acentos e barras, e a assinatura deixa de bater.
OpenAPI, Postman e Insomnia
O contrato completo, com os endpoints, os objetos e os webhooks, está em OpenAPI 3.1, gerado a partir do código da API (nunca fica desatualizado):
Postman: Import › Link, cole o endereço do JSON e confirme. Em Authorization da coleção, escolha Bearer Token e informe o token.
Insomnia: Create › Import › URL com o mesmo endereço; depois, em cada requisição (ou no ambiente), use Bearer Token.
Geradores de SDK (OpenAPI Generator, por exemplo) aceitam o mesmo arquivo.
curl -o openapi.yaml https://docs.odsign.com.br/api/openapi.yamlNão encontrou o que procurava?
Nossa equipe ajuda você. Conte o que aconteceu e responderemos por e-mail.