Docs
Try Apollo Studio

Schema reporting protocol reference

Add automatic schema registration to your GraphQL server


This document specifies the protocol that any GraphQL server can implement to enable automatic schema registration with the Apollo schema registry.

For reference, Apollo Server uses this protocol in its schema reporting plugin (see the source).

If you add schema reporting support to a GraphQL server library, please let us know in the Apollo community forums!

The reportSchema mutation

A server that reports its schema to Apollo does so periodically via a GraphQL API hosted at the following endpoint:

https://schema-reporting.api.apollographql.com/api/graphql

Specifically, a server executes the reportSchema mutation, which has the following shape:

mutation ReportSchemaMutation($coreSchema: String, $report: SchemaReport!) {
reportSchema(coreSchema: $coreSchema, report: $report) {
inSeconds
withCoreSchema
... on ReportSchemaError {
code
message
}
}
}

This mutation always requires a report input argument, which has the following minimum shape:

{
"bootID": "abc123",
"coreSchemaHash": "F908D0B24486E2B2A032ED019A657E7243BCDB7DC5D939AF69820FE7BADFB200",
"graphRef": "docs-example-graph@production"
}

The details of this operation and its inputs are described below.

Required information

To successfully report a schema to Apollo, your server requires the following information:

Graph API key

All requests to Apollo's schema reporting endpoint require an X-API-Key header:

X-API-Key: service:docs-example-graph:abc123

The value of this header is a graph API key. Developers obtain this API key from Apollo Studio and provide it to their server instance on startup.

We strongly recommend that your server accept the graph API key via an environment variable (e.g., APOLLO_KEY). This is because API keys are secret credentials that developers should never commit to version control.

Graph ref

Whenever a server reports its schema, it does so to a particular graph and variant in Apollo Studio. Apollo uses strings called graph refs to describe particular graph-variant combinations:

my-graph-id@my-variant

A developer can obtain a variant's graph ref from the variant's Schema page in Apollo Studio.

As with API keys, we recommend that developers provide their graph ref to your server via an environment variable (e.g., APOLLO_GRAPH_REF) that's loaded on startup.

Boot ID

Whenever an instance of your server boots up, it should generate a UUID to identify itself among other instances of the same server. You provide this ID with each reportSchema mutation you execute, so that Apollo can distinguish between requests from different instances.

An instance's boot ID should not persist across restarts.

Schema hash

Every reportSchema mutation requires the SHA-256 hash of the schema string you're reporting to Apollo, as a hexadecimal string:

F908D0B24486E2B2A032ED019A657E7243BCDB7DC5D939AF69820FE7BADFB200

You should always normalize your schema before generating this hash, as explained below.

Protocol sequence

The following sections illustrate the communication sequence between a GraphQL server (hereafter referred to as the edge server) and the schema reporting endpoint (Apollo) after the server starts up. Each of these sections illustrates the same concepts in different formats.

Diagram

Edge ServerApolloTopreportSchema(coreSchema=null, report={...})ReportSchemaResponse(withCoreSchema = false, inSeconds = 57s)Wait 57 secondsGo to TopReportSchemaResponse(withCoreSchema = true, inSeconds = 0s)Wait 0 secondsreportSchema(coreSchema="…", report={...} executableSchema = '…')Persist schemaReportSchemaResponse(withCoreSchema = false, inSeconds = 62s)Wait 62 secondsGo to TopReportSchemaError(code, message)Stop until error is fixedWait 20 secondsGo to Topalt[The reported schema hash is already registered][The reported schema hash is NOT already registered][reportSchema request is malformed or otherwise invalid][reportSchema request failed with non-2xx HTTP response]Edge ServerApollo

Step-by-step description

  1. On startup, the edge server executes the reportSchema mutation, providing a SchemaReport object with the server's details as input.

    • See the SchemaReport object's fields, and its minimum shape.
    • In this initial call, set the coreSchema argument of reportSchema to null.
    • If this or any other reportSchema request fails with a non-2xx response, the edge server should retry after 20 seconds.
  2. If the mutation succeeds, Apollo responds with a ReportSchemaResponse object. This response tells the edge server:

    • How many seconds to wait before sending the next reportSchema request (inSeconds)
    • Whether the next reportSchema request should include the coreSchema that corresponds to the coreSchemaHash provided in the previous request (withCoreSchema)

    If the mutation fails, Apollo responds with a ReportSchemaError object. In this case, the edge server should stop reporting its schema. This error should not cause the server to crash.

    • The message field of ReportSchemaError provides a human-readable message describing the error.
    • Correct the error and deploy a new version of your edge server to resume schema reporting.
  3. Assuming success in step 2, the edge server waits the specified number of seconds, then executes the reportSchema mutation again.

  4. Go to step 2.

