Errors, rate limits and retries
Error format
Every error is application/problem+json (RFC 9457) with a stable type:
{
"type": "https://developer.nasam.co/errors/invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "brandId must be a positive integer",
"requestId": "4f6c2b0e-1d7a-4c55-9a43-7f1f3e8c2a10",
"errors": [{ "pointer": "/brandId", "detail": "must be a positive integer" }]
}
Branch on type (or status), not on detail or title: detail explains this occurrence and its wording can change. errors appears on invalid-request and points at each invalid field: a JSON Pointer into the body, or /name for a query or path parameter.
type | Status | Meaning | Retry? |
|---|---|---|---|
invalid-request | 400 | A parameter or body field is missing, has the wrong type, or has a value the operation does not accept. | No. Fix the request. |
unauthorized | 401 | The key is missing, unknown or revoked. See authentication. | No. |
forbidden | 403 | The key's user lacks the permission, or the request names a brand outside its scope. | No. |
not-found | 404 | The resource does not exist or is outside the key's scope, or the path is not an MM-API operation. | No. |
conflict | 409 | The request conflicts with the resource's current state. | Read the resource again, then decide. |
idempotency-in-progress | 409 | A request with the same Idempotency-Key is still running. | Yes, after a short wait. |
idempotency-key-reused | 422 | The Idempotency-Key was used with a different request. | No. Use a new key for a new request. |
payload-too-large | 413 | The request body is larger than the request size limit. | No. Send a smaller body. |
rate-limited | 429 | Too many requests. | Yes, after Retry-After seconds. |
internal | 500 | Nasam failed to complete the request. | Yes, with backoff. Send an Idempotency-Key on writes so a retry cannot apply twice. |
Ignore problem fields you do not recognise; new ones can be added.
Request IDs
Every response carries X-Request-Id, and every error body repeats it as requestId. Log it with each failed call and include it when you write to support; it lets Nasam find the exact request.
You can send your own X-Request-Id (up to 128 characters) to tie Nasam's logs to yours. Nasam echoes a well-formed value and generates one otherwise.
Rate limit
The current limit is 120 requests per fixed one-minute window per API key. Every authenticated request counts, whatever its status. Each response reports where you stand:
RateLimit-Policy: "default";q=120;w=60
RateLimit: "default";r=87;t=23
q is the quota and w the window in seconds. r is the number of requests left in this window and t the seconds until it resets. Over the limit, the API returns 429 rate-limited with Retry-After in seconds. Wait that long before the next request.
The limit is published as the current one, not a guarantee. Read it from RateLimit-Policy rather than hard-coding 120, and pace from RateLimit: when r is low, wait t seconds. Two keys on the same user each have their own window.
Requests that fail authentication are limited separately, at 60 a minute per client IP address. That 429 carries Retry-After only.
Retry writes safely with Idempotency-Key
A timeout on a write leaves you unsure whether it ran. Send an Idempotency-Key header on POST, PUT, PATCH and DELETE, and you can repeat the request without applying it twice:
curl -X POST 'https://api.nasam.co/mm-api/v1/fulfillment/orders/packed' \
-H "Authorization: Bearer $NASAM_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 8d0c3c4e-6a8f-4d53-9b8e-0f2f8b1f6d21' \
-d '{"orderIds": [88123]}'
- Generate a new unique value (a UUID works) for each logical write, and reuse it only to retry that same write.
- Within 24 hours, a retry with the same key, method, path and body returns the stored response with
Idempotent-Replayed: true. The original status and body are replayed;X-Request-IdandRateLimitdescribe the retry. - A response with status below 500, including a
4xxerror, is stored and replayed. After a5xxthe key is released, so retrying it runs the write again. - The same key with a different body returns
422idempotency-key-reused. - The same key while the first request is still running returns
409idempotency-in-progress. Wait and retry.
Idempotency keys belong to the API key that sent them, so two systems with different API keys cannot collide. After 24 hours a stored response is discarded and the value counts as new.
A retry policy that works
- Retry
429,500,idempotency-in-progressand network errors. Do not retry other4xx. - On
429, waitRetry-Afterseconds. Otherwise back off exponentially with jitter, for example 1, 2, 4, 8 seconds, and stop after a few attempts. - Send an
Idempotency-Keyon every write, and keep it the same across retries of that write. - Some batch writes, such as marking orders packed, report a result per item inside a
2xxresponse. Read the item results and resend only the items that failed, under a new key.