Skip to content

GraphPilot API

The GraphPilot API is the GraphQL API the portal and the gpilot CLI are built on. Scripts and CI jobs call it directly. This page lists the operations documented for that use; the portal reads more through it (billing, members, settings), which is not covered here.

TopicWhere
Which permission each operation needsPermissions
Every error codeCodes
Operation limits, page sizes, batch sizes, stream lifetimesLimits
TransportAddress
Queries and mutationsPOST https://api.graphpilot.io/graphql, with a JSON body holding query and, where used, variables
Subscriptionswss://api.graphpilot.io/graphql, protocol graphql-transport-ws
Purging without GraphQLPOST https://api.graphpilot.io/purge, with a JSON body; see purging

Send an API key as a bearer credential:

Authorization: Bearer <api-key>
PropertyBehavior
SchemeWritten exactly Bearer, followed by one space.
Where keys come fromCreated in the portal under the organization’s Settings → API keys.
ScopeOne organization. Each operation acts in the organization the key belongs to. The one operation that takes an organization argument, deleteOrganization, only accepts that same organization.
PermissionsThe permissions selected when the key was created. See permissions.
Validation cacheA key’s validation result is cached for up to 60 seconds, so a revoked key can take up to a minute to stop working. See how long key changes take.
A missing, unknown or unusable credentialSee when a credential is refused.
SubscriptionsThe key travels in the connection_init payload, not in an HTTP header: { "Authorization": "Bearer <api-key>" }. Key names are matched case-insensitively.
Terminal window
curl -sS https://api.graphpilot.io/graphql \
-H "Authorization: Bearer $GRAPHPILOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ organization { slug services { subdomain } } }"}'

Errors follow the GraphQL response format: an errors array beside data, in an ordinary 200 response, with a code in extensions.code or, for some refusals, extensions.error_code. Every code is on codes. There are two exceptions. A credential can be refused before any operation runs (see below). An operation over one of the API’s operation limits is refused with 400 and OPERATION_LIMIT_EXCEEDED before it is validated.

How a bad credential is answered depends on what it looks like. A credential that was presented is never downgraded to an anonymous request: it is either accepted or refused before any operation runs. Read extensions.code whatever the status is.

The request carriesStatusWhat happens
No Authorization header200The request runs without an identity, and each operation refuses on its own terms: UNAUTHENTICATED, or Insufficient permissions for a read of organization data.
A value starting with sk_ that is not a usable key: never issued, revoked, rotated or mistyped after the prefix, a key that is not scoped to an organization, or one whose every permission is one an API key may not hold401Refused by the API before any operation runs, with WWW-Authenticate: Bearer error="invalid_token" and a GraphQL error whose extensions.code is UNAUTHENTICATED. The message names the reason, for example “API key not accepted: revoked, rotated or mistyped.” A value that cannot be a key at all (fewer than 8 characters after sk_, more than 512 in all, or a character a bearer credential may not contain) is refused the same way without being looked up, with “API key not accepted: it is not a well-formed key. Check that the whole key was copied.”
Any other bearer value that does not verify as a sign-in token: a key missing its sk_ prefix, a truncated or malformed value, an expired session token, or Bearer with nothing after it401Refused by the edge in front of the API before any operation runs, with a GraphQL error whose extensions.code is UNAUTHENTICATED and whose message is “The provided token could not be verified.”
An sk_ key that could not be checked at that moment503Nothing is said about the key. The response carries Retry-After: 5 and a GraphQL error whose extensions.code is SERVICE_UNAVAILABLE. Send the request again after that many seconds.
An sk_ key GraphPilot has not seen recently, sent while its client is over the validation budget for unrecognized keys429The key was not checked, so nothing is said about it. The response carries Retry-After with the seconds until the budget allows another check, and a GraphQL error whose extensions.code is TOO_MANY_REQUESTS. A key the API accepted within the last minute is answered from its cache and is not affected.

On a subscription, where the key travels in connection_init, the same outcomes close the WebSocket instead: 4401 for a refusal, 1013 (try again later) for a key that could not be checked or was not checked for want of budget.

