# Portí NFS-e — guia de integração

API para outros sistemas pedirem a emissão de NFS-e. Base: `https://nfse.porti.digital`.
Especificação legível por máquina: `https://nfse.porti.digital/docs/swagger.json` (OpenAPI 3.0).

## Autenticação

Toda chamada leva a chave do seu sistema:

```
X-Api-Key: <sua-chave>
```

`Authorization: Bearer <sua-chave>` também funciona. Cada chave tem escopos (`nfse:issue`,
`nfse:read`, `admin`) e pode ser limitada a emitentes específicos. Um cliente só enxerga as notas
que ele mesmo criou.

## Emitir

```bash
curl -X POST https://nfse.porti.digital/api/v1/nfse \
  -H "X-Api-Key: $CHAVE" -H "content-type: application/json" -d '{
    "issuerCode": "ANARAQUEL",
    "sourceType": "repasse",
    "sourceId": "REP-2026-09-0042",
    "serviceDescription": "Serviços médicos prestados em setembro de 2026",
    "grossValue": "1500.00",
    "customer": {
      "taxIdType": "CNPJ",
      "taxId": "11222333000181",
      "name": "Clínica Exemplo LTDA",
      "email": "financeiro@exemplo.com.br",
      "address": { "cityIbgeCode": "4209102", "cep": "89201100", "street": "Rua das Flores", "number": "100", "district": "Centro" }
    },
    "callbackUrl": "https://seu-sistema/webhooks/nfse"
  }'
```

Resposta 202 com o documento criado e o `jobId`. Com `"mode": "sync"` a chamada espera a SEFIN e
devolve 200 já com o resultado.

## Tomador pessoa física

O tomador pode ser pessoa jurídica ou pessoa física: basta enviar `taxIdType: "CPF"` e o CPF em `taxId`.
O documento pode vir com pontuação, a pontuação é removida, e os dígitos verificadores são conferidos.
Endereço é opcional para pessoa física.

```json
"customer": { "taxIdType": "CPF", "taxId": "111.444.777-35", "name": "Maria de Souza", "email": "maria@exemplo.com" }
```

## Competência

A competência da NFS-e é sempre a data em que a nota é emitida. O campo `competenceDate` é opcional:
se enviado, serve só como referência enquanto o documento aguarda emissão, e na emissão o sistema grava
o dia da nota, tanto na NFS-e quanto no registro.

## Idempotência

`sourceType` + `sourceId` identificam o evento de negócio que originou a nota. Repetir a mesma dupla
devolve 200 com o documento existente e `deduplicated: true`, nunca uma segunda nota. Se o seu sistema
perder a resposta, consulte por origem:

```
GET /api/v1/nfse/by-source/repasse/REP-2026-09-0042
```

## Acompanhar

| Rota | Serve para |
|---|---|
| `GET /api/v1/nfse/{id}` | estado atual, chave de acesso, número da NFS-e, rejeição |
| `GET /api/v1/nfse` | lista com filtros (emitente, status, competência, período, busca) e totais |
| `GET /api/v1/nfse/{id}/xml` | XML autorizado (`?type=signed` devolve a DPS assinada) |
| `GET /api/v1/nfse/{id}/pdf` | DANFSE em PDF |
| `GET /api/v1/nfse/{id}/events` | eventos locais e os registrados no provedor |
| `POST /api/v1/nfse/{id}/cancel` | cancelamento (`motivo` 1, 2 ou 9 e `justificativa` de 15 a 255 caracteres) |
| `POST /api/v1/nfse/{id}/reconcile` | reconsulta o provedor quando o estado é `UNKNOWN` |
| `GET /api/v1/issuers` | emitentes que a sua chave pode usar |
| `GET /api/v1/monitor` | saúde do serviço, para o seu monitoramento |

## Estados

```
DRAFT → VALIDATED → SIGNED → SENDING → (PROCESSING) → AUTHORIZED | REJECTED | UNKNOWN → CANCELLED
```

`UNKNOWN` significa que o envio saiu e a resposta não chegou. O sistema reconcilia sozinho consultando
o provedor; nenhuma nota é reenviada às cegas, então não existe risco de emissão em duplicidade.

## Webhook

Se você informar `callbackUrl`, recebe um POST JSON a cada desfecho, com os headers:

| Header | Conteúdo |
|---|---|
| `X-Nfse-Event` | `nfse.authorized`, `nfse.rejected`, `nfse.unknown` ou `nfse.cancelled` |
| `X-Nfse-Delivery` | identificador da entrega |
| `X-Nfse-Document` | id do documento |
| `X-Nfse-Signature` | `sha256=<HMAC-SHA256 do corpo com o seu webhookSecret>` |

Confira a assinatura antes de processar e responda 2xx. Sem 2xx há novas tentativas com espera
crescente, até oito.

## Erros

Todos devolvem JSON com `error`. Os mais comuns:

| Código | Significado |
|---|---|
| 400 | dados inválidos (vem com `issues` apontando o campo) |
| 401 | chave ausente ou inválida |
| 403 | escopo insuficiente ou emitente não liberado para a sua chave |
| 409 | estado incompatível, por exemplo cancelar o que não está autorizado |
| 422 | rejeição fiscal, com `sefinCode` e a mensagem do provedor |
| 502 | falha de transporte com o provedor |

## Certificado digital

Cada prestador assina com o certificado A1 do próprio CNPJ. A SEFIN recusa declaração assinada por
terceiro, inclusive por contador com procuração (rejeição E0718), e a Focus nem aceita o cadastro da
empresa com certificado de outro CNPJ. Não existe, hoje, emissão por procuração.
