API errors
Read time: 9 minutes. Who it's for: Developers handling failures from the QuotWay API.
What does an error look like?
Every error is an RFC 9457 problem-details document with
Content-Type: application/problem+json:
{
"type": "https://www.quotway.com/docs/api/errors#insufficient_scope",
"title": "Forbidden",
"status": 403,
"detail": "This API key is missing the scope(s): write_quotes.",
"code": "insufficient_scope",
"request_id": "req_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
"required_scopes": ["write_quotes"]
}
| Field | Meaning |
|---|---|
type |
A link to this page, at the anchor for the error. |
title |
A short, human summary. |
status |
The HTTP status, repeated in the body. |
detail |
A human-readable explanation of this occurrence. Show it in logs; don't parse it. |
code |
The stable, machine-readable error code. Branch on this. |
request_id |
The request's id (also in the X-Request-Id header). Quote it when you contact support. |
| extra members | Some codes add fields - errors, required_scopes, required_plan, reason, line_index, floor_discount_pct, lines, resolution, daily_limit. Each is listed with its code below. |
Branch on code, not on status or detail. Several codes share a status (there are six
different 409s), and detail wording can change.
Validation errors (invalid_request, invalid_custom_fields) add an errors array with one entry
per problem:
{
"type": "https://www.quotway.com/docs/api/errors#invalid_request",
"title": "Invalid request",
"status": 400,
"detail": "The request body is invalid.",
"code": "invalid_request",
"request_id": "req_8c1d…",
"errors": [
{ "field": "lines.0.variant_id", "message": "No such product variant in this store." },
{ "field": "buyer.email", "message": "Invalid email address" }
]
}
In what order are requests checked?
Knowing the order tells you which error wins when several apply:
- HTTP method →
405 method_not_allowed - API availability →
503 api_disabled - API key →
401 invalid_api_key/401 test_key_on_live_store - IP allowlist →
403 ip_not_allowed - Plan →
402 plan_required - Scopes →
403 insufficient_scope - Rate limits →
429 rate_limited - Request body →
413,415,400 invalid_json - Idempotency key →
400 invalid_idempotency_key,409 idempotency_key_in_use,422 idempotency_key_reused - The endpoint itself → everything else (including
429 email_quota_exceeded)
Which errors should I retry?
| Retry? | Codes |
|---|---|
Yes, after the Retry-After header |
rate_limited (429), email_quota_exceeded (429), api_disabled (503), idempotency_key_in_use (409) |
Yes, with backoff and the same Idempotency-Key |
internal_error (500), and network failures or timeouts with no response |
| Only after fixing something | every other code - the same request will fail the same way |
Use exponential backoff with jitter for 5xx responses and network errors, and always send an
Idempotency-Key on POSTs so a retry can't create a second quote or message. See
Idempotency.
Error codes
Authentication and access
invalid_api_key - 401
Meaning: The Authorization header is missing or isn't Bearer <key>, or the key is
malformed, fails its checksum, doesn't exist, has been revoked, or has expired - or the store no
longer has QuotWay installed (uninstalling revokes every key). All of these return
the same response on purpose, so the API never reveals whether a key exists. The response includes
WWW-Authenticate: Bearer realm="QuotWay API".
What to do: Check you're sending Authorization: Bearer qw_live_… with the complete key (57
characters in total, no quotes or trailing whitespace). In Settings → Integrations → API keys,
check the key's status - if it's Expired or Revoked, create or roll a new one. After a roll, the
old key stops working 24 hours later.
test_key_on_live_store - 401
Meaning: A qw_test_ key was used, but its store isn't a Shopify development store. Test keys
only work on development stores.
What to do: Use a qw_live_ key for this store.
ip_not_allowed - 403
Meaning: The key has an IP allowlist and this request came from an address that isn't on it.
What to do: Add your integration's egress IP address to the key's Allowed IP addresses (exact addresses; CIDR ranges aren't supported), or call from an allowed address.
plan_required - 402
Meaning: The store's QuotWay plan doesn't include the API. The API is on the Enterprise
plan (a live trial counts). Extra member: required_plan ("enterprise").
What to do: The store owner can upgrade in QuotWay → Billing. Keys aren't deleted when a store changes plan - they start working again as soon as the store is back on Enterprise.
insufficient_scope - 403
Meaning: The key is valid but lacks a scope this endpoint needs. Extra member:
required_scopes - the missing scope(s).
What to do: Scopes can't be added to an existing key. Ask a store Admin to create a new key
with the scopes listed in required_scopes, then revoke the old one. See
Scopes.
Request format
method_not_allowed - 405
Meaning: The path exists but doesn't support this HTTP method (for example PUT /v1/quotes).
The Allow header lists the methods it does support. This is checked before authentication.
What to do: Use a method from the Allow header.
invalid_json - 400
Meaning: The request body isn't valid JSON.
What to do: Send a well-formed JSON body. Check for trailing commas, unescaped quotes, or a shell that mangled the payload.
unsupported_media_type - 415
Meaning: The request has a body but its Content-Type isn't application/json.
What to do: Send Content-Type: application/json.
payload_too_large - 413
Meaning: The request body is larger than 256 KB.
What to do: Send a smaller body. A quote request allows at most 100 lines - well under the limit - so a body this large usually means something unintended is being sent.
invalid_request - 400
Meaning: The request is well-formed JSON but something in it is invalid. Extra member:
errors - an array of { field, message }, one per problem (when the problem is tied to a field).
Common causes:
- a required body is missing (
A JSON request body is required.); - a body field fails validation, or is unknown - request bodies are strict, so an unexpected
key such as
"price"on a line is rejected rather than silently ignored; - a line's
variant_idisn't in this store or its product isn't active; acustomer_idisn't a customer of this store; - a query parameter is invalid (
limitoutside 1–50, an unknownstatusor eventtype, a date that isn't ISO-8601, a malformedcursor,customer_emailused withoutread_customer_data); starting_afterdoesn't match an event in the 30-day window;- a webhook URL is rejected (not HTTPS, an IP address instead of a hostname, a non-standard port, credentials in the URL, or an internal or QuotWay-owned host).
What to do: Read errors[].field and errors[].message, fix the request, and send it again.
If you sent an Idempotency-Key, use a new one for the corrected request (see
idempotency_key_reused).
not_found - 404
Meaning: The resource doesn't exist in this store - a quote, document or webhook endpoint
id that's unknown, deleted, or belongs to another store (the API never distinguishes "not yours"
from "doesn't exist"). Also returned for any path under /v1/ that isn't an API endpoint.
What to do: Check the id and that you're using the right store's key. Quote ids are the id
field (for example clz8k2x9f0001qw7h3m4n5p6r), not the quote number (QW-1042).
Rate limiting and availability
rate_limited - 429
Meaning: The key (120 requests per minute) or the store (300 per minute, across all its keys)
has run out of requests. The Retry-After header gives the seconds to wait.
What to do: Wait for Retry-After seconds, then retry. Spread requests out rather than
bursting; see Rate limits.
email_quota_exceeded - 429
Meaning: The store has used up its daily allowance of API requests that can email a buyer -
creating a quote, posting a buyer-facing message, and sending a proposal. The allowance is 2,000
per day on a paid plan, 200 during a trial, and 50 on a development store or with a
qw_test_ key; it refills evenly over 24 hours. Extra member: daily_limit. The Retry-After
header gives the seconds until the next request is allowed. Internal notes, reads and webhook
management don't count.
What to do: Wait for Retry-After, then retry. If your integration legitimately needs more,
contact support with your use case.
api_disabled - 503
Meaning: The QuotWay API is temporarily switched off for every store - during maintenance or
an incident. It's checked before authentication, so every request gets it. The response includes
Retry-After: 300.
What to do: Retry after the Retry-After interval, with backoff. Nothing is wrong with your
key or request. Webhook deliveries are not lost during an API outage - use
GET /v1/events to catch up if you need to.
internal_error - 500
Meaning: Something went wrong on QuotWay's side. The error has been logged.
What to do: Retry with exponential backoff, re-using the same Idempotency-Key on POSTs - a
5xx response is never stored against the key, so the retry genuinely runs again. If it persists,
contact support with the request_id.
Idempotency
invalid_idempotency_key - 400
Meaning: The Idempotency-Key header isn't 1–255 printable ASCII characters (spaces aren't
allowed).
What to do: Use a UUID (v4) per logical operation.
idempotency_key_reused - 422
Meaning: This Idempotency-Key was already used, within the last 24 hours and by the same API
key, for a different request (a different path or body).
What to do: Generate a new key for every new operation. Re-use a key only to retry the
identical request. This includes a request you corrected after a 4xx - the corrected request is
a new operation and needs a new key.
idempotency_key_in_use - 409
Meaning: A request with this Idempotency-Key is still being processed. Usually Retry-After: 2 is included.
What to do: Wait a couple of seconds and retry with the same key. You'll get the original
response, marked Idempotent-Replayed: true.
Creating quotes (POST /v1/quotes)
not_eligible - 422
Meaning: The store's quote targeting rules don't allow this request - the same rules that
decide where the "Request a quote" button appears. Extra members: reason -
NO_MATCHING_PRODUCT_RULE or NO_MATCHING_CUSTOMER_RULE - and, for a product mismatch,
line_index (the zero-based index of the first line that didn't qualify).
What to do: Remove or replace the ineligible line, or pass a customer_id that matches the
store's customer rules. If every product should be quotable from your integration, ask the merchant
to review their targeting rules.
invalid_custom_fields - 422
Meaning: The store's quote form has custom fields, and the custom_fields you sent are missing
a required answer or fail a field's validation. Extra member: errors - one entry per field, with
field as custom_fields.<field key>.
What to do: Send the missing or corrected values under custom_fields, keyed by each field's
key as configured in the merchant's quote form.
quote_creation_paused - 409
Meaning: The store's subscription is frozen (for example after a failed payment), so new quotes can't be created. Existing quotes can still be read.
What to do: Retry later. The store owner needs to resolve billing in Shopify.
Sending proposals (POST /v1/quotes/{id}/send-proposal)
no_staged_proposal - 409
Meaning: Nobody has priced and saved a proposal for this quote in QuotWay yet. The API sends the staged proposal; it never sets prices.
What to do: Have a team member open the quote in QuotWay, price it in the proposal editor and save it as a draft, then call the endpoint again.
below_price_floor - 409
Meaning: At least one line in the staged proposal is priced below the store's price floor (its
maximum discount below catalog). Extra members: floor_discount_pct, lines (each with title and
discount_pct) and resolution.
What to do: Either ask the merchant to review the prices, or - if sending below the floor is
intended - send the request again with {"confirm_below_floor": true}. That is the same explicit
confirmation the admin asks for, and it's recorded.
invalid_proposal - 422
Meaning: The staged proposal can't be sent as it stands - for example a line is missing a price,
or a fixed-amount discount is larger than the proposal subtotal. detail says why.
What to do: Fix the proposal in QuotWay's proposal editor, save it, and try again.
not_editable - 409
Meaning: The quote isn't in a state a proposal can be sent from. Proposals can only be sent
while the quote is submitted, in_review or countered.
What to do: Fetch the quote and check its status. If a proposal is already out, wait for the
buyer to respond.
state_conflict - 409
Meaning: The quote changed while the send was being prepared (someone else acted on it at the same moment), so QuotWay stopped rather than overwrite that change.
What to do: Fetch the quote again, check it still needs sending, and retry with a new
Idempotency-Key.
send_incomplete - 409
Meaning: The proposal version was created, but a later step of the send didn't finish. The quote may be partly updated.
What to do: Don't retry blindly. Open the quote in QuotWay (or fetch it) and check its
status and version_number before doing anything else.
Webhook endpoints (/v1/webhooks)
limit_reached - 409
Meaning: The store already has the maximum of 20 webhook endpoints.
What to do: Delete an endpoint you no longer use (DELETE /v1/webhooks/{id}, or in
Settings → Integrations → Webhooks), then create the new one.
Related
解決しませんか?サポートチームがお手伝いします。