Invoices
Issue NFS-e, fetch status, download documents and request email delivery.
Permissions: nfse.invoices.issue to issue/request email; nfse.invoices.view for fetches, documents and signed URLs.
/v1/invoices202 AcceptedRequests NFS-e issuance. Issuance is asynchronous: HTTP success means the request was registered/queued — it does not mean fiscal authorization. These examples use the CNPJ (companyTaxId) of a company registered with municipalityCode and defaultServiceCode. The issuer municipality always comes from the company.
| Field | Type | Required | Description |
|---|---|---|---|
| companyId | string UUID | Send this field or companyTaxId; if both exist, companyId takes precedence. | |
| companyTaxId | string | CNPJ of a company already registered in the account/environment; punctuation optional. | |
| externalId | string | Reference from the integrating system; does not replace the idempotency key. | |
| customer | object | Optional: omit it or send {} for an invoice without tomador (the customer is left out of the DPS). When sent with any data, taxId is required (valid CPF or CNPJ, normalized); name is optional, email (optional; required when email sending is enabled; no email format validation in this DTO/domain) and address (optional: street, city, state, zip, country are optional strings; if you fill in city or state, fill in both; state must be valid). | |
| service | object | ✓ | code is optional: without it the company defaultServiceCode is used and, if the company has none, 100901 (LC 116 item 10.09, representation of any nature); exactly 6 digits when provided. municipalityCode inherits the company municipality; when provided, exactly 7 digits and sets the service location. description is optional: when empty, the name of the service code is used. Empty, null or invalid values of the other fields do not use the defaults. |
| amount | number | ✓ | Value in reais, positive, up to 9999999999.99 with at most 2 decimals. |
| competenceDate | string ISO 8601 | ✓ | Valid competence date. Without options.type it cannot be in the future (422 ERROR_FUTURE_DATE). With options.type: "SCHEDULE" a future date is accepted (see Scheduled invoices). |
| discount | number | Unconditional discount in BRL, from 0 up to the invoice amount, at most 2 decimal places. | |
| deduction | number | Deduction from the ISS base declared by the issuer, in BRL: greater than 0 and less than amount minus discount, at most 2 decimal places (0 is the same as omitting it). Sent in the DPS as vDedRed/vDR, it reduces the ISS tax base. The invoice is still issued for the full amount. The issuer is responsible for the declared value; Corvo does not check whether the deduction is allowed. IBS/CBS is still calculated on the amount without the deduction. See ISS base deduction. | |
| boolean | Requests a notification; defaults to false. | ||
| options.email.send | boolean | When set, takes precedence over email. | |
| options.type | string | IMMEDIATE (default) or SCHEDULE. SCHEDULE schedules issuance for a future competenceDate; see Scheduled invoices. |
curl -X POST "$BASE_URL/invoices" \
-H "X-API-Key: $CORVO_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: pedido_123_emissao' \
-d '{
"companyTaxId": "11222333000181",
"externalId": "pedido_123",
"customer": {
"name": "Cliente Exemplo",
"taxId": "52998224725",
"email": "cliente@example.com",
"address": { "city": "São Paulo", "state": "SP" }
},
"service": {
"description": "Prestação de serviço"
},
"amount": 150.50,
"competenceDate": "2026-10-01",
"options": { "email": { "send": true } }
}'Response — 202 Accepted
{
"data": {
"id": "6c4d2e81-0a3b-4c5d-9e7f-1a2b3c4d5e6f",
"companyId": "3f1c8a90-6b2e-4d5a-9c11-8e2f0a7b4d11",
"environment": "test",
"externalId": "pedido_123",
"idempotencyKey": "pedido_123_emissao",
"status": "queued",
"amount": 150.5,
"currency": "BRL",
"competenceDate": "2026-10-01T00:00:00.000Z",
"stage": "pending",
"createdAt": "2026-10-10T12:00:00.000Z"
}
}/v1/invoices/batches202 AcceptedRequests the issuance of **1 to 50 invoices in a single call**, for one company or for several. Each item has the same fields as POST /v1/invoices plus an optional idempotencyKey. Items are checked one by one: valid items are saved together in a single transaction, invalid items come back as rejected with the reason and are **not** saved. Issuance stays asynchronous, as in POST /v1/invoices.
| Field | Type | Required | Description |
|---|---|---|---|
| invoices | array | ✓ | List of 1 to 50 items. Each item takes the fields of POST /v1/invoices (companyId or companyTaxId, service, amount, competenceDate, optional customer, discount, deduction, options...). Items can belong to different companies of the account. Fewer than 1 or more than 50 items returns 400 ERROR_VALIDATION for the whole request. |
| invoices[].idempotencyKey | string | Idempotency key of the item (up to 255 characters). With the same key and the same content, repeating the batch returns the invoice already saved with replayed: true instead of issuing again. The Idempotency-Key header is not used by this route. |
curl -X POST "$BASE_URL/invoices/batches" \
-H "X-API-Key: $CORVO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"invoices": [
{
"companyTaxId": "11222333000181",
"idempotencyKey": "planilha-out-linha-2",
"customer": { "name": "Cliente A", "taxId": "52998224725" },
"service": { "description": "Consultoria" },
"amount": 150.50,
"competenceDate": "2026-10-01"
},
{
"companyTaxId": "45723174000110",
"idempotencyKey": "planilha-out-linha-3",
"service": { "description": "Manutenção mensal" },
"amount": 300,
"competenceDate": "2026-10-01"
}
]
}'Response — 202 Accepted
{
"data": {
"total": 3,
"accepted": 2,
"rejected": 1,
"results": [
{
"index": 0,
"status": "accepted",
"invoice": {
"id": "6c4d2e81-0a3b-4c5d-9e7f-1a2b3c4d5e6f",
"companyId": "3f1c8a90-6b2e-4d5a-9c11-8e2f0a7b4d11",
"environment": "test",
"idempotencyKey": "planilha-out-linha-2",
"status": "queued",
"amount": 150.5,
"currency": "BRL",
"competenceDate": "2026-10-01T00:00:00.000Z",
"stage": "pending",
"createdAt": "2026-10-10T12:00:00.000Z"
}
},
{
"index": 1,
"status": "accepted",
"replayed": true,
"invoice": {
"id": "0b7a1f52-3c4d-4e6f-8a9b-2c3d4e5f6a7b",
"status": "queued",
"idempotencyKey": "planilha-out-linha-3",
"amount": 300
}
},
{
"index": 2,
"status": "rejected",
"error": {
"code": "ERROR_VALIDATION",
"message": "Request validation failed",
"details": [
{ "field": "amount", "code": "ERROR_OUT_OF_RANGE", "message": "amount must be greater than 0" }
]
}
}
]
}
}/v1/invoices/calculate200 OKCalculates the taxes of an invoice **without issuing it**: it stores no invoice, uses no credit, reserves no DPS number, schedules nothing and does not call Sefin. Use it to check the fiscal setup and show the amount to the customer before POST /v1/invoices.
| Field | Type | Required | Description |
|---|---|---|---|
| (corpo) | objeto | ✓ | Same body as POST /v1/invoices (company, service, amount, competenceDate, optional customer, discount, deduction). Requires the nfse.invoices.issue permission. It does not require a certificate or a ready company. |
curl -X POST "$BASE_URL/invoices/calculate" \
-H "X-API-Key: $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"companyTaxId": "11222333000181",
"service": { "code": "060101", "description": "Haircut" },
"amount": 50,
"competenceDate": "2026-10-13"
}'Response — 200 OK
{
"data": {
"preEmission": true,
"status": "ready",
"disclaimer": "Pre-emission calculation. Values are not a fiscal document and may differ from the final validation by Sefin.",
"competenceDate": "2026-10-13",
"taxRegime": "MEI",
"classification": {
"serviceCode": "060101",
"source": "official_catalog",
"operationNature": "SERVICE",
"nbsCode": null,
"cst": null,
"classCode": null,
"serviceProfileId": null,
"catalogValidated": false
},
"applicability": {
"customerRequired": false,
"strategy": "mei",
"iss": {
"status": "NOT_APPLICABLE",
"reason": "ISS recolhido no DAS-MEI"
},
"federalWithholding": {
"status": "NOT_APPLICABLE",
"reason": "no federal withholding on payments to Simples Nacional/MEI optants (IN RFB 1.234/2012, art. 4, XI)"
},
"cbs": {
"status": "NOT_APPLICABLE",
"reason": "MEI não destaca IBS/CBS na fase de teste de 2026"
},
"ibs": {
"status": "NOT_APPLICABLE",
"reason": "MEI não destaca IBS/CBS na fase de teste de 2026"
}
},
"taxes": {},
"withholdings": [],
"netReceivableAmount": "50.00",
"collectionWaived": false,
"calculation": {
"source": "none",
"calculatorVersion": null,
"rulesVersion": "2026.10-r1",
"legalReferences": [
"Ato Conjunto RFB/CGIBS nº 4/2026; orientação CGNFS-e de 07/08/2026"
]
},
"warnings": [
{
"code": "CLASSIFICATION_NOT_CATALOG_VALIDATED",
"message": "Service tax classification was not validated against the official catalog",
"step": "ServiceTaxClassifier"
}
],
"pendingFields": [],
"blocking": null,
"trace": [
{
"step": "CompanyFiscalProfile",
"status": "applied",
"durationMs": 2.4
},
{
"step": "ServiceTaxClassifier",
"status": "applied",
"durationMs": 7.1
},
{
"step": "TaxApplicabilityResolver",
"status": "applied",
"durationMs": 0.3
},
{
"step": "RtcTaxCalculator",
"status": "skipped",
"durationMs": 0.03,
"reason": "MEI não destaca IBS/CBS na fase de teste de 2026"
},
{
"step": "FiscalValidator",
"status": "applied",
"durationMs": 0.1
}
]
}
}Scheduled invoices
Use options.type: "SCHEDULE" with a future competenceDate to have the invoice issued only on that day. Corvo releases issuance at 00:00 America/Sao_Paulo on the competenceDate. Until then the invoice stays status: "queued" and scheduledFor holds the release instant (ISO); for immediate invoices scheduledFor is null.
| Situation | Result |
|---|---|
No options.type (or IMMEDIATE) and a future competenceDate | 422 with ERROR_FUTURE_DATE in details[].code. |
SCHEDULE and a future competenceDate within the limit | 202, status: queued, scheduledFor set; issuance happens on that date. |
SCHEDULE and a competenceDate beyond the limit (default 15 days ahead) | 422 with ERROR_SCHEDULE_HORIZON_EXCEEDED in details[].code. |
SCHEDULE and a competenceDate of today or in the past | Immediate issuance, scheduledFor: null. |
Schedule an invoice
curl -X POST "$BASE_URL/invoices" \
-H "X-API-Key: $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_123_scheduled" \
-d '{
"companyTaxId": "11222333000181",
"service": { "description": "Haircut" },
"amount": 50,
"competenceDate": "2026-10-20",
"options": { "type": "SCHEDULE" }
}'ISS base deduction
The issuer can declare in deduction an amount that the municipality's legislation allows to be deducted from the ISS tax base. The value is sent in the DPS as vDedRed/vDR and reduces the ISS base; the invoice is still issued for the full amount (amount).
| Field / tax | How it is handled |
|---|---|
amount | Total amount of the invoice. |
deduction | Amount to deduct from the ISS base. Greater than 0 and less than amount minus discount; 0 is the same as omitting it. |
| ISS | Base = amount − discount − deduction. |
| Federal withholdings | Base unchanged: amount − discount. |
| IBS/CBS | Calculated on the amount without the deduction. |
| Net receivable | Does not change with the deduction. |
| Validation errors | 422 with out_of_range (negative, or not less than amount minus discount) or invalid_decimal_places in details[].code, on field: "deduction". |
| Response | The invoice echoes deduction when one was declared. |
Invoice with a deduction
curl -X POST "$BASE_URL/invoices" \
-H "X-API-Key: $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_123" \
-d '{
"companyTaxId": "11222333000181",
"service": { "description": "Maintenance service" },
"amount": 100,
"deduction": 40,
"competenceDate": "2026-10-20"
}'Invoice schema
Each invoice has these fields:
| Fields | Type / notes |
|---|---|
id, companyId | Identification strings. |
environment | test or live. |
externalId, idempotencyKey | String or null. |
status | Enum described below. |
customerSnapshot | taxId, name, email?, address?: { city, state, country? }. |
serviceSnapshot | description, code, municipalityCode. |
taxSnapshot | Fiscal information object. |
amount, currency, competenceDate | Number in reais, currency and ISO 8601 date-time (the competence date at midnight UTC, e.g. 2026-10-01T00:00:00.000Z). |
discount, deduction | Optional numbers in reais: the unconditional discount and the issuer-declared deduction from the ISS base (see ISS base deduction). |
dpsId, dpsSeries, dpsNumber | String/string/number or null. |
accessKey, nfseNumber | String/number or null. |
fiscalProvider, fiscalSchemaVersion | Strings. |
issuedAt, authorizedAt | ISO date or null. |
emailSnapshot | Optional object with send. |
lastErrorCode, lastErrorMessage, lastErrorProvider | String or null. |
documentsReadyAt | ISO date or null. |
lastEmailStatus, lastEmailSentAt | queued, sent, failed or null; ISO date or null. |
documentsStatus | Derived state: pending or ready. |
emailStatus | Derived state: queued, sent, failed or null. |
stage | Derived state: pending, authorized, documents_ready, completed, failed. |
scheduledFor | ISO release date of a scheduled invoice, or null. |
cancellationReason, cancellationDescription | Reason (issue_error, service_not_provided or other) and justification of the cancellation request, or null. |
cancelledAt | ISO date when the invoice reached cancelled, or null. |
cancellationRequestedAt | ISO date of a cancellation requested while the invoice was processing, or null. The invoice is cancelled at the Sefin as soon as it is authorized. |
createdAt, updatedAt | ISO dates. |
documentKeyVersion | Optional number, when present in the snapshot. |
requestHash is removed from serialization. The current response uses documentsReadyAt/documentsStatus, not the documentsReady boolean shown by a Swagger DTO. There is no lastEmail object in the current JSON — use the fields above. stage: completed can also occur with the email in failed; check emailStatus to know whether a send succeeded.
Fiscal statuses: queued, processing, authorized, rejected, retry_pending, reconciliation_required, manual_review, failed, cancelling, cancelled. Do not treat authorization as immediate availability of every document.
/v1/invoices200 OKLists invoices, newest first.
| Field | Type | Required | Description |
|---|---|---|---|
| companyId | string UUID | Filters by company. | |
| status | string | One of the fiscal statuses above. | |
| from | string ISO 8601 | Inclusive filter by competence date — not by createdAt. | |
| to | string ISO 8601 | Inclusive filter by competence date — not by createdAt. | |
| externalId | string | Exact match. | |
| limit | integer | Integer between 1 and 1000; defaults to 50. | |
| cursor | string | nextCursor from the previous page, keeping the same filters. |
curl "$BASE_URL/invoices?status=authorized&limit=50" \
-H "X-API-Key: $CORVO_API_KEY"Response — 200 OK
{
"data": [
{
"id": "6c4d2e81-0a3b-4c5d-9e7f-1a2b3c4d5e6f",
"status": "authorized",
"amount": 150.5,
"competenceDate": "2026-10-01T00:00:00.000Z",
"createdAt": "2026-10-10T12:00:00.000Z"
}
],
"pagination": {
"limit": 50,
"hasMore": true,
"nextCursor": "eyJ2IjoxLCJjcmVhdGVkQXQiOiIyMDI2LTEwLTA5VDEyOjAwOjAwLjAwMDAwMFoiLCJpZCI6IjNmMWM4YTkwIn0"
}
}/v1/invoices/:id200 OKFetches a single invoice by id.
curl "$BASE_URL/invoices/$INVOICE_ID" \
-H "X-API-Key: $CORVO_API_KEY"Response — 200 OK
{
"data": {
"id": "6c4d2e81-0a3b-4c5d-9e7f-1a2b3c4d5e6f",
"status": "authorized",
"stage": "documents_ready",
"documentsStatus": "ready",
"accessKey": "35260712345678900000350010000000011000000019",
"nfseNumber": 123
}
}/v1/invoices/:id/xml200 OKDownloads the XML of an authorized invoice.
curl "$BASE_URL/invoices/$INVOICE_ID/xml" \
-H "X-API-Key: $CORVO_API_KEY" --fail --output nfse.xml/v1/invoices/:id/pdf200 OKDownloads the PDF of an authorized invoice.
curl "$BASE_URL/invoices/$INVOICE_ID/pdf" \
-H "X-API-Key: $CORVO_API_KEY" --fail --output nota.pdf/v1/invoices/:id/events200 OKFetches the status history of an invoice, oldest first. Paginated like any list (limit, cursor).
curl "$BASE_URL/invoices/$INVOICE_ID/events" \
-H "X-API-Key: $CORVO_API_KEY"Response — 200 OK
{
"data": [
{
"id": "e1d2c3b4-5a6f-4b7c-8d9e-0f1a2b3c4d5e",
"invoiceId": "6c4d2e81-0a3b-4c5d-9e7f-1a2b3c4d5e6f",
"fromStatus": "queued",
"toStatus": "authorized",
"reason": null,
"createdAt": "2026-10-10T12:01:00.000Z"
}
],
"pagination": {
"limit": 50,
"hasMore": false,
"nextCursor": null
}
}/v1/invoices/:id/download-url200 OKGenerates a short-lived signed URL for the invoice documents.
| Field | Type | Required | Description |
|---|---|---|---|
| format | string | ✓ | "pdf" or "xml". |
curl -X POST "$BASE_URL/invoices/$INVOICE_ID/download-url" \
-H "X-API-Key: $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "format": "pdf" }'Response — 200 OK
{
"data": {
"downloadUrl": "https://storage.example.com/documento-assinado",
"expiresAt": "2026-10-10T15:00:00.000Z",
"fileName": "nfse-123.pdf",
"mimeType": "application/pdf"
}
}/v1/invoices/:id/email202 AcceptedRequests email delivery of the invoice to the address stored in customerSnapshot.email; no alternative recipient is accepted. The invoice must be authorized with documents ready.
curl -X POST "$BASE_URL/invoices/$INVOICE_ID/email" \
-H "X-API-Key: $CORVO_API_KEY" \
-H 'Idempotency-Key: pedido_123_email'Response — 202 Accepted
{
"data": {
"emailId": "pedido_123_email",
"status": "queued"
}
}/v1/invoices/:id/cancel200 OK / 202 AcceptedCancels an invoice. A queued invoice (scheduled or not yet processed) is cancelled immediately; an authorized NFS-e moves to cancelling while event 101101 is sent to the Sefin. A processing invoice (being issued) registers the request: 202, still processing, with cancellationRequestedAt set.
curl -X POST "$BASE_URL/invoices/$INVOICE_ID/cancel" \
-H "X-API-Key: $CORVO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"reason":"issue_error","description":"Service amount was informed incorrectly"}'Response — 202 Accepted
{
"data": {
"id": "6f1c0e2a-4b7d-4c1e-9a55-2b8f7c3d9e10",
"status": "cancelling",
"stage": "completed",
"cancellationReason": "issue_error",
"cancellationDescription": "Service amount was informed incorrectly",
"cancelledAt": null,
"cancellationRequestedAt": null
}
}