Skip to content

API

Errors

Every IntoSQL API error code with its HTTP status, cause and fix, plus rate limits, idempotency keys and how to retry safely.

View as Markdown

When the API can't do what a request asks, it answers with a 4xx or 5xx status and an error object. The code is stable between versions, so your code can branch on it. The message is for people and can change.

A conversion that runs and then fails is different: the request succeeded, and the conversion ends with status: "failed" and the reason in error. See Failed conversions.

The error object

Every error has the same shape:

JSON
{
    "error": {
        "code": "too_many_active",
        "message": "5 conversions are already queued or running on this account. Start this one when one of them ends.",
        "request_id": "req_01k7r3j9qf8m2v6x4t0c5n1h8d"
    }
}

When the request has missing or invalid fields, the code is invalid_request and fields lists the problems per field:

HTTP
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json

{
    "error": {
        "code": "invalid_request",
        "message": "Some fields are missing or invalid.",
        "fields": {
            "spec": ["The spec field is required."]
        },
        "request_id": "req_01k7r3jb2c4e6g8j0m2p4r6t8v"
    }
}
Field Type Meaning
code string Stable identifier. Branch on this.
message string What went wrong, for people. Don't parse it.
fields object Only with invalid_request: field name to a list of messages.
request_id string The answer's X-Request-Id. Quote it when you write to us.

Every answer, error or not, carries an X-Request-Id header. The TypeScript SDK throws errors as IntoSQLError with status, code, message, fields and requestId.

Error codes

Code Status Cause Fix
unauthenticated 401 No Authorization header, or the key is wrong or revoked. Send Authorization: Bearer <key> with a key from Settings, API keys.
forbidden 403 The token wasn't made for the API, so it can't use conversions. Use a key made in Settings, API keys.
suspended 403 The account is suspended. Contact support.
needs_pro 402 The account has no IntoSQL Pro plan. Subscribe to Pro on /pricing.
over_quota 402 The file is larger than what the plan has left this month. Check usage, wait for next month, or split the file.
too_big 413 bytes is over 5 GiB, the largest upload (one PUT to storage), or a file sent by url is over 50 GiB. Send a large file by url, or split it.
too_many_active 429 Five conversions are already queued or running on the account. Sent when you start a conversion or send a url. Wait Retry-After seconds, or for a webhook that one ended, then try again.
invalid_request 422 Missing or invalid fields, listed in fields. Also an Idempotency-Key over 255 characters. Fix the fields. Send either name and bytes, or url, never both.
invalid_spec 422 spec isn't a valid conversion spec. The message names the problem. Check it against Conversion spec, or validate it with intosql validate.
invalid_url 422 url isn't HTTPS, or its host has a private, local or reserved address. Use an https:// address reachable from the internet, on port 443 or 8443.
idempotency_mismatch 422 The Idempotency-Key was already used in the last 24 hours for a different request. Use a new key for each new request.
request_in_progress 409 A request with the same Idempotency-Key is still running after 10 seconds. Retry with the same key in a moment.
not_uploaded 409 You started a conversion before its file arrived whole, or the uploaded file isn't bytes long. Finish the PUT to upload.url, then start it again. If the size was wrong, open a new conversion.
already_started 409 The conversion is already queued, running or finished. Read it with GET /conversions/{id} instead of starting it again.
already_finished 409 You canceled a conversion that already ended as done or failed. Delete it instead, if you want its files gone.
not_finished 409 You sent a conversion as a case before it ended. Wait for it to end, then send it.
file_gone 409 You sent a case with share: file, but the conversion's file was deleted or expired. Send the case with the report only, or convert the file again and send that one.
not_found 404 No conversion with that id on this account, or it was deleted. Also any unknown path. Check the id and the path.
rate_limited 429 More than 120 requests in a minute with this key. Wait, then retry. See Rate limits.
method_not_allowed 405 The path exists, but not with that method. Check the method in the API reference.
http_error varies Any other HTTP error. Check the method and path in the API reference.
server_error 500 Something failed on IntoSQL's side. It was reported. Retry with the same Idempotency-Key.

needs_pro, suspended, forbidden and unauthenticated are checked before anything else, on every endpoint.

Rate limits

There are two limits:

  • Requests: 120 a minute per API key. Each key has its own limit, so one script can't slow another key of the same account.
  • Active conversions: five queued or running at once per account, across all its keys. Conversions waiting for their upload don't count.

Successful answers carry the request limit in headers:

