Docs

Errors

The error format, what each code means and when to retry a request.

Every error response has the same shape, on every endpoint. The HTTP status gives the category and error.code gives the exact error. When the problem is in the fields, the details list carries every field with a problem at once, so you can fix them all in one go.

The format

422: two fields with problems
{
  "error": {
    "type": "validation_error",
    "code": "invalid_parameters",
    "message": "2 fields have problems. See details.",
    "request_id": "req_9Vb4Xk2Lq8Mz",
    "doc_url": "https://docs.cartaflow.com.br/en/errors#invalid_parameters",
    "details": [
      {
        "param": "to.postal_code",
        "code": "postal_code_not_found",
        "message": "We could not find postal code 01001-999. Check the 8 digits."
      },
      {
        "param": "document.url",
        "code": "address_area_not_blank",
        "message": "There is content in the address area of the first page. Leave the area blank or use address_placement: extra_page."
      }
    ]
  }
}
FieldWhat it is
typeThe error category. Each type maps to an HTTP status (table below).
codeThe exact error. Use this field in your code: a code never changes meaning. New codes may be added; handle an unknown code by its type.
messageAn explanation for people. The wording can change, so do not match on messages in your code.
request_idThe id of the request, the same as the Request-Id header. Send it to support when you need help.
doc_urlA link to this code on this page.
detailsThe fields with problems, each with param (the field, in dot notation), code and message. Only present for validation_error; null for other types.

When there is an error, nothing is created or charged.

Message language

message is in Portuguese by default. To get messages in English, send the Accept-Language: en header. Codes are the same in every language.

curl "https://api.cartaflow.com.br/v1/letters/ltr_doesnotexist" \
  -H "Authorization: Bearer $CARTAFLOW_API_KEY" \
  -H "Accept-Language: en"

Error types

HTTPerror.typeWhat it means
400, 413invalid_request_errorWe could not read the request: the JSON is malformed, a header is wrong or the body is too large.
401authentication_errorThe API key is missing, invalid or revoked.
402billing_errorThe account balance does not cover the operation.
403permission_errorThe account cannot perform this operation right now.
404not_found_errorThe object or the endpoint does not exist.
409conflict_errorThe request is valid, but the current state of the object does not allow the operation.
422validation_errorWe read the request, but one or more fields have invalid values. The details list says which ones and why.
429rate_limit_errorThe account went over its requests-per-second limit.
500, 503api_errorSomething failed on CartaFlow’s side. It is not a problem with your request.

Error codes

The value of error.code. Each error's doc_url points to its row in this table.

error.codeHTTPWhat happenedHow to fix it
invalid_json400The request body is not valid JSON.Check quotes, commas and braces. Build the body with your language’s JSON encoder instead of by hand.
unsupported_content_type400The Content-Type header is not accepted on this endpoint.Send application/json. Only POST /v1/letters and POST /v1/letters/quote also accept multipart/form-data, to upload the PDF with the request.
invalid_idempotency_key400The Idempotency-Key header is empty or longer than 255 characters.Use a short unique identifier, such as the invoice id in your system or a UUID.
request_too_large413The request body is larger than 4.5 MB, the most the API accepts in one request.Send the PDF by URL (document.type = url) or with POST /v1/uploads (document.type = upload). Both take PDFs of up to 10 MB.
api_key_missing401The request has no Authorization header.Send Authorization: Bearer followed by your API key.
api_key_invalid401The key does not exist or is mistyped.Copy the key again from the dashboard, under Desenvolvedores > Chaves de API (Developers > API keys). It starts with cf_live_.
api_key_revoked401The key was revoked in the dashboard.Create a new key in the dashboard and update your system’s environment variable.
insufficient_balance402The available balance is lower than the price of the mailing.Top up your balance in the dashboard. Check GET /v1/balance before large sends.
account_suspended403The account is suspended and cannot create mailings.Contact CartaFlow support from the dashboard.
resource_not_found404There is no object with that id in your account.Check the id, character by character. Each account only sees its own objects: the id of another account’s object also gets a 404.
endpoint_not_found404The path or the HTTP method does not exist in the API.Check the URL, the /v1 prefix and the HTTP method shown in the reference.
postal_code_not_found404The postal code (CEP) you looked up does not exist in the Correios database.Check the 8 digits with the person or the source record.
letter_not_cancelable409The letter’s status is no longer scheduled or received: it is already in production, posted or failed.Retrieve the letter to see its current status. It cannot be canceled after production starts.
verification_not_cancelable409The verification ended without a confirmation (failed or expired) and cannot be canceled or revoked.Retrieve the verification to see its current status. To try again, create a new verification.
verification_not_awaiting_code409The verification is not waiting for a code: the letter was not posted yet, or the verification is already finished, canceled or revoked.Only check codes while the status is awaiting_code. The error message tells you the current status.
idempotency_key_reused409This Idempotency-Key was already used with a different body or on another endpoint in the last 24 hours.Use a new key for each different operation. Reuse a key only to retry the exact same request.
idempotency_key_in_use409A request with the same Idempotency-Key is still being processed.Wait a second and retry. You will get the response of the first request.
invalid_parameters422One or more fields have invalid values.Read the details list: each item has the field (param), a code and a message.
rate_limit_exceeded429The account made more requests per second than its limit.Wait the seconds given in the Retry-After header and retry. Spread out the calls for large sends.
internal_error500Unexpected error at CartaFlow.Retry with the same Idempotency-Key. If it persists, send the request_id to support.
service_unavailable503The API is temporarily unavailable, for maintenance or overload.Retry after the time in the Retry-After header, with the same Idempotency-Key.

