Build your integrationSpecs

Generate from GraphQL

Generate a CLI, MCP server, or native SDK from GraphQL SDL, introspection JSON, or an introspectable endpoint.

Generate CLI, MCP, and SDK Targets from a GraphQL schema. Supply SDL, introspection JSON, or an endpoint that permits introspection, then configure the API URL and authentication that SDL cannot describe.

Use Connect a spec URL to generate a package or the Quickstart to save a Project. This page explains GraphQL inputs and generated behavior.

Inputs

Three shapes work as a project's spec URL or as an ad hoc input:

  • SDL: a .graphql schema file.
  • Introspection JSON: the __schema payload.
  • A GraphQL endpoint URL: Typeship runs introspection server-side and uses the endpoint as the client's default baseUrl. Add write-only source headers for protected endpoints. Typeship sends them during the initial GET and introspection POST but never returns them or writes them into generated artifacts. Use SDL when introspection is disabled.

What the schema cannot say

A GraphQL schema has no equivalent of OpenAPI's servers and securitySchemes. When the URL is an introspectable GraphQL endpoint, Typeship uses it as the generated client's default baseUrl and defaults to bearer authentication. A URL or pasted SDL document still generates without an endpoint; you pass baseUrl when constructing that client.

For a Project, open Authentication to choose bearer tokens, an API-key header, an API-key header that also accepts OAuth bearer tokens, basic authentication, an API key sent as the Basic username, or anonymous access. This updates the shared Spec for every Target. Keep the endpoint, environments, and custom scalar mappings in the Spec settings. One-shot CLI and API calls accept the same values under config.graphql. See Configuration.

  • Endpoint: every request is one POST to the configured URL. A schema fetched from its endpoint already has a default. Named environments become client environments.
  • Auth: bearer sends Authorization: Bearer and is the default. basic suits key-pair APIs. basic_api_key suits APIs whose key is the Basic-auth username with an empty password: customers set one variable, such as PARCEL_API_KEY, and the client sends Authorization: Basic <base64 of key:>. api_key sends a configured header. api_key_or_bearer sends a key in the configured header and also accepts an OAuth access token as Authorization: Bearer on the same client. With api_key_header: Authorization, personal keys go out as the raw Authorization: <key> value; customers set PARCEL_API_KEY for a key or PARCEL_TOKEN for an access token, and the key wins when both are set. none generates no credential option. Generated documentation, CLI, and MCP surfaces describe the selected scheme.
  • Name: the package and client names (parcel, ParcelClient) come from title, or from the endpoint's host (or the first environment's) when it is not set. With neither, the names use the placeholder GraphQL API and generation warns; set title to replace it.

Headers an API needs on every call, such as a version header, go in defaultHeaders on the client.

The mapping

  • Each root field becomes an operation. Queries land on client.query, mutations on client.mutation. Field arguments become a typed body object, required when any argument is non-null.
  • Object types carry an optional __typename literal. Unions, and interfaces with implementers, become a union of their concrete types, so results narrow on __typename.
  • Custom scalars use Spec.graphql.scalars for Projects or config.graphql.scalars for one-shot generation. Supported JSON representations are string, integer, number, boolean, and json. Scalars with well-known names need no mapping: Date, DateTime, DateTimeISO, Time, TimelessDate, LocalDate, LocalTime, Duration, ISO8601Duration, UUID, GUID, URL, URI, Email, and EmailAddress are strings, and JSON and JSONObject are any JSON value. DateTime and DateTimeISO are also date-times: the Go SDK types them as timestamps, and the CLI and MCP server accept relative forms such as -7d for them. A configured mapping overrides these. Any other unmapped scalar stays untyped and produces a warning.
  • Subscriptions are skipped, with a warning.
  • Operation documents (files of queries you already wrote) are not an input. Generation works from the schema alone.

Selections

A schema does not say which fields to fetch. Generated methods select scalar and enum fields to depth 2 by default. A union or interface with several concrete types selects only __typename, id, and a few fields the interface declares; pass select for member fields. A union's only member is selected inside ... on. Deprecated fields, and fields documented as internal or as needing admin rights or a special scope, are left out of defaults. Every default is checked against the schema when the package is generated.

Methods returning objects accept an optional select argument, and so do connections, where it selects each item. Scalar-returning methods do not.

In TypeScript, select is a typed object checked against the schema. A typo is a compile error, and the result type narrows to exactly what was picked:

const client = new ParcelClient({
  baseUrl: "https://sandbox.api.parcel.example/graphql",
  basicAuth: { username: publicKey, password: privateKey },
});

const whole = await client.query.transaction({ id: "txn_1" });
const slim = await client.query.transaction(
  { id: "txn_1" },
  { id: true, amount: true, customer: { email: true } },
);
// slim.data: { id: string; amount: number; customer: { email: string | null } | null } | null

Unions and interfaces take on, keyed by concrete type name, and come back discriminated by __typename:

const node = await client.query.node(
  { id },
  {
    id: true,
    __typename: true,
    on: {
      Transaction: { amount: true },
      Customer: { email: true },
    },
  },
);

if (node.ok && node.data?.__typename === "Transaction") {
  node.data.amount;
}

Each method exports its default selection. Add a field by spreading it: { ...queryTransactionSelection, status: true }.

A raw selection-set string returns the full type.

Python result types are TypedDicts. A field is required only when every default selection that returns its type selects it; other fields, such as nested connections, are NotRequired, so a type checker flags issue["attachments"] instead of the call raising KeyError. Read those fields with .get(). Selection<T> and Selected<T, S> are exported for custom helpers.

client.query.transaction(id="txn_1", select="{ id amount }")

Connections

Relay-style connections auto-paginate. Typeship detects a list operation when the field accepts first and after and returns edges plus pageInfo.

Iteration yields the items themselves. When the connection offers nodes, pages select and read nodes; otherwise each edge's node is yielded. Each page uses the previous endCursor as its next after value:

for await (const transaction of client.query.transactions({ first: 50 })) {
  transaction.amount;
}

select on a connection picks the fields of each item. The connection and the pageInfo fields pagination reads are added to the query, and in TypeScript each item's type narrows to what was picked:

for await (const transaction of client.query.transactions(
  { first: 50 },
  { id: true, amount: true },
)) {
  transaction.amount;
}

A raw selection that names nodes, edges, or pageInfo selects the whole connection instead. The page info is still added.

  • A call that passes neither first nor last sends first: 100, because Relay servers such as GitHub reject a connection query without one. Set graphql.page_size (1 to 1000) to change the default. A connection whose first argument has a schema default keeps the server's default.
  • Passing last pages backward: each next request sends before with the previous page's startCursor and stops when hasPreviousPage is false. This needs a connection that accepts last and before.
  • A page whose response has no item list fails with a pagination error instead of ending the iteration as if the list were empty.

Errors

GraphQL may report failures in a 200 response containing an errors array. The runtime throws GraphQLRequestError with the full array. It is an ApiError, so one ApiError check covers GraphQL and HTTP failures.

A field whose union or interface result includes error types reports failures as data instead. When the result resolves to an error type, the runtime throws PayloadError with that result as its body and the type name in typename. By default, error types are the members whose names end in Error, such as UserError or AccessDeniedError, when the result can also be something else. If your schema names them differently, list them in graphql.error_types (for example ["Problem", "ValidationFailure"]); the list replaces the default, and an empty list turns the check off. A name that is not an object type in the schema produces a generation warning. The generated CLI exits 1 and the MCP tool returns an error for the same result. The runtime always selects __typename on these fields. See Failures inside successful responses.

HTTP errors, malformed successful JSON, and transport failures use the same status family classes, ResponseParseError, and TransportError as OpenAPI. Successful calls resolve to the selected field value rather than the GraphQL envelope.

Partial data

A server can return data for the fields it resolved and errors for the rest, such as a permission error on one field. By default the call fails, and the error keeps the data that did arrive:

  • MCP: the tool call succeeds with { "data": ..., "errors": [...], "partial": true }, so an agent can use what arrived and see what is missing. A response with errors and no data is still a tool error.
  • TypeScript: GraphQLRequestError.data holds the operation's data, or undefined when none came back.
  • Python: GraphQLRequestError.data holds the operation's data, or None.
  • Go: GraphQLError.Data holds the raw data. DecodeData decodes it into the method's result type and reports whether there was any.

To receive partial data as a successful result, create the TypeScript client with graphqlPartial: "allow" or the Python client with graphql_partial="allow". The call then returns the data. In TypeScript, read the errors from ResponseMeta.graphqlErrors in the per-call onResponse option:

const client = new ParcelClient({
  baseUrl: "https://sandbox.api.parcel.example/graphql",
  graphqlPartial: "allow",
});

const transaction = await client.query.transaction({ id: "txn_1" }, undefined, {
  onResponse: (meta) => {
    if (meta.graphqlErrors) console.warn(meta.graphqlErrors);
  },
});

When the operation's field comes back null, there is no partial data and the call fails with the errors either way.

Limits

  • Runtime validation does not cover GraphQL operations.
  • Spec patches address OpenAPI documents. GraphQL schemas generate unpatched, with a warning when patches are configured.
  • The free plan's 25-operation allowance counts root fields, in schema order (queries first, then mutations). The generated README says when a Target was capped.

Identify the signed-in account

Generated CLIs can use a GraphQL query such as viewer or me for whoami. The query must run without required arguments. Optional arguments and arguments with schema defaults are allowed; mutations are never identity reads.

Set Identity operation in Authentication when you want to choose a specific query, such as query.viewer. The generated CLI's whoami and auth check call it with the current credential, and MCP uses it as the identity tool. Your API must validate that credential and enforce the caller's permissions. A query that allows anonymous access cannot prove authentication just by returning a successful response.

On this page