Pseudocode example

val schema = normalize("type Query { .. }")
val report = SchemaReport(..)
val withSchema = false
function sendReport() {
val coreSchema = withSchema ? schema : null
val response = reportSchema(coreSchema, report)
if (response.code) {
throw exception
return
}
withSchema = response.withExecutableSchema
setTimeout(sendReport, response.inSeconds)
}
sendReport()

SchemaReport fields

These are the fields of the SchemaReport input type you provide as an argument to the reportSchema mutation.

Name /
Type
Description
bootId

String!

Required. A randomly generated UUID that's unique for each instance of your edge server. Set this value on server startup (a given value should not persist across restarts).

coreSchemaHash

String!

Required. The hexadecimal string representation of the normalized schema document's SHA-256 hash.

graphRef

String!

Required. Indicates which Apollo Studio graph and variant the server is reporting its schema to (e.g., my-graph-id@my-variant). See Graph ref.

serverId

String

An ID that's unique for each instance of your edge server. Unlike bootId, this value should persist across an instance's restarts. In a Kubernetes cluster, this might be the pod name, whereas the container can restart.

userVersion

String

An arbitrary string you can set to distinguish data sent by different versions of your edge server. For example, this can be the SHA of the Git commit for your deployed server code. We plan to make this value visible in Apollo Studio.

runtimeVersion

String

The runtime that your edge server is running, such as node 12.03.

libraryVersion

String

The name and version of the server and/or reporting agent your edge server is using, such as apollo-server-2.8 or graphql-java-3.1.

platform

String

The infrastructure environment that your edge server is running in (localhost, kubernetes/deployment, aws lambda, google cloud run, google cloud function, AWS ECS, etc.)

Schema normalization

Two semantically identical schemas can appear different to the schema registry, such as when those schemas list the same set of object fields in a different order. To avoid this scenario, every edge server should normalize its schema before sending it to the registry.

To normalize your schema, do all of the following:

  • Apply stable sorting (such as alphabetical) to the order of all type, field, and argument definitions.
  • Remove all redundant whitespace.
  • Remove all comments (but not docstrings).

Note that runtime dependencies on your schema document might result in poor user experience in tracking your schema changes, or even throttling of service availability.

Type definitions

The schema reporting protocol uses the following GraphQL types, referred to in Protocol sequence above:

type Mutation {
reportSchema(coreSchema: String, report: SchemaReport!): ReportSchemaResult
}
# This type's fields are documented above.
input SchemaReport {
bootId: String!
coreSchemaHash: String!
graphRef: String!
libraryVersion: String
platform: String
runtimeVersion: String
serverId: String
userVersion: String
}
interface ReportSchemaResult {
inSeconds: Int!
withCoreSchema: Boolean!
}
type ReportSchemaResponse implements ReportSchemaResult {
inSeconds: Int!
withCoreSchema: Boolean!
}
type ReportSchemaError implements ReportSchemaResult {
code: ReportSchemaErrorCode!
inSeconds: Int!
message: String!
withCoreSchema: Boolean!
}
enum ReportSchemaErrorCode {
BOOT_ID_IS_NOT_VALID_UUID
BOOT_ID_IS_REQUIRED
CORE_SCHEMA_HASH_IS_NOT_SCHEMA_SHA256
CORE_SCHEMA_HASH_IS_REQUIRED
CORE_SCHEMA_HASH_IS_TOO_LONG
EXECUTABLE_SCHEMA_ID_IS_NOT_SCHEMA_SHA256
EXECUTABLE_SCHEMA_ID_IS_REQUIRED
EXECUTABLE_SCHEMA_ID_IS_TOO_LONG
GRAPH_REF_INVALID_FORMAT
GRAPH_REF_IS_REQUIRED
GRAPH_VARIANT_DOES_NOT_MATCH_REGEX
GRAPH_VARIANT_IS_REQUIRED
LIBRARY_VERSION_IS_TOO_LONG
PLATFORM_IS_TOO_LONG
RUNTIME_VERSION_IS_TOO_LONG
SCHEMA_IS_NOT_PARSABLE
SCHEMA_IS_NOT_VALID
SERVER_ID_IS_TOO_LONG
USER_VERSION_IS_TOO_LONG
}
Edit on GitHub
Previous
Using the Rover CLI
Next
Graphs and variants