openapi: 3.1.0 info: title: ShieldSign PKI - API version: 0.3.0 description: | API de assinatura digital de PDF (PAdES) com certificados ICP-Brasil e de PKI privada. A chave privada nunca sai do computador do usuário. **Autenticação.** Toda chamada leva `Authorization: Bearer ssk_...` (a chave de API de um cliente) e deve partir do **seu servidor**, nunca do navegador. Cada cliente tem um limite de requisições por minuto e, opcionalmente, uma cota mensal de assinaturas concluídas. Toda resposta traz o cabeçalho `X-Request-Id`. **Fluxo de uma assinatura.** 1. `POST /v1/pades-signatures` (ou `.../multipart`) envia o PDF e a política e devolve um `token` de sessão (43 caracteres, válido por 15 minutos). 2. O certificado do usuário é lido no computador dele. `POST .../{token}/prepare` valida o certificado e devolve os dados a assinar (`toSignData`). 3. O usuário assina localmente. `POST .../{token}/complete` devolve o PDF assinado, já com o carimbo de tempo. 4. `DELETE .../{token}` apaga a sessão e os arquivos do servidor. A sessão pertence ao cliente que a criou: outro cliente recebe `404 token_not_found`. **Prazo e repetição segura.** `POST .../{token}/extend` devolve os 15 minutos, até o teto de 60 minutos desde o início, e `GET .../{token}` informa a fase e o prazo. Se a resposta do `complete` se perder, repita a chamada com o mesmo cabeçalho `Idempotency-Key`: a API devolve a mesma resposta (`Idempotent-Replayed: true`), sem assinar de novo e sem gastar a cota. Sem a chave, uma conclusão `inline` é de uso único. **Lotes.** `POST /v1/pades-signatures/multipart` envia um ou vários PDFs em binário. O `prepare` devolve um item por documento em `digests`, na mesma ordem, e uma única aprovação assina todos. Com `output=link`, o `complete` devolve links para baixar cada PDF ou um `.zip`. Cada documento concluído é uma transação, para uso, cota e auditoria. **Nomes de certificado.** `subjectName` e `issuerName` são texto legível, para exibir. `subjectDn` e `issuerDn` estão em RFC 4514 (`CN=...,OU=...,O=ICP-Brasil,C=BR`), o mesmo formato que `acceptableIssuers` aceita. `subjectCn` e `issuerCn` são só o Common Name (o do titular, já sem o `:CPF/CNPJ`). **Limites** (serviço da Shield): 30 MiB por PDF, 256 MiB por envio `multipart`, 50 documentos por lote e 32 MiB de corpo em chamadas JSON. servers: - url: https://api.pki.shieldsign.com.br security: - ApiKey: [] paths: /v1/pades-signatures: post: operationId: startPadesSignature summary: Inicia a assinatura e devolve o token da sessão requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StartPadesRequest' } responses: '200': description: Sessão criada content: application/json: schema: { $ref: '#/components/schemas/StartPadesResponse' } '400': { $ref: '#/components/responses/Error' } '503': { $ref: '#/components/responses/Error' } /v1/pades-signatures/multipart: post: operationId: startPadesSignatureMultipart summary: Inicia a assinatura de um ou vários PDFs enviados em binário description: | `multipart/form-data` com os campos de texto `signaturePolicyId`, `securityContextId`, `callbackArgument` e `acceptableIssuers` (opcionais) e uma ou mais partes `file` (PDF). A ordem das partes `file` define o índice dos documentos. Cada arquivo vai direto para o disco, em blocos. Erros: 400 (sem arquivo, não é PDF, lote grande demais, campos ausentes), 403 `quota_exceeded` (o lote precisa caber na cota restante do mês), 408 `upload_timeout` (o envio passou do tempo total permitido, 10 min no padrão: a sessão é cancelada), 413 `payload_too_large`, 507 `storage_full`. requestBody: required: true content: multipart/form-data: schema: type: object required: [signaturePolicyId, securityContextId, file] properties: signaturePolicyId: { type: string } securityContextId: { type: string } callbackArgument: { type: string } acceptableIssuers: type: string description: Array JSON de textos (`["CN=AC Exemplo",""]`) ou um único item; o campo pode repetir. Ver `StartPadesRequest.acceptableIssuers`. file: { type: array, items: { type: string, format: binary }, minItems: 1 } responses: '200': description: Sessão criada content: application/json: schema: { $ref: '#/components/schemas/StartPadesResponse' } '400': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '413': { $ref: '#/components/responses/Error' } '507': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}: get: operationId: getPadesSession summary: Estado da sessão (fase, prazo e, se concluída, o resultado sem o PDF) description: | Use depois de um tempo-limite para saber se a conclusão chegou a acontecer. Não revela o PDF (baixe com `documents/{index}`). Em sessões `inline` sem `Idempotency-Key` nada fica retido: após o `complete` o token é 404. parameters: [{ $ref: '#/components/parameters/Token' }] responses: '200': description: Estado da sessão content: application/json: schema: { $ref: '#/components/schemas/SessionStatus' } '404': { $ref: '#/components/responses/Error' } delete: operationId: discardPadesSignature summary: Descarta a sessão e apaga os arquivos do servidor na hora parameters: [{ $ref: '#/components/parameters/Token' }] responses: '204': { description: Descartada } '404': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}/extend: post: operationId: extendPadesSession summary: Renova o prazo da sessão (15 minutos a partir de agora, até 60 minutos desde o início) description: | Para certificado em nuvem com aprovação no celular que demora mais que o prazo normal. Também renova o prazo dos arquivos no servidor. Passou de 60 minutos desde o `start`: 409 `invalid_session_state`. parameters: [{ $ref: '#/components/parameters/Token' }] responses: '200': description: Prazo renovado content: application/json: schema: type: object required: [expiresInSeconds, extendableForSeconds] properties: expiresInSeconds: { type: integer } extendableForSeconds: { type: integer, description: "Quanto do teto de 60 minutos ainda estava disponível antes desta renovação." } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}/documents/{index}: get: operationId: downloadSignedPdf summary: Baixa um PDF assinado (sessão concluída com `output=link`) parameters: - { $ref: '#/components/parameters/Token' } - { name: index, in: path, required: true, schema: { type: integer, minimum: 0 } } responses: '200': description: PDF assinado content: application/pdf: schema: { type: string, format: binary } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}/documents.zip: get: operationId: downloadSignedZip summary: Baixa todos os PDFs assinados num `.zip` (sessão concluída com `output=link`) parameters: [{ $ref: '#/components/parameters/Token' }] responses: '200': description: Arquivo .zip (nomes `001--assinado.pdf`, ...) content: application/zip: schema: { type: string, format: binary } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}/prepare: post: operationId: preparePadesSignature summary: Recebe o certificado e devolve os dados a assinar parameters: [{ $ref: '#/components/parameters/Token' }] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/PreparePadesRequest' } responses: '200': description: Dados para assinatura content: application/json: schema: { $ref: '#/components/schemas/PreparePadesResponse' } '400': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '422': description: | Recusa do format-engine. Códigos: `invalid_certificate`, `certificate_not_trusted`, `certificate_issuer_not_allowed`, `policy_not_configured`, `security_context_not_configured`, `invalid_pdf`, `invalid_visual_representation`. `certificate_not_trusted`, `invalid_certificate` e `certificate_issuer_not_allowed` trazem `error.reason` (subcódigo estável, sem dados pessoais): `expired`, `not_yet_valid`, `chain`, `key_usage`, `revoked`, `revocation_unavailable`, `algorithm`, `malformed`, `issuer_not_allowed`. `409 signature_field_exists` (o PDF já tem um campo com o `signatureFieldName` pedido). content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': { $ref: '#/components/responses/Error' } /v1/pades-signatures/{token}/complete: post: operationId: completePadesSignature summary: Recebe a assinatura produzida pelo agente e devolve o PDF assinado description: | Com `Idempotency-Key`, uma conclusão que deu certo pode ser repetida (mesmo token, mesma chave) e devolve a MESMA resposta com o cabeçalho `Idempotent-Replayed: true`, sem assinar de novo nem contar a cota. Chave diferente, ou sem chave, numa sessão já concluída: 409 `invalid_session_state`. **409 `complete_in_progress`**: a 1ª chamada ainda está sendo concluída (ex.: carimbo de tempo lento): espere e repita com a MESMA chave. **429 `rate_limited`** (com `Retry-After`) na repetição de uma resposta `inline` grande quando a API está entregando outras ao mesmo tempo: a conclusão NÃO foi executada de novo; tente de novo. Falha de negócio (4xx) não consome a chave: a sessão continua e a mesma chave serve na próxima tentativa que der certo. parameters: - { $ref: '#/components/parameters/Token' } - name: Idempotency-Key in: header required: false description: "1 a 64 caracteres [A-Za-z0-9_-]. Gere um valor novo por tentativa de ASSINAR (não por tentativa de rede)." schema: { type: string, minLength: 1, maxLength: 64, pattern: '^[A-Za-z0-9_-]{1,64}$' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CompletePadesRequest' } responses: '200': description: PDF assinado content: application/json: schema: { $ref: '#/components/schemas/CompletePadesResponse' } '400': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '410': { $ref: '#/components/responses/Error' } '422': description: | `invalid_signature` (não confere com o certificado/digest). A sessão é preservada e o cliente pode reenviar `complete`. content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': { $ref: '#/components/responses/Error' } /v1/signature-verifications: post: operationId: verifyPadesSignatures summary: Verifica as assinaturas de um PDF description: | Confere CADA assinatura do PDF: integridade, assinatura criptográfica, cadeia do certificado até as raízes do `securityContextId`, alterações feitas depois de assinar, carimbo de tempo e política ICP-Brasil declarada (conferida contra a LPA oficial). Cobrada como transação `pades_verification` (preço próprio no plano; não consome a cota de assinaturas). Sem raízes configuradas para o contexto a resposta é erro (nunca "confiável" por omissão). `status` de cada assinatura: `valid`, `invalid`, `untrusted`, `revoked`, `modified_after_signing`. Atenção: `signingTime` é o que o próprio signatário declarou; sem carimbo de tempo confiável ele não prova quando assinou. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/VerifyPadesRequest' } responses: '200': description: "Relatório (PDF sem assinaturas devolve `signatureCount: 0` e `allValid: false`)" content: application/json: schema: { $ref: '#/components/schemas/VerifyPadesResponse' } '400': { $ref: '#/components/responses/Error' } '422': description: "`invalid_pdf` (ilegível, criptografado), `security_context_not_configured`, `too_many_signatures`" content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': { $ref: '#/components/responses/Error' } /v1/certificate-authentications: post: operationId: startCertificateAuthentication summary: Inicia a autenticação por certificado (login com e-CPF/e-CNPJ) description: | Cria um desafio de **uso único** (nonce de 256 bits, validade de 5 minutos). O usuário assina `dataToSign` com o certificado (A1, A3 por extensão + agente; no SDK JS: `signer.sign(dataToSign, "SHA-256")`) e o seu servidor conclui em `POST /v1/certificate-authentications/{token}/complete`. A mensagem tem separação de domínio (`ShieldSign-Auth-v1`) e leva o cliente e o contexto: nunca é um PDF nem um hash de PDF. Transação `certificate_authentication` (uso/fatura próprios; não consome a cota de assinaturas). requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StartCertificateAuthenticationRequest' } responses: '200': description: Desafio criado content: application/json: schema: { $ref: '#/components/schemas/StartCertificateAuthenticationResponse' } '400': { $ref: '#/components/responses/Error' } /v1/certificate-authentications/{token}/complete: post: operationId: completeCertificateAuthentication summary: Conclui a autenticação por certificado description: | Valida a cadeia do certificado contra as raízes do `securityContextId` (e `acceptableIssuers`, e a revogação se `checkRevocation`) e a assinatura sobre o desafio. A mensagem é montada pela API a partir do desafio guardado. **O desafio é consumido atomicamente: qualquer tentativa (certa ou errada) o queima**; para tentar de novo, inicie outro. Token de outro cliente, vencido, já usado ou de assinatura PAdES: 404 `token_not_found`. Erros do motor: `certificate_not_trusted`, `certificate_issuer_not_allowed`, `invalid_signature`, `invalid_certificate`, `certificate_revoked`... `authenticated: true` prova a posse da chave; quem é o usuário (CPF/CNPJ de `certificate.pkiBrazil`) o seu servidor decide. parameters: - { name: token, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CompleteCertificateAuthenticationRequest' } responses: '200': description: Autenticado content: application/json: schema: { $ref: '#/components/schemas/CertificateAuthenticationResponse' } '404': { $ref: '#/components/responses/Error' } '422': description: Recusado pelo motor (código estável em `error.code`) content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': { $ref: '#/components/responses/Error' } /v1/private-security-contexts: get: operationId: listPrivateSecurityContexts summary: Lista os contextos de segurança privados do cliente responses: '200': description: Contextos (sem o PEM; só emissor, validade e thumbprint) content: application/json: schema: type: object properties: contexts: type: array items: { $ref: '#/components/schemas/PrivateSecurityContext' } '401': { $ref: '#/components/responses/Error' } /v1/private-security-contexts/{slug}: parameters: - { name: slug, in: path, required: true, schema: { type: string, pattern: '^[a-z0-9][a-z0-9-]{0,39}$' } } put: operationId: putPrivateSecurityContext summary: Cria ou substitui uma PKI privada (corporativa) do cliente description: | Cadastra as raízes (e, opcionalmente, intermediárias) da PKI da empresa (ex.: Microsoft AD CS) e passa a valer como `securityContextId: "private:"`. Separação estrita: o motor, nesse contexto, confia SOMENTE nestas âncoras (nunca nas raízes globais ICP-Brasil), e nenhuma âncora própria entra em contexto global. Cada cliente só vê e usa os seus. Só `pades-basic` e `pades-basic-lt` são aceitas (as políticas ICP-Brasil exigem o contexto `icp-brasil`). `pades-basic-lt` = PAdES-B-LT: **revogação obrigatória** com as LCRs enviadas em `crls` (o motor não busca nada na rede para PKI privada) e DSS embutido; sem LCR válida não assina (`revocation_unavailable`), certificado revogado = `certificate_revoked`, sempre antes do PIN. Validação: só `CERTIFICATE` (chave privada colada = 400 e nada é gravado); raiz = autoassinada, CA, não vencida; intermediária = CA, não autoassinada; até 50 certificados e 512 KB; até 20 contextos por cliente. Substituir troca TODAS as âncoras; sessões já preparadas terminam com as âncoras do `prepare`. Quem tem a chave de API pode alterar a confiança do cliente: guarde-a como segredo (as alterações vão para a auditoria). requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [roots] properties: description: { type: string, maxLength: 200 } roots: { type: array, minItems: 1, items: { type: string, description: "PEM (um ou mais blocos)" } } intermediates: { type: array, items: { type: string } } crls: { type: array, maxItems: 20, items: { type: string, description: "LCR em PEM (`X509 CRL`)" }, description: "LCRs das ACs acima. Cada uma precisa ser emitida e assinada por uma AC do contexto, já válida e com nextUpdate no futuro. Total até 512 KB." } responses: '201': { description: Criado, content: { application/json: { schema: { $ref: '#/components/schemas/PrivateSecurityContext' } } } } '200': { description: Substituído, content: { application/json: { schema: { $ref: '#/components/schemas/PrivateSecurityContext' } } } } '400': { $ref: '#/components/responses/Error' } delete: operationId: deletePrivateSecurityContext summary: Exclui o contexto (nunca cai para as raízes globais) responses: '204': { description: Excluído } '404': { $ref: '#/components/responses/Error' } /v1/private-security-contexts/{slug}/crls: parameters: - { name: slug, in: path, required: true, schema: { type: string, pattern: '^[a-z0-9][a-z0-9-]{0,39}$' } } put: operationId: putPrivateSecurityContextCrls summary: Troca só as LCRs do contexto privado description: | Para a rotina do cliente que repete isto a cada publicação de LCR da AD CS (elas vencem em dias). As âncoras (raízes/intermediárias) ficam como estão; as LCRs anteriores são TODAS substituídas (`[]` remove todas). LCR vencida, ainda não válida, de AC que não é do contexto ou com a assinatura errada = 400 e nada muda. A PKI de duas camadas precisa da LCR da raiz (cobre a intermediária) e da LCR da intermediária (cobre o usuário). requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [crls] properties: crls: { type: array, maxItems: 20, items: { type: string } } responses: '200': { description: Contexto atualizado, content: { application/json: { schema: { $ref: '#/components/schemas/PrivateSecurityContext' } } } } '400': { $ref: '#/components/responses/Error' } components: securitySchemes: ApiKey: type: http scheme: bearer description: Chave `ssk__` criada com `shield-admin key create`; exibida uma única vez. parameters: Token: name: token in: path required: true schema: { type: string, minLength: 43, maxLength: 43, pattern: '^[A-Za-z0-9_-]{43}$' } responses: Error: description: | Erro. Além dos códigos de cada operação, qualquer rota pode responder: 401 `unauthorized` (chave ausente, inválida ou revogada; cabeçalho `WWW-Authenticate: Bearer`), 403 `tenant_suspended` (cliente suspenso) ou `quota_exceeded` (cota mensal esgotada, só no `start`), 429 `rate_limited` (cabeçalho `Retry-After` em segundos) e 503 `store_unavailable`. content: application/json: schema: { $ref: '#/components/schemas/Error' } schemas: Base64: type: string contentEncoding: base64 StartPadesRequest: type: object required: [pdfToSign, signaturePolicyId, securityContextId] properties: pdfToSign: { $ref: '#/components/schemas/Base64' } signaturePolicyId: type: string examples: [pades-basic, pades-icpbrasil-ad-rb] description: | `pades-basic` (PAdES-B-B) vem embutida. As políticas ICP-Brasil (AD-RB etc.) dependem de configuração no engine (OID e hash do documento da política); sem ela retornam 422 `policy_not_configured`. securityContextId: type: string examples: [icp-brasil] description: Define as raízes de confiança usadas para validar a cadeia do certificado. callbackArgument: type: string description: Valor opaco devolvido no `complete`. acceptableIssuers: type: array maxItems: 20 items: { type: string, minLength: 1, maxLength: 300 } description: | Restringe quem pode assinar. Cada item é a impressão digital SHA-256 (64 hex) ou o DN (RFC 4514, qualquer ordem, sem diferenciar maiúsculas) de uma **AC da cadeia do certificado** (intermediária ou raiz; o certificado final não conta). Basta um item casar. A conferência é feita pelo motor DEPOIS de validar a cadeia contra as raízes do contexto, então não dá para burlar com uma AC de mesmo nome. Sem casar: 422 `certificate_issuer_not_allowed`, no `prepare`, antes de o usuário digitar o PIN. Vazio/ausente = sem restrição. Document: type: object required: [index, name, sha256, size, transactionId] properties: index: { type: integer } name: { type: string } sha256: { type: string, description: "SHA-256 (hex) do PDF enviado." } size: { type: integer } transactionId: { type: string, description: "Id público da transação do documento (painel e auditoria)." } StartPadesResponse: type: object required: [token, documents] properties: token: { type: string } documents: type: array items: { $ref: '#/components/schemas/Document' } PreparePadesRequest: type: object required: [certificate] properties: certificate: { $ref: '#/components/schemas/Base64' } certificateChain: type: array items: { $ref: '#/components/schemas/Base64' } visualRepresentation: allOf: [{ $ref: '#/components/schemas/VisualRepresentation' }] description: Assinatura visível em TODOS os documentos da sessão. Sem este campo (nem `visualRepresentations`) a assinatura é invisível. visualRepresentations: type: array items: oneOf: - $ref: '#/components/schemas/VisualRepresentation' - type: 'null' description: | Lote: um item por documento, na ordem do envio (`null` = esse documento fica sem assinatura visível). O tamanho precisa ser igual ao número de documentos. Não combine com `visualRepresentation`. signatureFieldName: type: string pattern: '^[A-Za-z][A-Za-z0-9_-]{0,63}$' description: | Nome do campo de assinatura em TODOS os documentos. Padrão: `ShieldSignature1` ou, se o PDF já tem assinaturas (vários signatários em sequência), o primeiro `ShieldSignatureN` livre (a 2ª assinatura vira `ShieldSignature2`, a 3ª `ShieldSignature3`...). Nome já usado no PDF: 409 `signature_field_exists`; nome fora do padrão: 400. signatureFieldNames: type: array items: { type: [string, 'null'], pattern: '^[A-Za-z][A-Za-z0-9_-]{0,63}$' } description: "Lote: um nome por documento (`null` = nome padrão). Não combine com `signatureFieldName`." PrivateSecurityContext: type: object properties: id: { type: string, examples: ['private:corp'] } slug: { type: string } description: { type: string } roots: { type: array, items: { $ref: '#/components/schemas/PrivateContextCert' } } intermediates: { type: array, items: { $ref: '#/components/schemas/PrivateContextCert' } } crls: type: array items: type: object properties: sha256: { type: string } issuer: { type: string } thisUpdate: { type: string } nextUpdate: { type: string } nextUpdateTs: { type: integer } revokedCount: { type: integer } expired: { type: boolean, description: "A LCR venceu: o motor a recusa e `pades-basic-lt` deixa de assinar até ser renovada." } createdAt: { type: string } updatedAt: { type: string } PrivateContextCert: type: object properties: thumbprint: { type: string } subject: { type: string } issuer: { type: string } notBefore: { type: string } notAfter: { type: string } VisualRepresentation: type: object additionalProperties: false required: [rect] description: | Onde e como desenhar a assinatura. O servidor só RENDERIZA o pedido (a inteligência de posicionamento é do cliente). Coordenadas em pontos (1/72 pol), origem no canto INFERIOR esquerdo da página (padrão PDF). Erros de validação: `400 invalid_visual_representation` (página inexistente, retângulo fora da página, texto que não cabe, imagem inválida...); a sessão continua aberta para nova tentativa. properties: page: description: Página a partir de 1, ou `last`. Padrão 1. oneOf: - { type: integer, minimum: 1 } - { type: string, enum: [last] } rect: type: object additionalProperties: false required: [x, y, width, height] description: Mínimo de 20 x 10 pontos; precisa caber inteiro na página (CropBox, ou MediaBox se não houver). properties: x: { type: number } y: { type: number } width: { type: number } height: { type: number } text: type: string maxLength: 2000 description: | Texto do carimbo (acentos e `\n` funcionam; é quebrado em linhas para caber no retângulo, e recusado se não couber na altura). Marcadores: `{{signer}}` (nome do titular, sem o ":CPF" do CN), `{{date}}` (data/hora UTC do preparo), `{{issuer}}`, `{{cpf}}`, `{{cnpj}}`, `{{company}}` (razão social do e-CNPJ) (vazios se o certificado não os tiver). Outros `{{...}}` são recusados. Ausente = "Assinado digitalmente por {{signer}} / Data: {{date}}"; `""` = só a imagem (exige `image`). Fonte: DejaVu Sans embutida, mais larga que a Helvetica/Arial. Largura média por caractere, em múltiplos do corpo: minúsculas ~0,52; dígitos ~0,58; MAIÚSCULAS ~0,64 (nomes da ICP-Brasil vêm em maiúsculas). Caracteres por linha ≈ (largura do `rect` - 6) / (corpo x fator): com corpo 9 e 243 pt de largura, ~49 minúsculas ou ~40 MAIÚSCULAS. A entrelinha é 1,25 x o corpo e a área útil desconta 3 pt de margem em cada lado (e o espaço da imagem quando `textFlow=beside`). Se não couber: 400 `invalid_visual_representation` (ou use `autoFontSize` para reduzir o corpo até 5 pt). fontSize: { type: integer, minimum: 5, maximum: 48, default: 9 } fontColor: { type: string, pattern: '^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$', default: '#000000', description: "Cor do texto." } textAlign: { type: string, enum: [left, center, right], default: left } textVerticalAlign: { type: string, enum: [top, middle, bottom], default: middle, description: "Posição vertical do bloco de texto." } includeSigningTime: { type: boolean, default: true, description: "Só vale para o texto PADRÃO (sem `text`): `false` omite a linha \"Data: ...\"." } autoFontSize: { type: boolean, default: false, description: "Texto que não cabe: reduz o corpo (até 5 pt) em vez de recusar com `invalid_visual_representation`." } image: type: string description: PNG ou JPEG em base64 (até 2 MB, 12 megapixels), desenhado ao fundo do retângulo; aceita data URL. imagePlacement: type: object additionalProperties: false description: | Onde a imagem fica dentro do retângulo. Sem este objeto: centro, tamanho máximo (mantendo a proporção) e atrás do texto (comportamento original). Para repetir o carimbo do Rest PKI (texto à esquerda, selo no canto superior direito): `{ "horizontal": "right", "vertical": "top", "width": 40, "height": 40, "margin": 3, "textFlow": "beside" }`. properties: horizontal: { type: string, enum: [left, center, right], default: center } vertical: { type: string, enum: [top, middle, bottom], default: middle } scale: { type: number, exclusiveMinimum: 0, maximum: 1, default: 1, description: "Fração do retângulo que a imagem pode ocupar." } width: { type: number, minimum: 1, maximum: 10000, description: "Largura máxima em pontos (substitui `scale`)." } height: { type: number, minimum: 1, maximum: 10000, description: "Altura máxima em pontos (substitui `scale`)." } margin: { type: number, minimum: 0, default: 0, description: "Pontos entre a imagem e a borda do retângulo (menor que metade do menor lado)." } textFlow: type: string enum: [overlay, beside] default: overlay description: "`overlay`: texto por cima da imagem. `beside`: o texto ocupa o espaço que sobra ao lado da imagem (exige `horizontal` left ou right)." imageOpacity: { type: number, exclusiveMinimum: 0, maximum: 1, default: 1 } border: { type: boolean, default: true } DigestFields: type: object required: [toSignData, toSignHash, digestAlgorithm, digestAlgorithmOid, signatureAlgorithm] properties: toSignData: allOf: [{ $ref: '#/components/schemas/Base64' }] description: DER dos atributos assinados CMS; é este o dado que a chave privada assina. toSignHash: allOf: [{ $ref: '#/components/schemas/Base64' }] description: Hash de `toSignData` com `digestAlgorithm`, para assinadores que só aceitam o hash. digestAlgorithm: { type: string, examples: [SHA-256] } digestAlgorithmOid: { type: string, examples: ['2.16.840.1.101.3.4.2.1'] } signatureAlgorithm: { type: string, examples: [sha256_rsa, sha256_ecdsa] } PreparePadesResponse: description: | Com UM documento, os campos de `DigestFields` vêm também no topo (formato original). Em lote só vêm em `digests` (mesma ordem dos documentos), para ninguém assinar só o primeiro por engano. allOf: - $ref: '#/components/schemas/DigestFields' required: [digests] properties: digests: type: array items: allOf: - $ref: '#/components/schemas/DigestFields' - type: object required: [index] properties: { index: { type: integer } } certificate: allOf: [{ $ref: '#/components/schemas/CertificateInfo' }] description: | Identidade do certificado que vai assinar, JÁ validado (cadeia, validade, uso de chave, emissor aceito): o mesmo bloco do `complete`. Compare `pkiBrazil.cpf`/`cnpj` com o esperado e recuse ANTES do PIN, sem gastar a transação. signatureFieldNames: type: array items: { type: string } description: Nome do campo de assinatura criado em cada documento (na ordem de `digests`). warnings: type: array items: { type: string } SessionStatus: type: object required: [token, phase, expiresInSeconds, extendableForSeconds, documents] properties: token: { type: string } phase: { type: string, enum: [started, prepared, completed] } expiresInSeconds: { type: integer } extendableForSeconds: { type: integer, description: "Por quanto tempo ainda dá para renovar (teto de 60 minutos desde o `start`)." } documents: { type: array, items: { $ref: '#/components/schemas/Document' } } callbackArgument: { type: [string, 'null'] } result: allOf: [{ $ref: '#/components/schemas/CompletePadesResponse' }] description: "Só em `completed`: o mesmo resultado do `complete`, sem `signedPdf`." CompletePadesRequest: type: object description: Informe `signature` (um documento) OU `signatures` (lote, uma por documento, na ordem de `digests`). properties: signature: allOf: [{ $ref: '#/components/schemas/Base64' }] description: | Valor bruto da assinatura sobre `toSignData`. RSA PKCS#1 v1.5; ECDSA aceito em DER ou r||s (PKCS#11). signatures: type: array items: { $ref: '#/components/schemas/Base64' } output: type: string enum: [inline, link] description: | `inline` (padrão com 1 documento): `signedPdf` em base64 na resposta (até 32 MiB) e a sessão é apagada. `link` (padrão em lote): links de download válidos até o fim da sessão. Se um documento do lote for recusado (`invalid_signature`) nada é gerado e a sessão é preservada para nova tentativa; a mensagem diz `documento N: ...`. SignedDocument: allOf: - $ref: '#/components/schemas/Document' - type: object required: [signedSha256, signedSize] properties: signedSha256: { type: string } signedSize: { type: integer } downloadUrl: { type: string, description: "Só em `output=link`." } CompletePadesResponse: type: object required: [documents, certificate] properties: signedPdf: allOf: [{ $ref: '#/components/schemas/Base64' }] description: Só em `output=inline`. documents: type: array items: { $ref: '#/components/schemas/SignedDocument' } zipUrl: { type: string, description: "Só em `output=link`." } certificate: { $ref: '#/components/schemas/CertificateInfo' } callbackArgument: { type: [string, 'null'] } warnings: type: array items: { type: string } description: | Avisos do servidor. `tsa_not_configured`: a política (AD-RC/AD-RA) pede carimbo de tempo, mas este servidor está em modo de desenvolvimento sem TSA; o DSS foi embutido com o relógio local e a assinatura NÃO vale como AD-RC/AD-RA no ITI. ltv: type: object description: AD-RC/AD-RA — o que foi embutido no PDF. properties: profile: { type: string, enum: [AD-RC, AD-RA] } dss: { type: boolean, description: "Document Security Store com certificados, LCRs e respostas OCSP da cadeia." } documentTimestamp: { type: boolean, description: "Carimbo de tempo do documento (AD-RA) selando o DSS." } certs: { type: integer } crls: { type: integer } ocsps: { type: integer } StartCertificateAuthenticationRequest: type: object required: [securityContextId] properties: securityContextId: { type: string, examples: [icp-brasil] } acceptableIssuers: type: array maxItems: 20 items: { type: string, minLength: 1, maxLength: 300 } description: Mesma restrição de emissor da assinatura (ver `StartPadesRequest.acceptableIssuers`). checkRevocation: { type: boolean, default: true, description: "Exige a situação de revogação (ICP-Brasil: LCR/OCSP; PKI privada: as LCRs enviadas). PADRÃO true: certificado revogado ou sem como consultar NÃO autentica (`certificate_revoked` / `revocation_unavailable`). false só em ambiente sem fonte de revogação." } callbackArgument: { type: string } StartCertificateAuthenticationResponse: type: object required: [token, transactionId, dataToSign, digestAlgorithm, expiresIn] properties: token: { type: string } transactionId: { type: string } dataToSign: { $ref: '#/components/schemas/Base64' } digestAlgorithm: type: string examples: [SHA-384] description: | Algoritmo com que `dataToSign` deve ser assinado. É **SHA-384 de propósito** (a assinatura de PDF usa SHA-256): uma assinatura SHA-256, como a que a extensão produz para `hash`/`hashes`, não vale como login (separação de domínio contra relé). Use sempre o valor devolvido. expiresIn: { type: integer, description: Segundos de validade do desafio (300). } CompleteCertificateAuthenticationRequest: type: object required: [certificate, signature] properties: certificate: { $ref: '#/components/schemas/Base64' } certificateChain: { type: array, items: { $ref: '#/components/schemas/Base64' } } signature: { $ref: '#/components/schemas/Base64' } CertificateAuthenticationResponse: type: object required: [authenticated, transactionId, certificate] properties: authenticated: { type: boolean, const: true } transactionId: { type: string } certificate: { $ref: '#/components/schemas/CertificateInfo' } callbackArgument: { type: [string, 'null'] } VerifyPadesRequest: type: object required: [pdfToVerify, securityContextId] properties: pdfToVerify: { $ref: '#/components/schemas/Base64' } securityContextId: { type: string, examples: [icp-brasil] } VerifyPadesResponse: type: object required: [transactionId, signatureCount, allValid, signatures] properties: transactionId: { type: string } signatureCount: { type: integer } allValid: { type: boolean, description: "Verdadeiro só se há ao menos uma assinatura e todas estão `valid`." } documentTimestampCount: { type: integer, description: "Carimbos de tempo do documento (AD-RA); `signatureCount` conta só assinaturas de pessoas." } ltv: type: object description: Validação de longo prazo embutida (DSS). properties: dss: { type: boolean } certs: { type: integer } crls: { type: integer } ocsps: { type: integer } signatures: type: array items: { $ref: '#/components/schemas/SignatureVerification' } SignatureVerification: type: object required: [fieldName, status, intact, valid, trusted] properties: kind: { type: string, enum: [document_timestamp], description: "Presente só no carimbo de tempo do documento (AD-RA); ausente = assinatura de pessoa." } fieldName: { type: string } subFilter: { type: string, examples: [ETSI.CAdES.detached] } status: { type: string, enum: [valid, invalid, untrusted, revoked, modified_after_signing] } intact: { type: boolean, description: "O PDF assinado não foi adulterado dentro da área coberta." } valid: { type: boolean, description: "A assinatura criptográfica confere com o certificado." } trusted: { type: boolean, description: "A cadeia chega a uma raiz do contexto de segurança." } revoked: { type: boolean } coversWholeFile: { type: boolean, description: "Falso se o PDF recebeu atualizações depois desta assinatura." } coverage: { type: string } modificationLevel: { type: [string, 'null'] } laterSignatures: type: array items: { type: string } description: "Nomes das assinaturas que vieram DEPOIS desta (vários signatários em sequência). Elas não contam como alteração: `coversWholeFile` é falso, mas `status` continua `valid`." digestAlgorithm: { type: string } signingTime: { type: [string, 'null'], format: date-time } signer: { $ref: '#/components/schemas/CertificateInfo' } messages: type: array description: | Erros e avisos em português, prontos para a tela (equivalente ao `errors`/`warnings` do PadesSignatureExplorer do Rest PKI). Decida pelo `code` (estável); o `text` pode mudar de redação. Erros: `invalid`, `unreadable`, `revoked`, `untrusted`, `modified_after_signing`. Avisos: `timestamp_missing` (sem carimbo de tempo a data é só a declarada pelo signatário), `timestamp_invalid`, `timestamp_untrusted`, `policy_unknown_or_altered`, `policy_not_in_force`. items: type: object required: [level, code, text] properties: level: { type: string, enum: [error, warning, info] } code: { type: string } text: { type: string } timestamp: type: object properties: present: { type: boolean } valid: { type: boolean } trusted: { type: boolean } time: { type: [string, 'null'] } policy: type: [object, 'null'] properties: oid: { type: string } uri: { type: [string, 'null'] } digest: { type: string } name: { type: [string, 'null'], examples: [AD-RB v1.3] } hashOk: { type: [boolean, 'null'] } inForce: { type: [boolean, 'null'] } error: { type: string } CertificateInfo: type: object properties: subjectName: { type: string, description: "Texto legível (\"Common Name: X; Organizational Unit: Y\"), só para exibir." } issuerName: { type: string } subjectDn: { type: string, description: "DN em RFC 4514 (`CN=...,OU=...,O=ICP-Brasil,C=BR`): o formato de `acceptableIssuers`; o mesmo do agente/extensão.", examples: ['CN=AQUILA MENDES DA CRUZ GONCALVES:12922254755,OU=AC SyngularID Multipla,O=ICP-Brasil,C=BR'] } issuerDn: { type: string } subjectCn: { type: string, description: "Só o Common Name, pronto para a tela (o titular já sem o `:CPF/CNPJ`)." } issuerCn: { type: string } serialNumber: { type: string } thumbprint: { type: string, description: SHA-256 hex do certificado } validityStart: { type: string, format: date-time } validityEnd: { type: string, format: date-time } pkiBrazil: type: object description: | Campos ICP-Brasil lidos do SAN do certificado, para auditoria sem refazer o parsing do X.509. Sempre presente; todos `null` fora da ICP-Brasil. Só dígitos, sem máscara; campo zerado/malformado = `null`. properties: kind: { type: [string, 'null'], enum: [pf, pj, null], description: "pf = e-CPF; pj = e-CNPJ." } cpf: { type: [string, 'null'], description: "CPF do TITULAR (2.16.76.1.3.1, 11 dígitos). Em e-CNPJ é null: veja responsibleCpf." } cnpj: { type: [string, 'null'], description: "2.16.76.1.3.3, 14 dígitos." } birthDate: { type: [string, 'null'], description: "AAAA-MM-DD (titular)." } nis: { type: [string, 'null'] } rg: { type: [string, 'null'] } rgIssuer: { type: [string, 'null'] } responsibleName: { type: [string, 'null'], description: "2.16.76.1.3.2 (e-CNPJ)." } responsibleCpf: { type: [string, 'null'], description: "2.16.76.1.3.4 (e-CNPJ)." } responsibleBirthDate: { type: [string, 'null'] } email: { type: [string, 'null'] } companyName: { type: [string, 'null'], description: "e-CNPJ: razão social, lida do Common Name do certificado (sem o `:CNPJ`). Null se não for e-CNPJ." } Error: type: object required: [error] properties: error: type: object required: [code, message] properties: code: { type: string } message: { type: string } reason: type: string enum: [expired, not_yet_valid, chain, key_usage, revoked, revocation_unavailable, algorithm, malformed, issuer_not_allowed, other] description: | Só nas recusas de certificado (`certificate_not_trusted`, `invalid_certificate`, `certificate_issuer_not_allowed`, `certificate_revoked`, `revocation_unavailable`): motivo estável, sem dados pessoais. `chain` = a cadeia não fecha numa raiz do contexto (inclui AC desconhecida e autoassinado); `key_usage` = o certificado não serve para assinar.