API reference
Every endpoint, every field, every example. The integration guide (entity model, polling, dry runs, mappable fields, kinds, the full Python loop) lives inside each tag's section in the interactive reference below, right above the endpoints it explains. Scroll the sidebar or follow a docUrl from any error response.
Download the OpenAPI 3.1 spec (YAML)
Error codes
Every error response from the Public API carries a stable wire code. Branch your integration on the code, not on message (codes are immutable for the life of v1; messages may be reworded for clarity). Every docUrl returned by the API resolves to an anchor in the table below.
Codes fire in one of three modes:
- Envelope errors (sync). The request never produced an
import_runrow.4xx/5xxwith an RFC 7807 body. IncludesMISSING_FILE,MALFORMED_MULTIPART,EMPTY_BODY,UNSUPPORTED_MIME,TOO_LARGE,IDEMPOTENCY_KEY_RESERVED,UNAUTHORIZED,FORBIDDEN,IMPORT_IN_PROGRESS,INVALID_KIND,INVALID_QUERY,INVALID_CURSOR,INVALID_STATUS,RATE_LIMIT_EXCEEDED,NOT_FOUND,INTERNAL_ERROR. - Parse-level errors (async). A run was created and the parser surfaced a problem after the 202. The run lands in
state: FAILEDwith the code onrun.error.code. IncludesEMPTY_FILE,TOO_MANY_HEADERS,TOO_MANY_ROWS,MACRO_REJECTED,MALFORMED_CSV,MALFORMED_XLSX,MISSING_REQUIRED,ANTHROPIC_OUTAGE,RUN_TIMED_OUT,STAGING_EXPIRED. - Per-row errors (async). A row failed validation or required normalization. Surfaces in the
failed(orwarnings) results bucket. IncludesTYPE_MISMATCH,UNIQUE_VIOLATION,IDEMPOTENT_KEY_MISMATCH,CONSUMER_ERROR,GEOCODING_FAILED,UNIT_NORMALIZED(warning, not failure).
| Code | HTTP | Description | Remediation |
|---|---|---|---|
INVALID_KIND | 400 | The :kind path segment is not a registered consumer. | Use one of the consumers documented in the reference (currently jobs; others land additively). |
UNSUPPORTED_MIME | 415 | The uploaded file is not a CSV or XLSX. Both the Content-Type header and the filename extension fell outside the allowlist. | Convert the source to .csv or .xlsx and set the matching MIME type. |
TOO_LARGE | 413 | The uploaded file exceeds the 5 MB cap. | Split the file into smaller batches. Per-file cap is 5 MB; per-file row cap is 750. |
TOO_MANY_ROWS | 202 (async) | The parsed file has more than 750 data rows. Run lands FAILED. | Split the file into smaller batches. The cap is a UX guard, not a Postgres limit. |
TOO_MANY_HEADERS | 202 (async) | The parsed file has more than 50 header columns. Run lands FAILED. | Trim the export to only the columns Offload consumes. Extra columns slow mapping and trigger this cap. |
EMPTY_FILE | 202 (async) | The parsed file has no header row, no data rows, or only a header. Run lands FAILED. | Confirm the export from the upstream system actually wrote rows. A header-only export usually means an upstream filter returned no records. |
MALFORMED_CSV | 202 (async) | The CSV parser could not finish reading the file. Run lands FAILED. | Open the file in a spreadsheet, confirm it loads, and re-export as UTF-8 CSV. |
MALFORMED_XLSX | 202 (async) | The XLSX parser could not finish reading the workbook. Run lands FAILED. | Open the workbook, confirm it loads, and re-save. Corruption typically comes from interrupted writes upstream. |
MACRO_REJECTED | 202 (async) | The uploaded XLSX contains a vbaProject.bin stream (macros). Run lands FAILED. | Save the workbook as a macro-free .xlsx and re-upload. |
RATE_LIMIT_EXCEEDED | 429 | The org's per-day LLM mapping budget or per-minute push rate has been exceeded. | Honor the Retry-After header. For sustained needs, ask Customer Success about a per-org cap raise. |
ANTHROPIC_OUTAGE | 202 (async) | The LLM mapper could not reach Anthropic during a cold-mapping run. Run lands FAILED. | Retry after the upstream outage clears. Preset-hit runs are unaffected. |
MISSING_REQUIRED | 202 (async) | After mapping, no header was matched to a field the kind requires (e.g. jobCode / loadNumber for kind=jobs). Run lands FAILED. | Confirm the export includes the required columns for the kind. Use the in-app mapping review to inspect what the mapper saw. |
TYPE_MISMATCH | 202 (async) | A row had a value that could not be coerced to the target field type. Surfaces in the per-row failed bucket. | Inspect the row's errors[].field and confirm the upstream system is exporting the expected type (date format, numeric, etc.). |
UNIQUE_VIOLATION | 202 (async) | A row would have violated a unique constraint (e.g. duplicate load number within the same project). | De-duplicate upstream or rely on the row-level diff: an unchanged duplicate lands in the skipped bucket, not failed. |
IDEMPOTENT_KEY_MISMATCH | 202 (async) | A previously-seen natural key now resolves to a different upstream value. Possible upstream mutation. | Investigate why the upstream key now maps differently. Common cause: the source system reused a job code. |
CONSUMER_ERROR | 202 (async) | A kind-specific consumer threw an unexpected error mid-row (not a typed validation failure). | Report the run id to support. The structured error log under bulk_import.row_failed will pinpoint the cause. |
NOT_FOUND | 404 | The run, results bucket, or resource does not exist within this token's organization. | Verify the run id and that the token still belongs to the org that created the run. |
INVALID_STATUS | 400 | The run exists but its current state disallows the requested operation. | Inspect the run's state. Only QUEUED runs can be staged-confirmed; only RUNNING runs can be cancelled. |
INVALID_QUERY | 400 | A query-string parameter failed validation (out of range, unparseable date, unknown bucket). | Re-read the endpoint reference and confirm the parameter shape and range. |
UNIT_NORMALIZED | 202 (async) | A row's unit was normalized to Offload's canonical form (e.g. lbs → pounds). Lands in the warnings bucket. | No action required. Inspect the warning to confirm the normalization matches your intent. |
GEOCODING_FAILED | 202 (async) | A row's origin or destination address could not be geocoded. | Verify the address is complete and well-formed. Add city/state when the upstream system only supplies a street. |
UNAUTHORIZED | 401 | The Bearer token is missing, malformed, unknown, expired, or revoked. | Verify Authorization: Bearer <token> is set and the token has not been rotated. Mint a fresh token under Settings → API Keys if needed. |
MISSING_FILE | 400 | The multipart envelope did not include a file part. | Send the upload as multipart/form-data with a single part named file. |
MALFORMED_MULTIPART | 400 | The request body could not be parsed as multipart/form-data. | Confirm the Content-Type is multipart/form-data with a valid boundary. Do not send raw JSON. |
EMPTY_BODY | 400 | The uploaded file part was 0 bytes. | Ensure the file exists on disk and the read succeeded before the POST. |
IMPORT_IN_PROGRESS | 409 | A run with the same fingerprint (sha256(kind + file bytes + length)) is still QUEUED or RUNNING. | Poll the existingRunUrl on the response and let the in-flight run resolve before retrying. |
RUN_TIMED_OUT | 202 (async) | A QUEUED or RUNNING run was older than 30 minutes and the stuck-run reaper transitioned it to FAILED. | Retry the upload. Stuck runs usually indicate a worker crash; the run id will be in the engineering audit log. |
STAGING_EXPIRED | 202 (async) | A QUEUED run staged for human review was not confirmed before nextAction.expiresAt. Run lands FAILED. | Re-upload the file. The org admin should confirm the mapping in the staging deep link before it expires next time. |
INVALID_CURSOR | 400 | A pagination cursor was unparseable or pointed past the result set. | Start without a cursor and walk forward only with the nextPageUrl returned by the previous page. |
IDEMPOTENCY_KEY_RESERVED | 400 | The Idempotency-Key header is reserved in the v1 contract. Sending any non-empty value returns this 400 so the reservation is loud rather than silently accepted and ignored. | Drop the Idempotency-Key header from your request. M1's row-level diff already prevents duplicate writes on safe retries (same file, same params). A Stripe-style cached-response path may light up in v1.x additively without breaking the wire. |
INTERNAL_ERROR | 500 | An unhandled server-side error occurred while processing the request. The full error is logged server-side; the redacted envelope carries the requestId so support can trace it. | Retry the request after a short backoff. If the same requestId reproduces, send it to support — the matching server-side log line will pinpoint the cause. |
FORBIDDEN | 403 | The token authenticated successfully, but your organization is not authorized for the requested operation — for example, the public API has not been enabled for your organization. | Confirm the public API is enabled for your organization and mint your token under Settings → API Keys. If you need it enabled, contact support@letsoffload.com. |
Was this helpful?