ShellYard

June 1, 2026 · graphql · api · introspection · devops

GraphQL Introspection: How to Map an Unknown Schema Fast

You've been pointed at a GraphQL endpoint with no docs. Here's how introspection works, the auth-on-introspection trap that catches half the field, and what to do when production has turned it off.

By ShellYard

A colleague drops a GraphQL endpoint URL into Slack and says "let me know if you can pull customer billing data from this." You hit /graphql and get the polite "use a GraphQL client" response. No schema docs are linked in the repo. Where do you start?

GraphQL has a built-in answer for this — introspection — and once you've seen it work the whole "how do I figure out what queries this server supports" question becomes ~30 seconds of work. Here's how it actually works, the auth gotcha that catches half the field, and what to do when production has introspection turned off.

What introspection is

Every spec-compliant GraphQL server exposes a meta-schema — a set of types you can query to discover the schema itself. The most useful entry point is __schema:

{
  __schema {
    queryType { name }
    mutationType { name }
    types {
      name
      kind
      fields {
        name
        type { name kind }
      }
    }
  }
}

Send that as a POST body with Content-Type: application/json against the GraphQL endpoint, and you get back a JSON document describing every type, query, mutation, and subscription the server supports. The full introspection query (the one good GraphQL clients generate behind the schema panel) is more elaborate — it walks input types, enums, descriptions, and deprecation reasons — but the principle is the same: ask the server about itself.

Here's the minimal curl:

curl -X POST https://api.example.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ __schema { types { name kind } } }"}'

That returns a flat list of every type the server defines, which is enough to start exploring. Iterate from there: pick a type, query its fields, follow the references.

What a good GraphQL client does for you

You don't want to be doing this by hand for long. Any decent GraphQL client (GraphiQL, Postman's GraphQL view, Insomnia, Bruno, ShellYard) does these on connect:

  1. Fires the full introspection query against the endpoint and parses the response.
  2. Renders the schema as a navigable tree — root Query and Mutation types at top, nested types under them, field signatures shown inline.
  3. Provides autocomplete in the query editor based on the introspected schema. You type customer(id: …) and the client knows customer exists and what arguments it takes.
  4. Validates queries before sending — if you write customers.email and the field is customers.emailAddress, the client tells you before the request goes out.

The schema panel turns "wander around with curl" into "click a field, see its sub-fields, drag the ones you want into the query editor." For an unfamiliar API, that's the difference between an hour and ten minutes.

The auth-on-introspection gotcha (where half the field gets stuck)

Here's the trap that consumes more debugging time than any other GraphQL workflow issue: the introspection request is itself a GraphQL request, and many production GraphQL servers require auth on every request — including introspection.

The bug looks like this. You configure auth on your collection (a Bearer token, an API key in a header, whatever). Your queries work — { customer(id: "123") { name } } returns the customer. You click "fetch schema" or open the schema panel, and the client errors with something like:

{"errors":[{"message":"Unauthenticated"}]}

…or it just shows an empty schema tree.

This is a real bug in a surprising number of API clients. The auth config gets applied to the send() path (when you fire a user-defined query) but not to the fetchSchema() path (when the client introspects on connect). The server demands a token on both, and the client only sends it on one.

The fix on your side is a config thing: explicitly enable "send auth on introspection" if your client offers the toggle. The fix in the client itself is that the auth-applying code should be shared between query execution and introspection — there's no good reason for them to diverge. (We hit this bug in ShellYard during pre-launch QA and tracked it as a regression — the GraphQL test in our QA checklist now explicitly verifies "Auth config passed to fetchSchema" so it can't regress again. ShellYard sends auth on both paths today.)

What production hides

The other thing worth knowing: introspection is often disabled on production GraphQL endpoints for security. The reasoning is that exposing your full schema gives an attacker a complete map of every mutation and type — including any internal admin-only fields you forgot to mark @private. So production responds to the introspection query with:

{"errors":[{"message":"GraphQL introspection is not allowed by Apollo Server"}]}

…or returns a 400, or returns an empty schema.

This isn't your client's bug; it's a server-side setting. What you can do:

  • Hit the staging or dev environment, which usually has introspection on. Map the schema there, then run your prod queries against prod.
  • Ask for the schema as an SDL file.graphql text format. Most GraphQL projects generate one at build time. A schema.graphql checked into the repo or attached to a CI artifact is the canonical source.
  • Use the codegen output if the team uses GraphQL Code Generator — schema.json in the generated folder is the introspection result, frozen at build time. It works in any client that imports schemas from disk.

For your own GraphQL servers, the standard pattern is: introspection on in dev/staging, off in production. Tools like Apollo Server expose a introspection: process.env.NODE_ENV !== 'production' config switch for exactly this.

A practical workflow for exploring an unknown schema

When someone drops a new GraphQL endpoint on you, the order I follow:

  1. Send the bare introspection query with the curl above. If it errors with an auth message, you know auth is required. If it returns data, the server's friendly.
  2. Open a GraphQL client and configure auth (Bearer, API key, whatever your access uses). Make sure the auth applies to introspection — toggle the setting if it exists.
  3. Open the schema panel. Find Query (the root). Expand its fields to see top-level entry points: customer(id: ID!), orders(first: Int, after: String), etc.
  4. Pick one and click into it. The return type shows its fields. Drag the ones you want into the query editor.
  5. Run with variables. Don't hardcode IDs in the query body — pass them in the variables panel. The query stays portable; the values change.
  6. If the endpoint has subscriptions, the schema panel shows them too. Test those over WebSocket. (How to test WebSocket and SSE endpoints covers the transport side.)

For mutations, the muscle memory is similar: pick the mutation, fill in the input types in the variables panel, run.

When introspection isn't enough

Some teams write fields with descriptions and deprecation reasons baked into the schema — description: "The legacy customer record. Prefer Customer instead." — and a good client renders those next to the field name. When introspection includes descriptions, it's almost a docs site for free. When it doesn't (because the schema author didn't write them, or because production stripped them), you fall back to:

  • The team's internal docs (schema.graphql in the repo with comments).
  • Reading the resolver code if you have access.
  • Asking the human who built it.

The mistake to avoid is guessing — GraphQL is type-safe enough that running a malformed query gets you a precise error message about which field is wrong. Use that. Iterate until the query parses, then iterate on semantics.

Where this fits in the cluster

The pillar for this series lists GraphQL handling as one of the surface-area requirements for an account-free API client. If you're picking a destination off Postman, make sure auth applies to introspection in whichever tool you choose — it's the single GraphQL-specific bug that distinguishes "fine" clients from "broken under realistic auth" clients. The Postman migration post covers what carries across, and the OAuth2 walkthrough is worth reading next if your GraphQL API is OAuth2-protected (most internal APIs at any size are).

If you want a client where introspection-with-auth is a tested-not-promised behavior, alongside SSH tunnels for private-network GraphQL endpoints, ShellYard is what I use. Free, local, no account: download it here.