Guias de Desenvolvimento

Política e Conformidade

Signing Sessions

Sessão de Confiança

Modelos de Solução

Guias dos SDKs

Tratamento de Erros

A API retorna erros no formato RFC 7807 Problem Details. Todos os SDKs mapeiam os erros para exceções tipadas, facilitando o tratamento específico por tipo de erro.


ProblemDetail (RFC 7807)

Toda resposta de erro da API segue este formato:

{
  "type": "https://api.signdocs.com.br/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Transaction tx-abc-123 not found",
  "instance": "/v1/transactions/tx-abc-123"
}
Campo Descrição
type URI que identifica o tipo de erro
title Resumo legível do erro
status Código HTTP
detail Explicação específica desta ocorrência
instance URI da requisição que causou o erro
code Código estável do caso específico, quando houver (ex.: TIMESTAMP_UNAVAILABLE, SIGNER_TURN). Prefira-o a interpretar detail.
retryable Presente quando a API afirma que reenviar a mesma requisição é seguro

Exceções por status HTTP

Status Exceção Descrição
400 BadRequestError Requisição malformada ou parâmetros inválidos
401 UnauthorizedError Token ausente, expirado ou inválido
403 ForbiddenError Sem permissão para o recurso
404 NotFoundError Recurso não encontrado
409 ConflictError Conflito de estado (ex: transação já finalizada)
422 UnprocessableEntityError Dados válidos sintaticamente mas semanticamente incorretos
429 RateLimitError Limite de requisições excedido
500 InternalServerError Erro interno do servidor
503 ServiceUnavailableError Serviço temporariamente indisponível

Erros de infraestrutura (não-HTTP):

Exceção Descrição
AuthenticationError Falha na obtenção do token OAuth2
ConnectionError Falha de conexão de rede
TimeoutError Timeout da requisição ou duração máxima de retry excedida

Tratamento por tipo de erro

import {
  NotFoundError,
  RateLimitError,
  ConflictError,
  SignDocsBrasilApiError,
  SignDocsBrasilError,
} from '@signdocs-brasil/api';

try {
  const tx = await client.transactions.get('tx-inexistente');
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log('Não encontrado:', err.detail);
  } else if (err instanceof RateLimitError) {
    console.log('Rate limit, retry em:', err.retryAfterSeconds, 'segundos');
  } else if (err instanceof ConflictError) {
    console.log('Conflito:', err.detail);
  } else if (err instanceof SignDocsBrasilApiError) {
    console.log('Erro de API:', err.status, err.title, err.detail);
    console.log('ProblemDetail:', err.problemDetail);
  } else if (err instanceof SignDocsBrasilError) {
    console.log('Erro de infraestrutura:', err.message);
  }
}
from signdocs_brasil.errors import (
    NotFoundError,
    RateLimitError,
    ConflictError,
    SignDocsBrasilApiError,
    SignDocsBrasilError,
)

try:
    tx = client.transactions.get('tx-inexistente')
except NotFoundError as e:
    print(f'Não encontrado: {e.problem_detail.detail}')
except RateLimitError as e:
    print(f'Rate limit, retry em: {e.retry_after_seconds}s')
except ConflictError as e:
    print(f'Conflito: {e.problem_detail.detail}')
except SignDocsBrasilApiError as e:
    print(f'Erro de API: {e.problem_detail.status} {e.problem_detail.title}')
except SignDocsBrasilError as e:
    print(f'Erro de infraestrutura: {e}')
import (
    "errors"
    signdocs "github.com/signdocsbrasil/signdocsbrasil-go/v2"
)

tx, err := client.Transactions.Get(ctx, "tx-inexistente")
if err != nil {
    var notFound *signdocs.NotFoundError
    var rateLimit *signdocs.RateLimitError
    var conflict *signdocs.ConflictError
    var apiErr *signdocs.ApiError

    if errors.As(err, &notFound) {
        fmt.Println("Não encontrado:", notFound.ProblemDetail.Detail)
    } else if errors.As(err, &rateLimit) {
        fmt.Println("Rate limit, retry em:", rateLimit.RetryAfterSeconds, "segundos")
    } else if errors.As(err, &conflict) {
        fmt.Println("Conflito:", conflict.ProblemDetail.Detail)
    } else if errors.As(err, &apiErr) {
        fmt.Println("Erro de API:", apiErr.StatusCode, apiErr.ProblemDetail.Title)
    } else {
        fmt.Println("Erro:", err)
    }
}

// Funções auxiliares
if signdocs.IsNotFound(err) { /* ... */ }
if signdocs.IsRateLimit(err) { /* ... */ }
if signdocs.IsConflict(err) { /* ... */ }
import com.signdocsbrasil.api.exceptions.*;