Detail codes

In a validation_error, each item of details has one of these codes, along with the field in param.

details[].codeWhat is wrong with the fieldHow to fix it
requiredA required field is missing.Send the field named in param.
unknown_parameterThe field does not exist on this endpoint.Check the spelling. The API rejects unknown fields so a typo never goes unnoticed.
invalid_typeThe value has the wrong type, such as text where a number is expected.Send the type shown in the reference. Booleans are true or false, without quotes.
invalid_valueThe value is not accepted: it is not in the list of allowed values, or it does not go with another field of the request, such as letter and document together in a verification.Use one of the values listed in the field reference. The message explains why.
invalid_formatThe format is wrong: URL, date, base64, postal code or code.Follow the format and the example in the field reference.
too_shortThe text or list is shorter than the minimum.Check the minimum in the field reference.
too_longThe text or list is longer than the maximum.Check the maximum in the field reference, and abbreviate if it is an address.
out_of_rangeThe number or date is outside the allowed range, such as a send_date that is not between tomorrow and 180 days ahead.Check the minimum and maximum in the field reference.
requires_registeredreturn_receipt and restricted_delivery only work with registered: the return receipt and restricted delivery are services of a registered letter.Add registered to postal_services, or remove the services that depend on it.
postal_code_not_foundThe postal code (CEP) does not exist in the Correios database.Check the 8 digits. You can look it up first with GET /v1/postal-codes/{postal_code}.
address_incompleteThe address could not be completed: the field was not in the request and the postal code does not provide it. This happens with postal codes that cover a whole city and have no street or neighborhood.Send the field named in param, such as street and neighborhood.
document_unreachableWe could not download the PDF from the URL within 10 seconds, or the server answered with an error.Use a public or signed HTTPS URL (S3, GCS, Azure) that stays valid for at least 10 minutes.
upload_not_foundThere is no upload with that id in your account.Check the upload_id. Use the id returned by POST /v1/uploads, with the same account as the API key.
upload_expiredThe upload was created more than 24 hours ago and can no longer be used.Create a new upload with POST /v1/uploads and send the file again.
upload_already_usedThe upload was already used by another letter. Each upload is good for one letter only.Create a new upload for each letter. To retry the same creation safely, use the same Idempotency-Key.
upload_file_missingThe file was never sent to the upload’s upload_url.Send the PDF with a PUT to the upload_url, with the Content-Type: application/pdf header, and wait for the 2xx response before creating the letter. If the link expired (it works for 2 hours), create a new upload.
document_invalidThe file is not a valid PDF: it is corrupted or in another format.Open the file in a PDF reader to check it. For base64, send only the content, without the data:application/pdf;base64, prefix.
document_encryptedThe PDF is password protected.Generate the PDF without a password or printing restrictions.
document_too_largeThe PDF is larger than its way in allows: 10 MB by URL or with POST /v1/uploads, 4 MB in multipart/form-data and 3 MB in base64 (once decoded).Bring images down to 300 dpi, the most print makes use of. For a large PDF, use a URL or POST /v1/uploads, which take up to 10 MB.
document_page_sizeA page is not A4 portrait (210 x 297 mm).Export every page of the PDF as A4, portrait orientation.
too_many_sheetsThe letter would need more than 6 sheets, the most that fits in one envelope.Use double_sided: true to print on both sides, or split the content into more than one letter.
address_area_not_blankThere is text or an image in the address area of the first page, where CartaFlow prints the recipient.Leave the area blank in your layout, or use address_placement: extra_page.
variable_missingThe PDF has a marker, such as {{name}}, that is missing from variables.Send a value for every marker in the PDF. To leave one blank, send an empty string.
variable_not_foundYou sent a variable the PDF does not have.Check the name letter by letter. In a PDF made in Word, open the PDF and check that the marker shows up whole, braces included, as selectable text.
document_text_unreadableThe PDF has no selectable text: it is an image or a scan. Without text, we cannot find the markers, such as variables or the {{OTP_CODE}} of a verification.Export the PDF straight from the editor (in Word: File > Save as > PDF) instead of scanning or printing an image to PDF.
otp_code_missingThe PDF of a verification has no {{OTP_CODE}} marker, the place where CartaFlow prints the code.Type {{OTP_CODE}}, in capitals and with the braces, where the code should appear, and export the PDF again with selectable text. Or create the verification without document, with the CartaFlow letter template.

