Error and log codes
GraphPilot reports what it did with a stable code rather than a sentence. This page lists the codes of the edge, the telemetry values recorded beside them, the GraphPilot API’s error codes and the CLI’s exit codes.
Edge codes
Section titled “Edge codes”An edge code appears in three places, so a support thread can start from any of them:
- on the response, as
gp-cache-reasonfor the cache codes, or in the error body for the codes that reject a request, - in your logs: a decision or refusal is recorded on the request itself and listed under
Edge codes at the end of the request’s Overview tab in Insights, and a warning or error
is one of the log lines, read through the API’s
Service.logs(both stored under the same rules as telemetry, below), - in your request telemetry, which the portal’s Insights view reads, stored under the rules on what Insights needs.
Reading a code
Section titled “Reading a code”Some codes refine another one. REQUEST_BODY_TOO_LARGE refines PAYLOAD_TOO_LARGE:
one request can log both, as a cause and its outcome, and they share a request_id. A view
that wants a single line per event can drop the parent and keep the child.
A refining code is for your logs, not for the caller. The client is told the parent, because its situation and its fix are the same whichever of the two ceilings answered, and which one did is something only you can see. Do not switch on a refining code in client code: it never arrives there. The tables below mark each one log only.
Where a row below says what a code’s detail names (a header, a cookie, a client), that detail is shown beside the code: under Edge codes in the request’s Overview tab for a decision or refusal, or on the log line for a warning or error.
The request was rejected
Section titled “The request was rejected”These end the request with the status shown. The caller receives the code in the body: for a
GraphQL request, as a GraphQL error carrying it in extensions.code; for any other request, as an
application/problem+json body carrying it in title. Rows marked log only appear in your
logs and telemetry, while the caller is told the code they refine.
Your origin is not called for any of them, except BAD_GATEWAY, GATEWAY_TIMEOUT and the codes
that refine them, which are produced after the request was sent on, to your origin or, with
shielding, to the shield, and no usable answer came back.
| Code | Status | Refines | Meaning |
|---|---|---|---|
UNAUTHENTICATED | 401 | A credential GraphPilot could not accept. | |
AUTH_ | 401 | UNAUTHENTICATED | The signature did not verify, the token expired, it carries no exp and the provider does not set allow_, or a required claim did not match. |
AUTH_ | 401 | UNAUTHENTICATED | The token names a key GraphPilot does not have, and refreshing did not produce it. |
AUTH_ | 401 | UNAUTHENTICATED | No key material at all for this provider, affecting every request. |
FORBIDDEN | 403 | The operation is not on this service’s allowlist. | |
INTROSPECTION_ | 403 | The service does not answer schema introspection. Does not apply to operations sent over a subscription socket. | |
METHOD_ | 405 | The operation may not be run with the method the request used. The response carries an Allow header naming the method to use. | |
PAYLOAD_ | 413 | The request body is over a limit this service sets, checked before the body is parsed. This is what the caller is told for either of the two rows below; the message carries no numbers, so a caller learns only that it sent too much. | |
REQUEST_ | 413 | PAYLOAD_ | Log only. The body was over request., the service-wide ceiling, checked before the client is even identified. |
CLIENT_ | 413 | PAYLOAD_ | Log only. The body cleared the service-wide ceiling but was over the max_body set on the client profile that matched it. Its detail names the client. |
UNSUPPORTED_ | 415 | The body sent to a GraphQL path is not a single operation GraphPilot can check (a JSON batch, a multipart upload, text, no content type, or JSON that names no operation), and the service does not forward that kind unchecked. See bodies on a GraphQL path. | |
INGRESS_ | 415 | UNSUPPORTED_ | Log only. Names the kind of body that was refused (batch, multipart or other); listing that kind in request. forwards such bodies to your origin unchecked. |
INVALID_ | 400 | The request on a GraphQL path carries an operation GraphPilot cannot read unambiguously, so it is refused rather than forwarded unchecked: your origin might read it differently, and none of GraphPilot’s checks would then have seen what it runs. The message says what was wrong with the request’s form. | |
INGRESS_ | 400 | INVALID_ | Log only. reason says what could not be read: duplicate_ (the JSON body repeats a key within one object), too_deep (the JSON body nests more than 128 levels), repeated_ (a GET repeats query, operationName, variables or extensions), unreadable_ (a GET’s variables or extensions is not JSON, repeats a key, or nests too deep), query_ (a POST, or a GET with a body, carries query, variables or extensions in its URL, or an operationName that differs from the body’s), or coded_body (the body carries a Content- such as gzip, deflate, br or zstd: a body on a GraphQL path must be sent uncompressed; identity counts as uncompressed). Refused whatever request. lists. |
NOT_ | 404 | The request carries a GraphQL operation on a path that is not one of this service’s GraphQL paths (request.), where the GraphQL checks do not run, so it was refused rather than forwarded unchecked. Sent while at least one of those checks is in force, which by default it is. The message names the GraphQL paths. A query that is not a GraphQL document, such as a search term, is never refused. See GraphQL on other paths. | |
INGRESS_ | 404 | NOT_ | Log only. form says where the operation was found: json_body (a JSON body whose top-level query is a GraphQL document, or that carries extensions.), document_ (an application/ body), form_body (an application/ body) or query_string (the URL’s query or extensions parameter). protections lists the checks the operation would have gone around: introspection_, trusted_, client_, max_, max_, max_. Point the client at a GraphQL path, or add this path to request. if your origin serves GraphQL there. |
GRAPHQL_ | 400 | INVALID_ | Log only. The GraphQL document could not be parsed. A leading byte-order mark is skipped first. A block string with a backslash directly before a quote is refused here too, because parsers disagree on where such a string ends. |
GRAPHQL_ | 400 | INVALID_ | Log only. The operation selects no field your deployed schema defines. |
GRAPHQL_ | 400 | INVALID_ | Log only. The document defines several operations and the request named none, so none could be selected. |
GRAPHQL_ | 400 | INVALID_ | Log only. The request named an operation the document does not define. |
QUERY_ | 400 | The operation nests deeper than this service allows. The message gives the depth and the limit. Also returned, whatever request. says and even with it set to 0, for a document nested more than 128 levels deep, counted on its brackets before it is parsed or on its fields with fragments expanded: the ceiling GraphPilot keeps for itself so a document cannot exhaust its stack. Does not apply to operations sent over a subscription socket. | |
DOCUMENT_ | 400 | The operation’s document is longer than this service allows (request., or a lower client profile value). The message gives the length and the limit. See query document limits. Does not apply to operations sent over a subscription socket. | |
DOCUMENT_ | 400 | The operation makes more selections than this service allows (request., or a lower client profile value), counted with fragment spreads expanded. The message gives the count and the limit. Does not apply to operations sent over a subscription socket. | |
RESERVED_ | 400 | The document uses an alias, or a field name without an alias, that starts with origin. (__gp_ by default). GraphPilot names the fields it adds with that prefix and removes everything carrying it from the response, so a key of your own there would disappear from the answer. Every operation and fragment in the document is checked, including ones the request does not run. The message names the key and the prefix; rename the alias. An empty field_prefix turns the check off. See selection set expansion. | |
UNKNOWN_ | 403 | The request matched no client profile this service declares, and client. is "reject". | |
CLIENT_ | 403 | The client is declared, but the version it reported is below that profile’s min_version. The required version is in the message. | |
PERSISTED_ | 403 | The client’s profile sets persisted_query = "required" and the payload was not a hash on its own. A document is refused even when a hash travels beside it, which is what an APQ registration looks like. Does not apply to operations sent over a subscription socket. | |
INTERNAL_ | 500 | GraphPilot failed to process the request. The message says nothing about why. | |
BAD_GATEWAY | 502 | The origin could not be reached or did not answer usably. | |
ORIGIN_ | 502 | BAD_GATEWAY | Log only. Your origin compressed its response although GraphPilot asked for an uncompressed one, and the body would decode to more than GraphPilot processes. |
ORIGIN_ | 502 | BAD_GATEWAY | Log only. Your origin compressed its response although GraphPilot asked for an uncompressed one, and the body did not decode under the coding its Content- named. |
ORIGIN_ | 502 | BAD_GATEWAY | Log only. Your origin compressed its response with a coding GraphPilot cannot decode. It decodes gzip, deflate, br and zstd. |
SHIELD_ | 502 | BAD_GATEWAY | Log only. With shielding, the shield answered this edge location with a compressed body although the edge asked for an uncompressed one, and the edge could not decode it. reason says why (unsupported, corrupt or too_large). The edge location does not then call your origin itself. |
SHIELD_ | 502 | Log only. The caller receives BAD_GATEWAY. With shielding, a mutation could not be sent through the shield, so this edge location answered 502 rather than send it to your origin itself, which could run it twice and would purge this location’s cache before the shield’s. reason says why, with the same values as SHIELD_. A request whose operation the edge cannot see (a persisted query hash without a document) is treated the same way when the failure came after the request may have left the edge location, since it may be a mutation. shield_ is UNAVAILABLE. | |
GATEWAY_ | 504 | With shielding, the request was sent to the shield and no usable response came back: the hop exceeded its time budget, or the connection dropped or the response was incomplete, oversized or malformed. Applies to queries and mutations alike. The edge location does not then call your origin itself, so your origin is never called twice for the same request. shield_ is TIMEOUT. See shielding and the origin connection. |
A body sent to a GraphQL path (request.graphql_paths) is read as one operation: JSON carrying
a query or a persisted query hash, or an application/graphql document, which is checked like
any other operation. A JSON body that repeats a key or nests more than 128 levels deep, and a
document that does not parse, are refused with 400 and INVALID_GRAPHQL_REQUEST. Any other body is refused with 415 and UNSUPPORTED_GRAPHQL_BODY before it
reaches your origin, unless its kind is listed in
request.forward_unchecked_bodies. See persisted
queries. A GraphQL operation sent to any other
path is refused with 404 and NOT_A_GRAPHQL_PATH; see GraphQL on other
paths.
GraphPilot sends every origin request with Accept-Encoding: identity, because it has to read
the response body to derive surrogate keys and strip the fields it injected. The edge compresses
toward the client itself. An origin that compresses anyway costs a decode
(ORIGIN_RESPONSE_DECODED, below) or, when decoding fails, a 502. See response
headers.
See authentication, limits and origin timeouts for what each of these is protecting.
The response was not cached
Section titled “The response was not cached”Every code here arrives on gp-cache-reason alongside gp-cache: PASS. MISS means the
response was stored, so a response that was not stored never reports MISS, whatever the reason
(see which status a refused store
reports). The response is still
delivered to the client in full; only storing was refused. One code in this group,
CACHE_SKIPPED_NO_ORIGIN_RESPONSE, never reaches gp-cache-reason, because no response
reaches the client on its path: it appears only in log lines and
request telemetry. The order these are evaluated in is
on when a response is cached.
| Code | Meaning | What to do |
|---|---|---|
CACHE_ | The operation was a mutation or a subscription. Only a query is ever cached, whatever the origin’s Cache- said. | Nothing. This is by design. |
CACHE_ | Neither an origin Cache- header nor a schema cache policy applied. | Send Cache- from your origin, or annotate the schema with @cacheControl. |
CACHE_ | The request’s own Cache- said no-store, so its response was not cached. The cache was still read. | Nothing, unless the client should not send it. Set cache. to cache such responses anyway. See the client’s own Cache-. |
CACHE_ | The cache already held an entry for this key that the request did not use, because it was older than the matching client profile’s max_age or could not be read, and the entry was not replaced. An unreadable entry is normally overwritten with the origin’s answer (reported as MISS); this code means the overwrite was not possible. The response was served from your origin without being stored, and the entry is unchanged. | Nothing for a client with a max_age: this is what the cap does. |
CACHE_ | The cache had no usable entry, but no origin response came back to store: the origin request failed, or GraphPilot answered the request itself before reaching the origin. Nothing was cached. Log and telemetry only, never on gp-. | Look at the request’s other codes: an origin failure (BAD_GATEWAY, a timeout) or a refusal explains it. |
CACHE_ | The deciding policy stated no freshness: the origin response carried neither max-age nor s-maxage, or the schema policy resolved no positive maxAge. Checked before the discriminators, so it is also what a private response with no max-age reports. | Add max-age or s-maxage at the origin, or a maxAge to every type the query selects. |
CACHE_ | The origin response was marked no-store. | Nothing, unless the marking is wrong. |
CACHE_ | The origin response was marked no-cache, which forbids reuse without revalidating with the origin first. | Use max-age to cache it, no-store not to. |
CACHE_ | Your origin answered with a 4xx or 5xx status that cache. does not list, so the response was not stored, whatever its Cache- said. | Nothing, unless the error is meant to be cached. To store one on purpose, such as a 404 for a resource that does not exist, list its status in cache.. |
CACHE_ | The response to a GraphQL request was not JSON GraphPilot could parse, or was larger than it parses. It was delivered to the client unchanged but not stored, because neither GraphQL errors nor surrogate keys could be read from it. | Check that your origin answers GraphQL requests with a JSON body. |
CACHE_ | Your origin returned a GraphQL error. An errored response describes the moment it was produced rather than the data, and caching it would serve that failure to every later request for the same query until the entry expired. | Nothing, unless the error is unexpected. Fix it at the origin. |
CACHE_ | The response is partitioned, by an origin private or by the schema’s scope for the query (also when the origin header decided the lifetime), but at least one discriminator it is keyed on was not available, so it could not be filed. On the schema path its detail separates names the request did not carry from names nothing configures. Also reported when the request carried a credential and a [[cache. rule that partitions the response resolved to nothing or to the empty string; its detail names those rules under unresolved. | For a private response, configure cache. and check that its expression resolves for this request. For a named scope, check that every name it lists has a [[cache. entry whose expression resolves. |
CACHE_ | The query selected a field, or a fragment on a type, that the deployed schema does not declare, at any depth, usually because your origin was deployed before the schema was. Who may share that field’s value is unknown, so the response was not stored, whatever the origin’s Cache- said. | Deploy the schema your origin runs. See deploy the schema before the origin. |
CACHE_ | The request carried a credential (Authorization, a Cookie naming at least one cookie that is not on cache., or the header an [auth. block is configured to read), the origin never said the response may be held by a shared cache, and no discriminator identified the caller. Its detail names which header carried the credential, and for cookie also names the first blocking cookie, unless the header’s value carries no readable cookie name at all. | Mark the response public or s-maxage if it is the same for everyone, configure a private policy, or add the cookie to cache. if it carries no session, as App Engine’s GAESA does by default. See authenticated requests. |
CACHE_ | The response carried Set-Cookie or Set-Cookie2 setting at least one cookie that is not on cache.. A cache entry would re-assert that cookie on every hit until it expired. Its detail names the blocking cookie, not just the header. | Stop setting cookies on reads, mark the response no-store, or add the cookie to cache. if it carries no session. This is the most common cause of a service that caches nothing. |
CACHE_ | The response carried a header stating something about one caller’s session: an authentication challenge, Clear-, or Set-Login. | Send it only on the responses that need it. |
CACHE_ | The response carried Vary: *, so no cache entry could legitimately match a later request. | Name the headers you actually vary on. |
CACHE_ | The response carried no surrogate keys. | Rare. Check that the request reached the GraphQL layer at all. |
CACHE_ | The surrogate keys exceeded the 16,000-byte cache header limit. | Paginate the response. See how many keys fit on one response. |
CACHE_ | The response could not be encoded for storage. | Nothing. This is an internal fault, not a configuration problem. |
CACHE_ | The cache storage refused the write. | Nothing. This is an internal fault. |
CACHE_ | With shielding, the response came from the shield, which had already held it for its whole max-age, so no lifetime was left for a copy at this edge location. An entry never lives longer in total than your origin allowed. | Nothing. This is by design. |
CACHE_ | With shielding, the shield’s response was served but not stored at this edge location: it came back STALE or PASS, or its metadata or age could not be read. A stale or uncacheable response is never copied to every edge location. | Look at the shield’s own reason for why the shield did not store it: the portal shows it beside this code. When shield_ is HIT or MISS, SHIELD_ on the same request says what could not be read. |
A response with a 4xx or 5xx status is not stored by default, even when its Cache-Control
permits it, and reports PASS with CACHE_SKIPPED_ERROR_STATUS. A status listed in
cache.policy.store_error_statuses is stored under the ordinary rules instead. See an error
status is not stored unless you opt
in.
The cache was not consulted
Section titled “The cache was not consulted”Reported alongside gp-cache: PASS, like the codes above. Most of these passes are decided
before a lookup; CACHE_PASS_READ_DISABLED follows a lookup that failed. Either way the request
went to your origin and its response was not stored.
| Code | Meaning |
|---|---|
CACHE_ | The service’s cache bypass policy matched this request. |
CACHE_ | The request method is listed in cache., which defaults to OPTIONS. |
CACHE_ | A WebSocket upgrade is never cacheable and always reaches the origin. |
CACHE_ | The request produced no cache key. Also for a GraphQL request whose body carries a document together with a persisted query hash that is not that document’s hash: it is never looked up or stored, and runs as before. |
CACHE_ | The cache could not be read, so the request went to the origin. |
CACHE_ | The client profile that matched this request sets cache = "off". |
CACHE_ | The request’s own Cache- said no-cache or max-age=0, and cache. is "honor". Off by default, because any caller can send it. |
Something succeeded
Section titled “Something succeeded”These are not problems. They appear in logs and telemetry, not as a gp-cache-reason.
| Code | Refines | Meaning |
|---|---|---|
CACHE_STORED | The response was written to the cache. | |
CACHE_ | CACHE_STORED | The request carried a credential and the origin never said the response may be held by a shared cache, so the entry was partitioned under the private cache policy rather than shared. |
CACHE_ | CACHE_STORED | The origin response carried stale- together with must- or proxy-. Those forbid serving it once stale, so the entry was stored for its max-age only and will not be served STALE. Its detail carries the dropped window as stale_. |
CACHE_ | Your origin sent a Cache- header, so it decided the response, outranking the schema’s @cacheControl policy. overrode_ says whether there was a schema policy to outrank, and cache_ carries the header verbatim. | |
CACHE_ | Your origin sent a Cache- that constrains neither storage nor freshness (a bare private, for example), so it decided nothing and the schema’s @cacheControl policy governed instead. A private on such a header still partitions the entry. Turned off with cache.. | |
CACHE_ | The schema’s @cacheControl policy decided the response. max_age is the lowest across every field the query selected, and max_ names the fields that set it (only those that declared the value rather than inherited it, capped and marked and more when there were further ones). Not restored on a cache hit. | |
CACHE_PURGED | Surrogate keys were purged from the cache. See automatic invalidation. | |
ORIGIN_ | A failed origin request was sent again, per the configured retry policy. | |
SHIELD_ | With shielding, the request could not be sent through the shield, or what came back was not a shield response, so this edge location went to the origin itself. A query falls back on any of these failures. A mutation never falls back: it is answered 502 instead (SHIELD_), and so is a request whose operation the edge cannot see once it may have left this location. A failure after the shield may have started on the request is a 504 (GATEWAY_) instead, for every request. | |
SHIELD_ | With shielding, the shield’s response was served to the client, but this edge could not read what it needs to store the response here (its metadata was missing, too large, or did not decode), so it was passed through without being cached at this location. | |
SHIELD_ | With shielding, the shield purged a mutation’s surrogate keys but could not tell this edge location which, so the edge did not purge them again on the way back. reason says why (too_large, unencodable or undecodable). This location’s copy of those entries is then cleared only when the shield’s purge reaches it. | |
SHIELD_ | A request arrived claiming to come from another edge location but did not verify as such, or arrived at a location other than the shield it names, so it is served as an ordinary request rather than as a hop from another edge location. When it came from another edge location, the shield records it as a hop entry rather than as a client request; like every request the shield runs, it counts at the shield toward request units. | |
ORIGIN_ | Your origin compressed its response although GraphPilot asked for an uncompressed one, so GraphPilot decoded it first. The response is keyed and cached as usual; encoded_ and decoded_ carry both sizes. Configure your origin to honor Accept-Encoding: identity to save the work. | |
INGRESS_ | A header in the reserved gp- namespace that the client sent was dropped. Its detail names the header. See headers. | |
INGRESS_ | A header that a [[cache. entry claims was dropped from the client’s request, so a caller cannot choose its own value. Its detail names the header, never its value. | |
CLIENT_ | A client profile sets a min_version that is not a valid version, so the floor is ignored and no request is refused for it. Your traffic is unaffected; the setting is not doing anything. | |
CLIENT_ | A client profile requires persisted queries while persisted_query. is false (for example with preset = "off", the default), so the service offers the client nothing it could send. The requirement is ignored rather than refusing all of that client’s traffic. | |
CACHE_ | A cached entry was older than the max_age on the matching client profile, so that one request went to the origin. The response reports PASS with CACHE_. The entry is unchanged and still serves other clients. |
Persisted queries
Section titled “Persisted queries”See persisted queries for the switches these belong to.
| Code | Status | Refines | Meaning |
|---|---|---|---|
PERSISTED_ | 200 | The request carried a hash, and this service does not resolve persisted queries (persisted_query. is false). Sent to the client as a GraphQL error, with HTTP 200 so that Apollo clients read it rather than treating it as a transport failure. | |
PERSISTED_ | 200 | A persisted query hash is not registered at this location. Under APQ this is the normal signal for the client to resend the document. Sent as a GraphQL error with HTTP 200, for the same reason. | |
PERSISTED_ | A client registered a persisted query with this service. | ||
PERSISTED_ | A persisted query was offered under a hash that is not its own and was not registered. The operation still runs, so the client receives your origin’s status. | ||
PERSISTED_ | 405 | METHOD_ | Log only. The caller receives METHOD_. An operation sent as a GET, as a document or as a persisted query hash, is not a query. |
PERSISTED_ | 403 | FORBIDDEN | Log only. The caller receives FORBIDDEN. An operation that is not in this service’s registry was refused. |
PERSISTED_ | 403 | FORBIDDEN | Log only. The caller receives FORBIDDEN. The registry could not be read, so the operation was refused. |
PERSISTED_ | An operation ran that only_ would have refused. Emitted while audit_ is on, so you can see what your manifest is still missing. |
Telemetry values
Section titled “Telemetry values”A few fields of request telemetry carry stable values of their own rather than codes. They are what the Lookup and policy details in the Insights request detail are drawn from.
cache_lookup
Section titled “cache_lookup”What the cache lookup found. Empty when no lookup ran (a pass decided before the lookup, or lean mode).
| Value | Meaning |
|---|---|
FOUND | An entry was found and answered the request, as a HIT or a STALE. |
NOT_FOUND | The cache held no usable entry for the key. |
UNREADABLE_, UNREADABLE_, UNREADABLE_ | An entry was found but could not be read. It is replaced with your origin’s answer; there is nothing to do. |
DECLINED_ | An entry was found and was older than the max_age of the client profile that asked. The entry is left as it is. |
FAILED | The lookup itself failed: the cache was not reached (CACHE_). |
cache_policy_source
Section titled “cache_policy_source”Which policy decided whether the response was stored, recorded whether the decision was to store or to refuse.
| Value | Meaning |
|---|---|
ORIGIN_ | Your origin’s Cache- decided, outranking any schema policy. |
SCHEMA_ | The schema’s @cacheControl policy decided. |
NONE | Neither applied: no origin header that decides, and no schema hint for what the query selected. |
shield_pop, shield_result and shield_node
Section titled “shield_pop, shield_result and shield_node”With shielding switched on, three further fields record the hop
to the shield. shield_pop is the shield location’s alias, shield_node the individual node
within it that answered, and shield_result how the hop ended:
| Value | Meaning |
|---|---|
HIT | The shield answered from its own cache. |
MISS | The shield had no usable entry, fetched the response from your origin and stored it. |
STALE | The shield answered a stale entry, per its own stale handling. |
PASS | The shield stored nothing for this request: it did not use its cache, did not store your origin’s response, or answered without its cache, for example because your origin failed or the request was refused there. The shield’s own reason is recorded on its entry. |
FALLBACK | The request could not be sent through the shield, so this edge location went to the origin itself. See SHIELD_. |
TIMEOUT | No usable response came back from the shield: the hop exceeded its time budget, or the connection dropped or the response was incomplete, oversized or malformed. The caller receives 504. See timeouts on a shielded service. |
UNAVAILABLE | A mutation could not be sent through the shield, so this edge location answered 502 rather than send it to your origin itself. See SHIELD_. |
shield_node is empty when no shield answered: on FALLBACK, UNAVAILABLE and TIMEOUT.
The portal’s Insights view shows them in the request detail panel, and can filter the list by the shield result.
GraphPilot API codes
Section titled “GraphPilot API codes”The GraphPilot API reports errors in the GraphQL response format, in an ordinary
200 response. The exception is a credential that is presented and refused: an sk_ key that is
not a usable key, and any other bearer value that does not verify as a sign-in token, are refused
with 401 and UNAUTHENTICATED before any operation runs, an sk_ key that could not be
checked at that moment with 503 and SERVICE_UNAVAILABLE, and an unrecognized sk_ key sent
over the validation budget with 429 and
TOO_MANY_REQUESTS. See when a credential is
refused. POST /purge,
the REST form of two purge mutations, carries the same codes and answers each with a matching HTTP
status instead of 200.
Most errors carry their code in extensions.code; some refusals carry it in
extensions.error_code instead. Read both, and branch on the code.
{ "errors": [ { "message": "Not authenticated to an organization", "extensions": { "code": "UNAUTHENTICATED" } } ], "data": null}extensions.code
Section titled “extensions.code”| Code | Meaning |
|---|---|
UNAUTHENTICATED | The request carried no valid credential, or the credential is not scoped to an organization. In a 200 for a missing credential; in a 401 for an sk_ key that is not a usable key and for any other bearer value that does not verify. |
SERVICE_ | An sk_ key could not be checked at that moment. In a 503 with Retry-After; it says nothing about the key, so send the request again. |
FORBIDDEN | The credential is valid but lacks the permission the operation needs. Some operations name the missing subject and action in extensions. Also returned when an API key asks for or spends a confirmation code, and when the service did not accept the purge request’s signature, which a redeploy of the service resolves. |
NOT_FOUND | The thing addressed does not exist in the caller’s organization. A service in another organization is reported the same way, as Service not found. |
BAD_REQUEST | The input was refused, for example a surrogate key that is too long, too many keys in one purge, or a purgeByQueryHash with a variablesHash but no operationName for which your request telemetry holds no operation name (pass operationName, or null for an anonymous operation). The message says which. |
BAD_ | An argument over a documented bound, such as a persisted query push over 100 documents or a document over 100,000 bytes. The message names the bound, so the caller can chunk. |
PRECONDITION_ | The operation cannot run in the current state, for example a purge against a service that has never been deployed or has no active hostname, or a deployment that asks for the newest schema or configuration before one was uploaded (send withoutSchema or withoutConfig to deploy without it). |
OPERATION_ | Over HTTP in a 400: the operation’s document is over one of the API’s operation limits, checked before it is validated. extensions. names the limit (bytes, tokens, depth, selections or aliases) and extensions. its value. |
INVALID_ | cancelDeployment on a deployment that is no longer STARTED or PENDING. |
CONFIG_ | createDeployment with both withoutConfig and configVersion. Send one of them. |
SERVICE_ | applyServiceSetupSuggestion or keepServiceAsPlainHttp for a service that has no setup. |
SERVICE_ | A setup mutation that the setup’s current state does not allow, for example applying a suggestion while the first deployment is still running. Read Service. and its canRetryDetection, canApplySuggestion and canKeepAsPlainHttp. |
SERVICE_ | applyServiceSetupSuggestion for a suggestion without a schema, because introspection did not answer. Read the schema with gpilot schema pull and deploy it. |
SETUP_, SETUP_ | applyServiceSetupSuggestion for a service whose newest configuration or schema upload is your own, not one the setup wrote. The setup never replaces your files: set request. in your own gpilot.toml and run gpilot deploy. Retrying the detection and keeping plain HTTP stay allowed. |
ASSET_ | createDeployment for a service whose schema source is Introspect at deploy, or applyServiceSetupSuggestion: an upload of the service’s configuration or schema landed while GraphPilot was writing the same file, and nothing was written. Send the same request again. After applyServiceSetupSuggestion the setup stays in SUGGESTION_. |
SERVICE_ | retryServiceSetupDetection before any deployment of the service completed: detection runs through the service’s own hostname, which needs a live deployment. |
ORIGIN_ | createDeployment with a configuration whose origin timeout is above its ceiling on your plan. Nothing is created. The message names the key, the value and the ceiling. See origin timeouts. |
RUNTIME_ | The runtime version named in the request (createDeployment with coreVersion, or setServiceRuntimePin) was withdrawn. The message names the version and, where one was given, the reason. Pick another version. |
RUNTIME_ | createDeployment without coreVersion for a service pinned to a runtime version that was withdrawn. A withdrawn version can no longer be deployed, so nothing is created. The message names the pinned version and, where one was given, the reason. Pin a supported version or remove the pin. |
INVALID_ | The confirmation code was never issued for this action and subject, has expired, has run out of attempts, or is wrong. The four cases read the same. Request a new code. |
TOO_ | Too many confirmation codes requested: wait a minute and try again. Also, in a 429 with Retry-After, an sk_ key the API has not seen recently, sent while its client is over the validation budget for unrecognized keys. The key was not checked, so it says nothing about the key: send the request again after Retry-After. |
ACCOUNT_ | The billing account funding the organization is locked, so every mutation outside billing is refused (queries still answer). extensions. says why: PAYMENT_ (a payment is overdue: pay the outstanding invoice), FREE_TIER (a Glider (Free) account was locked under fair use this month: the lock lifts at the start of the next month (UTC), or at once on a paid plan once no invoice is outstanding) or MANUAL (contact support). extensions. lists the refused root fields. POST /purge answers it with 403. |
ORGANIZATION_ | Another organization already uses that slug. |
ORGANIZATION_ | createOrganization when your billing account already funds as many organizations as it may, 3 on every plan. Nothing is created. Delete an organization, or contact support to fund more. See organizations per billing account. |
TERMS_ | createOrganization, or subscribePlan for a paid plan, without an acceptance of the terms in force. extensions. is missing (no acceptance sent) or outdated (another version than legalTerms.), and extensions. names the version to accept. Nothing is created or ordered. See terms acceptance. |
BUSINESS_ | subscribePlan for a paid plan without the confirmation of ordering as a business (extensions. not_). Nothing is ordered. |
ACCOUNT_ | A mutation outside billing while the billing account that funds the organization is locked. extensions. is PAYMENT_ or MANUAL, and extensions. names the refused root fields. The whole operation is refused; queries are never refused. Over HTTP and WebSocket alike. |
ACCOUNT_ | Any mutation, billing included, in an organization that is being deleted, except a support request. extensions. is ACCOUNT (the account is being deleted) or ORGANIZATION (the organization is), and extensions. names the refused root fields. |
OUTSTANDING_ | A plan order while an invoice is still unpaid. Pay the outstanding invoice first. |
PAYMENT_ | A plan order while an earlier order is still being completed. Confirm its payment from the invoice if the bank asks, otherwise wait a minute and try again. |
PLAN_ | A plan order while an earlier plan change is waiting for the bank to confirm its payment. Confirm it from the invoice, or try again once it is discarded after about 23 hours. See when your bank asks you to confirm the payment. |
PAYMENT_ | The payment method was declined. |
RATE_LIMITED | The payment provider is rate limiting. Try again later. |
UNAVAILABLE | The payment provider could not be reached. |
INVALID_ | A billing checkout named a return URL the portal does not own. |
DOMAIN_ | The hostname is already attached to a service. |
DOMAIN_ | Not a valid hostname. |
DOMAIN_ | The plan’s custom domain allowance, pooled across every organization the billing account funds, is used up. |
DOMAIN_ | Custom domains are not included in the plan. |
DOMAIN_ | The organization has not verified a domain that covers this hostname. |
DOMAIN_ | Only a failed domain can be retried. |
SERVICE_ | A service must keep at least one active domain. |
SERVICE_ | A service must have exactly one primary domain, so the primary cannot be removed. |
SERVICE_ | The primary domain must be active. |
SERVICE_ | The platform domain can be disabled but not removed. |
INTERNAL_, INTERNAL_ | A failure on the GraphPilot side. The message stays generic; retrying is the available action. |
extensions.error_code
Section titled “extensions.error_code”Some refusals carry extensions.error_code, and where it applies field (the
input field) and description.
| Code | Meaning |
|---|---|
INSUFFICIENT_ | A read or write of organization data without a valid credential or permission. Message Insufficient permissions. |
SERVICE_ | The plan’s service limit is reached. See quotas. |
SUBDOMAIN_ | Another service of the organization already has this subdomain. For a service directly under graphpilot. (created before hostname labels), any other such service. |
SUBDOMAIN_ | A subdomain must be between 2 and 63 characters. |
SUBDOMAIN_ | A subdomain may contain only lowercase letters, numbers and hyphens. |
SUBDOMAIN_ | The subdomain is reserved. Only for a service directly under graphpilot.; under a hostname label every valid name is allowed. |
ORIGIN_ | The origin URL must be a valid HTTP or HTTPS URL. |
COLOR_ | A service color must be a hex color code. |
ORIGIN_ | The origin URL may name only the scheme, host and port, such as https:/. Remove the path, query or fragment; the paths served as GraphQL are set with request.. |
SERVICE_ | New services cannot be created right now, through no fault of the request. GraphPilot is notified automatically. Try again later. |
DOMAIN_, DOMAIN_ | As in extensions., for a write that was refused. |
UNIQUE_, CHECK_, FOREIGN_, NOT_ | A value already exists, is invalid, references a record that does not exist, or is missing. The message says which. |
Deployment failures
Section titled “Deployment failures”A deployment that fails after it was created reports why in Deployment.errorMessage, in the
portal and in gpilot. A failure to load an auth provider’s key set
starts with one of these codes, names the provider and every source that was tried, and stops the
deployment before it is activated, because the new version would otherwise refuse every token for
that provider with 401.
A deployment that ends CANCELLED because GraphPilot stopped it carries a reason in
Deployment.errorMessage too, without a code:
Replaced by a newer deployment of this service before it went live. when a newer deployment of
the same service replaced it, and Cancelled before it went live. otherwise. A cancellation that
was requested records none.
| Code | Meaning |
|---|---|
JWKS_ | The provider’s jwks_url is not a URL GraphPilot fetches: it does not parse, its scheme is not https, it has no host, or it carries credentials. Plain http is accepted only on localhost, 127.0.0.1 and [::1]; on any other host it is refused with a key set must be served over https. Fix the URL and deploy again. |
JWKS_ | No source answered with a usable response (a failed connection, a non-2xx status, or a body that is too large). The step is retried, since the issuer may be back a moment later. |
JWKS_ | A source answered, but what it served holds no key the provider can verify with: not JSON, no keys, an empty set, or only keys of another type than the provider’s algorithms need. |
CLI exit codes
Section titled “CLI exit codes”The gpilot CLI’s exit codes are stable, so scripts can branch on them.
| Code | Meaning |
|---|---|
0 | Success, or you canceled at a confirmation prompt, or you stopped watching a deployment (Ctrl+C, then “stop watching”). |
1 | General error: invalid input, something not found, a command that refused to run without --yes, or a deployment that failed, was canceled from somewhere else, or whose wait timed out. |
2 | Usage error: an unknown command, flag or value. |
3 | Authentication failed: no usable credential, or the GraphPilot API rejected it (HTTP 401). A permission the credential lacks is 4, not 3. |
4 | Network or GraphPilot API error, including a GraphQL error in the API’s answer. |
5 | Not used. A new deployment cancels one in progress instead of conflicting with it, so no command returns it, and it will not be given a new meaning. |
6 | This CLI version is too old. Upgrade it (see staying up to date). |
7 | The deployment’s outcome is unknown: watching it ended before it reported one (the progress stream closed, or could not be opened), and checking the deployment until --timeout ran out did not settle it. It is still open, or every check failed. Check with gpilot status. |
130, 143 | gpilot service create --deploy was stopped while following the setup: Ctrl+C gives 130, SIGTERM gives 143 (Unix). It prints its normal output first (with -o, the one document, signing key included), and the setup goes on at GraphPilot; follow it with gpilot service setup -. |