HTTP
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117

Over the limit, the API answers 429 with rate_limited, with X-RateLimit-Remaining: 0 and a Retry-After header that says how many seconds to wait before the next request.

too_many_active also answers 429, with Retry-After: 30. A slot frees as soon as a running conversion ends, which a webhook tells you.

Idempotency

Every POST (opening, starting and canceling a conversion, and sending a case) accepts an Idempotency-Key header, so a request you send again after a timeout or a lost connection runs only once. GET and DELETE ignore it: they are safe to repeat as they are.

HTTP
POST /api/v1/conversions HTTP/1.1
Authorization: Bearer <key>
Content-Type: application/json
Idempotency-Key: import-ventas-2026-10-16

How the key works:

Situation Answer
First request with the key Runs normally. The answer is kept for 24 hours, unless it is a 5xx.
Same key, same method, path and body, within 24 hours The first answer again, with the same status and the header Idempotent-Replayed: true. Nothing runs.
Same key, different method, path or body 422 idempotency_mismatch.
Same key while the first request is still running Waits up to 10 seconds for the first answer, then 409 request_in_progress.
Key longer than 255 characters 422 invalid_request.

Details that matter:

  • Keys belong to the account. Two keys of the same account share them.
  • The body is compared byte for byte. Send the retry with exactly the same body. Serializing the same object again with keys in another order counts as a different request.
  • Errors are kept too. A 4xx such as invalid_spec is replayed for the same key. After fixing the request, use a new key.
  • 5xx answers aren't kept, so a retry with the same key after server_error runs again.

Use a random UUID per request, or a key tied to the work, such as import-<file>-<date>, so a retry after your process restarts reuses it.

Retry safely

  • Retry on a lost connection, a timeout, and the codes rate_limited, too_many_active, request_in_progress and server_error (any 5xx). Wait Retry-After seconds when the answer has it, otherwise double the wait between attempts, and send the same Idempotency-Key with each POST.
  • Never retry already_started: the conversion is on its way, so read it instead. Don't retry unchanged on other 4xx codes either; they describe the request or the account, and sending it again gets the same answer.
  • Reads and deletes (GET, DELETE) are safe to repeat as they are.
  • The file upload to upload.url is a PUT to storage, outside the API. Repeating it replaces the file, which is safe before you start the conversion. The address works for an hour.

Every IntoSQL SDK does all of this: it adds a random Idempotency-Key to every POST, keeps it across retries, and retries lost connections and the codes above, twice by default.

Failed conversions

Once a conversion is queued, problems with the file no longer come back as HTTP errors. The conversion ends with status: "failed", and error says why, with a stable code:

JSON
{
    "data": {
        "id": "01k7r3j9qf8m2v6x4t0c5n1h8d",
        "status": "failed",
        "error": {
            "code": "encrypted_file",
            "message": "\"ventas.xlsx\" is protected with a password.",
            "problems": []
        },
        "output": null,
        "finished_at": "2026-10-16T14:03:27+00:00"
    }
}
Field Type Meaning
code string Stable. Branch on this.
message string What went wrong, for people.
problems array What the engine found that stopped it, such as a spec value out of range, each with its code.

When the engine refuses the file, code is the engine's own: empty_file, corrupt_file, encrypted_file, unsupported_format, sheet_not_found, no_columns, or invalid_spec with the reasons in problems. Errors explains each one. The rest happen on our side:

Code Cause Fix
unreachable A file sent by url couldn't be downloaded: no answer, an error status, or too many redirects. Check the address answers from the internet.
download_stalled The download stopped sending, or ran past its hour. Try again, or upload the file instead.
too_big The file sent by url turned out larger than a conversion takes. Split the file.
over_quota The file sent by url is larger than what the plan has left this month. Check usage, or wait for next month.
not_uploaded The upload changed or went missing after the conversion started. Open a new conversion.
timed_out It ran past three hours, download included. Split the file and convert the parts.
account_suspended The account was suspended while it waited. Contact support.
internal_error It stopped on IntoSQL's side. The failure was reported. Run it again.

A failed or canceled conversion doesn't count toward the month's allowance. The SDKs raise ConversionFailedError with the code and the message, and a webhook sends conversion.failed with the whole conversion in data.

If a file didn't convert well, or at all, send it to us: a failed conversion keeps its upload until it expires, so the case can carry the file.

Rows the engine rejects don't fail the conversion. They are counted in report.rows.rejected, with the reason for each in report.problems.