Skip to main content

Overview

When an ION API request fails, two things tell you what went wrong. The HTTP status code gives you a coarse category and the errors[] payload in the response body gives you the specific message. That message often points directly at the fix. A typical error response looks like this:
GraphQL operations always return HTTP 200 even when the operation itself fails. The failure shows up in errors[]. Authentication and transport failures are the exception. The auth layer can return a 4xx or 5xx status code before GraphQL runs.

HTTP status reference

One exception to the 400 row: a request whose body is entirely empty returns 200 with the error in errors[], like any other GraphQL error.

401 Unauthorized

ION rejects the request at the authentication layer. Here are the common messages and their fixes:

403 Forbidden

The request authenticated successfully, but the principal isn’t authorized for the operation. The distinct causes each have a different fix.
If a 403 says “permission”, check the user’s role. If it says “not found” or refers to an ID, check the entity’s tenant. If it says “read-only”, check the org’s billing and compliance state.

Sample 403 payload

The action verb (UPDATE) and the resource type (Purchase Order) are the two pieces an admin needs to find the missing role permission.

404 Not Found

ION returns a 404, and a GraphQL null for the field, when the entity doesn’t exist or doesn’t belong to your tenant. The two cases are intentionally indistinguishable to prevent enumeration. A 404 doesn’t leak that an ID exists in another org. Common causes:
  • An ID typo.
  • The entity was soft-deleted.
  • A cross-tenant ID. See 403 above. Sometimes this surfaces as a 404 instead, depending on the resolver.
If the entity should exist, query a filtered list such as parts(filters: { id: { in: [42] } }) { edges { node { id } } } instead of part(id: "42"). The filtered query returns an empty edges array. That confirms a tenant or scope issue.

409 Concurrency conflict

ION uses optimistic concurrency control via the _etag field. Every mutable entity returns an _etag on read. Mutations require you to pass back the _etag you received. If another writer modified the entity in the meantime, the etag won’t match and ION returns a 409.
The fix is always the same:
  1. Re-read the entity to get the latest _etag and the latest field values.
  2. Re-apply your changes on top of the new state.
  3. Retry the mutation with the new _etag.
This is intentional. Re-reading surfaces the other writer’s changes before you overwrite them. Always re-read and re-apply, even when you intend to discard the other writer’s changes.

422 Validation error

The request was structurally valid and authorized, but a field value violated a domain rule:
  • A quantity is zero or negative.
  • A required field is missing.
  • A date is out of range.
  • A string exceeds the maximum length.
  • An enum value is not allowed.
The error message names the field and the constraint:
These errors are deterministic. Fix the input and retry.

429 Rate limit

ION applies fair-use rate limiting on the /graphql endpoint. Production traffic patterns rarely hit it. If you do, the response is HTTP 429. Address it in this order:
  1. Implement exponential backoff in your client. Start at 1 second, double up to about 30 seconds, and add jitter.
  2. Batch related queries. One query selecting many fragments beats several queries that each select one fragment.
  3. Cache locally. For data that doesn’t change often, such as parts and procedures, don’t re-fetch every time.

5xx Server errors

A 5xx response indicates a failure on ION’s side. These responses are safe to retry with exponential backoff.
Standard recovery pattern:
  1. Retry with exponential backoff. Use 1, 2, 4, and 8 seconds.
  2. After three consecutive failures across about 30 seconds, surface the error to the user or on-call.
  3. Check ION’s status page if available, or contact support if the error persists.

GraphQL errors[] shape

Inside the GraphQL response body, an HTTP 200, the errors array is the source of truth:
A partial data payload with a non-empty errors[] means the returned fields are valid and one or more other fields failed. Most clients merge these. Yours should too.

Troubleshooting flow

  1. Check the HTTP status.
  2. If it’s a 200, parse errors[] from the response body.
  3. Match the message against the tables on this page.
  4. If the message is unfamiliar, its wording usually points at the offending field, permission, or resource. Search for it in Authentication and the tables on this page.
  5. If you’re still stuck, reproduce the call in the API Playground, which surfaces errors more readably. Paste the request and error into a support ticket.
Most 403s are fixed by granting the user’s role the missing permission. Check permissions before you change code. For a 409, show the conflict to a human so they can re-merge.