A client that only parses 200 responses meets the 401 on exactly the case a typo in a configuration file or a revoked key produces. Handle every status.

Most operations start from a service. Look one up by its subdomain (the <subdomain> in https://<subdomain>.<label>.graphpilot.cloud, the value --service takes in the CLI):

query {
services(condition: { subdomain: "my-shop" }) {
id
subdomain
name
}
}

services returns a list holding the one service with that subdomain. id is the service’s id in this API. Every serviceId argument on this page accepts this id; the purge mutations also accept the subdomain.

OperationKindDoes
organizationQueryThe organization the request is scoped to, or null.
organizationsQueryEvery organization the caller has access to.
services(condition:)QueryThe organization’s services.
createService(input: { service: { name, subdomain, originUrl, environment, color }, deploy })MutationCreates a service. The payload’s signingKey is the service’s signing key in full, the only time it is returned. deploy: true also starts the setup: a first deployment without a schema and without a configuration file, then GraphQL detection. Omitted or false, nothing is deployed.
classifyServiceOrigin(originUrl:)QueryWhether the edge could reach an origin URL, decided from the URL alone without calling it: reachableFromEdge, and for a private or local address a reason (PRIVATE_ADDRESS, LOOPBACK_ADDRESS, LINK_LOCAL_ADDRESS, SHARED_ADDRESS, UNSPECIFIED_ADDRESS, MULTICAST_ADDRESS, RESERVED_ADDRESS, LOCAL_HOSTNAME or INVALID_URL) and a message. An IPv4 address carried in an IPv6 one (NAT64, translated, 6to4) is judged as that IPv4 address.
updateServiceById(input: { id, patch })MutationChanges a service’s settings and origin, and its schemaSource.
Service.schemaSourceFieldWhere a deployment takes its schema from when it names none: UPLOAD (the newest upload, the default), NONE (no schema) or INTROSPECT_AT_DEPLOY (read from the origin through the service’s hostname at every deployment). See schema source.
Service.hasOwnDeploymentFieldtrue once a deployment requested by a member or an API key completed, as opposed to the automatic first deployment of the setup.
requestActionConfirmation(input: { action, subjectId })MutationSends the caller a confirmation code by email. Returns sentTo (masked) and expiresAt.
deleteService(input: { serviceId, confirmationCode })MutationDeletes a service and tears down its edge infrastructure. Returns Boolean!.
legalTermsQueryThe terms in force: version, url and privacyPolicyUrl. Answered without a credential.
createOrganization(input:)MutationCreates an organization. Refused for an API key. Requires termsAcceptance: { termsVersion } naming legalTerms.version, see terms acceptance. A billing account funds at most 3 organizations; a fourth is refused with ORGANIZATION_QUOTA_EXCEEDED.
updateOrganization(input: { organizationId, name, slug, domains, websiteUrl, industry })MutationChanges the caller’s current organization. organizationId must name it; every other field is optional.
deleteOrganization(organizationId:, confirmationCode:)MutationDeletes the caller’s current organization. Returns Boolean!.
deleteMyAccount(input: { confirmationCode })MutationStarts deleting the caller’s own account; the deletion completes in the background. Returns Boolean!. See your account.
Service.url, Service.primaryDomainFieldThe hostname the service answers on: url as https:// plus the hostname, primaryDomain as its ServiceDomain. The service hostname until a custom domain is made primary with setPrimaryDomain.
Organization.accountLockReason, accountLockedAt, accountLockDueAt, accountTerminationDueAt, accountTerminatedAtFieldThe state of the billing account that funds the organization, readable without billing access: why and since when it is locked (PAYMENT_FAILED or MANUAL), and for an overdue payment the day it is locked, the day the paid plan may be terminated, and when it was. null when they do not apply.

Creating an organization and ordering a paid plan each accept the terms in force, and the API refuses both without that acceptance. legalTerms.version is the version to name; any other is refused, so read it again after the terms change.

OperationInputRequired
createOrganizationtermsAcceptance: { termsVersion }Always.
subscribePlantermsAcceptance: { termsVersion, actsAsBusiness, companyName }For a paid plan. actsAsBusiness must be true (the customer orders for a company or as a self-employed professional, a business in the sense of § 14 BGB). companyName is optional. Ignored for the free plan.

A refusal changes nothing. Without an acceptance, or with another version, the error is TERMS_NOT_ACCEPTED, with extensions.reason set to missing or outdated and extensions.currentVersion naming the version to accept. Without the business confirmation it is BUSINESS_CONFIRMATION_REQUIRED, with extensions.reason set to not_confirmed. Each accepted request is recorded with the version, the time and the user, by id and with the email address and name of the user’s login at that moment; a paid order before the order is sent. If the user’s details cannot be read, the request fails and nothing is created or ordered.

Invoices are addressed to the name on the billing account’s billing address (BillingAddress.companyName). A paid order with a companyName that is not blank replaces that name before the order is sent, and if that fails, the request fails and nothing is ordered. Without one, or with a blank one, the billing address keeps its name.

Deleting a service, an organization or your account needs a code sent by email.

RuleDetail
ActionsDELETE_SERVICE, DELETE_ORGANIZATION, DELETE_ACCOUNT (the action of requestActionConfirmation).
WhoAn interactive user only. An API key is refused with FORBIDDEN, both when requesting and when spending a code.
AuthorizationRequesting a code runs the same checks as the action itself: a subject the caller cannot see is NOT_FOUND.
LifetimeA code expires after 10 minutes and is spent by the action it confirms, so it works once.
Attempts5 per code. Every attempt counts, a correct one included; from the sixth on, even the right code is refused with INVALID_CONFIRMATION_CODE. Request a new code.
A new codeReplaces the live code for the same action and subject: the earlier one stops working.
Rate limitAt most 3 codes per user per 60 seconds, counted across all three actions (TOO_MANY_REQUESTS).
A wrong codeINVALID_CONFIRMATION_CODE, whether it was never issued, expired, out of attempts or mistyped.

A deployment builds from the schema and configuration last uploaded for the service, unless it names other versions or asks for none.

OperationKindDoes
createSchemaUploadUrl(serviceId:)MutationA signed URL. Upload the schema with PUT and Content-Type: application/graphql. The response’s x-goog-generation header is the version.
createConfigUploadUrl(serviceId:)MutationThe same for the configuration, with Content-Type: application/toml.
createConfigDownloadUrl(serviceId:)MutationA signed URL to GET the stored configuration. NOT_FOUND if none was uploaded.
Service.latestSchema, Service.latestConfigFieldversion, md5, crc32c, size, createdAt, and commitHash and commitMessage when the upload sent them as x-goog-meta-commit-hash and x-goog-meta-commit-message.
createDeployment(input: { serviceId, schemaVersion, withoutSchema, configVersion, withoutConfig, forceBuild, coreVersion })MutationStarts a deployment. An omitted schema version follows the service’s schemaSource (the latest upload by default); an omitted config version takes the latest upload. withoutSchema: true deploys without a schema, as a plain HTTP service with the GraphQL features off, and records a null schema version; it cannot be combined with schemaVersion. withoutConfig: true deploys without a configuration file, every setting at its default, and records a null config version; it cannot be combined with configVersion (CONFIG_VERSION_NOT_ALLOWED). forceBuild rebuilds even when a cached build exists. coreVersion is a runtime version; omitted, the service’s pin is used, else the stable version. For a service that introspects at deploy, refused with ASSET_CHANGED_CONCURRENTLY when an upload of the same file landed while GraphPilot was writing it; nothing is written, so send it again. Returns deployment.
cancelDeployment(input: { deployment })MutationCancels a deployment in STARTED or PENDING. Otherwise INVALID_STATUS. Returns result.
destroyDeployment(input: { serviceId })MutationTears down the service’s deployed edge infrastructure, keeping the service, which can be deployed again. Runs asynchronously; returns success and service. Deleting a service does this too.
deploymentProgress(deploymentId:)QueryThe deployment’s progress, once.
deploymentProgress(deploymentId:)SubscriptionThe same, sent on connect and again on every change.
Service.deployedSchemaFieldThe schema the service’s most recent completed deployment was built from. null if it was never deployed or deployed without a schema.
Deployment.schemaDocument, Deployment.configDocumentFieldThe schema and the configuration a deployment was built from. null when the deployment pinned none. configDocument needs service:edit, like createConfigDownloadUrl, because a configuration can hold a provider’s shared secret: without it the field alone is refused with FORBIDDEN and the rest of the query answers. schemaDocument needs only deployment:view, and configAnalysis stays readable with secrets redacted.
Deployment.configVersion, Deployment.usesDefaultConfigFieldThe configuration generation the deployment pinned, null for a deployment without a configuration file, for which usesDefaultConfig is true. Its configAnalysis still lists every setting, each with source: default.
Service.defaultConfigFieldThe effective defaults: the configuration report of the service’s newest completed deployment without a configuration file, the same shape as configAnalysis. null when there is none.
Deployment.requestedBy, Deployment.requestedByPlatformFieldWho asked for the deployment: requestedBy is a person or an API key, null when nobody was recorded. For a deployment GraphPilot requested itself, requestedBy is null and requestedByPlatform names why: SERVICE_SETUP for the automatic first deployment of the setup. requestedByPlatform is null for every other deployment.
Deployment.schemaSource, Deployment.schemaIntrospectionFieldWhere the deployment’s schema came from: UPLOAD, INTROSPECTION or NONE (null for deployments from before this was recorded). For a service that introspects at deploy, schemaIntrospection has succeeded, path, error and fallbackSchemaFrom, the upload time of the schema used instead when introspection failed (null when there was none, and the deployment then has no schema).
Deployment.schemaChangeFieldWhat changed against the schema that was live when the deployment was requested: previousSchemaVersion, addedTypes, removedTypes, addedFields and removedFields (as Type.field), and a summary such as +3 types, -1 field or No changes. null when either side has no schema.
Deployment.errorMessageFieldWhy the deployment ended without going live. For FAILED, the failure (see deployment failures). For CANCELLED, the reason when GraphPilot canceled the run: Replaced by a newer deployment of this service before it went live. when a newer deployment of the service replaced it, Cancelled before it went live. otherwise; null when the cancellation was requested, as with cancelDeployment. null for every other status.
Service.deploymentsFieldThe service’s deployments, newest first, with status (STARTED, PENDING, COMPLETED, FAILED or CANCELLED), diagnostics and configAnalysis.
DeploymentProgress fieldMeaning
deploymentIdThe deployment id.
statusRUNNING, COMPLETED, FAILED or CANCELLED.
stepsEach step’s id, name, status (PENDING, RUNNING, COMPLETED, FAILED, SKIPPED), startedAt, completedAt and error.
progresstotal, completed and percentage of steps.
timestampUnix time in milliseconds.
query {
services(condition: { subdomain: "my-shop" }) {
deployments(first: 1) {
id
status
createdAt
diagnostics { origin source severity message key line column }
configAnalysis {
unknown { path line }
inert { path line }
}
}
}
}

What the build found wrong in your schema and configuration without refusing the deployment.

FieldTypeMeaning
originString!Which document the finding is about: schema or config.
sourceString!Which check produced it, such as cache_control, surrogate_key or auth.
severitySchemaDiagnosticSeverity!ERROR (what you wrote has no effect), WARNING (it works as written, probably not as meant) or INFO (a notice, nothing to fix: for example that a deployment has no schema). None of them fails the deployment.
messageString!The finding, in words.
keyStringFor a configuration finding, the key it is about, such as client.profile[0].when. null for a schema finding.
lineIntOne-based line in the schema. null where the finding has no position, and always null for a configuration finding.
columnIntOne-based column in the schema, on the same terms as line.

null for the whole list means nothing was recorded: the deployment predates stored diagnostics or never got as far as building. An empty list means the build found nothing. origin and key read schema and null on records written before those fields existed. What each source reports is under deployments.

FieldTypeMeaning
unknown[ConfigKeyReference!]!Keys your configuration writes that nothing reads, usually a misspelling.
inert[ConfigKeyReference!]!Keys that are valid and read by nothing, reported only where you wrote them.
settings[ConfigSetting!]!Every setting the configuration resolved to, sorted by path, defaults included.
configString!The file name the configuration was read from.
versionInt!The shape of the stored report.

Each ConfigSetting has a path (such as client.profile[0].max_age), a value (JSON, null when redacted), a source (explicit when your configuration names the path, default when it does not) and redacted (true when the value is withheld). Service.configAnalysis returns the same report for the deployment the service runs now.

Each ConfigKeyReference has a path (the key) and a line (one-based, in the configuration file the deployment was built from). configAnalysis is null for a build with no configuration report, such as one older than 90 days.

The setup a service created with deploy: true goes through, the same one the portal shows as a card on the service’s dashboard and gpilot service create walks through. Poll Service.setup about every three seconds while state is DEPLOYING, WAITING_FOR_CERTIFICATE, DETECTING or APPLYING.

OperationKindDoes
Service.setupFieldThe setup, or null for a service created without it.
retryServiceSetupDetection(input: { serviceId })MutationProbes the origin again (state DETECTING). Needs deployment:edit. Starts a setup for a service that has none, once a deployment of it completed (otherwise SERVICE_NOT_LIVE).
applyServiceSetupSuggestion(input: { serviceId })MutationUploads the suggested schema and, when it is not the default, the suggested configuration, and starts a deployment with them, requested by the caller (state APPLYING). Only in SUGGESTION_READY and with a schema (SERVICE_SETUP_SUGGESTION_INCOMPLETE otherwise). Never replaces files of your own: refused with SETUP_WOULD_OVERWRITE_CONFIG or SETUP_WOULD_OVERWRITE_SCHEMA when the service’s newest configuration or schema upload was not written by the setup; canApplySuggestion does not reflect this. Refused with ASSET_CHANGED_CONCURRENTLY when an upload of the service’s configuration or schema landed while GraphPilot was writing it; nothing is written and the setup stays in SUGGESTION_READY, so apply again. Needs deployment:edit. Returns setup and deployment.
keepServiceAsPlainHttp(input: { serviceId })MutationCloses the setup and leaves the service as it is (state DONE, outcome PLAIN_HTTP). Not while something is running. Needs service:edit.

A mutation the current state does not allow is refused with SERVICE_SETUP_STATE_CONFLICT; the setup’s canRetryDetection, canApplySuggestion and canKeepAsPlainHttp say which are allowed. A setup does not stay in APPLYING or DETECTING: a step that cannot finish ends in FAILED with SETUP_INTERRUPTED.

ServiceSetup fieldMeaning
stateCREATED, DEPLOYING, WAITING_FOR_CERTIFICATE, DETECTING, SUGGESTION_READY (GraphQL found), NO_GRAPHQL_FOUND, ORIGIN_UNREACHABLE (the edge could not reach the origin), SKIPPED_PRIVATE_ORIGIN (a private or local origin, not deployed), APPLYING, DONE or FAILED.
outcomeFor DONE: GRAPHQL (the suggestion went live), PLAIN_HTTP (kept as plain HTTP) or OWN_DEPLOYMENT (a deployment of the caller’s own replaced the setup).
messageA sentence for the current state.
errorCodeFor FAILED, ORIGIN_UNREACHABLE, NO_GRAPHQL_FOUND and SKIPPED_PRIVATE_ORIGIN: NOT_ALLOWED_TO_DEPLOY, PRIVATE_ORIGIN, DEPLOYMENT_FAILED, CERTIFICATE_FAILED, CERTIFICATE_TIMEOUT (the certificate was still pending after 60 minutes), ORIGIN_UNREACHABLE, NO_GRAPHQL_FOUND, DETECTION_FAILED, SETUP_INTERRUPTED (a step stopped before it could finish, for example an apply that got no deployment within 10 minutes; retry the detection or keep plain HTTP), ACCOUNT_LOCKED or BEING_DELETED (the first deployment was not requested because the billing account is locked or the organization or account is being deleted).
initialDeployment, appliedDeploymentThe automatic first deployment, and the one applyServiceSetupSuggestion started.
detectiongraphqlPath (the path that answered { __typename }), originUnreachable (no probe got an answer from the origin), introspectionAvailable, introspectionError, attempts, and the probes, each with path, status, result and durationMs. result is GRAPHQL, NOT_GRAPHQL (the origin answered, but not as GraphQL, including its own 5xx), UNREACHABLE (the edge’s own 502 with BAD_GATEWAY, or no answer), REFUSED (the edge refused GraphPilot’s probe; the setup is then FAILED with DETECTION_FAILED) or NOT_TRIED.
suggestiongraphqlPath, configDocument (the gpilot.toml it deploys, null for the defaults), schemaDocument (the schema read by introspection, null without one) and typeCount.
createdAt, stateChangedAt, historyWhen the setup started and last changed state, and every state it passed through with its time and message.

A service created with deploy: true by a caller who may not create deployments is created, and its setup is FAILED with NOT_ALLOWED_TO_DEPLOY. Creating the service never fails because the setup could not be started; the service then has setup: null. The service hostname is Service.url.

The catalog of runtime versions a service can be built against. The two queries are answered without a credential.

OperationKindDoes
runtimeVersions(includeYanked: Boolean = false)QueryEvery version, newest first. Withdrawn versions only with includeYanked: true.
runtimeChangelog(from:, to:)QueryThe versions released after from, up to and including to (default: the stable version), newest first, withdrawn ones included.
Service.runtimePinFieldThe version the service is pinned to. null follows the stable version.
Service.runtimeVersionFieldThe version the active deployment was built with. null for a service never deployed.
Service.runtimeTargetFieldThe version the next deployment is built with: the pin, else the stable version. null while the pin names a withdrawn version.
setServiceRuntimePin(input: { serviceId, version, deployNow })MutationPins the service to version, or with version: null removes the pin. deployNow (default true) rebuilds the service with the version the pin resolves to; false saves the pin alone, and the service’s next deployment uses it. Even with deployNow: true, no deployment starts when the live deployment already runs that version and no deployment is in progress. Returns service, resolvedVersion (the pinned version, or the stable version for no pin), outcome and deployment. outcome is DEPLOYMENT_STARTED, ALREADY_RUNNING, DEPLOYMENT_DEFERRED (deployNow: false) or NEVER_DEPLOYED (the service’s first deployment uses the pin); deployment is the new deployment for DEPLOYMENT_STARTED and null otherwise. Removing the pin while no stable version exists is refused with PRECONDITION_FAILED (No runtime version is on channel stable yet).
RuntimeVersion fieldMeaning
versionThe name to pin.
statusACTIVE (offered), DEPRECATED (still served, no longer offered) or YANKED (withdrawn).
releasedAtWhen the version was made available.
yankedAt, yankReasonWhen and why it was withdrawn. null unless YANKED.
notesRelease notes, in Markdown. Empty when there are none.
channelsThe channels that point at it now, such as stable.

Four mutations purge a service’s cache. All take a serviceId (the id or the subdomain) and return the same PurgePayload. Every failure is a GraphQL error rather than a payload with success: false.

MutationAddresses
purgeBySurrogateKeyOne or more surrogate keys, or a cache key written with its ck: prefix
purgeByQueryHashA query, optionally narrowed to one variable set
purgeByRequestThe cache entry one telemetry request read or wrote
purgeAllEverything the service has cached
mutation {
purgeBySurrogateKey(input: {
serviceId: "my-shop"
keys: ["Product:id:42"]
}) {
purgedKeyCount
purgedKeys
}
}

purgedKeyCount counts keys, not cache objects. Inputs, soft purges and failure modes are in purging through the API.

purgeBySurrogateKey and purgeAll can also be sent without GraphQL, to POST /purge on the same host. It runs the mutation with the same credential and permission, answers 200 with the PurgePayload as the body, and answers a refusal with a matching HTTP status and the same errors list. The body forms and the status for each code are under over REST.

Terminal window
curl -sS https://api.graphpilot.io/purge \
-H "Authorization: Bearer $GRAPHPILOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"serviceId":"my-shop","keys":["Product:id:42"]}'

Every invalidation of a service’s cache, automatic and manual, newest first. See verifying a purge. The history is request telemetry, stored and kept under the rules on what Insights needs.

query {
services(condition: { subdomain: "my-shop" }) {
purges(
first: 20
timeRange: { from: "2026-09-22T00:00:00Z", to: "2026-09-23T00:00:00Z" }
filter: { trigger: [MANUAL] }
) {
nodes { id timestamp trigger soft keys keyCount error actor { email } }
pageInfo { hasNextPage endCursor }
}
}
}
ArgumentTypeMeaning
timeRangeTimeRangeInput!Required. from and to as ISO 8601 timestamps, both inclusive. Clipped to what your plan retains; a window entirely older returns an empty page.
firstPositiveIntPage size. Default 20, at most 1000.
afterStringThe endCursor of the previous page.
filterPurgesFilterInputOptional. trigger (AUTO, MANUAL), scope (EVERYTHING, KEYS), soft, outcome (SUCCEEDED, FAILED), key (at least one key containing this text, matched literally) and actorUserIds. Fields combine with AND.
PurgeEntry fieldTypeMeaning
idID!The entry’s id.
timestampDateTime!When the purge ran.
triggerPurgeTrigger!AUTO for a purge caused by a mutation, MANUAL for one sent through the API, the CLI or the portal.
softBoolean!true when entries were marked stale rather than removed.
keys[String!]!The surrogate keys addressed. Empty for a purge of everything.
keyCountInt!How many keys were addressed, not cache objects. 0 for a purge of everything.
errorStringnull when no error was recorded. Otherwise the cache is known to be wrong, and for a keyed purge an unknown part of the keys may already have been purged. The value is a fixed message, not the edge’s own failure text.
actorUserThe member who sent a manual purge. null for an automatic purge, for a purge sent with an API key (which is not recorded under a user id), and for a user who no longer exists.
triggeredByRequestRequestEntryFor an automatic purge, the mutation request that caused it, while it is within telemetry retention.
targetRequestRequestEntryFor a purge addressed by request id, the request named, while it is within telemetry retention.

See persisted queries for how the registry is used at the edge.

OperationKindDoes
pushPersistedQueries(input: { serviceId, documents: [{ hash, document }] })MutationRegisters manifest documents. Additive and idempotent: nothing is removed, and re-sending an entry is a no-op. Each hash is the hex SHA-256 of its document and is verified.
removePersistedQueries(input: { serviceId, hashes })MutationRevokes operations: deletes the registry entries and purges everything cached for them. Clients still sending these hashes break.
Service.persistedQueriesFieldThe manifest: every pushed operation, with usage(startTime, endTime).
Service.persistedQueryCountFieldHow many operations the manifest declares, revoked ones excluded.
Service.clientRegisteredQueries(first, after)FieldOperations clients registered over APQ that the manifest does not declare, one page of hashes at a time.
Service.registryEntry(hash:)FieldOne registry entry by hash, or null, with origin (APQ or PUSHED).
ResultFields
pushPersistedQueriesresults, one per document in the order sent: hash, status (ADDED, ALREADY_PRESENT, ADOPTED, REJECTED) and reason for a rejection; and warning, non-null when the service’s deployed configuration is not known to turn persisted queries on. A REJECTED entry does not fail the batch.
removePersistedQueriesresults, one per hash: removed (the registry held it and it was deleted), purged (everything cached for it was cleared; false means repeat the call for that hash) and recorded (the withdrawal was written to your manifest).

The request telemetry behind Insights. Every list takes a required timeRange, clipped to what your plan retains, and a filter (RequestsFilterInput); a filter or a sort your plan does not include is refused with FORBIDDEN.

OperationKindDoes
Service.requests(first, after, timeRange, filter, sort)FieldRequests, newest first. The connection’s analytics carries timeSeries(groupBy, granularity, topGroups), metrics(granularity), summary and top(limit).
Service.request(id:)FieldOne request by id.
Service.operations(...), Service.operation(id:)FieldThe same for GraphQL operations.
Service.errors(...)FieldFailures grouped by groupBy.
Service.clients(...)FieldTraffic by client.
Service.suggestions(field, search, limit)FieldValues seen for a filter field within the plan’s telemetry retention, for autocomplete. limit defaults to 20 and is clamped to 100.
Service.logs(first, after, startTime, endTime, requestId, level, target)FieldThe service’s log lines.
serviceRequestsStream(serviceId, after, filter)SubscriptionNew requests as they are recorded. Without after, only entries arriving after the subscription starts.
serviceOperationsStream(serviceId, after, filter)SubscriptionThe same for GraphQL operations.
serviceLogsStream(serviceId, after, level, target, requestId)SubscriptionNew log lines.

A request carries two ids. RequestEntry.id is its identity in GraphPilot, the one Service.request(id:), gpilot requests and the portal’s Entry ID use. RequestEntry.edgeRequestId is the edge platform’s own id for the request (Fastly’s request id), for correlating it with the edge platform’s logs, for example in a support case. It is visible on every plan, and null on a request recorded before GraphPilot stored it.

A request stream polls for new entries and closes itself after 5 minutes without one; a log stream closes after 30 minutes. Resubscribe with the last cursor as after to continue. Subscriptions authenticate in connection_init (see authentication).

OperationKindDoes
Service.signingKey, Service.pendingSigningKeyFieldThe active and the pending key, redacted: gpsk_, 24 bullets and the key’s last four characters. null without service:edit (and always for an API key), or when there is none.
createService payload signingKeyFieldThe key a new service is issued, in full. The only time it is returned.
rotateServiceSigningKey(input: { service })MutationGenerates a pending key. The payload’s pendingSigningKey is the key in full, the only time it is returned.
cancelServiceSigningKeyRotation(input: { service })MutationDiscards the pending key.
activateServiceSigningKey(input: { service })MutationMakes the pending key the active one.

service is the service’s id. How rotation works is on request signing.

OperationKindDoes
addServiceDomain(input: { serviceId, fqdn })MutationAttaches a hostname and starts its verification. Needs a paid plan and a domain your organization has verified. Returns the ServiceDomain.
ServiceDomain.dnsChallengesFieldThe DNS records to create for verification.
ServiceDomain.certificateStatusFieldFor a service platform hostname that gets a certificate of its own: PENDING while it is issued, ISSUED once the hostname serves TLS, FAILED when issuance gave up. null for every other hostname.
retryDomainVerification(id:)MutationRetries verification of a failed domain. Returns the ServiceDomain.
setPrimaryDomain(id:)MutationMakes an active domain the service’s primary hostname. Returns the Service.
removeServiceDomain(id:)MutationDetaches a domain. Starts a deployment that takes the hostname off the edge; the service keeps answering on it until that deployment finishes. Returns the Service.
enablePlatformDomain(serviceId:), disablePlatformDomain(serviceId:)MutationTurns the <subdomain>.<label>.graphpilot.cloud hostname on or off. It can be disabled, not removed, and only while the service has another active domain. Disabling also turns off an older <subdomain>.graphpilot.cloud kept beside it while a service is moved onto its label; enabling turns on only the current one. Returns the Service.

See custom domains for plans and verification.

The IP ranges Fastly’s edge network reaches your origin from, for an origin allowlist, which also has the example query and why to re-read them.

FieldTypeMeaning
ipv4[String!]!IPv4 ranges in CIDR notation.
ipv6[String!]!IPv6 ranges in CIDR notation.

clientIp: String is your public IP address as GraphPilot’s edge saw the request, IPv4 or IPv6. It answers only a signed-in caller (a person or an API key) and is null for an anonymous one, and null too when the edge reported no address. It carries no cache lifetime, so no response holding it is stored. gpilot requests --ip reads it. It is not affected by telemetry.client_ip, which changes what Insights stores: on a service that sets it to "truncated" or "none", RequestClient.ip is the truncated address or null, and the geo and network fields that setting drops are null (utcOffset reads 0).