Skip to content
Navo24 Navo24

Core concepts

Errors and rate limits

Response envelopes, error codes, and the usage headers that ride on every call.

Response envelopes

Products share a small set of shapes. Read the envelope before the payload.

  • TrackingMCP and the SchedulesMCP public surface wrap success in { "ok": true, "data": ... }.
  • AirCargoMCP and FreightRatesMCP return the payload under { "data": ... }.
  • LoadingMCP returns { "ok": true, "plan": { ... } }.
  • The TrackingMCP DCSA feed at /v2/events is the one exception. It follows the standard rather than our envelope, so it answers a bare JSON array and puts pagination in the response headers.

The keyed TrackingMCP surface returns a coded error object, so you can branch on the code and show the message:

{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "identifier_type is required",
    "severity": "warning"
  }
}

The open TrackingMCP endpoints answer a plain message instead, and they are explicit rather than silent:

{
  "ok": false,
  "message": "That number fails the ISO check digit, which almost always means one digit is off. Did you mean MEDU1234562?"
}

Codes you will meet on TrackingMCP

CodeStatusWhat it means
VALIDATION_ERROR400A required field is missing or out of range. Posting the wrong field names lands here.
UNAUTHORIZED401The key is missing, malformed or revoked.
UNIQUE_QUOTA_EXCEEDED402The add would pass the shipment quota on your plan.
CONTAINER_QUOTA_EXCEEDED402No container slot is free on your plan.
PAYG_SPEND_CAP_REACHED402Pay-as-you-go only: the add would cross the spend cap you set.
INSUFFICIENT_CREDITS402MCP calls only: your Navo Tokens balance cannot pay for the call. Nothing is charged. The error also carries credits_required, credits_balance, top_up_url and packages (see Navo Tokens).
KEY_PRODUCT_NOT_ALLOWED403The key is scoped away from tracking.
NOT_FOUND404No record with that UUID on your account.
REFERENCE_NOT_RESOLVABLE404No line recognises the reference.
AUTO_CANT_FIND_INFO404The line could not be identified for that reference.
ALREADY_TRACKED409That identifier is already tracked under a different line. Adding it again with the same line, or without a line, answers 200 with the record.
CONTAINER_ADD_IN_PROGRESS409The same identifier is mid-add on another request.
MCP_QUOTA_EXCEEDED429MCP calls without a Navo Tokens wallet: the monthly MCP call limit is spent.
quota_exceeded429REST: the call limit of your plan or of the key is spent. A refused call is not counted.
RATE_LIMITED429Too soon after the last recheck of that box.
DB_ERROR, INTERNAL_ERROR500Our side. Retry.

The one that is not a failure

Two 202 answers mean “accepted, still resolving”:

  • GET /v1/containers/lookup/{identifier} answers 202 with the code STILL_RESOLVING when the line is known but has not answered in time. Resolution continues in the background. The body carries retry_after_seconds and progress, and the response has a Retry-After header. Poll again on that delay rather than adding the box a second time.
  • POST /v1/containers answers 202 for a new reference that did not resolve within the request: with "resolution_status": "pending" and a poll_url, or with STILL_RESOLVING when the line is known. Poll the record.

Note that ok is false on STILL_RESOLVING and true on the pending add. Branch on the status code, not on ok alone, or a healthy in-progress lookup will read as an error.

FreightRatesMCP uses a structured error object:

{ "error": { "code": "not_found", "message": "No rate for CNSHA->NLRTM." } }

Status codes

CodeMeaning
200Success. TrackingMCP answers this to an add of a box you already track.
201Created. A TrackingMCP add of a new box that resolved within the request.
202Accepted and still resolving. See the note above.
400The request is malformed, or a required parameter is missing.
401The key is missing or not recognised.
402A container or shipment quota, or a pay-as-you-go spend cap, stands in the way of an add; your account has no access to the product (not_entitled on FreightRatesMCP); or an MCP call’s Navo Tokens balance cannot pay.
403The key is not allowed this product, or the access has ended (access_expired).
404The identifier resolved to nothing, or there is no rate for the lane.
405Wrong method. FreightRatesMCP is GET only, except POST /v1/rates/quote and the webhook routes.
409The same record is already tracked, or an identical request is in flight.
413, 422FreightRatesMCP quote: the body is too large, or the quote request cannot be priced.
429You are over the rate limit or a call quota. Back off and retry.
5xxOur side, or an upstream source. Retry.

