Documentação da API
Integre assinatura simples por código de e-mail ou WhatsApp, com a marca do parceiro, ou assinatura qualificada ICP-Brasil com certificado em nuvem (Bird ID, VIDaaS) ou A1 — direto ou em sessão opcional de até 7 dias. Envie PDF ou JSON e acompanhe o resultado por webhook ou consulta de status. Serve para empresas, clínicas, ERPs e plataformas SaaS.
Os exemplos usam dados e credenciais fictícios. Para receber suas credenciais, cadastre-se como parceiro.
Novo: gere um exemplo de integração completo na sua linguagem e framework com o gerador de código com IA.
Gerar meu códigoAssinatura eletrônica simples · whitelabel
Use mode: "simple" para confirmar o aceite com código de uso único, sem certificado ICP-Brasil. Envie um signatário (signer) ou várias pessoas em paralelo (signers, até 20): cada uma recebe link e código próprios, e o documento é concluído quando todas assinarem. A rubrica desenhada é opcional e não substitui o código.
{
"mode": "simple",
"document_name": "Contrato de exemplo",
"document": {
"title": "Contrato",
"body_text": "Texto de exemplo para revisão."
},
"signer": {
"full_name": "Maria Exemplo",
"email": "maria@example.com"
},
"send_email_invitation": true,
"expires_in_hours": 168,
"allow_drawn_signature": true,
"external_id": "contrato-123"
}- • Envie
pdf_base64oudocument. Por segurança,pdf_urlnão é aceito neste modo. - • Informe
signer.email,signer.phoneou os dois. Useotp_channel: "email"(padrão) ou"whatsapp"(Zapclient); sem o campo, o canal é o e-mail quando existe, senão WhatsApp. Telefones brasileiros de 10 ou 11 dígitos recebem DDI 55 automaticamente; outros países exigem país + DDD + número. Nome é obrigatório; CPF é opcional e declarado, não validado como identidade pelo código. - • Para várias pessoas no mesmo PDF, envie
signers(lista de 1 a 20) no lugar designer. Cada pessoa tem seu próprio canal, link e código;signeresignersnão podem ser enviados juntos. - • Expiração: 1 a 720 horas; padrão 168 horas. Código: 10 minutos, 5 tentativas, intervalo de 60 segundos e até 10 envios por documento.
- •
test_modenão é aceito no modo simples: a confirmação precisa ser real. - • Configure nome, logo e cor em Área do cliente → Sua marca. A marca é preservada em cada solicitação.
- • Cadastre a origem do app em Área do cliente → Credenciais para receber eventos seguros do embed.
- • Convite por WhatsApp: envie
send_whatsapp_invitation: truee cada pessoa comphonerecebe uma mensagem com o nome dela, o nome da sua marca, o documento e o botão "Revisar e assinar". Pode ser combinado comsend_email_invitation. - • Resposta:
signature_id,sign_url,expires_at,invitation_sent,email_invitation_sentewhatsapp_invitation_sent. Comsigners, recebe tambémsigner_counte a listasignerscom umsign_urlpor pessoa e o resultado do convite de cada uma. Se o convite falhar, use o link retornado. - • O PDF concluído inclui aceite, método, data UTC, IP quando disponível, navegador, hash original e QR de conferência. Não possui certificado ICP-Brasil; avalie a adequação ao seu documento.
- • As consultas de status, download, cancelamento e webhooks são as mesmas da assinatura em nuvem.
Confirmação pelo WhatsApp
O código é enviado pelo Zapclient usando o template codigo_temporario, em pt_BR, com botão de copiar. O canal e o destinatário ficam fixos na criação. As credenciais de envio são da plataforma: não envie chaves Zapclient, template ou código OTP na chamada da API. A marca personaliza a tela, os e-mails e o registro final; o template do WhatsApp não inclui a logo do parceiro.
{
"mode": "simple",
"otp_channel": "whatsapp",
"document_name": "Termo de aceite",
"document": {
"title": "Termo de aceite",
"body_text": "Texto fictício para revisão e aceite."
},
"signer": {
"full_name": "Maria Exemplo",
"email": "maria@example.com",
"phone": "5511999999999"
},
"send_email_invitation": false,
"expires_in_hours": 168,
"allow_drawn_signature": true,
"external_id": "termo-123",
"webhook_url": "https://seusistema.com.br/webhooks/assinatura"
}{
"signature_id": "UUID_DA_SOLICITACAO",
"status": "pending_authorization",
"mode": "simple",
"provider": "whatsapp",
"sign_url": "https://assinatura.online/assinar/TOKEN_DA_SOLICITACAO",
"expires_at": "2026-10-16T17:00:00.000Z",
"invitation_sent": false
}Várias pessoas no mesmo documento
Envie signers no lugar de signer para colheita em paralelo: cada pessoa abre um link próprio, recebe o código pelo seu canal e vê o progresso das demais no próprio painel. Enquanto faltarem assinaturas, cada uma baixa uma cópia individual com o seu carimbo; o documento consolidado, com todas as assinaturas, é liberado quando a última pessoa assinar.
{
"mode": "simple",
"document_name": "Contrato de prestação de serviços",
"document": {
"title": "Contrato",
"body_text": "Texto fictício para revisão e aceite."
},
"signers": [
{
"full_name": "Maria Exemplo",
"email": "maria@example.com"
},
{
"full_name": "João Exemplo",
"phone": "5511999998888",
"otp_channel": "whatsapp"
},
{
"full_name": "Ana Exemplo",
"email": "ana@example.com",
"phone": "5521999997777",
"otp_channel": "whatsapp"
}
],
"send_email_invitation": true,
"send_whatsapp_invitation": true,
"expires_in_hours": 168,
"external_id": "contrato-124"
}{
"signature_id": "UUID_DA_SOLICITACAO",
"status": "pending_authorization",
"mode": "simple",
"provider": "email",
"sign_url": "https://assinatura.online/assinar/TOKEN_DA_PRIMEIRA_PESSOA",
"expires_at": "2026-10-16T17:00:00.000Z",
"invitation_sent": true,
"signer_count": 3,
"signers": [
{
"name": "Maria Exemplo",
"email": "maria@example.com",
"phone": null,
"channel": "email",
"sign_url": "https://assinatura.online/assinar/TOKEN_1",
"status": "pending"
},
{
"name": "João Exemplo",
"email": null,
"phone": "5511999998888",
"channel": "whatsapp",
"sign_url": "https://assinatura.online/assinar/TOKEN_2",
"status": "pending"
},
{
"name": "Ana Exemplo",
"email": "ana@example.com",
"phone": null,
"channel": "whatsapp",
"sign_url": "https://assinatura.online/assinar/TOKEN_3",
"status": "pending"
}
]
}Papel de cada pessoa
Envie role (até 60 caracteres) em cada signatário, por exemplo "Contratante", "Contratado" ou "Testemunha". O papel aparece na tela de assinatura, no registro do PDF, na consulta de status e nos webhooks.
{
"signers": [
{
"full_name": "Maria Exemplo",
"email": "maria@example.com",
"role": "Contratante"
},
{
"full_name": "João Exemplo",
"phone": "5511999998888",
"role": "Testemunha"
}
]
}Ordem de assinatura
Por padrão, signing_order: "parallel": todos podem assinar ao mesmo tempo. Com "sequential", a ordem da lista signers vale: só a primeira pessoa recebe o convite; cada uma é avisada automaticamente (pelos canais de convite escolhidos) quando a anterior assinar. Quem abrir o link antes da vez pode revisar o documento, mas não recebe código (erro not_your_turn).
{
"signing_order": "sequential",
"send_email_invitation": true,
"signers": [
{
"full_name": "Maria Exemplo",
"email": "maria@example.com",
"role": "Locadora"
},
{
"full_name": "João Exemplo",
"email": "joao@example.com",
"role": "Locatário"
}
]
}Rubrica visual nas páginas
Envie stamps em cada signatário para colocar a rubrica (nome, data, canal, desenho quando houver e QR de conferência) em páginas do próprio documento. page começa em 1; x e y são frações de 0 a 1 medidas a partir do canto superior esquerdo. Até 50 posições por pessoa. A rubrica só aparece depois que a pessoa assina; o registro completo continua nas folhas finais.
{
"signers": [
{
"full_name": "Maria Exemplo",
"email": "maria@example.com",
"stamps": [
{
"page": 1,
"x": 0.08,
"y": 0.85
},
{
"page": 3,
"x": 0.6,
"y": 0.8
}
]
}
]
}Lembrete antes de expirar
reminder_hours_before (padrão 24, de 0 a 168; 0 desativa) define quando quem ainda não assinou recebe um lembrete por e-mail e/ou WhatsApp, pelos contatos cadastrados. É enviado um lembrete por pessoa; na ordem sequencial, só para quem está na vez.
Reenviar convite
POST /api/public/v1/signatures/resend reenvia o convite (como lembrete) para quem ainda não assinou. Sem signer_sign_url, vale para todas as pessoas pendentes. channels é opcional (email, whatsapp); por padrão usa os contatos cadastrados. Limite: um reenvio a cada 10 minutos por pessoa e no máximo 10 convites.
{
"signature_id": "UUID_DA_SOLICITACAO",
"signer_sign_url": "https://assinatura.online/assinar/TOKEN_2",
"channels": [
"whatsapp"
]
}{
"signature_id": "UUID_DA_SOLICITACAO",
"status": "pending_authorization",
"resent": [
{
"name": "João Exemplo",
"sign_url": "https://assinatura.online/assinar/TOKEN_2",
"email_sent": false,
"whatsapp_sent": true,
"skipped": null
}
]
}Enviar ou reenviar o PDF assinado
POST /api/public/v1/signatures/deliver envia o PDF assinado de um documento concluído para todas as pessoas do documento, nos canais indicados. A cobrança é por envio (cada pessoa em cada canal), debitada da carteira: WhatsApp R$ 0,30 e e-mail R$ 0,10. Pessoas sem o contato do canal não são cobradas; sem saldo, o envio é pulado; se o envio falhar, o valor é estornado. Limite: 10 envios por documento por hora. Indisponível para contas cobradas pela Prescri. No WhatsApp, a mensagem usa um template aprovado pela Meta com o PDF anexo e botão para abrir o documento — o parceiro não precisa criar nem configurar template; paciente, clínica, sistema, documento e assinante são preenchidos automaticamente.
Opcional: envie delivery.filename na criação (simples ou qualificada) para definir o nome do anexo. No envio/reenvio, o mesmo campo substitui o nome somente daquela chamada, sem alterar o PDF ou a assinatura. Acentos são preservados; caracteres de caminho são removidos e a extensão é sempre .pdf. Sem personalização, usamos o nome do documento com _Assinado.pdf. Isso não ativa o envio automático nem muda a cobrança.
{
"signature_id": "UUID_DA_SOLICITACAO",
"channels": [
"whatsapp",
"email"
],
"delivery": {
"filename": "Relatório Médico.pdf"
}
}Entregar a quem não assina (ex.: paciente)
Envie recipients (1 a 5 pessoas, cada uma com phone e/ou email) para entregar o PDF somente a essas pessoas — por exemplo, o paciente de uma receita assinada só pelo médico com Bird ID ou VIDaaS. Vale para assinatura qualificada e simples; quem assinou não recebe nessa chamada. A cobrança é a mesma: por pessoa e por canal.
{
"signature_id": "UUID_DA_SOLICITACAO",
"channels": [
"whatsapp"
],
"recipients": [
{
"name": "Nome do Paciente",
"phone": "5549999990000"
}
],
"delivery": {
"filename": "Receita.pdf"
}
}Envio automático ao concluir: no POST /signatures/create (Bird ID, VIDaaS ou A1), informe delivery.whatsapp/delivery.email com phone/email_to (ou o objeto patient). Assim que o médico assinar, o PDF vai ao paciente sem nova chamada — o médico continua o único assinante. doctor_cpf é aceito como sinônimo de signer.cpf. Mesma cobrança por pessoa e canal; sem saldo, o envio é pulado sem afetar a assinatura.
{
"provider": "birdid",
"doctor_cpf": "12345678900",
"document_name": "Receita",
"pdf_url": "https://seusistema.com/receita.pdf",
"patient": {
"full_name": "João da Silva",
"phone": "5511999998888"
},
"delivery": {
"whatsapp": true,
"email": true,
"phone": "5511999998888",
"email_to": "joao@email.com",
"patient_name": "João da Silva",
"filename": "Receita.pdf"
}
}{
"signature_id": "UUID_DA_SOLICITACAO",
"sent": 3,
"failed": 0,
"skipped": 0,
"charged_cents": 70,
"deliveries": [
{
"channel": "whatsapp",
"recipient": "***4321",
"status": "sent",
"charged_cents": 30,
"reason": null
},
{
"channel": "whatsapp",
"recipient": "***8765",
"status": "sent",
"charged_cents": 30,
"reason": null
},
{
"channel": "email",
"recipient": "m***@example.com",
"status": "sent",
"charged_cents": 10,
"reason": null
}
]
}Recusa pelo signatário
Na tela de assinatura, a pessoa pode escolher "Não concordo e quero recusar" e informar o motivo. O documento passa para status: "rejected", os demais links deixam de aceitar assinatura e você recebe o webhook signature.rejected com nome, papel, motivo e data. Na consulta de status, o signatário traz rejected_at e rejection_reason.
{
"event": "signature.rejected",
"signature_id": "UUID_DA_SOLICITACAO",
"data": {
"status": "rejected",
"mode": "simple",
"signer": "João Exemplo",
"role": "Testemunha",
"reason": "Valor diferente do combinado",
"rejected_at": "2026-10-10T14:00:00.000Z"
}
}Documento concluído
Quando a última pessoa assina, o PDF final é gerado e cada signatário com e-mail recebe automaticamente o link para baixar o documento e o registro. Você recebe o webhook signature.completed.
Neste modo, provider na resposta indica o canal (email ou whatsapp), não um certificado. Não há endpoint público para o parceiro enviar ou confirmar OTP: o fluxo acontece na tela de assinatura. Não altere a identidade do destinatário durante a confirmação. Assinatura avançada não está disponível.
// Carregue https://assinatura.online/assinatura-sdk.js no app.
// Obtenha sign_url do SEU servidor, que guarda API Key e Secret.
AssinaturaOnline.open({
signUrl: resposta.sign_url,
mode: "iframe",
container: "#assinatura",
height: 900,
onEvent(event) {
if (event.type === "signature.completed") {
// Consulte o status pelo seu servidor antes de liberar o documento.
}
}
});Início rápido
- 1. Crie a solicitação com um PDF ou com um documento em JSON.
- 2. Envie o
sign_urlao signatário (ou abra pelo SDK embed). Exceção: no A1 direto ou sessão A1, a resposta já vem comstatus: "signed"e o PDF, sem link. - 3. Na simples, o signatário revisa, aceita e confirma o código. Na qualificada, aprova com Bird ID ou VIDaaS, ou envia o próprio A1 na página hospedada.
- 4. Receba o webhook
signature.completede baixe o PDF assinado.
BASE_URL=https://assinatura.online/api/public/v1Teste em 2 minutos
Cole no terminal com suas credenciais. O test_mode gera um envio sem assinatura válida.
curl -X POST "https://assinatura.online/api/public/v1/signatures/create" \
-H "X-API-Key: $ASSINATURA_API_KEY" \
-H "X-API-Secret: $ASSINATURA_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"provider":"birdid","document_name":"Contrato de exemplo","pdf_url":"https://seusistema.com.br/exemplo.pdf","signer":{"cpf": "52998224725","full_name":"Maria Exemplo"},"test_mode":true}'Autenticação
Toda chamada leva sua chave e seu segredo nos cabeçalhos. Faça as chamadas só a partir do seu servidor e guarde o segredo em variáveis de ambiente.
X-API-Key: pk_exemplo_0000000000000000
X-API-Secret: sk_exemplo_0000000000000000
Content-Type: application/jsonCertificados suportados
Certificado A1 (.pfx / .p12)
Usa certificados A1 ICP-Brasil em arquivo .pfx ou .p12 compatível. Valide o arquivo e a cadeia do seu certificado antes de usar documentos reais. Pela API: página hospedada (sign_url), A1 direto no corpo da chamada ou sessão A1 opcional de até 7 dias. Também disponível no assinador online.
Certificado em nuvem
Integração direta com Bird ID (Soluti) e VIDaaS (Valid). O signatário aprova no aplicativo do celular com PIN ou biometria.
Sem plugin de assinatura no navegador
Não precisa de WebPKI, drivers nem extensões no navegador. Para Bird ID e VIDaaS, é necessário ter o certificado em nuvem e o aplicativo do provedor configurados. No A1, o arquivo e a senha são enviados ao serviço para assinar: no fluxo direto e na página hospedada ficam só em memória; na sessão A1 opcional ficam cifrados até a sessão expirar ou ser revogada.
- Padrão PAdES, conferível no validador oficial do ITI.
- Tokens e cartões USB (A3 físico) não são aceitos, porque dependem de software instalado no computador.
Criar solicitação com PDF
curl -X POST "$BASE_URL/signatures/create" \
-H "X-API-Key: $ASSINATURA_API_KEY" \
-H "X-API-Secret: $ASSINATURA_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"provider": "birdid",
"document_name": "Contrato de prestação de serviço",
"pdf_url": "https://seusistema.com.br/contratos/2431.pdf",
"signer": { "cpf": "52998224725", "full_name": "Maria Exemplo", "email": "maria@exemplo.com.br" },
"external_id": "pedido-2431",
"metadata": { "unidade": "matriz" },
"webhook_url": "https://seusistema.com.br/webhooks/assinatura",
"redirect_url": "https://seusistema.com.br/assinatura-concluida"
}'{
"signature_id": "6f1c…",
"status": "pending_authorization",
"sign_url": "https://assinatura.online/assinar/EXEMPLO",
"expires_at": "2026-10-06T15:30:00.000Z",
"provider": "birdid"
}provider:birdid,vidaasoua1. Coma1, osign_urlabre uma página onde o signatário envia o próprio certificado A1 (.pfx) e a senha, usados só em memória durante a assinatura; o CPF do certificado precisa ser igual asigner.cpf(senão o erro écertificate_cpf_mismatch).- A1 síncrono (sistemas com cofre próprio): com
provider: "a1", envie tambémcertificate: { "pfx_base64": "…", "password": "…" }. A assinatura acontece na mesma chamada e a resposta já trazstatus: "signed",synchronous: true,signature_token,pdf_urlepdf_base64(enviereturn_pdf: falsepara omitir). O certificado e a senha são usados só em memória e não são gravados. Semcertificate, continua valendo osign_urlpara o titular assinar no navegador. Para não reenviar o arquivo a cada chamada, use a sessão A1 opcional (até 604800 s / 7 dias), em que o titular autoriza e o material fica cifrado até expirar ou ser revogado. - Envie
pdf_url(HTTPS) oupdf_base64— nunca os dois. Limite de 15 MB. signer.cpfé obrigatório: o provedor usa o CPF para localizar o certificado.- Opcional:
stamp_optionspara posicionar o carimbo com QR Code. Por padrão, documentos sem número SNCR recebem o carimbo no canto superior direito do cabeçalho (primeira página); com SNCR, o quadro oficial ocupa o topo e o carimbo vai para o canto inferior direito, acima do rodapé. - Na assinatura qualificada,
stamp_options.pageaceitafirst,lastouall. Essa escolha prevalece sobrepageIndex(índice iniciado em 0, ainda aceito).allaplica o carimbo antes da assinatura em todas as páginas; o quadro SNCR permanece somente na primeira. Na simples, rubricas continuam usandosigners[].stampscom página numérica iniciada em 1.
{
"stamp_options": {
"page": "last",
"position": "bottom-right"
},
"delivery": {
"filename": "Relatório Médico.pdf"
}
}Criar solicitação com JSON (sem PDF)
Envie o objeto document no lugar do PDF. Nós montamos o PDF com cabeçalho da sua marca, paginação automática, rodapé com endereço e o carimbo com QR Code de validação. Use body_text para texto corrido (contratos, termos, atestados, laudos) e items para listas (cláusulas, medicamentos, orientações).
{
"provider": "vidaas",
"document_name": "Termo de aceite",
"signer": { "cpf": "52998224725", "full_name": "Maria Exemplo" },
"document": {
"title": "Termo de aceite de serviço",
"subtitle": "Versão 3",
"body_text": "Declaro que li e aceito as condições...\n\nSegundo parágrafo.",
"items": [
{ "title": "Prazo", "description": "12 meses a partir da assinatura." },
{ "title": "Dipirona 500 mg", "quantity": "1 caixa", "instructions": "1 comprimido a cada 6 horas" }
],
"patient_name": "João Exemplo",
"place_and_date": "São Paulo, 5 de outubro de 2026",
"logo_url": "https://seusistema.com.br/logo.png",
"clinic_name": "Empresa Exemplo",
"clinic_cnpj": "00.000.000/0001-00",
"clinic_phone": "(11) 0000-0000",
"address": { "street": "Rua Exemplo", "number": "100", "district": "Centro",
"city": "São Paulo", "state": "SP", "zip": "00000-000" }
}
}Modelos de saúde prontos (document.type)
Informe document.type e a API monta o modelo completo: cabeçalho da clínica, profissional, caixa do paciente, itens, data por extenso, identificação do prescritor centralizada abaixo da data (com a menção “Assinado digitalmente”) e espaço à direita, acima do rodapé, para o carimbo de assinatura com QR Code. A lista de modelos fica em GET /api/public/v1/document-types (sem autenticação). Vale para assinatura qualificada e para mode: "simple".
- •
prescription_simple— receita simples; itens comcontinuous_userecebem "[USO CONTÍNUO]". - •
prescription_rce— Receita de Controle Especial (RCE), 2 vias com comprador/fornecedor e quadro de numeração SNCR do médico. - •
prescription_ret— Receita Sujeita à Retenção (RET), 2 vias e numeração SNCR; inclui antimicrobianos e agonistas GLP-1 conforme as regras da Anvisa. - •
prescription_special_control— depreciado, sem numeração (equivale ao"C"antigo). Aceito só por compatibilidade; veja Saúde e SNCR. - •
notification_b/notification_a— número em destaque no topo. Semdocument.sncr_number, usamos a carteira SNCR do médico (ou osncr_reservation_token). - •
medical_certificate— exigedays_off(0 = comparecimento). O CID só aparece se você enviarcid, com nota de autorização do paciente. - •
medical_report— exigebody_text; seções opcionaishistory,conduct,conclusion. - •
exam_request— lista de exames emitemseclinical_indication.
{
"provider": "birdid",
"document_name": "Receita 001",
"signer": {
"cpf": "52998224725"
},
"document": {
"type": "prescription_ret",
"clinic": {
"name": "Clínica Exemplo",
"cnes": "1234567",
"phone": "(11) 0000-0000",
"address": "Av. Exemplo, 100 - São Paulo - SP",
"logo_url": "https://seusistema.com.br/logo.png"
},
"prescriber": {
"name": "Dra. Maria Exemplo",
"crm": "123456",
"crm_uf": "SP",
"specialty": "Neurologia",
"rqe": "9876"
},
"patient": {
"name": "João Exemplo",
"cpf": "12345678909",
"birth_date": "1985-04-12",
"address": "Rua Exemplo, 1 - São Paulo/SP"
},
"items": [
{
"name": "Amoxicilina 500 mg",
"quantity": "21 cápsulas",
"instructions": "1 cápsula de 8 em 8 horas por 7 dias.",
"continuous_use": false
}
],
"notes": "Retorno em 30 dias.",
"city": "São Paulo",
"state": "SP",
"issued_at": "2026-10-15"
}
}Envelopes: vários PDFs em um único link
Reúna de 1 a 10 PDFs em documents. Cada pessoa recebe um único link para revisar todos os documentos e confirmar o aceite com um código. Os mesmos signatários assinam todos os documentos, em paralelo ou em ordem.
{
"mode": "simple",
"document_name": "Admissão — contrato e termo",
"documents": [
{
"document_name": "Contrato",
"pdf_base64": "BASE64_DO_CONTRATO"
},
{
"document_name": "Termo de confidencialidade",
"pdf_base64": "BASE64_DO_TERMO"
}
],
"signers": [
{
"full_name": "Maria Exemplo",
"email": "maria@example.com",
"stamps": [
{
"document_index": 0,
"page": 1,
"x": 0.1,
"y": 0.7
},
{
"document_index": 1,
"page": 1,
"x": 0.1,
"y": 0.7
}
]
}
],
"send_email_invitation": true,
"signing_order": "parallel",
"external_id": "admissao-123"
}- Limites: 10 PDFs, 10 MB por arquivo, 20 MB no total e 500 páginas no envelope. PDFs protegidos por senha não são aceitos.
- Não combine
documentscompdf_base64,pdf_urloudocumentna raiz. Envelopes são exclusivos do modo simples, semqualified_signer. - Criação retorna
signature_id,document_count,documents(id, name, position, sha256) esignerscom um link por pessoa. O envelope não pode ser alterado depois de enviado. - Todos os PDFs só são concluídos quando todas as pessoas assinarem. Recusa, cancelamento e expiração encerram o envelope inteiro, sem consumo. Cobrança por documento concluído: 3 PDFs = 3 documentos, independentemente da quantidade de signatários.
GET /signatures/get?signature_id=…retorna a lista e os hashes de cada PDF. O webhooksignature.completedé único por envelope e incluidocument_countedocuments. Confirme o resultado consultando a API.GET /signatures/pdf?signature_id=…&document_id=…: PDF individual.GET /signatures/pdf?signature_id=…&format=zip: todos os PDFs com registro de aceite e manifesto de integridade. Sem parâmetros adicionais: PDF reunido com registro consolidado. Downloads da API exigem credenciais do parceiro.- Envios pagos de PDF continuam por destinatário e canal: o anexo enviado é a cópia reunida do envelope; não há cobrança de envio por PDF interno.
Consultar status
curl "$BASE_URL/signatures/get?signature_id=6f1c…" \
-H "X-API-Key: $ASSINATURA_API_KEY" -H "X-API-Secret: $ASSINATURA_API_SECRET"
{
"signature_id": "6f1c…",
"status": "signed",
"signer_count": 2,
"pending_signers": 0,
"signers": [
{ "name": "Maria Exemplo", "channel": "email", "status": "signed", "signed_at": "2026-10-05T12:01:02.110Z" },
{ "name": "João Exemplo", "channel": "whatsapp", "status": "signed", "signed_at": "2026-10-05T12:04:11.482Z" }
],
"pdf_url": "https://assinatura.online/d/EXEMPLO",
"signed_at": "2026-10-05T12:04:11.482Z",
"external_id": "pedido-2431",
"dispensed_at": null,
"sncr_number": null,
"test_mode": false
}Estados: pending_authorization, authorized, signed, failed, cancelled, expired. O PDF assinado pode ser baixado em GET /signatures/pdf?signature_id=…. A consulta também retorna modee otp_channel (email ou whatsapp na simples; null nas demais modalidades). Use mode: "simple" para distinguir registro de aceite de assinatura com certificado.
Cancelar
curl -X POST "$BASE_URL/signatures/cancel" \
-H "X-API-Key: $ASSINATURA_API_KEY" -H "X-API-Secret: $ASSINATURA_API_SECRET" \
-H "Content-Type: application/json" -d '{ "signature_id": "6f1c…" }'
{ "signature_id": "6f1c…", "status": "cancelled" }Assinatura híbrida: simples + ICP-Brasil
Combine, no mesmo documento, várias pessoas assinando com código (e-mail ou WhatsApp) e uma pessoa final assinando com certificado ICP-Brasil (Bird ID, VIDaaS ou A1). Envie mode: "simple" com a lista de signers e o bloco qualified_signer.
{
"mode": "simple",
"document_name": "Contrato de locação",
"pdf_base64": "JVBERi0xLjQK...",
"signing_order": "sequential",
"send_email_invitation": true,
"send_whatsapp_invitation": true,
"signers": [
{ "full_name": "Maria Exemplo", "email": "maria@exemplo.com.br", "otp_channel": "email", "role": "Locatária" },
{ "full_name": "João Exemplo", "phone": "5561900000000", "otp_channel": "whatsapp", "role": "Fiador" }
],
"qualified_signer": {
"full_name": "Ana Exemplo",
"cpf": "52998224725",
"email": "ana@exemplo.com.br",
"provider": "birdid"
},
"webhook_url": "https://seusistema.com.br/webhooks/assinatura"
}- A etapa ICP-Brasil acontece sempre por último, depois que todos os signatários simples concluírem, sobre o PDF final — qualquer alteração posterior invalidaria a assinatura PAdES.
qualified_signer.provider:birdid,vidaasoua1. O CPF precisa corresponder ao certificado usado.- Se
qualified_signer.emailfor informado, a pessoa recebe o link por e-mail quando chegar a vez dela. - O webhook de conclusão só é emitido depois da etapa ICP-Brasil. O status permanece pendente até lá.
- Os signatários simples continuam sem certificado ICP-Brasil; apenas a assinatura final é qualificada.
Sessões: uma autorização, vários documentos
Para Bird ID e VIDaaS, crie uma sessão e envie o authorize_url ao signatário. Ele aprova uma vez no aplicativo do certificado. Enquanto a sessão estiver ativa, cada POST /signatures/create com session_id é assinado na hora, sem nova aprovação. O CPF do documento precisa ser o mesmo da sessão. A duração máxima depende do provedor. Para certificado A1 também existe sessão opcional (até 604800 s / 7 dias) — veja A1 direto e sessão A1.
{ "provider": "birdid", "signer": { "cpf": "00000000000", "full_name": "Maria Exemplo" },
"session_lifetime_seconds": 28800, "redirect_url": "https://seusistema.com.br/voltar" }
# 201 Created
{ "session_id": "a1b2…", "status": "pending_authorization",
"authorize_url": "https://assinatura.online/sessao/…", "session_lifetime_seconds": 28800 }GET /sessions/get?session_id=a1b2… → { "status": "authorized", "signatures_count": 0, … }
POST /signatures/create { "provider": "birdid", "session_id": "a1b2…", "signer": { "cpf": "00000000000" }, … }
→ { "status": "signed", "signature_token": "…", "sign_url": "https://assinatura.online/d/…" }
POST /sessions/revoke { "session_id": "a1b2…" } → encerra antes do prazoAbrir a sessão na primeira assinatura
Se preferir não criar a sessão antes, envie request_session: true no primeiro POST /signatures/create (somente birdid ou vidaas, sem session_id e fora do modo de testes). A resposta já traz o session_id. Quando o signatário autorizar esse primeiro documento no sign_url, o documento é assinado e a mesma autorização ativa a sessão — os próximos documentos com esse session_id são assinados na hora. O prazo segue session_lifetime_seconds, limitado ao máximo de cada provedor (Bird ID até 7 dias; VIDaaS até 12 horas). Se o primeiro documento não for autorizado, a sessão não é ativada.
{ "provider": "vidaas", "request_session": true, "session_lifetime_seconds": 43200,
"document_name": "Receita 001", "pdf_url": "https://seusistema.com.br/r/001.pdf",
"signer": { "cpf": "00000000000", "full_name": "Maria Exemplo" } }
# 201 Created
{ "signature_id": "…", "status": "pending_authorization", "sign_url": "https://assinatura.online/assinar/…",
"session_id": "a1b2…", "session_status": "pending_authorization", "session_lifetime_seconds": 43200 }
# Depois que o signatário autorizar o primeiro documento:
GET /sessions/get?session_id=a1b2… → { "status": "authorized", "signatures_count": 1, … }
POST /signatures/create { "provider": "vidaas", "session_id": "a1b2…", … } → { "status": "signed", … }Boa prática: envie poucas requisições em paralelo (por exemplo 3 a 5) ao assinar vários documentos na mesma sessão. Não há endpoint de lote: cada PDF tem hash, código e carimbo próprios.
Dispensação e conferência
Registre que um documento assinado foi conferido ou usado — por exemplo receita dispensada na farmácia, retirada de produto ou conferência no balcão. Cada documento só pode ser dispensado uma vez.
{
"signature_id": "6f1c…",
"dispensed_by": "Farmácia Exemplo — CNPJ 00.000.000/0001-00",
"notes": "Dispensado integralmente"
}
# 200 OK
{ "signature_id": "6f1c…", "dispensed_at": "2026-10-05T14:20:00.000Z" }Webhooks
Cadastre uma URL padrão na Área do Cliente (aba Webhooks, com botão de teste) ou envie webhook_url em cada requisição — a URL da requisição tem prioridade. Enviamos um POST nos eventos abaixo, assinado com HMAC-SHA256 usando seu API Secret. Em caso de falha, tentamos de novo até 4 vezes. Valide sempre com o corpo cru da requisição.
X-Assinatura-Event: signature.completed
X-Assinatura-Timestamp: 1791203051
X-Assinatura-Signature: t=1791203051,v1=<hex>
X-Assinatura-Delivery: <id>
X-Assinatura-Attempt: 1{
"event": "signature.completed",
"signature_id": "6f1c…",
"status": "signed",
"external_id": "pedido-2431",
"pdf_url": "https://assinatura.online/d/EXEMPLO",
"signature_token": "EXEMPLO",
"signed_at": "2026-10-05T12:04:11.482Z",
"metadata": { "unidade": "matriz" }
}{
"event": "signature.participant_signed",
"signature_id": "6f1c…",
"status": "pending_authorization",
"signer": "Maria Exemplo",
"signer_status": "signed",
"signed_at": "2026-10-05T12:01:02.110Z",
"pending": 1
}{
"event": "signature.cancelled",
"signature_id": "6f1c…",
"status": "cancelled",
"cancelled_by": "organization",
"external_id": "pedido-2431",
"occurred_at": "2026-10-05T12:10:00.000Z"
}{
"event": "signature.expired",
"signature_id": "6f1c…",
"status": "expired",
"expired_at": "2026-10-06T15:30:00.000Z",
"external_id": "pedido-2431"
}{
"event": "signature.dispensed",
"signature_id": "6f1c…",
"status": "signed",
"pdf_url": "https://assinatura.online/d/EXEMPLO",
"dispensed_at": "2026-10-05T14:20:00.000Z",
"dispensed_by": "Farmácia Exemplo",
"notes": "Dispensado integralmente"
}import crypto from "node:crypto";
const TOLERANCIA_S = 300; // mesma tolerância do verifyWebhook do SDK
/** Valida o webhook antes de qualquer efeito. O segredo fica só no servidor. */
export function webhookValido(timestamp: string, header: string, rawBody: string): boolean {
if (!/^\d{1,12}$/.test(timestamp)) return false; // Unix em segundos
const agora = Math.floor(Date.now() / 1000);
if (Math.abs(agora - Number(timestamp)) > TOLERANCIA_S) return false; // passado ou futuro
const recebido = /(?:^|,)\s*v1=([0-9a-f]{64})(?:,|$)/i.exec(header)?.[1]?.toLowerCase();
if (!recebido) return false; // 64 hex antes de comparar
const esperado = crypto
.createHmac("sha256", process.env.ASSINATURA_API_SECRET!)
.update(`${timestamp}.${rawBody}`) // corpo BRUTO, sem reserializar
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(recebido, "hex"), Buffer.from(esperado, "hex"));
}
// No handler: rawBody = await request.text(); se !webhookValido(...) → 401.
// Depois (ex.: signature.completed): consulte GET /signatures/get; se não confirmar
// "signed" → 503. Então atualize o pedido E grave a deduplicação na MESMA transação
// (helper seu) e só responda 2xx após o COMMIT; erro → 503 para nova tentativa.
// O payload não tem event_id. signature_id + event basta para completed, mas
// participant_signed ocorre várias vezes: inclua signer e signed_at na chave.Saúde e SNCR
Módulo para clínicas, prontuários e telemedicina. Precisa ser habilitado na sua conta (campos médicos). Inclua medical_fields (CRM, UF e especialidade) para que apareçam no carimbo.
Antes de liberar: enquanto o módulo não estiver habilitado, qualquer requisição com medical_fields, prescription_type, cnes ou reserva SNCR retorna 403 medical_fields_disabled e nada é assinado. Para assinar documentos comuns no mesmo momento, envie a requisição sem esses campos. Para habilitar, fale com o suporte; depois da liberação, reenvie a mesma requisição.
- Período de transição até 29/10/2026. A partir de 30/10/2026, a Receita de Controle Especial (
RCE) e a Receita Sujeita à Retenção (RET, que engloba antimicrobianos e agonistas GLP-1), quando eletrônicas, devem ser emitidas integradas ao SNCR, com numeração. O papel continua sendo opção, conforme as regras da Anvisa. Fonte: Anvisa — nova etapa do SNCR e documentos do SNCR. - Notificações com numeração:
NRA(amarela),NRB(azul B1),NRB2(azul B2),NRR(retinoides) eNRT(talidomida). Os aliasesA,B,B2,ReTtambém são aceitos. - Receitas numeradas sem cota prévia da VISA:
RCE(Receita de Controle Especial) eRET(Receita Sujeita à Retenção: antimicrobianos e agonistas GLP-1; retinoides sãoNRR). Não depender de cota da VISA não dispensa a numeração. O médico pede pelo link gov.br um bloco fixo de 1.000 números; a Anvisa devolve um intervaloinicio/fim, que expandimos sem perda de zeros ou prefixos.prescription_type: "RCE"ou"RET"sempre exige número da carteira — sem saldo,409 sncr_no_numbers. Modelos JSON próprios:prescription_rceeprescription_ret. - Compatibilidade depreciada —
"C"/prescription_special_control: a API ainda aceita esses IDs, sem numeração, para não quebrar integrações antigas. Eles não atendem à numeração exigida para nova emissão eletrônica de RCE/RET a partir de 30/10/2026. Migre:"C"→"RCE"ou"RET";prescription_special_control→prescription_rceouprescription_ret. Não há conversão automática nem classificação pelo título; a escolha do tipo é de quem prescreve. - Notificações (
NRA,NRB,NRB2,NRR,NRT): de 10 a 50 números por pedido, a partir da cota liberada pela VISA. A quantidade também é validada no servidor. GET /sncr/balancemantém o objetobalancepor tipo (institutional/doctor) e acrescentasncr_type,requires_sncr,required,can_issue(comdoctor_cpf),visa_quota_required,min_per_requestefixed_block.visa_quota_required: falsenão significa dispensa de número: só indica que a cota não depende da VISA. O saldo conta apenas números de produção;homologation.availablemostra os de homologação, que nunca são reservados.- Modo de testes (
test_mode: true) nunca reserva nem consome numeração real; modelos numerados recebem o marcadorTESTE-SEM-VALOR. - Reservas (
sncr_reservation_token) só são vinculadas a solicitações do mesmo médico (signer_cpf), do mesmo tipo e de produção — senão409 sncr_reservation_mismatch/sncr_invalid_reservation. - Sem cadastro prévio de médicos: o médico é identificado pelo CPF do signatário (com CRM/UF em
medical_fields) e aparece automaticamente na sua Área do Cliente. Você também pode cadastrar, listar e remover médicos antecipadamente em/doctors, respeitando o limite contratado. - Numeração oficial (SNCR instituído pela RDC 873/2024; receituários eletrônicos na RDC 1.000/2025): os números só vêm do SNCR da Anvisa — nas Notificações, a partir da cota que a Vigilância Sanitária liberou para o CPF/CRM do médico; em RCE/RET, por bloco de 1.000 sem cota da VISA. O médico autoriza com login gov.br — basta conta gov.br válida para obter os números (Manual API SNCR, p. 5); a assinatura final mantém exigências próprias — e os números entram na carteira dele. Faixas de talonário de papel não são aceitas.
- A reserva usa bloqueio de linha: duas receitas simultâneas nunca recebem o mesmo número. Se a solicitação for cancelada, expirar ou falhar, o número volta ao saldo; ao concluir a assinatura, fica marcado como usado.
- Quando não há mais números disponíveis, a API devolve
sncr_no_numbers(409).
.br, HTTPS e o mesmo Origin no início do login e na troca do session_id. Por isso todo connect_url novo aponta para https://sncr.prescri.com.br/sncr/conectar/… e o retorno gov.br é https://sncr.prescri.com.br/sncr/retorno, independentemente do endereço usado para chamar a API. O site e a API continuam em assinatura.online; links antigos são encaminhados ao endereço dedicado antes de qualquer pedido. A troca da sessão é feita no servidor com o Origin real do navegador, conferido contra esse endereço. O fluxo ainda depende de teste real com login gov.br antes de ser tratado como pronto para produção. Fonte: Manual API SNCR v5 — Anvisa.{
"doctor_cpf": "52998224725",
"crm": "123456", // apenas números
"crm_uf": "SP",
"doctor_name": "Dra. Ana Exemplo", // opcional
"env": "prd", // "hmg" para homologação da Anvisa (números sem validade real)
"sncr_type": "RCE" // opcional: NRA, NRB, NRB2, NRR, NRT, RCE ou RET (padrão RCE)
}
# 201 Created
{ "connect_url": "https://sncr.prescri.com.br/sncr/conectar/sncr_…?tipo=RCE", "expires_at": "…", "env": "prd", "sncr_type": "RCE" }{ "doctor_cpf": "52998224725", "sncr_type": "NRB", "ttl_minutes": 30 }
# 201 Created
{ "sncr_type": "NRB", "sncr_number": "2610.2-23.0165500",
"reservation_token": "rsv_…", "expires_at": "…", "source": "anvisa" }
# Depois, em /signatures/create, envie "sncr_reservation_token": "rsv_…".
# Sem token, o número é reservado automaticamente na criação.
# O PDF recebe automaticamente o quadro da Notificação de Receita
# (tipo + número, topo da 1ª página). Para desligar: "sncr_stamp": false.
DELETE /sncr/reserve?reservation_token=rsv_…
# Desistiu antes de criar o documento? Devolve o número à carteira na hora.
# 200 { "released": true, "sncr_type": "NRB", "sncr_number": "2610.2-23.0165500" }
# 404 sncr_invalid_reservation — expirada, inexistente ou já vinculada a documento
GET /sncr/balance?doctor_cpf=52998224725
# → saldo da carteira do médico por tipo (available / reserved / used)# 200 OK
{
"doctors": [
{ "id": "…", "doctor_cpf": "52998224725", "crm": "123456", "crm_uf": "SP",
"full_name": "Dra. Ana Exemplo", "can_sign": true, "signatures_count": 0 }
],
"total": 1, "signers_limit": 10, "unlimited": false, "remaining": 9
}{
"doctor_cpf": "52998224725",
"crm": "123456", // apenas números (opcional)
"crm_uf": "SP", // opcional
"doctor_name": "Dra. Ana Exemplo" // opcional
}
# 201 Created (novo) ou 200 (atualização)
{ "doctor": { "id": "…", "doctor_cpf": "52998224725", "crm": "123456", "crm_uf": "SP", … }, "created": true }
# 403 quando o limite contratado foi atingido
{ "error": "Limite de 10 pessoas assinantes do ciclo atingido…", "code": "signers_limit_reached" }# Cotação (não cobra)
{ "signers": 15, "preview": true }
# Cobrança — exige o cabeçalho Idempotency-Key
Idempotency-Key: upg-2026-10-cliente-42
{ "signers": 15 }
# 200 OK
{ "id": "…", "status": "paid", "from_signers": 10, "to_signers": 15,
"amount_cents": 7185, "days_remaining": 15, "cycle_end": "2026-11-05" }
# 402 card_required | payment_declined — plano anterior mantido
# 409 downgrade_not_allowed | subscription_not_active | no_change
GET /billing/upgrade # → vagas atuais, próxima renovação e cartão (bandeira/final)# Agendar redução para a renovação
POST /billing/downgrade
{ "signers": 5 }
# 200 → { "status": "scheduled", "signers": 10, "scheduled_signers": 5, "effective_at": "2026-11-05" }
# Agendar cancelamento (próxima cobrança não é emitida)
POST /billing/schedule
{ "action": "cancel" }
# Consultar ou desfazer o agendamento
GET /billing/schedule
DELETE /billing/schedule
# 409 subscription_not_active — fatura em aberto: regularize antes de alterar
# 409 not_a_downgrade | cancel_scheduled{ "doctor_cpf": "52998224725" }
# 200 OK
{ "ok": true, "removed_cpf": "52998224725", "note": "Os números SNCR já recebidos para este CPF permanecem na carteira da sua conta…" }
# 404 quando o CPF não pertence à sua conta
{ "error": "Médico não encontrado nesta conta.", "code": "doctor_not_found" }{
"provider": "birdid",
"document_name": "Notificação de Receita B",
"signer": { "cpf": "52998224725", "full_name": "Dra. Ana Exemplo" },
"medical_fields": { "crm": "123456", "crm_uf": "SP", "specialty": "Psiquiatria" },
"prescription_type": "B",
"document": { "title": "Notificação de Receita B", "items": [ … ] }
}
# 201 Created → "sncr_number" vem da carteira SNCR do médico (CPF do signatário)O carimbo traz os dados do profissional e os metadados de assinatura no padrão ICP-Brasil. A numeração é solicitada ao SNCR no navegador do médico, após o login gov.br; a API guarda, reserva e consome esses números. O registro de uso pela farmácia continua sendo feito no SNCR. Incluir CRM não verifica o registro profissional. A conformidade final de cada documento de saúde com as normas do CFM, do ITI e da Anvisa é responsabilidade de quem emite.
A1 direto e sessão A1
Há dois jeitos de usar um certificado A1 pela API. Eles não são o mesmo fluxo da Prescri: lá o titular sempre confirma pela própria tela; aqui o A1 direto envia o arquivo no corpo e assina na hora, e a sessão A1 (opcional) usa uma confirmação do titular em authorize_url.
A1 direto (síncrono)
- Formato original:
certificate: { pfx_base64, password }. Também aceitos:a1_certificate_base64oua1_pfx_base64+a1_password. Enviar os dois formatos (ou aliases diferentes) retorna400 certificate_conflict. - Antes de gravar qualquer coisa verificamos base64, tamanho (até 100 KB), senha, se a chave privada corresponde ao certificado, validade e CPF. Falhas nessa fase não criam solicitação, PDF nem reserva SNCR:
invalid_certificate,invalid_certificate_password,certificate_key_mismatch,certificate_expired,certificate_not_yet_valid,certificate_cpf_mismatch,certificate_too_large. - Se algo falhar depois de criada a solicitação, o erro traz
signature_idestatus: "failed"; a reserva SNCR volta ao saldo. Um documento já assinado nunca volta para pendente, mesmo que o webhook ou o envio falhem depois. - Lemos do certificado apenas nome, CPF e validade. A cadeia ICP-Brasil e a revogação (LCR/OCSP) não são verificadas pela API.
- O arquivo e a senha ficam só em memória durante a chamada e não são gravados nem registrados em logs.
curl -X POST https://assinatura.online/api/public/v1/signatures/create \
-H "X-API-Key: $API_KEY" -H "X-API-Secret: $API_SECRET" -H "Content-Type: application/json" \
-H "Idempotency-Key: contrato-2431-v1" \
-d '{
"provider": "a1",
"document_name": "Contrato de prestação de serviço",
"signer": { "cpf": "52998224725" },
"pdf_base64": "JVBERi0x...",
"a1_pfx_base64": "MIIK...",
"a1_password": "SENHA_DO_CERTIFICADO"
}'
# 201 { "signature_id": "…", "status": "signed", "provider": "a1", "synchronous": true,
# "signature_token": "…", "pdf_url": "…", "pdf_base64": "…" }Sessão A1 (opcional, até 7 dias)
POST /sessions/createcomprovider: "a1"devolveauthorize_url. O titular abre o link, envia o .pfx e a senha e confirma o consentimento. Conferimos CPF, validade e par de chaves.- Prazo:
session_lifetime_secondsaté604800(7 dias), limitado à validade do certificado. - O material fica cifrado no servidor (AES-256-GCM, chave dedicada fora do banco), preso ao cliente, ao CPF e à sessão. Nunca vai para metadata, logs ou para o navegador do parceiro.
- Depois,
POST /signatures/createcomprovider: "a1"esession_idassina na hora (via_session: true), sem enviar arquivo. POST /sessions/revokeapaga o material na hora; ao expirar, ele é apagado pela rotina de manutenção. Sessões expiradas respondem410 session_expired; revogadas,410 session_revoked.- Se o servidor não tiver a chave de cifragem, só este recurso responde
503 a1_session_not_configured; o A1 direto, a página hospedada e as sessões Bird ID/VIDaaS continuam iguais.
const h = { "X-API-Key": process.env.API_KEY, "X-API-Secret": process.env.API_SECRET, "Content-Type": "application/json" };
const s = await fetch("https://assinatura.online/api/public/v1/sessions/create", { method: "POST", headers: h,
body: JSON.stringify({ provider: "a1", signer: { cpf: "52998224725" }, session_lifetime_seconds: 604800 }) }).then(r => r.json());
// envie s.authorize_url ao titular; depois de autorizada:
const r = await fetch("https://assinatura.online/api/public/v1/signatures/create", { method: "POST", headers: { ...h, "Idempotency-Key": "laudo-2431-sessao-01" },
body: JSON.stringify({ provider: "a1", session_id: s.session_id, signer: { cpf: "52998224725" }, document_name: "Laudo técnico", pdf_url: "https://seusistema.com/rx.pdf" }) }).then(r => r.json());import os, requests
h = {"X-API-Key": os.environ["API_KEY"], "X-API-Secret": os.environ["API_SECRET"]}
s = requests.post("https://assinatura.online/api/public/v1/sessions/create", headers=h, json={"provider": "a1", "signer": {"cpf": "52998224725"}, "session_lifetime_seconds": 604800}).json()
# depois que o titular autorizar em s["authorize_url"]:
r = requests.post("https://assinatura.online/api/public/v1/signatures/create", headers={**h, "Idempotency-Key": "laudo-2431-sessao-01"},
json={"provider": "a1", "session_id": s["session_id"], "signer": {"cpf": "52998224725"}, "document_name": "Laudo técnico", "pdf_url": "https://seusistema.com/rx.pdf"}).json()Retentativas seguras (Idempotency-Key)
- Opcional em
POST /signatures/create(assinatura qualificada). Envie o cabeçalhoIdempotency-Key(8 a 128 caracteres: letras, números,. _ : -). - Mesma chave + mesmo conteúdo: devolve a mesma resposta (cabeçalho
Idempotent-Replayed: true), sem nova solicitação nem nova cobrança. Mesma chave + conteúdo diferente:409 idempotency_key_reused. Ainda em andamento:409 idempotency_in_progresscom osignature_idquando já existir. - Depois de um timeout, repita com a mesma chave ou consulte
GET /signatures/get?id=pelosignature_id. As chaves valem 24 horas e são separadas por cliente. - Certificados e senhas não entram na impressão digital nem no registro. O
external_idcontinua livre e não passa a ser único.
import uuid, requests
key = f"rx-{uuid.uuid4()}"
for tentativa in range(3):
try:
r = requests.post(f"https://assinatura.online/api/public/v1/signatures/create", headers={"X-API-Key": API_KEY, "X-API-Secret": API_SECRET, "Idempotency-Key": key}, json=payload, timeout=30)
if r.status_code != 409 or r.json().get("code") != "idempotency_in_progress":
break
except requests.Timeout:
continueMatriz de compatibilidade
| Recurso | Contrato antigo | Novo (aditivo) |
|---|---|---|
| prescription_type | A, B, B2, R, T, C, RCE, RET e NRA…NRT | Iguais; conflito com o modelo JSON → 422 prescription_type_conflict |
| Modelos JSON | IDs anteriores mantidos | prescription_rce, prescription_ret; catálogo com sncr_type/requires_sncr |
| "C" / prescription_special_control | Sem numeração | Depreciado: ainda aceito, sem conversão silenciosa; não serve para RCE/RET eletrônica a partir de 30/10/2026 — migre para RCE/prescription_rce ou RET/prescription_ret |
| /sncr/connect-url | doctor_cpf, crm, crm_uf, env | sncr_type opcional (padrão RCE); links antigos abrem em NRB |
| /sncr/balance | balance por tipo | + sncr_type, requires_sncr, required, can_issue, homologation |
| signer.cpf | signer.cpf, signer_cpf, doctor_cpf | Sem mudança |
| A1 | certificate { pfx_base64, password }, A1 hospedado | + a1_certificate_base64 / a1_pfx_base64 + a1_password; validação antes de gravar; sessão A1 |
| Sessões | birdid, vidaas | + a1 (opt-in) |
| Simples / híbrida | mode: "simple", qualified_signer | Sem mudança |
| Retentativa | — | Idempotency-Key opcional |
SDKs de servidor: Node.js e Python
SDKs leves, sem dependências externas, para usar no seu servidor. Cobrem assinatura simples, híbrida e qualificada, consulta de status, download do PDF, cancelamento, reenvio, sessões e validação de webhook HMAC em tempo constante.
Node.js / TypeScript
Node 18+, um arquivo com tipos .d.ts. Guia passo a passo →
Python
Python 3.9+, apenas biblioteca padrão. Guia passo a passo →
Nunca use a API Key e o Secret no navegador. Para abrir a assinatura dentro do seu app (iframe ou popup), use o SDK embed abaixo junto com o sign_url gerado no servidor.
SDK embed (navegador)
Para prontuários e ERPs que querem a assinatura dentro da própria tela. O SDK abre o fluxo do signatário e avisa sua página sobre cada etapa por postMessage. A aprovação no Bird ID/VIDaaS sempre abre numa janela própria, porque os provedores não permitem iframe.
<script src="https://assinatura.online/assinatura-sdk.js"></script>
<script>
// sign_url vem da resposta de /signatures/create (chamada no seu servidor)
AssinaturaOnline.open({
signUrl: "https://assinatura.online/assinar/EXEMPLO",
mode: "modal", // "modal" (janela) ou "iframe"
container: "#assinatura", // obrigatório no modo iframe
onEvent(event) {
// widget.opened | widget.ready | signature.completed | signature.failed | widget.closed
if (event.type === "signature.completed") console.log(event.pdf_url);
},
});
</script>Sem SDK: iframe, popup ou nova aba
A tela de assinatura detecta sozinha quando está dentro de um iframe ou de um popup e mostra uma versão limpa, sem menu nem rodapé — só a marca, o progresso e o resultado. Você não precisa mudar nenhum parâmetro na chamada da API.
- • iframe ou modal: use o
sign_urldireto nosrc. Modo limpo automático. - • Popup (
window.open): modo limpo automático. - • Nova aba ou redirecionamento: acrescente
?embed=1aosign_urlpara forçar o modo limpo.
<!-- Modo limpo automático dentro do iframe -->
<iframe
src="https://assinatura.online/assinar/EXEMPLO"
style="width:100%;height:720px;border:0"
allow="clipboard-write"
></iframe>
<!-- Nova aba: force o modo limpo -->
<a href="https://assinatura.online/assinar/EXEMPLO?embed=1" target="_blank" rel="noopener">
Assinar documento
</a>Modo de testes (opcional, por envio)
Não existe ambiente separado: as mesmas credenciais assinam documentos reais desde o primeiro dia. Se quiser validar a integração sem certificado real, envie "test_mode": true em /signatures/create. A solicitação é concluída na hora, o webhook signature.completed chega com "test_mode": true e o PDF recebe a marca d’água “DOCUMENTO DE TESTE — SEM VALIDADE JURÍDICA”.
- Não usa certificado ICP-Brasil e não conta no limite mensal.
provideré opcional nesse modo.- Nunca use documentos de teste como documentos válidos.
- Não use
test_mode: truecommode: "simple". Teste a simples com documento fictício e destinatário autorizado; a confirmação por código é real.
Erros
Erros vêm como { "error": "mensagem", "code": "codigo" }.
| HTTP | code | Significado |
|---|---|---|
| 400 | invalid_document / invalid_pdf_source | Documento JSON ou PDF inválido |
| 400 | invalid_signer_cpf | CPF ausente ou inválido (opcional na simples) |
| 400 | invalid_phone | WhatsApp ausente ou inválido; use país + DDD + número |
| 400 | simple_test_not_supported | test_mode: true não é aceito na simples |
| 400 | invalid_otp | Código incorreto, expirado ou solicitação indisponível na confirmação |
| 429 | rate_limited | Aguarde o intervalo de reenvio ou limite de códigos atingido |
| 503 | whatsapp_not_configured / whatsapp_delivery_failed / email_delivery_failed | Canal indisponível ou falha ao enviar código |
| 401 | missing_credentials / invalid_credentials | Revise X-API-Key e X-API-Secret |
| 402 | trial_expired / monthly_limit_reached | Avaliação encerrada ou limite do mês |
| 403 | medical_fields_disabled | Módulo de saúde não habilitado |
| 404 | not_found | Solicitação não encontrada |
| 409 | already_signed / not_signed / already_dispensed | Estado não permite a operação |
| 409 | sncr_no_numbers | Sem numeração SNCR na carteira do médico |
| 410 | sncr_manual_import_disabled | Importação manual de faixas desativada |
| 410 | expired | Prazo de assinatura encerrado |
| 413 | pdf_too_large | PDF acima de 15 MB |
| 500 | internal_error | Tente novamente |
