O SignDocs Brasil suporta assinaturas digitais utilizando certificados ICP-Brasil A1 (arquivos PKCS#12/PFX baseados em software). Diferentemente de assinaturas eletrônicas simples (aceitar com clique, OTP), assinaturas com certificado digital produzem um PDF com um contêiner PKCS#7/CMS SignedData embutido, criptograficamente vinculado ao certificado ICP-Brasil do signatário. O PDF resultante é verificável pelo Validador do ITI e por qualquer leitor compatível com PAdES.
O protocolo de assinatura é uma troca em duas fases entre sua aplicação (o cliente) e a API do SignDocs Brasil (o servidor):
Você só precisa nos enviar seu certificado folha -- nós resolvemos a cadeia completa ICP-Brasil automaticamente.
Sua chave privada nunca sai da sua infraestrutura. O servidor nunca a vê. O único material criptográfico transmitido é:
| Propriedade | Valor |
|---|---|
| Algoritmo de hash | SHA-256 |
| Algoritmo de assinatura | RSASSA-PKCS1-v1_5 (SHA-256 com RSA) |
| Formato do contêiner | PKCS#7 / CMS SignedData (RFC 5652) |
| Tipo de assinatura no PDF | PAdES (CMS destacado no PDF /ByteRange) |
| Atributos autenticados | contentType, messageDigest, signingTime |
| Cadeia de certificados | Resolvida no servidor a partir das CAs ICP-Brasil incluídas |
O perfil de política DIGITAL_CERTIFICATE produz duas etapas ordenadas:
CLICK_ACCEPT -- O signatário reconhece o documento.DIGITAL_SIGN_A1 -- O fluxo de assinatura criptográfica descrito neste guia.Um certificado ICP-Brasil A1 é entregue como um arquivo .pfx (PKCS#12) contendo sua chave privada, seu certificado folha e possivelmente certificados de CAs intermediárias. Para a fase de preparação, você precisa extrair apenas o certificado folha em formato PEM. A chave privada permanece com você.
from cryptography import x509
from cryptography.hazmat.primitives.serialization import pkcs12, Encoding
# Load PFX
with open("certificate.pfx", "rb") as f:
pfx_data = f.read()
private_key, certificate, additional_certs = pkcs12.load_key_and_certificates(
pfx_data, b"your-pfx-password"
)
# Export leaf certificate as PEM
leaf_pem = certificate.public_bytes(Encoding.PEM).decode("utf-8")
print(leaf_pem)
# -----BEGIN CERTIFICATE-----
# MIIGxTCCBK2gAwIBAgIIU+...
# -----END CERTIFICATE-----
const forge = require("node-forge");
const fs = require("fs");
// Load PFX
const pfxDer = fs.readFileSync("certificate.pfx", "binary");
const pfxAsn1 = forge.asn1.fromDer(pfxDer);
const pfx = forge.pkcs12.pkcs12FromAsn1(pfxAsn1, "your-pfx-password");
// Extract leaf certificate
const certBags = pfx.getBags({ bagType: forge.pki.oids.certBag });
const certBag = certBags[forge.pki.oids.certBag];
// The leaf is the non-CA certificate
const leafBag = certBag.find((bag) => {
const cert = bag.cert;
const bc = cert.getExtension("basicConstraints");
return !bc || !bc.cA;
});
const leafPem = forge.pki.certificateToPem(leafBag.cert);
console.log(leafPem);
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Enumeration;
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream("certificate.pfx")) {
ks.load(fis, "your-pfx-password".toCharArray());
}
Enumeration<String> aliases = ks.aliases();
String alias = aliases.nextElement();
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
// Convert to PEM
String base64 = Base64.getEncoder().encodeToString(cert.getEncoded());
String leafPem = "-----BEGIN CERTIFICATE-----\n"
+ base64.replaceAll("(.{64})", "$1\n")
+ "\n-----END CERTIFICATE-----";
System.out.println(leafPem);
using System.Security.Cryptography.X509Certificates;
using System.Text;
var collection = new X509Certificate2Collection();
collection.Import("certificate.pfx", "your-pfx-password",
X509KeyStorageFlags.Exportable);
// Find the leaf (end-entity) certificate
X509Certificate2 leaf = null;
foreach (var cert in collection)
{
// Leaf certs do not have the CA basic constraint
foreach (var ext in cert.Extensions)
{
if (ext is X509BasicConstraintsExtension bc && bc.CertificateAuthority)
goto next;
}
leaf = cert;
break;
next:;
}
// Export as PEM
byte[] derBytes = leaf.Export(X509ContentType.Cert);
string base64 = Convert.ToBase64String(derBytes, Base64FormattingOptions.InsertLineBreaks);
string leafPem = $"-----BEGIN CERTIFICATE-----\n{base64}\n-----END CERTIFICATE-----";
Console.WriteLine(leafPem);
O servidor retorna um valor hashToSign (digest SHA-256 codificado em hexadecimal). Sua aplicação deve assinar este hash usando RSASSA-PKCS1-v1_5 com SHA-256 e retornar os bytes brutos da assinatura como base64.
Importante: O hash já foi calculado pelo servidor. Você deve executar a operação de assinatura RSA PKCS#1 v1.5 diretamente sobre este hash. NÃO calcule o hash novamente. A operação de assinatura aplica internamente o encapsulamento DigestInfo (AlgorithmIdentifier codificado em DER + hash) conforme especificado pelo PKCS#1 v1.5.
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import pkcs12
import base64
# Load private key from PFX
with open("certificate.pfx", "rb") as f:
private_key, _, _ = pkcs12.load_key_and_certificates(f.read(), b"your-pfx-password")
# hashToSign from the /signing/prepare response (hex string)
hash_to_sign_hex = "a1b2c3d4..." # from API response
hash_bytes = bytes.fromhex(hash_to_sign_hex)
# Sign with RSASSA-PKCS1-v1_5 SHA-256
# Use Prehashed because the server already computed the digest
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
signature = private_key.sign(
hash_bytes,
padding.PKCS1v15(),
Prehashed(hashes.SHA256())
)
raw_signature_base64 = base64.b64encode(signature).decode("utf-8")
print(raw_signature_base64)
const crypto = require("crypto");
const forge = require("node-forge");
const fs = require("fs");
// Load private key from PFX
const pfxDer = fs.readFileSync("certificate.pfx", "binary");
const pfxAsn1 = forge.asn1.fromDer(pfxDer);
const pfx = forge.pkcs12.pkcs12FromAsn1(pfxAsn1, "your-pfx-password");
const keyBags = pfx.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
const keyBag = keyBags[forge.pki.oids.pkcs8ShroudedKeyBag];
const forgePrivateKey = keyBag[0].key;
// Convert to Node.js crypto key
const privateKeyPem = forge.pki.privateKeyToPem(forgePrivateKey);
// hashToSign from the /signing/prepare response (hex string)
const hashToSignHex = "a1b2c3d4..."; // from API response
// Sign: crypto.sign with SHA-256 will apply DigestInfo + RSA PKCS#1 v1.5
// Since we already have the hash, we create a signer in raw mode
const sign = crypto.createSign("RSA-SHA256");
// We need to feed the original data that hashes to hashToSign,
// but since we only have the hash, use the lower-level approach:
const hashBuffer = Buffer.from(hashToSignHex, "hex");
// Build DigestInfo manually for pre-hashed data
const DER_SHA256_PREFIX = Buffer.from(
"3031300d060960864801650304020105000420",
"hex"
);
const digestInfo = Buffer.concat([DER_SHA256_PREFIX, hashBuffer]);
// RSA private encrypt (raw PKCS#1 v1.5 signature)
const key = crypto.createPrivateKey(privateKeyPem);
const signature = crypto.privateEncrypt(
{ key, padding: crypto.constants.RSA_PKCS1_PADDING },
digestInfo
);
const rawSignatureBase64 = signature.toString("base64");
console.log(rawSignatureBase64);
import java.io.FileInputStream;
import java.security.*;
import java.util.Base64;
// Load private key from PFX
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream("certificate.pfx")) {
ks.load(fis, "your-pfx-password".toCharArray());
}
String alias = ks.aliases().nextElement();
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "your-pfx-password".toCharArray());
// hashToSign from the /signing/prepare response (hex string)
String hashToSignHex = "a1b2c3d4..."; // from API response
byte[] hashBytes = hexStringToByteArray(hashToSignHex);
// Build DigestInfo (DER-encoded AlgorithmIdentifier + hash)
// SHA-256 DigestInfo prefix per RFC 3447 Section 9.2 Note 1
byte[] sha256Prefix = new byte[] {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte)0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
};
byte[] digestInfo = new byte[sha256Prefix.length + hashBytes.length];
System.arraycopy(sha256Prefix, 0, digestInfo, 0, sha256Prefix.length);
System.arraycopy(hashBytes, 0, digestInfo, sha256Prefix.length, hashBytes.length);
// Sign with NONEwithRSA (raw PKCS#1 v1.5 on pre-built DigestInfo)
Signature sig = Signature.getInstance("NONEwithRSA");
sig.initSign(privateKey);
sig.update(digestInfo);
byte[] signature = sig.sign();
String rawSignatureBase64 = Base64.getEncoder().encodeToString(signature);
System.out.println(rawSignatureBase64);
// Helper
static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
// Load private key from PFX
var collection = new X509Certificate2Collection();
collection.Import("certificate.pfx", "your-pfx-password",
X509KeyStorageFlags.Exportable);
X509Certificate2 cert = collection[0]; // leaf
RSA rsa = cert.GetRSAPrivateKey();
// hashToSign from the /signing/prepare response (hex string)
string hashToSignHex = "a1b2c3d4..."; // from API response
byte[] hashBytes = Convert.FromHexString(hashToSignHex);
// Sign the pre-computed hash with PKCS#1 v1.5
byte[] signature = rsa.SignHash(
hashBytes,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1
);
string rawSignatureBase64 = Convert.ToBase64String(signature);
Console.WriteLine(rawSignatureBase64);
Esta seção percorre o fluxo completo de ponta a ponta para assinar digitalmente um documento PDF com um certificado ICP-Brasil A1.
POST /oauth2/token com grant client_credentials).curl -X POST https://api.signdocs.com.br/v1/transactions \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": {
"profile": "DIGITAL_CERTIFICATE"
},
"signer": {
"name": "Maria da Silva",
"email": "maria@example.com",
"userExternalId": "user_12345",
"cpf": "12345678901"
},
"digitalSignature": {
"reason": "Concordo com os termos deste contrato",
"location": "Sao Paulo, SP",
"contactInfo": "maria@example.com"
},
"expiresInMinutes": 1440
}'
Resposta (201 Created):
{
"transactionId": "tx_01HQRS...",
"status": "CREATED",
"purpose": "DOCUMENT_SIGNATURE",
"policy": {
"profile": "DIGITAL_CERTIFICATE"
},
"steps": [
{
"stepId": "stp_01HQRS...",
"type": "CLICK_ACCEPT",
"status": "PENDING",
"order": 0
},
{
"stepId": "stp_01HQRT...",
"type": "DIGITAL_SIGN_A1",
"status": "PENDING",
"order": 1
}
],
"userExternalId": "user_12345",
"createdAt": "2026-02-15T14:00:00.000Z",
"expiresAt": "2026-02-16T14:00:00.000Z"
}
Guarde o transactionId, steps[0].stepId (CLICK_ACCEPT) e steps[1].stepId (DIGITAL_SIGN_A1).
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/document \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"content": "'$(base64 -w0 contract.pdf)'"
}'
Resposta (200 OK):
{
"transactionId": "tx_01HQRS...",
"documentHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"status": "DOCUMENT_UPLOADED"
}
Iniciar a etapa:
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/steps/${CLICK_STEP_ID}/start \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json"
Resposta (200 OK):
{
"stepId": "stp_01HQRS...",
"type": "CLICK_ACCEPT",
"status": "STARTED",
"message": "Click accept step started"
}
Concluir a etapa:
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/steps/${CLICK_STEP_ID}/complete \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"accepted": true,
"textVersion": "1.0"
}'
Resposta (200 OK):
{
"stepId": "stp_01HQRS...",
"type": "CLICK_ACCEPT",
"status": "COMPLETED",
"attempts": 1,
"result": {
"click": {
"accepted": true,
"textVersion": "1.0"
}
}
}
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/steps/${SIGN_STEP_ID}/start \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json"
Resposta (200 OK):
{
"stepId": "stp_01HQRT...",
"type": "DIGITAL_SIGN_A1",
"status": "STARTED",
"message": "Digital certificate signing step started. Use /signing/prepare to continue."
}
Envie seu certificado folha em formato PEM. Você só precisa enviar o certificado folha -- o servidor resolve a cadeia completa.
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/signing/prepare \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"certificateChainPems": [
"-----BEGIN CERTIFICATE-----\nMIIGxTCCBK2gAwIBAgIIU+w77w...(your leaf cert)...\n-----END CERTIFICATE-----"
]
}'
Corpo da requisição:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
certificateChainPems |
string[] |
Sim | Array de certificados X.509 codificados em PEM. No mínimo, inclua o certificado folha. Intermediários adicionais são aceitos, mas não obrigatórios. |
Resposta (200 OK):
{
"signatureRequestId": "sigreq_01HQRU...",
"hashToSign": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"hashAlgorithm": "SHA-256",
"signatureAlgorithm": "RSASSA-PKCS1-v1_5"
}
O hashToSign é o digest SHA-256 dos atributos autenticados codificados em DER (contentType + messageDigest + signingTime). Isto é o que sua chave privada deve assinar.
O signatureRequestId identifica esta sessão de assinatura e deve ser enviado de volta na chamada de conclusão. Ele expira após 15 minutos.
Utilizando os exemplos de código da Seção 3, assine o valor hashToSign com sua chave privada. O resultado é uma assinatura RSA bruta codificada em base64.
# Example using OpenSSL CLI for illustration:
echo -n "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" \
| xxd -r -p \
| openssl dgst -sha256 -sign private_key.pem -pkeyopt rsa_padding_mode:pkcs1 \
| base64 -w0
Nota: Em produção, utilize os exemplos específicos por linguagem da Seção 3, e não o OpenSSL CLI.
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/signing/complete \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"signatureRequestId": "sigreq_01HQRU...",
"rawSignatureBase64": "MEUCIQC7...base64-encoded-signature..."
}'
Corpo da requisição:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
signatureRequestId |
string |
Sim | O ID retornado por /signing/prepare. |
rawSignatureBase64 |
string |
Sim | Assinatura RSA PKCS#1 v1.5 bruta codificada em base64. |
Resposta (200 OK):
{
"stepId": "stp_01HQRT...",
"status": "COMPLETED",
"result": {
"digitalSignature": {
"certificateSubject": "CN=Maria da Silva, OU=AR Certisign, O=ICP-Brasil, C=BR",
"certificateSerial": "53ECFBEF...",
"certificateIssuer": "CN=AC Certisign Multipla G7, OU=AC Certisign Multipla G7, O=ICP-Brasil, C=BR",
"algorithm": "SHA256withRSA",
"signedAt": "2026-02-15T14:05:30.000Z",
"signedPdfHash": "b5bb9d8014a0f9b1d61e21e796d78dcc...",
"signatureFieldName": "Signature1"
}
}
}
Neste ponto, a etapa DIGITAL_SIGN_A1 está marcada como COMPLETED. O PDF assinado está armazenado no servidor.
curl -X POST https://api.signdocs.com.br/v1/transactions/${TX_ID}/finalize \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json"
Resposta (200 OK):
{
"transactionId": "tx_01HQRS...",
"status": "COMPLETED",
"evidenceId": "evi_01HQRV...",
"evidenceHash": "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
"completedAt": "2026-02-15T14:06:00.000Z"
}
A finalização verifica que todas as etapas foram concluídas, constrói o Pacote de Evidências criptográfico (.p7m) e copia o PDF assinado digitalmente para sua localização final. Para PDFs assinados digitalmente, o servidor NÃO aplica carimbo ou modifica o documento (fazê-lo invalidaria a assinatura PKCS#7 embutida).
curl -X GET https://api.signdocs.com.br/v1/transactions/${TX_ID}/download \
-H "Authorization: Bearer ${TOKEN}"
Resposta (200 OK):
{
"transactionId": "tx_01HQRS...",
"documentHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"originalUrl": "https://signdocs-evidence-bucket.s3.amazonaws.com/tenants/.../document.pdf?X-Amz-...",
"signedUrl": "https://signdocs-evidence-bucket.s3.amazonaws.com/tenants/.../document-signed.pdf?X-Amz-...",
"expiresIn": 3600
}
A signedUrl é uma URL pré-assinada do S3 válida por 1 hora. O PDF baixado contém uma assinatura PAdES embutida verificável pelo Adobe Acrobat, Validador do ITI e outras ferramentas compatíveis.
O SignDocs Brasil suporta múltiplos signatários assinando o mesmo documento PDF sequencialmente. Cada signatário recebe sua própria transação, mas todas as transações são vinculadas por meio de um grupo de documentos.
documentGroupId.signerIndex (base 1) e o grupo possui um totalSigners fixo.Signature1, Signature2, etc.).Crie uma transação por signatário, todas referenciando o mesmo documentGroupId:
Signatário 1 (primeiro a assinar):
curl -X POST https://api.signdocs.com.br/v1/transactions \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": { "profile": "DIGITAL_CERTIFICATE" },
"signer": {
"name": "Maria da Silva",
"email": "maria@example.com",
"userExternalId": "user_maria",
"cpf": "12345678901"
},
"documentGroupId": "group_contract_2026_001",
"signerIndex": 1,
"totalSigners": 3
}'
Signatário 2 (assina após o signatário 1 concluir):
curl -X POST https://api.signdocs.com.br/v1/transactions \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": { "profile": "DIGITAL_CERTIFICATE" },
"signer": {
"name": "Joao Santos",
"email": "joao@example.com",
"userExternalId": "user_joao",
"cpf": "98765432100"
},
"documentGroupId": "group_contract_2026_001",
"signerIndex": 2,
"totalSigners": 3
}'
Signatário 3 (assina por último):
curl -X POST https://api.signdocs.com.br/v1/transactions \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": { "profile": "DIGITAL_CERTIFICATE" },
"signer": {
"name": "Carlos Oliveira",
"email": "carlos@example.com",
"userExternalId": "user_carlos",
"cpf": "11122233344"
},
"documentGroupId": "group_contract_2026_001",
"signerIndex": 3,
"totalSigners": 3
}'
Os signatários devem concluir suas assinaturas na ordem do signerIndex:
/signing/prepare do signatário 2 for chamado, o servidor carrega automaticamente o PDF assinado pelo signatário 1 (armazenado como document-signed-1.pdf)./signing/prepare do signatário 3 for chamado, o servidor carrega o PDF assinado pelo signatário 2 (armazenado como document-signed-2.pdf).Se um signatário posterior chamar /signing/prepare antes que o signatário anterior conclua, o servidor retorna um erro 422 Unprocessable Entity:
{
"type": "https://api.signdocs.com.br/errors/unprocessable-entity",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Previous signer (index 1) must complete signing first"
}
Utilize o filtro documentGroupId no endpoint de listagem para recuperar todas as transações de um grupo:
curl -X GET "https://api.signdocs.com.br/v1/transactions?documentGroupId=group_contract_2026_001" \
-H "Authorization: Bearer ${TOKEN}"
O SignDocs Brasil inclui um conjunto abrangente de certificados de CAs intermediárias e raiz ICP-Brasil. Quando você envia seu certificado folha na requisição /signing/prepare, o servidor resolve automaticamente a cadeia completa de certificados usando o seguinte algoritmo:
O construtor de cadeia utiliza uma abordagem de correspondência de DN com múltiplas estratégias para lidar com as variações encontradas entre as CAs ICP-Brasil:
2.5.4.3 se torna CN), espaços em branco são normalizados.Se um certificado intermediário não for encontrado no armazenamento incluído, o servidor tenta obtê-lo por meio da extensão Authority Information Access (AIA):
caIssuers da extensão AIA (OID 1.3.6.1.5.5.7.48.2) é extraído do certificado.Isso garante que mesmo CAs intermediárias recém-emitidas que ainda não estão no armazenamento incluído possam ser resolvidas.
O contêiner PKCS#7 SignedData completo embutido no PDF inclui:
Isso significa que o PDF assinado é totalmente autocontido. Um verificador não precisa buscar certificados externos para validar a cadeia de assinatura.
Os pacotes de CAs intermediárias e raiz ICP-Brasil são atualizados periodicamente conforme o ITI publica novas CAs. Os pacotes são implantados como parte do pacote da função Lambda. Se você encontrar uma falha na resolução de cadeia para um certificado recém-emitido, o fallback por AIA deve tratá-la de forma transparente. Se os problemas persistirem, entre em contato com o suporte.
Todos os erros seguem o formato Problem Details da RFC 7807 com tipo de conteúdo application/problem+json.
{
"type": "https://api.signdocs.com.br/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Human-readable explanation of what went wrong",
"instance": "/v1/transactions/tx_01HQRS.../signing/prepare"
}
Retornado quando o PEM em certificateChainPems é malformado ou não é um certificado X.509 válido.
{
"type": "https://api.signdocs.com.br/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Invalid X.509 certificate in certificateChainPems"
}
Causa: A string PEM está sem o cabeçalho -----BEGIN CERTIFICATE-----, contém base64 corrompido, ou a estrutura ASN.1 não é um certificado X.509 válido.
Correção: Verifique se o PEM foi extraído corretamente do arquivo PFX. Certifique-se de que nenhum espaço em branco extra ou truncamento ocorreu durante o transporte.
{
"type": "https://api.signdocs.com.br/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "certificateChainPems array is required with at least one certificate"
}
Causa: O campo certificateChainPems está ausente, é nulo, está vazio ou não é um array.
Correção: Forneça pelo menos o PEM do certificado folha no array.
O servidor não rejeita certificados expirados na etapa /signing/prepare -- a política ICP-Brasil permite assinar com um certificado que era válido no momento da emissão. No entanto, verificadores podem sinalizar a assinatura durante a validação. Certifique-se de que seu certificado está vigente.
Se o construtor de cadeia não conseguir conectar o certificado folha a uma raiz ICP-Brasil confiável, o servidor registra um aviso, mas prossegue com a assinatura. O PKCS#7 resultante conterá a cadeia que foi possível resolver. Verificadores como o Validador do ITI podem rejeitar a assinatura se a cadeia estiver incompleta.
Para evitar isso, certifique-se de que seu certificado A1 foi emitido por uma CA ICP-Brasil reconhecida. O fallback por AIA trata a maioria dos casos de intermediários ausentes automaticamente.
{
"type": "https://api.signdocs.com.br/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Signature request has expired"
}
Causa: Mais de 15 minutos se passaram entre a chamada /signing/prepare e a chamada /signing/complete.
Correção: Chame /signing/prepare novamente para obter um novo signatureRequestId e hashToSign, depois assine e envie dentro de 15 minutos.
{
"type": "https://api.signdocs.com.br/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Signature request is COMPLETED"
}
Causa: O endpoint /signing/complete foi chamado duas vezes com o mesmo signatureRequestId.
Correção: Cada requisição de assinatura só pode ser concluída uma vez. Se precisar assinar novamente, chame /signing/prepare outra vez.
{
"type": "https://api.signdocs.com.br/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "Invalid base64 in rawSignatureBase64"
}
Causa: O campo rawSignatureBase64 não é base64 válido ou decodifica para um buffer vazio.
Correção: Certifique-se de que a assinatura está codificada corretamente em base64. Para uma chave RSA de 2048 bits, a assinatura bruta deve ter exatamente 256 bytes (344 caracteres em base64). Para uma chave de 4096 bits, 512 bytes (684 caracteres em base64).
{
"type": "https://api.signdocs.com.br/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "DIGITAL_SIGN_A1 step must be in STARTED status, current: PENDING"
}
Causa: O endpoint /signing/prepare foi chamado antes de iniciar a etapa DIGITAL_SIGN_A1.
Correção: Chame POST /v1/transactions/{id}/steps/{stepId}/start na etapa DIGITAL_SIGN_A1 primeiro.
{
"type": "https://api.signdocs.com.br/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Previous step CLICK_ACCEPT (order 0) must be completed first"
}
Causa: Tentou iniciar a etapa DIGITAL_SIGN_A1 antes de concluir a etapa CLICK_ACCEPT.
Correção: As etapas devem ser concluídas em ordem. Para o perfil DIGITAL_CERTIFICATE, conclua CLICK_ACCEPT antes de iniciar DIGITAL_SIGN_A1.
{
"type": "https://api.signdocs.com.br/errors/bad-request",
"title": "Bad Request",
"status": 400,
"detail": "DIGITAL_SIGN_A1 steps cannot be completed directly. Use POST /v1/transactions/{id}/signing/prepare and POST /v1/transactions/{id}/signing/complete instead."
}
Causa: Chamou POST /v1/transactions/{id}/steps/{stepId}/complete em uma etapa DIGITAL_SIGN_A1. Este tipo de etapa utiliza um protocolo dedicado de assinatura em duas fases.
Correção: Utilize os endpoints /signing/prepare e /signing/complete em vez do endpoint genérico de conclusão de etapa.
{
"type": "https://api.signdocs.com.br/errors/unprocessable-entity",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Document must be uploaded before signing"
}
Causa: Chamou /signing/prepare em uma transação que não possui documento PDF enviado.
Correção: Faça upload do documento PDF antes de iniciar o fluxo de assinatura.
Se o contêiner PKCS#7 montado (certificados + assinatura + atributos autenticados) exceder o tamanho do espaço reservado no PDF, a incorporação da assinatura falhará. Isso é extremamente raro com certificados ICP-Brasil A1, pois o espaço reservado é dimensionado para acomodar cadeias completas. Se encontrado, entre em contato com o suporte.
{
"type": "https://api.signdocs.com.br/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Transaction is already finalized"
}
Causa: Chamou /finalize em uma transação que já está no status COMPLETED.
{
"type": "https://api.signdocs.com.br/errors/unprocessable-entity",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Not all steps are completed. Pending: DIGITAL_SIGN_A1(STARTED)"
}
Causa: Chamou /finalize antes de todas as etapas serem concluídas.
Correção: Conclua todas as etapas (CLICK_ACCEPT e DIGITAL_SIGN_A1) antes de finalizar.
| Passo | Método | Endpoint | Finalidade |
|---|---|---|---|
| 1 | POST |
/v1/transactions |
Criar transação com perfil DIGITAL_CERTIFICATE |
| 2 | POST |
/v1/transactions/{txId}/document |
Fazer upload do documento PDF |
| 3a | POST |
/v1/transactions/{txId}/steps/{stepId}/start |
Iniciar etapa CLICK_ACCEPT |
| 3b | POST |
/v1/transactions/{txId}/steps/{stepId}/complete |
Concluir etapa CLICK_ACCEPT |
| 4 | POST |
/v1/transactions/{txId}/steps/{stepId}/start |
Iniciar etapa DIGITAL_SIGN_A1 |
| 5 | POST |
/v1/transactions/{txId}/signing/prepare |
Enviar certificado folha, receber hash |
| 6 | -- | (lado do cliente) | Assinar hash com chave privada |
| 7 | POST |
/v1/transactions/{txId}/signing/complete |
Enviar assinatura bruta |
| 8 | POST |
/v1/transactions/{txId}/finalize |
Finalizar e construir evidências |
| 9 | GET |
/v1/transactions/{txId}/download |
Obter URLs pré-assinadas para download |
| Endpoint | Scope |
|---|---|
| Criar transação | transactions:write |
| Fazer upload de documento | transactions:write |
| Iniciar / concluir etapas | steps:write |
| Preparar / concluir assinatura | steps:write |
| Finalizar transação | transactions:write |
| Baixar documento | transactions:read |