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:
{
  "errors": [
    {
      "message": "User does not have permission to update this resource."
    }
  ]
}
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

StatusMeaningWhen you’ll see it
200Success or GraphQL errorAlways for /graphql. Check errors[]
400Malformed requestInvalid JSON body, missing required field on the wire
401UnauthenticatedMissing, expired, or invalid token. See the 401 table
403Authenticated but not allowedPermission, scope, or org-isolation failure. See 403 Forbidden
404Resource not foundWrong ID, soft-deleted entity, or not visible to your org
409Concurrency conflictThe _etag mismatch. Your update raced another writer. See 409 Concurrency conflict
422Validation errorField value violates a domain rule, such as quantity ≤ 0
429Rate-limitedBack off and retry
5xxServer errorION-side failure. Safe to retry with exponential backoff

401 Unauthorized

ION rejects the request at the authentication layer. Here are the common messages and their fixes:
MessageCauseFix
Please provide proper credentialsNo Authorization header on the requestAdd Authorization: Bearer <token>.
Unable to parse authentication tokenMalformed token, such as one that is truncated or in the wrong formatVerify the full token is being sent. A common cause is an env var that lost a trailing character.
Unable to find appropriate RSA keyThe token was signed by a key ION doesn’t recognizeThe token came from the wrong auth provider. Check that you’re hitting the right client_id and audience.
Token is expiredThe access token’s exp claim has passedRefresh the token for OAuth, or re-issue it for an API key.
Unable to validate authentication tokenGeneric verification failureRe-issue the token. If it persists, check that the token audience and issuer match your environment.
Token organization does not match user's organizationThe token was issued for one org, but the user belongs to anotherRe-authenticate the user against their actual organization.
User <email> is deactivatedThe user has been deactivated in IONReactivate the user or use a different account.
Sorry, this organization is blocked from accessing IONThe org is in a blocked stateContact your CSM.
Login domain '<domain>' is not valid for this organizationThe user’s email domain isn’t allowed on the orgAdd the domain to org settings or use an allowed email.

403 Forbidden

The request authenticated successfully, but the principal isn’t authorized for the operation. The distinct causes each have a different fix.
CauseWhat happenedHow to fix
Missing role permissionThe user or service account’s role doesn’t include the action being performed. For example, the role lacks the “create purchase order” permission.An admin grants the role the missing permission, or assigns the user a role that has it.
Resource not visible to orgThe entity ID exists in another tenant’s data. ION treats it as nonexistent for this caller.Verify the ID belongs to your org. Cross-org references are never allowed.
Read-only organizationThe org is in read-only mode, such as a billing freeze or compliance hold, and any mutation is rejected.Contact your CSM to release the read-only flag.
Read-only schemaA specific schema, your tenant, is flagged read-only on the ION side.Contact support.
Deactivated userThe user account is deactivated. Auth still passes if the token is valid, but every operation returns a 403.Reactivate the user in admin settings.
Approval gate not satisfiedThe operation requires an approval that hasn’t been granted. For example, publishing a procedure that requires QE sign-off.Complete the approval flow first.
OAuth scope insufficientThe token was issued with limited scopes that don’t cover the requested operation.Re-authorize the OAuth app with broader scopes.
Feature flag disabledThe operation is gated behind a feature flag not enabled for your org.Contact your CSM to enable the feature for your tier.
Cross-org tokenA token issued for org A is trying to access org B’s data.Re-authenticate against the correct org.
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

{
  "errors": [
    {
      "message": "User does not have permission to perform UPDATE on Purchase Order."
    }
  ]
}
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.
{
  "errors": [{ "message": "Concurrent modification detected — etag mismatch." }]
}
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:
{
  "errors": [{ "message": "PartInventory quantity must be greater than 0." }]
}
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.
HTTP/1.1 502 Bad Gateway
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:
{
  "data": null,
  "errors": [
    {
      "message": "User does not have permission to perform UPDATE on Purchase Order.",
      "path": ["updatePurchaseOrder"],
      "locations": [{ "line": 2, "column": 3 }]
    }
  ]
}
FieldMeaning
messageHuman-readable error.
pathWhich field in your query failed.
locationsPosition in the query string for IDE and playground integrations.
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.