When to retry

Some errors are temporary and worth retrying. Others only go away when you change the request.

SituationWhat to do
429 (rate_limit_exceeded)Wait the seconds in the Retry-After header and retry.
500 (internal_error) and 503 (service_unavailable)Retry with growing waits: 1, 2, 4, 8 seconds, up to about 5 times. On a 503, respect Retry-After.
Network failure or timeout, no responseRetry as for a 500. You do not know whether the first request arrived, and the Idempotency-Key makes sure nothing is created twice.
409 with idempotency_key_in_useThe first request with the same key is still running. Wait a second and retry.
Any other 4xxDo not retry as is. Fix the request according to the code.

Always retry with the same Idempotency-Key. If the first attempt did create the letter, the retry returns that same letter instead of creating another one. See Idempotency.

An example handler

import { randomUUID } from 'node:crypto';

const RETRYABLE = new Set([429, 500, 503]);

async function cartaflow(path, { method = 'GET', body, idempotencyKey } = {}) {
  // One key per operation, created once and reused on every attempt.
  const key = idempotencyKey ?? (method === 'POST' ? randomUUID() : undefined);

  for (let attempt = 0; attempt < 5; attempt++) {
    let response;
    try {
      response = await fetch(`https://api.cartaflow.com.br/v1${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${process.env.CARTAFLOW_API_KEY}`,
          ...(body ? { 'Content-Type': 'application/json' } : {}),
          ...(key ? { 'Idempotency-Key': key } : {}),
        },
        body: body ? JSON.stringify(body) : undefined,
      });
    } catch {
      await sleep(2 ** attempt * 1000); // network failure: retry with the same key
      continue;
    }

    if (response.ok) return response.json();

    const { error } = await response.json();
    const inUse = response.status === 409 && error.code === 'idempotency_key_in_use';
    if (!RETRYABLE.has(response.status) && !inUse) {
      // An error that will not go away on its own: log it and show it to whoever must fix it.
      throw Object.assign(new Error(error.message), { code: error.code, details: error.details, requestId: error.request_id });
    }

    const retryAfter = Number(response.headers.get('Retry-After'));
    await sleep(retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 1000);
  }

  throw new Error('The CartaFlow API did not answer after 5 attempts.');
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Getting help from support

Every response, success or error, carries the Request-Id header, and errors also carry error.request_id. Log that value. When you contact support, send the request_id: it lets us find the exact request.

On this page