Error codes you will see in the FreightRatesMCP envelope include missing_key, invalid_key, revoked_key, expired_key, invalid_request, not_entitled, not_found, method_not_allowed, quota_exceeded, auth_unavailable and server_error. An MCP call whose Navo Tokens balance cannot pay gets 402 with insufficient_credits, carrying the same four optional fields as on TrackingMCP. AirCargoMCP, SchedulesMCP and LoadingMCP carry the same four fields on every Navo Tokens refusal too (see Navo Tokens).

What counts as a successful request

A success is a 2xx. Everything else is a refusal (4xx) or our failure (5xx). What is charged depends on how you call: REST with an API key is metered in calls against your plan’s call quota; an MCP call from an account with a Navo Tokens wallet is charged in Navo Tokens and never touches the call quota.

Product and accessSuccessful answersWhat is countedWhat is free
TrackingMCP, REST (API key)200; 201 new box resolved; 202 pending add; 202 STILL_RESOLVING1 call for every keyed request to a tracking path, counted when the key is checked, before the route runs. The route’s own answer does not change it: 404 REFERENCE_NOT_RESOLVABLE, 409 ALREADY_TRACKED, other 4xx and 5xx from the route, and a 429 rate-limit refusal are each 1 call. An add can also be refused with 402 when a container or shipment quota is used up.401; the quota refusal itself (429 quota_exceeded, 403 access_expired) and a key-limit refusal, which go to refused calls; account paths (/v1/usage, /v1/account, /v1/api-keys, /v1/billing, /v1/invoices, /v1/finance, /v1/team, /v1/webhooks, /v1/mcp). POST /v1/track/public takes no key and spends no quota; it is capped per account and per IP.
TrackingMCP, MCP (Navo Tokens)Same as RESTAdding a box: 250 Navo Tokens, taken before it is registered. Adding a box you already track again: free, unless it triggers a refresh (10). Reads (lookup, container detail, summary, demurrage): 10 Navo Tokens for each reference whose answer carries data; a B/L counts as one reference.402 INSUFFICIENT_CREDITS (nothing charged); an answer without data; 202 STILL_RESOLVING; 404 REFERENCE_NOT_RESOLVABLE; 404 AUTO_CANT_FIND_INFO; 409 ALREADY_TRACKED and 500 on an add (the 250 is refunded); a box we never find (refunded); get_account_access. No 429 MCP_QUOTA_EXCEEDED on this path.
FreightRatesMCP, REST (API key)2001 call for every keyed request, counted when the key is checked. A quote (POST /v1/rates/quote) that returns no rate is uncounted again. Other routes stay counted whatever they answer, including 404 not_found and 400.401; 402 not_entitled; the quota refusal (429 quota_exceeded, 403), which goes to refused calls; a quote with no result; webhook and account routes.
FreightRatesMCP, MCP (Navo Tokens)200100 Navo Tokens for a port pair with data. The same port pair again within 24 hours of the last charge: 10. After 24 hours it is priced as new (100). Lane cards, spot and quote are charged only when they return at least one rate.The balance is checked before the read: 402 insufficient_credits charges nothing. Also free: 404 not_found, an empty answer, 400, 413, 422, 5xx, the index list and lane history.

MCP calls from an account without a Navo Tokens wallet are counted in calls. On TrackingMCP they are counted as on REST and each also takes one MCP call from the plan’s monthly MCP limit; a refusal there is 429 MCP_QUOTA_EXCEEDED, and a purchased call pack with calls left lifts that limit. On FreightRatesMCP they are counted on the plan’s MCP line, with the same rules as REST, and the key’s own limits do not apply. get_account_access is always free.

Rate limits and usage

Limits are enforced over a rolling 20 second window, so bursts are smoothed rather than a single per-minute bucket. The defaults:

TierPer 20s windowSustained
Free / anonymous / no plan40 requests120 / min
Paid plan120 requests360 / min

Need a higher ceiling for batch workloads? Contact us and we raise it per account.

Every keyed response carries usage headers so you can watch your budget without a second call:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window (40 or 120).
X-RateLimit-RemainingRequests left in this window.
X-RateLimit-ResetUnix time (seconds) when the window resets.

When you exceed the window you get 429 with the standard envelope:

{ "ok": false, "error": { "code": "VALIDATION_ERROR", "message": "Rate limit exceeded", "severity": "warning" } }

Read the headers, and on a 429 back off with a short exponential delay before retrying. Public (unkeyed) endpoints are limited per IP; keyed endpoints are limited per account and plan.

Handling it well

  • Treat any non-2xx as a typed outcome, not a crash. Branch on the code.
  • Log the message. It is written to be read by a human on your side.
  • Do not hammer a 404. An identifier that fails the check digit will keep failing until it is corrected.
  • Do not retry a 409 ALREADY_TRACKED. The box is tracked under another line: read the record you already hold.