Issue tax documents in batch
curl --request POST \
--url https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch \
--header 'Content-Type: multipart/form-data' \
--form file=@example-fileimport requests
url = "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch"
files = { "file": ("example-file", open("example-file", "rb")) }
response = requests.post(url, files=files)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST'};
options.body = form;
fetch('https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"message": "Batch issuance requested.",
"count": 25
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "TAX_ISSUER_PROVIDER_ID_NOT_FOUND",
"origin": "ACCOUNTING",
"message": "Tax issuer provider id not exists"
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "TAX_ISSUER_NOT_FOUND",
"origin": "ACCOUNTING",
"message": "Tax issuer not found"
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "BAD_REQUEST",
"origin": "ACCOUNTING",
"message": "Bad Request: Your request is invalid or incomplete. Please make sure you are requesting with the right fields.",
"fields": [
{
"field": "file",
"message": "file is required",
"code": "FILE_REQUIRED"
}
]
}Issue tax documents in batch
Issues a tax document (NFSe) for every row of an uploaded CSV file.
POST
/
realms
/
{realmId}
/
organizations
/
{organizationId}
/
tax-document
/
batch
Issue tax documents in batch
curl --request POST \
--url https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch \
--header 'Content-Type: multipart/form-data' \
--form file=@example-fileimport requests
url = "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch"
files = { "file": ("example-file", open("example-file", "rb")) }
response = requests.post(url, files=files)
print(response.text)const form = new FormData();
form.append('file', '<string>');
const options = {method: 'POST'};
options.body = form;
fetch('https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.accounting.v2.portao3.com.br/realms/{realmId}/organizations/{organizationId}/tax-document/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"message": "Batch issuance requested.",
"count": 25
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "TAX_ISSUER_PROVIDER_ID_NOT_FOUND",
"origin": "ACCOUNTING",
"message": "Tax issuer provider id not exists"
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "TAX_ISSUER_NOT_FOUND",
"origin": "ACCOUNTING",
"message": "Tax issuer not found"
}{
"traceId": "1-68ee2a1b-3c4d5e6f7a8b9c0d1e2f3a4b",
"code": "BAD_REQUEST",
"origin": "ACCOUNTING",
"message": "Bad Request: Your request is invalid or incomplete. Please make sure you are requesting with the right fields.",
"fields": [
{
"field": "file",
"message": "file is required",
"code": "FILE_REQUIRED"
}
]
}Send a
The column names are exactly as listed, including the spelling of
multipart/form-data request whose file part is a CSV file: comma-separated, with a header row and one row per tax document to issue. The header names are case-sensitive and the column order does not matter.
| Column | Required | Meaning |
|---|---|---|
environment | Yes | LIVE to issue a real tax document, TEST to issue a test one. Your tax issuer must be configured for that environment. |
competenceDate | Yes | Competence date of the service, for example 2026-10-31. |
totalValue | Yes | Total value of the service in reais, for example 1500.50. A comma is accepted as the decimal separator (1500,50); do not use thousands separators. |
statementId | Yes | ID of the invoice statement this tax document refers to. It is the idempotency key of the row: a statement that already has an active tax document is skipped. |
customerName | Yes | Name of the customer. |
customerEmail | Yes | E-mail address of the customer. |
customerFederalDocument | Yes | Federal document of the customer, digits only: the CNPJ (or the CPF). |
customerMunicipalDocument | No | Municipal registration of the customer. |
customerstateDocument | No | State registration of the customer. |
customerphoneNumber | Yes | Brazilian phone number of the customer with area code, for example 34984325252 or +5534984325252. A number that is not a valid Brazilian one is left off the tax document. |
addressState | Yes | State of the customer’s address, for example MG. |
addressCity | Yes | City of the customer’s address, for example Uberlândia. |
addressStreet | Yes | Street of the customer’s address. |
addressNumber | Yes | Number of the customer’s address. |
addressComplement | No | Complement of the customer’s address. N/A is used when it is blank. |
addressNeighborhood | Yes | Neighborhood of the customer’s address. |
addressPostalCode | Yes | Postal code (CEP) of the customer’s address. |
customerstateDocument, customerphoneNumber and addressNeighborhood. Any other column is rejected, and so is a row that misses a required column.
Every tax document is issued with fixed values. The customer is registered as a company, the service is provided in Uberlândia (MG), and its description is “LICENCIAMENTO DE SOFTWARE - SAAS”. The document records an ISS rate of 2% (the NFSe itself is issued with the ISS rate registered for your tax issuer), not withheld at source. The municipal service description is “Licenciamento ou cessão de direito de uso de programas de computação.”, the operation nature is “Prestação de Serviços”, and there are no deductions, no discounts and no observation. The municipal service code, the service list item, the CNAE and the tax configuration come from your tax issuer’s configuration for the row’s environment.
The whole upload is validated first: if any row is invalid nothing is issued, and fields points at the row by its position, where invoices[0] is the first row after the header. When the upload is valid, the response only means the rows were accepted. Each row is then issued in the background, so a row can still fail later; a row whose statement already has an active tax document is skipped, which makes it safe to upload the same file again. A row the tax provider rejects for invalid data is skipped as well: fix its data and upload it again.Path Parameters
ID of your realm.
Format: UUID
ID of the organization.
Format: UUID
Body
multipart/form-data
The CSV file, with a header row and one row per tax document to issue. The columns are listed in the operation description. Required, not empty, at most 4 MB; its file name must end in .csv, or its content type be text/csv, application/vnd.ms-excel or text/plain.
Response
Successful response
Acknowledgement that a CSV batch was accepted. Each row is processed in the background.