Sign in
document · railway.com

Railway API reliability

How the Railway API behaves under failure: the error convention and code catalog, rate limits and headers, retry/idempotency guidance, and the versioning/deprecation policy.

What it says

The document its publisher serves

the skill Read from the publisher's own address, not re-hosted.
# Railway API reliability

How the Railway API behaves under failure — errors, rate limits, retries, and
deprecation — in one place, so an agent can depend on it without guessing.

The public API is GraphQL at a single endpoint,
`https://backboard.railway.com/graphql/v2`. Authentication is covered in
<https://railway.com/auth.md>; the endpoint and OAuth surface are described in
<https://railway.com/openapi.json>.

## Errors

Railway follows the GraphQL error convention, not HTTP status alone. Always
inspect the `errors` array, not just the status code.

- **HTTP 200 with an `errors` array** — execution and authorization failures.
  A query that runs but is denied returns 200; the failure is in `errors`.
- **HTTP 400** — the request could not be parsed or validated (malformed JSON,
  unknown field, bad variable). `extensions.code` carries the reason.
- **HTTP 429** — rate limited. See below.

Every error object carries a `message`, an `extensions.code`, and a
`traceId`. Include the `traceId` when reporting a problem — it is how support
finds the request.

```json
{
  "errors": [
    {
      "message": "Cannot query field \"nope\" on type \"Query\".",
      "extensions": { "code": "GRAPHQL_VALIDATION_FAILED", "traceId": "7992771584715554281" }
    }
  ],
  "data": null
}
```

Common `extensions.code` values:

| Code | Meaning | HTTP |
| --- | --- | --- |
| `GRAPHQL_PARSE_FAILED` | The query is not valid GraphQL syntax | 400 |
| `GRAPHQL_VALIDATION_FAILED` | The query references unknown fields or types | 400 |
| `BAD_USER_INPUT` | A field or variable failed validation | 400 |
| `INTERNAL_SERVER_ERROR` | An unexpected error, or an authorization denial (message `Not Authorized`) | 200 |

## Rate limiting

The API is metered per hour, per account or workspace token. The base budget is
**1,000 requests per hour** (60-minute window). Some
plans raise this; the response headers below always report the limit that
applies to your token.

Authenticated responses carry standard rate-limit headers so you can
self-throttle:

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed in the window |
| `X-RateLimit-Remaining` | Requests left in the current window |
| `X-RateLimit-Reset` | ISO-8601 time the window resets |
| `RateLimit-Policy` | The limit and window, advertised on every response (even unauthenticated) |

When you exceed the limit the API returns **HTTP 429** with a `Retry-After`
header (seconds). Back off until then; do not retry immediately.

## Retries

- **Reads (queries) are safe to retry.** They have no side effects.
- **Writes (mutations):** retry only on a network error or a 429/5xx where you
  did not receive a response. If you received a 200, the mutation ran — retrying
  may duplicate it. Most Railway mutations are keyed on the resource they act
  on, so a retry with the same arguments generally converges, but do not assume
  exactly-once.
- **Back off exponentially** and honor `Retry-After` on a 429.
- Carry the `traceId` from a failed response into any support request.

## Versioning and deprecation

Railway's public API is GraphQL, so **the schema is the contract**. New fields
and types are added without a version bump — additive changes never break an
existing query.

Fields being retired are marked `@deprecated` in the schema with a reason.
Introspect with `includeDeprecated: true` to see them:

```graphql
{ __type(name: "Project") { fields(includeDeprecated: true) { name isDeprecated deprecationReason } } }
```

Prefer non-deprecated fields; a deprecated field keeps working through its
sunset window and is removed only after it has been marked for a meaningful
period.

## Status and incidents

- **Status page:** <https://status.railway.com> — current platform status and
  incident history.
- **Webhooks:** react to deployment and service events instead of polling.
  Delivery is best-effort — each event is POSTed with a 30-second timeout and
  retried up to 3 times with exponential backoff, treated as delivered on any
  2xx/3xx — and carries no cryptographic signature, so authenticate the sender
  with a secret embedded in the webhook URL and reconcile against the API rather
  than treating a webhook as a guaranteed ledger. See
  <https://docs.railway.com/observability/webhooks>.

## Related

- Authentication: <https://railway.com/auth.md>
- OpenAPI description: <https://railway.com/openapi.json>
- API documentation: <https://docs.railway.com/integrations/api>
- Status: <https://status.railway.com>

## Open this page

<https://railway.com/api-reliability.md>
What this is

Document

text/markdownlast seen 2026-08-30

These are the publisher's own words, read from what they serve at their own address.

Where it lives

The publisher's own address

https://railway.com/api-reliability.md

This catalog links to it and never serves a copy, so what you get is whatever railway.com is serving now.

In its own words

What its publisher says you would ask it

  • how does the Railway API report errors
  • is the Railway API safe to retry
  • what are the Railway API rate limits
Tags

How its publisher filed it

docsapireliability