try {
    Transaction tx = client.transactions().get("tx-inexistente");
} catch (NotFoundException e) {
    System.out.println("Não encontrado: " + e.getProblemDetail().getDetail());
} catch (RateLimitException e) {
    System.out.println("Rate limit, retry em: " + e.getRetryAfterSeconds() + "s");
} catch (ConflictException e) {
    System.out.println("Conflito: " + e.getProblemDetail().getDetail());
} catch (ApiException e) {
    System.out.println("Erro de API: " + e.getStatus() + " " + e.getTitle());
} catch (SignDocsBrasilException e) {
    System.out.println("Erro de infraestrutura: " + e.getMessage());
}
use SignDocsBrasil\Api\Exceptions\NotFoundException;
use SignDocsBrasil\Api\Exceptions\RateLimitException;
use SignDocsBrasil\Api\Exceptions\ConflictException;
use SignDocsBrasil\Api\Exceptions\ApiException;
use SignDocsBrasil\Api\Exceptions\SignDocsBrasilException;

try {
    $tx = $client->transactions->get('tx-inexistente');
} catch (NotFoundException $e) {
    echo 'Não encontrado: ' . $e->getProblemDetail()->detail;
} catch (RateLimitException $e) {
    echo 'Rate limit, retry em: ' . $e->retryAfterSeconds . 's';
} catch (ConflictException $e) {
    echo 'Conflito: ' . $e->getProblemDetail()->detail;
} catch (ApiException $e) {
    echo 'Erro de API: ' . $e->getStatus() . ' ' . $e->getTitle();
} catch (SignDocsBrasilException $e) {
    echo 'Erro de infraestrutura: ' . $e->getMessage();
}
using SignDocsBrasil.Api;
using SignDocsBrasil.Api.Models;

try
{
    var tx = await client.Transactions.CreateAsync(request);
}
catch (NotFoundException ex)
{
    Console.WriteLine($"Não encontrado: {ex.Message}");
}
catch (ValidationException ex)
{
    Console.WriteLine($"Validação: {ex.Message}");
    foreach (var field in ex.Fields)
        Console.WriteLine($"  {field.Name}: {field.Message}");
}
catch (RateLimitException ex)
{
    Console.WriteLine($"Rate limit. Retry após {ex.RetryAfterSeconds}s");
}
catch (ConflictException ex)
{
    Console.WriteLine($"Conflito: {ex.Message}");
}
catch (SignDocsException ex)
{
    Console.WriteLine($"Erro {ex.StatusCode}: {ex.Message}");
}

Carimbo do tempo indisponível (TIMESTAMP_UNAVAILABLE)

Em tenants com carimbo do tempo ICP-Brasil habilitado, signing.complete responde 503 com code: "TIMESTAMP_UNAVAILABLE" quando a Autoridade de Carimbo do Tempo não emite o carimbo. A solicitação de assinatura continua pendente, e reenviar a mesma assinatura é seguro: o retry automático do SDK (abaixo) já faz isso, respeitando o Retry-After de 30 s. Se a exceção chegar ao seu código, a ACT continuou indisponível durante as tentativas. Reenvie a mesma rawSignatureBase64 com o mesmo signatureRequestId mais tarde, dentro dos 15 minutos da solicitação, sem pedir o PIN de novo.

SDK Como identificar
TypeScript err instanceof ServiceUnavailableError && err.code === 'TIMESTAMP_UNAVAILABLE'
Python isinstance(e, ServiceUnavailableError) and e.code == "TIMESTAMP_UNAVAILABLE"
Go errors.As(err, &sue) && sue.ProblemDetail.Code() == "TIMESTAMP_UNAVAILABLE"
Java e instanceof ServiceUnavailableException && "TIMESTAMP_UNAVAILABLE".equals(e.getProblemDetail().getCode())
PHP $e instanceof ServiceUnavailableException && $e->getErrorCode() === 'TIMESTAMP_UNAVAILABLE'
C# (.NET) ex is ServiceUnavailableException { Code: "TIMESTAMP_UNAVAILABLE" }

O detail desse erro é escrito para o signatário e pode ser exibido diretamente.


Comportamento de Retry

Os SDKs fazem retry automaticamente em erros transientes:

Parâmetro Valor padrão
Status codes com retry 429, 500, 503
Máximo de tentativas 5
Backoff Exponencial: 2^tentativa + jitter aleatório (0-1s)
Delay máximo por tentativa 30 segundos
Duração total máxima 60 segundos
Header Retry-After Respeitado quando presente
Idempotência X-Idempotency-Key gerado automaticamente em criação de transações

O retry é transparente — se todas as tentativas falharem, a exceção correspondente ao último status é lançada.


Boas práticas

  1. Sempre trate NotFoundError em operações de leitura
  2. Trate ConflictError ao finalizar transações (pode já estar finalizada)
  3. Não implemente retry manual para 429/500/503 — o SDK já faz automaticamente
  4. Use idempotency keys em operações de criação para evitar duplicatas em caso de retry
  5. Monitore RateLimitError em produção para ajustar volume de requisições