Credential Setup
Three credential types to configure: pass-gov (government portal password), pass-invoices (NFSe provider credentials), and digital certificate. Create, read, update, clear, and validate each one.
Overview
Three independent credential types can be stored for a fiscal entity. Each one runs its own async validation flow when the credentials are saved or updated, and exposes a PATCH .../validate endpoint for explicit re-validation.
CRUD endpoints
| Credential | Create | Read | Update | Delete / Clear | Validate |
|---|---|---|---|---|---|
| PassGov | POST .../pass-gov | GET .../pass-gov | PATCH .../pass-gov | PATCH .../pass-gov with empty password | PATCH .../pass-gov/validate |
| PassInvoices | POST .../pass-invoices | GET .../pass-invoices | PATCH .../pass-invoices | PATCH .../pass-invoices with empty password | PATCH .../pass-invoices/validate |
| Certificate | POST .../certificate | GET .../certificate | PATCH .../certificate | DELETE .../certificate | PATCH .../certificate/validate |
All endpoints are prefixed with /fiscal-entities/{fiscal_entity_id}. For example, the full path for creating a pass-invoices credential is POST /fiscal-entities/{id}/pass-invoices.
Clearing vs deleting
PassGov and PassInvoices use clearing (sending password: "" via PATCH) rather than a dedicated DELETE endpoint. The system appends inactive to Status and none to ValidationStatuses. No validation is triggered after a clear — an empty credential cannot be tested.
Certificate uses a dedicated DELETE /fiscal-entities/{id}/certificate endpoint which removes the stored certificate file, clears the password and validation status, and sets status = inactive.
All three flows are independent. Updating one does not affect the others. However, changing pass-invoices or certificate (the two emission credentials) resets the emission status — see Emission status guard below. Emission validation (the eNotas test-invoice flow) is a separate concern, triggered only by the explicit POST /fiscal-entities/{id}/invoices-emission/validate endpoint.
The async pattern
All three flows use the same async pattern, mirroring how PassGov has worked since the first implementation:
- Client calls
POSTorPATCHwith credentials. The action persists the record and setsvalidation_statuses = validating. - The system queues an asynchronous verification.
- The verification service validates the credentials against the government portal.
- The system writes the final result to
validation_statuses:succeeded,failed(for fatal credential errors), orsucceededwith a warning entry (for non-fatal provider responses). - Poll
GET .../pass-gov|pass-invoices|certificatefor the latest status. The HTTP response of the trigger endpoint returns immediately withvalidating.
Because the response returns immediately, clients should treat the create/update response as a "validation queued" signal, not a final result.
PassGov — government portal password
Validated against the government portal using the CNPJ, owner CPF, and password. Errors from the provider are mapped to fatal vs non-fatal: invalid credentials and 2FA are fatal; other provider errors become warnings. See the pass-gov section of the invoice emission setup guide for full details.
PassInvoices — prefeitura portal credentials
Validated against the government portal using the seller CNPJ and password (login/senha). The portal always returns HTTP 200 — the caller reads the valido boolean on the response.
CNPJ cross-check
On a successful response, the system reads the cnpj field returned by the portal and compares it against the seller CNPJ. A mismatch (e.g. credentials belong to a different entity) returns a 400 CNPJMismatch error and is recorded as failed. An empty returned cnpj is treated as a no-op (the portal did not include it on the response, which is valid for some auth flows).
JSON:API shape
{
"data": {
"id": "1",
"type": "fiscal-entity-pass-invoices",
"attributes": {
"username": "...",
"password": "",
"authentication_type": "pass_only",
"status": "active",
"validation_status": "succeeded",
"validation_at": "2026-07-07T10:00:00Z",
"validation_by_id": "...",
"validation_errors": [{"code": "unauthorized", "message": "..."}]
}
}
}
Clearing the password
Sending PATCH .../pass-invoices with password: "" clears the stored password, appends inactive to Status, and appends none to ValidationStatuses. No validation is triggered. The create endpoint requires a non-empty password.
Certificate — digital certificate
Validated against the government portal using the base64-encoded .pfx and its password. The system retrieves the stored certificate, decrypts the password, and sends them to the portal. The portal always returns HTTP 200 — the caller reads the valido boolean on the response.
Expired-certificate short-circuit
Before calling the portal, the system checks the certificate's expiration date. If the certificate is past its expiration date, it immediately writes validation_status = "failed" with a code: "certificate_expired" error — no API call is made. This avoids wasted requests on certs that can't possibly authenticate. (A newly uploaded certificate is always fresh, as the upload endpoint rejects expired PFX files.)
JSON:API shape
{
"data": {
"id": "1",
"type": "fiscal-entity-certificates",
"attributes": {
"expiration_date": "2027-04-15",
"password": "",
"status": "active",
"validation_status": "failed",
"validation_at": "2026-07-07T10:00:00Z",
"validation_by_id": "...",
"validation_errors": [{"code": "certificate_expired", "message": "Digital certificate has expired"}]
}
}
}
Deactivation
DELETE /fiscal-entities/{id}/certificate removes the stored certificate, clears the password and validation status, sets status = inactive, and appends none to the validation status history. No validation is triggered.
Update
Updating with a new file or new password triggers validation. Updating with only a metadata change (no file, no new password) does not.
Emission status guard
The system enforces a RequireValidAuthForEmission guard that prevents invoice emission unless at least one of the two emission credentials (pass-invoices OR certificate) has a valid, active authentication.
When the guard fires
Emission is blocked if:
- Neither pass-invoices nor certificate has a
status: "active"credential stored, or - The stored credential's
validation_statusisfailed(i.e. the last validation attempt found an invalid password or expired certificate).
Credential change → emission-status reset
Whenever either emission credential is created, updated, or cleared, the system resets the fiscal entity's emission_status to "missing_validation". This forces the operator to run a new emission validation (the test-invoice flow via POST .../invoices-emission/validate) before production invoices can be emitted.
| Action | emission_status after | Requires re-validation? |
|---|---|---|
| Create or update pass-invoices | missing_validation | Yes |
| Clear pass-invoices | missing_validation (or inactive if no certificate) | Yes |
| Create or update certificate | missing_validation | Yes |
| Delete certificate | missing_validation (or inactive if no pass-invoices) | Yes |
| Create or update pass-gov | Unchanged | No (not an emission credential) |
This ensures that any credential change invalidates the previous emission validation result. The guard (RequireValidAuthForEmission) fires only in the validate action (POST .../invoices-emission/validate) and during invoice creation — if neither credential has validation_status = succeeded, the operation returns 400 missing_auth.
PassGov changes never reset the emission status, because PassGov is used only for MEI diagnosis (DAS, CND, divida ativa), not for invoice emission.
Validation statuses
All three flows use the same four-state vocabulary on their respective validation_statuses field:
- none — no validation has been performed yet, or credentials have been cleared. Set explicitly when a credential is removed.
- validating — async worker is in flight. Persisted as the first entry in the history when the trigger fires.
- succeeded — the validation service returned successfully (or returned a non-fatal warning). The
validation_errorsarray may contain a single entry describing the warning. - failed — the validation service returned a fatal credential error. The
validation_errorsarray contains one entry with the original provider message and a stablecode(e.g.invalid_credentials).
The JSON:API serializer exposes the most recent validation_status entry as validation_status, validation_at, validation_by_id, and validation_errors. The full history lives in validation_statuses (dropped during serialization).
Error codes
The code field on validation_errors[0] carries the result. Pass-invoices and certificate validation surface the portal's structured error codes as the primary error vocabulary.
Validation error codes
| Code | Source | Meaning | Status |
|---|---|---|---|
invalid_credentials | CREDENCIAIS_INVALIDAS | CNPJ or senha rejected by the portal | failed |
certificate_not_recognized | CERTIFICADO_NAO_RECONHECIDO | Portal did not recognize the A1 certificate | failed |
certificate_password_invalid | CERTIFICADO_SENHA_INVALIDA | Password of the .pfx is wrong | failed |
certificate_base64_invalid | CERTIFICADO_BASE64_INVALIDO | Base64 of the .pfx is corrupted | failed |
required_field_missing | CAMPO_OBRIGATORIO_AUSENTE | Our request was missing a required field — bug on our side | failed |
timeout | TIMEOUT | Portal did not respond in time | succeeded with warning |
internal_error | ERRO_INTERNO | Portal returned an unexpected error | succeeded with warning |
CNPJMismatch | cross-check | Portal resolved a different CNPJ than expected — credentials belong to another entity | failed |
Fatal codes (failed): invalid_credentials, certificate_not_recognized, certificate_password_invalid, certificate_base64_invalid, required_field_missing, CNPJMismatch — these mean the credentials are definitively wrong. Transient errors (timeout, internal_error) are reported as warnings (validation recorded as succeeded with the error in validation_errors[]) so that portal hiccups don't block the user from saving credentials.