---
name: "graphql_basics"
description: "Querying GraphQL APIs: selection sets, variables, fragments, cursor pagination, introspection, and error handling for agents fetching data from GraphQL endpoints. Triggers on: query a GraphQL API, write a GraphQL query, fetch data via GraphQL, introspect a schema, paginate GraphQL results."
metadata: { "includeInPrompt": true }
---

## Purpose

Fetch data from GraphQL APIs correctly and efficiently: write queries that ask for exactly the fields needed, pass arguments via variables (never string interpolation), page through connection-style results, discover a schema via introspection, and handle GraphQL's distinctive error model (errors come back inside a 200 response). Scoped to *querying* an existing API — designing a GraphQL schema or operating a server is a different problem.

## Workflow

1. **Shape the selection set.** A GraphQL query names the exact fields you want; the server returns only those. Start minimal and add fields as needed — over-fetching is the one failure mode REST trained you into; GraphQL gives you the control, so use it. Nested objects select nested fields. Aliases (`me: user(login: "x")`) let the same field appear twice with different arguments.

2. **Pass arguments via variables, never string interpolation.** Define `$vars` at the top of the query and send them as a separate `variables` JSON object. Interpolating user input into the query string is an injection risk and breaks caching.

3. **Post the query as JSON.** A GraphQL operation is an HTTP POST to the endpoint with `Content-Type: application/json` and a body of `{"query": "...", "variables": {...}}` (an `operationName` key disambiguates when the document holds several operations).

Example with a public demo endpoint (the command shape is standard GraphQL-over-HTTP; confirm the endpoint responds before depending on it):

```bash
curl -s -X POST https://countries.trevorblades.com/ \
  -H "Content-Type: application/json" \
  -d '{"query":"query GetCountry($code: ID!) { country(code: $code) { name capital currency } }","variables":{"code":"US"}}'
```

Expected response shape: `{"data": {"country": {"name": ..., "capital": ..., "currency": ...}}}` — fields exactly as requested, nothing more.

4. **Paginate connection-style results.** The Relay connection pattern is the common convention: a field returns `edges { node { ... } cursor }` and `pageInfo { hasNextPage endCursor }`. Loop: request `first: N, after: $cursor`, collect edges, set `$cursor = pageInfo.endCursor`, repeat while `pageInfo.hasNextPage` is true. Prefer this over offset-style args when the API offers both — cursors stay stable when rows are inserted mid-pagination.

5. **Reuse fragments.** A fragment defines a field set once and spreads it wherever needed — one query pulling users in two places defines `fragment UserFields on User { id name }` and uses `...UserFields` in both. Keeps large queries consistent and smaller.

6. **Introspect the schema before guessing.** If the API has docs disabled, run an introspection query to list types and fields — never invent field names. Introspection can be disabled on hardened APIs; then fall back to the provider's docs or schema file.

```graphql
query {
  __schema {
    queryType { fields { name } }
    mutationType { fields { name } }
  }
}
```

For one type's fields: `query { __type(name: "Country") { fields { name type { name } } } }`.

7. **Handle GraphQL errors correctly.** A GraphQL server can return HTTP 200 *with* an `errors` array alongside partial `data`. Always check for an `errors` key in the response JSON — don't trust HTTP status alone. Non-2xx statuses are transport-level failures (auth, rate limit, wrong endpoint); the `errors` array is operation-level (bad field, validation, null in a non-null position). Retry the former, fix the query for the latter.

## Output Contract

After applying this skill, an agent should produce:

- A GraphQL query string with a named operation, a minimal selection set, fragments where fields repeat, and all arguments passed through a `variables` object.
- A curl (or equivalent) invocation showing the POST body format and the endpoint, with the response fields it expects back.
- For paginated data: a loop sketch using `edges`/`pageInfo`/`endCursor` with the termination condition stated (`hasNextPage == false`).
- Any introspection result it relied on (types/fields discovered), so a reader can verify the field names are real.

## Operating Rules

- **Variables over interpolation.** Every argument value goes in `variables`; no value ever gets concatenated into the query string. This is the injection rule for GraphQL.
- **Ask for only what you need.** Requesting the whole object "just in case" defeats the point of the query language and can trigger cost-limit rejections on metered APIs.
- **Verify field names before running.** Run an introspection query (or check docs) for any type you haven't queried before — a guessed field name is a silent failure that returns an `errors` entry instead of data.
- **Check the `errors` key on every response.** HTTP 200 does not mean success in GraphQL. Treat a non-empty `errors` array as the operation having failed, even when `data` is partially present.
- **Cursor pagination over offsets.** When an API offers connection-style pagination, use it; offsets shift under concurrent writes, cursors don't.
- **Introspection is a tool, not a weapon.** Some APIs disable it for a reason; don't route around a disabled introspection endpoint to probe internals — use the published docs.
- **Soften absolutes.** "Prefer variables" is stated as the rule with the reason (injection + cacheability); "check introspection when the API is unfamiliar" beats "always introspect".
