Swagger UI openapi.json openapi.yaml
Guide

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

CredentialCreateReadUpdateDelete / ClearValidate
PassGovPOST .../pass-govGET .../pass-govPATCH .../pass-govPATCH .../pass-gov with empty passwordPATCH .../pass-gov/validate
PassInvoicesPOST .../pass-invoicesGET .../pass-invoicesPATCH .../pass-invoicesPATCH .../pass-invoices with empty passwordPATCH .../pass-invoices/validate
CertificatePOST .../certificateGET .../certificatePATCH .../certificateDELETE .../certificatePATCH .../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:

  1. Client calls POST or PATCH with credentials. The action persists the record and sets validation_statuses = validating.
  2. The system queues an asynchronous verification.
  3. The verification service validates the credentials against the government portal.
  4. The system writes the final result to validation_statuses: succeeded, failed (for fatal credential errors), or succeeded with a warning entry (for non-fatal provider responses).
  5. Poll GET .../pass-gov|pass-invoices|certificate for the latest status. The HTTP response of the trigger endpoint returns immediately with validating.

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_status is failed (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.

Actionemission_status afterRequires re-validation?
Create or update pass-invoicesmissing_validationYes
Clear pass-invoicesmissing_validation (or inactive if no certificate)Yes
Create or update certificatemissing_validationYes
Delete certificatemissing_validation (or inactive if no pass-invoices)Yes
Create or update pass-govUnchangedNo (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 validating succeeded failed
  • 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_errors array may contain a single entry describing the warning.
  • failed — the validation service returned a fatal credential error. The validation_errors array contains one entry with the original provider message and a stable code (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

CodeSourceMeaningStatus
invalid_credentialsCREDENCIAIS_INVALIDASCNPJ or senha rejected by the portalfailed
certificate_not_recognizedCERTIFICADO_NAO_RECONHECIDOPortal did not recognize the A1 certificatefailed
certificate_password_invalidCERTIFICADO_SENHA_INVALIDAPassword of the .pfx is wrongfailed
certificate_base64_invalidCERTIFICADO_BASE64_INVALIDOBase64 of the .pfx is corruptedfailed
required_field_missingCAMPO_OBRIGATORIO_AUSENTEOur request was missing a required field — bug on our sidefailed
timeoutTIMEOUTPortal did not respond in timesucceeded with warning
internal_errorERRO_INTERNOPortal returned an unexpected errorsucceeded with warning
CNPJMismatchcross-checkPortal resolved a different CNPJ than expected — credentials belong to another entityfailed

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.