Skip to content

Limits and errors

A query nested more deeply than the depth limit is rejected before it runs. The limit is a server setting, 10 levels by default at this build. The published queries never need that much nesting.

Every query is given a cost before it runs. If the cost is over the maximum, the query is rejected and nothing is executed.

How the cost is worked out:

  1. Each field you select costs 1. orders and products cost 5 each. __typename is free.
  2. A field that takes first (or last) multiplies its own cost, plus the cost of everything selected under it, by the number you ask for.
  3. If you pass first as a variable, the variable’s value is used. If the server can’t read it as a whole number, it assumes 20.
  4. At this build, limit arguments (on consignments, consignmentInvoices, retailerStatements and retailerPayments) do not multiply the cost.

Worked example:

query RecentOrders {
orders(first: 50) {
edges {
node {
number
status
grandTotal { paise }
}
}
pageInfo { hasNextPage endCursor }
}
}
Part Cost
grandTotal { paise } 1 + 1 = 2
node { number status grandTotal {…} } 1 + (1 + 1 + 2) = 5
edges { node {…} } 1 + 5 = 6
pageInfo { hasNextPage endCursor } 1 + 2 = 3
orders(first: 50) {…} (5 + 6 + 3) × 50 = 700

The maximum is a server setting. Its default at this build is 5,000. Read the limit in force from the error rather than hard-coding it.

Example (with the default maximum):

{
"data": null,
"errors": [
{
"message": "Query cost 7000 exceeds the maximum allowed cost 5000. Request fewer fields or smaller pages.",
"extensions": { "code": "QUERY_COST_TOO_HIGH", "cost": 7000, "maxCost": 5000 }
}
]
}

If you hit it, select fewer fields or ask for smaller pages.

Schema introspection (__schema, __type) is turned off in production. Whether it is on is a server setting; this is the behaviour observed on the production API. A query such as { __schema { queryType { name } } } is rejected with:

GraphQL introspection has been disabled, but the requested query contained the field '__schema'.

Use the API reference on this site instead. It is generated from the production schema.

In production, unexpected errors are replaced with the message Unexpected error. and carry no extensions. Permission errors and query validation errors keep their real messages. This is the observed production behaviour; for example, a malformed cursor on products returns:

{
"data": null,
"errors": [
{ "message": "Unexpected error.", "locations": [{ "line": 1, "column": 3 }], "path": ["products"] }
]
}

Inputs that are not valid also end up as Unexpected error., for example:

  • a malformed cursor in after,
  • a negative first,
  • a malformed ID, such as a dealerId that is not a valid Dealer ID.

Check your inputs before you report one of these to your RaviFurnitures contact. Include the time of the request when you do.

A request that reaches the API returns JSON. Errors are listed in the top-level errors array. Check that array; don’t rely on the HTTP status alone.

{
"data": { "order": null },
"errors": [
{
"message": "You do not have access to this order.",
"locations": [{ "line": 1, "column": 3 }],
"path": ["order"],
"extensions": { "code": "PERMISSION_DENIED" }
}
]
}
extensions.code Meaning
PERMISSION_DENIED No usable key, or the key lacks a permission. See Authentication.
QUERY_COST_TOO_HIGH The query is over the cost limit.
none Validation errors (unknown field, wrong argument type) and masked errors.

A field that fails is returned as null. All published queries except order and product return a non-null list or page, so an error in one of them makes the whole data value null. If you ask for several top-level fields in one request, one failure can blank the others: keep each request to one top-level query.

Some unpublished operations report business errors in an errors list inside their own result (with field, code and message). The published queries don’t: their errors are only in the top-level errors array.

The published queries page in two different ways.

Used by orders, refunds and products.

  • first: how many items. Default 20, maximum 100. Larger values are reduced to 100 without an error.
  • after: the endCursor from the previous page.
  • pageInfo { hasNextPage endCursor } tells you whether to continue. totalCount gives the total.
  • orders and refunds come newest first. products comes newest first unless you pass sortBy or filter.search (a search orders by relevance).
query OrdersPage($after: String) {
orders(first: 100, after: $after) {
totalCount
edges { node { number } }
pageInfo { hasNextPage endCursor }
}
}

A cursor marks a position in the list, not a record. If new records arrive while you page through, the list shifts and you may see an item twice. If records are removed, you may miss one. Always upsert by a stable key (order number, product slug) and repeat full sweeps from time to time.

Used by consignments, consignmentInvoices, retailerStatements and retailerPayments.

  • limit: default 50. The maximum is 100 for consignments and 200 for the other three. Larger values are reduced without an error.
  • offset: how many records to skip.
  • Results come newest first. A page shorter than your limit or the cap, whichever is smaller, is the last one.

The same shifting applies when records are added during paging.

warehouses and lowStock return everything in one list.

The application applies no per-key or per-IP rate limit to the published queries at this build. Limits outside the application are not described here. Either way, don’t poll hard: run one sync worker per integration, and wait and retry with growing delays when requests fail.

Very large queries can run into server time limits and fail with Unexpected error.. Keep pages small.