Importar planilha
Emita notas em lote a partir de uma planilha CSV ou Excel: envio, revisão, emissão e acompanhamento dos contadores, por API ou MCP.
Emita centenas de notas a partir de uma planilha. Você envia um .csv ou .xlsx, a Corvo lê e confere cada linha em segundo plano, você revê os problemas se quiser, e então emite tudo. O mesmo fluxo existe no painel (Notas, Importar planilha) e para assistentes por MCP.
Como funciona
POST /v1/importsenvia o arquivo e responde na hora com o id da importação.- Consulte
GET /v1/imports/:idaté ostatusserready. Cada linha é conferida sozinha: uma linha ruim nunca impede as outras. - Revisão opcional:
GET /v1/imports/:id/rows?status=invalidmostra os problemas,PATCH /v1/imports/:id/rows/:linecorrige um,GET /v1/imports/:id/errors.csvbaixa só os erros. POST /v1/imports/:id/emitemite as linhas válidas. Acompanhe os contadores até ostatusserdone.
A planilha
A primeira linha é o cabeçalho. Os nomes são reconhecidos sem diferenciar maiúsculas e acentos, em português ou inglês (valor ou amount). O CSV pode usar ; ou , e codificação UTF-8, UTF-16 ou do Windows. Os valores aceitam 1.234,56, 1234.56 e R$ 1.234,56; as datas aceitam dd/mm/aaaa (também 7/11/2026, 07-11-2026 e 07.11.2026) e aaaa-mm-dd. Colunas que não casam com nenhum nome abaixo são ignoradas e listadas em unknown_columns da importação. Fórmulas nunca são executadas: só o valor guardado é lido.
| Coluna | Obrigatória | O que é |
|---|---|---|
cnpj_prestador | se não escolher a empresa | CNPJ da empresa que emite a linha. |
tomador_documento | não | CPF ou CNPJ do tomador, com ou sem pontuação. Zeros à esquerda perdidos pelo Excel são restaurados. |
tomador_nome | não | Nome do tomador. |
tomador_email | não | E-mail do tomador, para enviar a nota. |
descricao | sim | Descrição do serviço. |
valor | sim | Valor em reais, com até 2 casas decimais. |
codigo_servico | não | Código nacional do serviço (6 dígitos). Vazio usa o padrão da empresa. |
data_competencia | não | Data de competência. Vazio usa hoje (America/Sao_Paulo). |
pedido | não | Número do pedido ou da ordem de serviço impresso na nota (até 60 caracteres). |
observacoes | não | Texto livre impresso na nota como informação complementar (até 2.000 caracteres). |
tomador_nif, tomador_sem_nif | não | Cliente no exterior: a identificação fiscal dele, ou o motivo de não ter (dispensado ou nao exigido). Entra no lugar de tomador_documento. |
tomador_pais, tomador_codigo_postal, tomador_cidade, tomador_regiao, tomador_logradouro, tomador_numero, tomador_bairro, tomador_complemento | para cliente no exterior | Endereço no exterior. O país é o código de duas letras (por exemplo US); todos são obrigatórios, menos o complemento. Essas colunas não vêm no modelo básico. |
Regras
| Regra | Detalhe |
|---|---|
| Limites | Até 2.000 linhas e 10 MB por arquivo; só .csv ou .xlsx. Mais linhas falham a importação com ERROR_IMPORT_TOO_MANY_ROWS. |
| Empresa | Da coluna cnpj_prestador ou de companyId. Uma linha sem empresa fica invalid (ERROR_IMPORT_COMPANY_REQUIRED); um CNPJ diferente da empresa escolhida fica invalid (ERROR_IMPORT_COMPANY_MISMATCH). |
| Ambiente | Uma importação pertence ao ambiente da chave que a criou: uma chave test só vê importações de test, uma chave live só as de live. |
| Idempotência | Cada linha tem sua própria chave (import:<id>:<linha>), então repetir o emit ou refazer um job nunca emite uma linha duas vezes. |
| Não suportado | SCHEDULE e RECURRING não são aceitos dentro de uma planilha. |
| Permissões | Leitura (GET) exige invoices_view; enviar, corrigir, emitir e descartar exigem invoices_issue. |
Status e contadores
| status | Significado |
|---|---|
processing | O arquivo está sendo lido e cada linha conferida. |
ready | Lido. Dá para revisar, corrigir e emitir. |
emitting | Emitindo. Algumas notas podem ainda aguardar a prefeitura. |
done | Nada mais em andamento. Linhas aguardando créditos ainda podem ser emitidas de novo. |
failed | O arquivo não pôde ser lido (vazio, linhas demais ou não é uma planilha de verdade), ou a empresa escolhida não existe mais (ERROR_IMPORT_COMPANY_NOT_FOUND); veja o error_code. |
counters: total, valid, invalid, accepted (salvas e na fila de emissão), authorized (a prefeitura já autorizou), awaiting_credits (recusadas por falta de créditos: **não são erro**), failed (linhas inválidas mais as notas que a prefeitura recusou), pending (ainda aguardando ou em andamento), valid_amount e awaiting_amount em reais. Durante a emissão, authorized_amount, pending_amount e failed_amount mostram em reais quanto já foi autorizado, quanto está em andamento e quanto foi recusado (linhas inválidas nunca entram, porque não chegaram a ser enviadas).
/v1/imports202 AcceptedEnvia uma planilha (.csv ou .xlsx, até 2.000 linhas e 10 MB) como multipart/form-data. A leitura é **assíncrona**: a resposta é a importação a acompanhar, com status: "processing". Se este mesmo arquivo já foi enviado antes para a empresa e o ambiente, duplicate_of traz a importação anterior (um aviso, não um bloqueio).
Campos do formulário
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| file | file | ✓ | A planilha .csv ou .xlsx. |
| companyId | string (uuid) | Empresa que emite as linhas sem CNPJ. Uma linha com o CNPJ de outra empresa é recusada, para que a empresa errada nunca emita. |
curl -X POST "$BASE_URL/imports" \
-H "X-API-Key: $CORVO_API_KEY" \
-F "file=@notas-novembro.csv" \
-F "companyId=5b0e7c5a-5d0b-4f45-8d6c-3a2a1e9d7f20"Resposta — 202 Accepted: a importação a acompanhar
{ "data": { "id": "576905ae-1707-4848-9ed6-5a6e51800b74", "status": "processing", "duplicate_of": null } }/v1/imports200 OKLista as importações recentes do ambiente, da mais nova para a mais antiga, com os contadores (no máximo 20). Útil para reencontrar uma importação, por exemplo uma que aguarda créditos.
Query
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| limit | integer | De 1 a 20, padrão 10. |
curl "$BASE_URL/imports?limit=10" \
-H "X-API-Key: $CORVO_API_KEY"Resposta — 200 OK: lista de importações
{ "data": [ /* imports, like GET /v1/imports/:id */ ] }/v1/imports/:id200 OKStatus e contadores de uma importação. Consulte enquanto o status for processing ou emitting. Durante a leitura, rows_expected é quantas linhas o arquivo tem e counters.total quantas já foram conferidas.
curl "$BASE_URL/imports/576905ae-1707-4848-9ed6-5a6e51800b74" \
-H "X-API-Key: $CORVO_API_KEY"Resposta — 200 OK: a importação com os contadores
{
"data": {
"id": "576905ae-1707-4848-9ed6-5a6e51800b74",
"status": "ready",
"file_name": "notas-novembro.csv",
"company_id": null,
"error_code": null,
"rows_expected": 300,
"unknown_columns": [],
"emit_started_at": null,
"emit_finished_at": null,
"created_at": "2026-11-07T13:05:21.000Z",
"counters": {
"total": 300,
"valid": 297,
"invalid": 3,
"accepted": 0,
"authorized": 0,
"awaiting_credits": 0,
"valid_amount": 718602.19,
"awaiting_amount": 0,
"failed": 3,
"pending": 297,
"authorized_amount": 0,
"pending_amount": 718602.19,
"failed_amount": 0
}
}
}/v1/imports/:id/rows200 OKAs linhas de uma importação, **uma página por vez** (nunca a planilha inteira). Filtre por status para listar só as linhas com problema (invalid) e ler o motivo em error_code e error_message.
Query
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| status | string | valid, invalid, queued, accepted ou rejected. | |
| limit | integer | De 1 a 200, padrão 50; acima disso responde 400. | |
| cursor | string | nextCursor da página anterior. |
curl "$BASE_URL/imports/576905ae-1707-4848-9ed6-5a6e51800b74/rows?status=invalid&limit=50" \
-H "X-API-Key: $CORVO_API_KEY"Resposta — 200 OK: uma página de linhas
{ "data": [
{
"line": 42,
"data": {
"amount": "abc",
"competenceDate": "2026-11-07",
"companyTaxId": "61469487000140",
"customer": { "name": "Maria da Silva", "taxId": "52998224725" },
"service": { "description": "Consultoria mensal" }
},
"status": "invalid",
"error_code": "ERROR_VALIDATION",
"error_message": "Request validation failed",
"invoice_id": null
}
], "pagination": { "limit": 50, "hasMore": false, "nextCursor": null } }/v1/imports/:id/rows/:line200 OKCorrige uma linha e a confere de novo, somente enquanto a importação estiver ready. Envie os campos alterados em item. customer e service são mesclados campo a campo, então alterar o e-mail mantém o nome. companyId e companyTaxId não podem ser alterados. A resposta é a linha, valid ou ainda invalid com o motivo. Uma linha que já entrou na fila de emissão responde 409 com ERROR_IMPORT_STATE.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| item | object | ✓ | Campos da nota a alterar: amount, competenceDate, customer { name, taxId, email }, service { description, code }. |
curl -X PATCH "$BASE_URL/imports/576905ae-1707-4848-9ed6-5a6e51800b74/rows/42" \
-H "X-API-Key: $CORVO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{ "item": { "amount": 1500.5 } }'Resposta — 200 OK: a linha, conferida de novo
{ "data": {
"line": 42,
"data": {
"amount": 1500.5,
"competenceDate": "2026-11-07",
"companyTaxId": "61469487000140",
"customer": { "name": "Maria da Silva", "taxId": "52998224725" },
"service": { "description": "Consultoria mensal" }
},
"status": "valid",
"error_code": null,
"error_message": null,
"invoice_id": null
} }/v1/imports/:id/emit202 AcceptedEmite todas as linhas válidas, em pedaços de 50, com uma chave de idempotência por linha: chamar duas vezes nunca emite duas vezes. Se o limite do plano ou os créditos acabarem, as linhas restantes ficam como awaiting_credits (não são erro). Depois de recarregar, **chame de novo**: só essas linhas são enviadas.
curl -X POST "$BASE_URL/imports/576905ae-1707-4848-9ed6-5a6e51800b74/emit" \
-H "X-API-Key: $CORVO_API_KEY"Resposta — 202 Accepted: a importação, agora emitindo
{
"data": {
"id": "576905ae-1707-4848-9ed6-5a6e51800b74",
"status": "emitting",
"file_name": "notas-novembro.csv",
"company_id": null,
"error_code": null,
"rows_expected": 300,
"unknown_columns": [],
"emit_started_at": null,
"emit_finished_at": null,
"created_at": "2026-11-07T13:05:21.000Z",
"counters": {
"total": 300,
"valid": 297,
"invalid": 3,
"accepted": 0,
"authorized": 0,
"awaiting_credits": 0,
"valid_amount": 718602.19,
"awaiting_amount": 0,
"failed": 3,
"pending": 297,
"authorized_amount": 0,
"pending_amount": 718602.19,
"failed_amount": 0
}
}
}Outras rotas
| Método | Rota | Descrição | Status |
|---|---|---|---|
| DELETE | /v1/imports/:id | Descarta uma importação que ainda não foi emitida (nada é emitido). | 204 |
| GET | /v1/imports/template | Baixa o modelo de planilha (CSV). | 200 |
| GET | /v1/imports/:id/errors.csv | Baixa só as linhas que pedem atenção, em fluxo. As linhas que só aguardam créditos ficam de fora. | 200 |
Quando os créditos acabam
Ficar sem créditos não é falha da planilha. A emissão para, as linhas que não foram enviadas ficam como awaiting_credits (ERROR_MONTHLY_LIMIT_EXCEEDED, ERROR_INSUFFICIENT_CREDITS e códigos parecidos nas linhas) e nada se perde. Recarregue os créditos e chame POST /v1/imports/:id/emit de novo: só essas linhas são enviadas, com as mesmas chaves. Você reencontra a importação depois com GET /v1/imports.
Pelo MCP
Assistentes podem fazer o mesmo fluxo com as ferramentas import_create, import_list, import_get, import_rows, import_fix_row, import_emit e import_discard (veja a página de MCP). Uma planilha enviada por MCP é texto dentro da mensagem: CSV em content (ou um .xlsx em content_base64), até 1 MB; arquivos maiores vão por POST /v1/imports.