openapi: 3.1.0
info:
  title: typeship
  version: 1.0.0
  description: |
    Generate a zero-dependency SDK — TypeScript, Python, or Go — plus a CLI
    and an MCP server, from an OpenAPI spec.

    Every operation but one requires an API key, created in the console and
    sent as `Authorization: Bearer ak_...`. A browser session is not a
    credential for this API. The exception is POST /generate, which works
    anonymously with the free plan's limits.
  # The licence of the generated packages, not of the service. 3.1 takes an
  # SPDX identifier alongside the display name, and typeship prefers the
  # identifier because that is what npm and PyPI filter on. MIT is what every
  # API client in this ecosystem ships under, so it is the one nobody has to
  # ask about in a procurement review.
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://typeship.dev/api/v1

tags:
  - name: generate
    description: Run the generator on a spec. Nothing is stored.
  - name: projects
    description: A spec and everything generated from it. Create, configure, regenerate.
  - name: generations
    description: The result of one run for one language, with its files.
  - name: spec_versions
    description: Every distinct spec a project generated from.
  - name: account
    description: The account behind an API key and its defaults.
  - name: usage
    description: Hosted generation allowance and included endpoints.
  - name: api_keys
    description: List and revoke keys. Creation is console-only.

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: API key from the console, `ak_...`.

  schemas:
    SpecInput:
      type: object
      description: The spec to generate from. Provide exactly one of url or inline.
      properties:
        url:
          type: string
          description: |
            Publicly reachable URL of an OpenAPI document, a GraphQL SDL
            file, or a GraphQL endpoint (introspected automatically).
            Fetched server-side.
        inline:
          type: string
          description: Raw spec text (OpenAPI JSON/YAML or GraphQL SDL). Up to 10MB.
    GeneratedFile:
      type: object
      required: [path, content]
      properties:
        path:
          type: string
          description: Repo-relative path inside the generated package.
        content:
          type: string
    GenerationMeta:
      type: object
      required: [title, version, oas_version, package_name, client_name, targets]
      properties:
        title: { type: string }
        version: { type: string }
        spec_format:
          type: string
          enum: [openapi, graphql]
        oas_version:
          type: string
          description: Detected spec version, "2.0", "3.0", or "3.1".
        converted:
          type: boolean
          description: True when the input was Swagger 2.0 and was converted.
        package_name: { type: string }
        client_name: { type: string }
        targets:
          type: array
          items: { type: string, enum: [sdk, cli, mcp] }
        resource_count: { type: integer }
        operation_count: { type: integer }
        schema_count: { type: integer }
        paginated_operation_count: { type: integer }
        omitted_operation_count:
          type: integer
          description: Operations beyond the plan's endpoint allowance, not generated.
        pr_url:
          type: [string, "null"]
          description: Pull request opened by this regeneration, when one was.
        pr_number:
          type: [integer, "null"]
        changelog:
          type: string
          description: Markdown changelog entry for this regeneration, from the API surface diff. Absent on a first generation or when nothing changed.
        breaking_count:
          type: integer
          description: Breaking changes in the diff; removed methods and fields, changed types, inputs that became required.
        baseline:
          type: string
          enum: [destination, last-generation, none]
          description: What the diff was measured against; "destination" means the .typeship/surface.json merged in the destination repository.
        semver:
          type: string
          enum: [success, failure]
          description: The typeship/semver verdict on the regeneration pull request; failure means breaking changes without a major version bump.
        semver_note:
          type: string
          description: The verdict in one line, as the commit status describes it.
        previous_version:
          type: string
          description: The package version the destination had before this regeneration.
        file_count: { type: integer }
        total_lines: { type: integer }
    GenerationResult:
      type: object
      required: [files, warnings, meta]
      properties:
        files:
          type: array
          items: { $ref: "#/components/schemas/GeneratedFile" }
        warnings:
          type: array
          items: { type: string }
        meta: { $ref: "#/components/schemas/GenerationMeta" }
        limits: { $ref: "#/components/schemas/GenerationLimits" }
        claim:
          description: >-
            Anonymous, URL-sourced generations only. A link a signed-in
            person can open to turn this run into a project in their
            organization (same spec, language, platforms, config). Lasts
            seven days. Null for inline specs; absent on keyed calls.
          oneOf:
            - type: "null"
            - type: object
              required: [url, expires_at]
              properties:
                url: { type: string }
                expires_at: { type: string, format: date-time }
    GenerationLimits:
      type: object
      description: >-
        Present when the generation was capped: by the free plan, or because
        the call was anonymous. Absent on uncapped generations.
      required: [max_operations, omitted_operations, reason, upgrade_url]
      properties:
        max_operations:
          type: integer
          description: How many operations this generation was allowed to include.
        omitted_operations:
          type: integer
          description: How many operations in the spec were left out.
        reason:
          type: string
          enum: [anonymous, free_plan]
        signup_url:
          type: string
          description: Anonymous calls only. Where to create an account.
        upgrade_url:
          type: string
          description: Where the cap is lifted.
    Source:
      type: object
      description: Where the project's spec lives.
      required: [kind]
      properties:
        kind: { type: string, enum: [url, repo] }
        url:
          type: [string, "null"]
          description: kind url. Fetched server-side for every generation.
        repo:
          type: [string, "null"]
          description: kind repo, "owner/name". Watched via the GitHub App.
        path:
          type: [string, "null"]
          description: Path of the spec file inside the repository.
    SpecPatch:
      type: object
      description: |
        A fix applied to the spec before generation. Targets are JSON
        Pointers into the document. A patch whose target no longer exists is
        skipped and reported as a warning on the generation, never silently.
      required: [op, path]
      properties:
        op: { type: string, enum: [set, append, remove, rename] }
        path:
          type: string
          description: |
            JSON-Pointer-style path. Pattern segments enable bulk fixes:
            * (any child), ** (any depth), [key=value] (filter), e.g.
            /paths/**/parameters/[name=account_id]/schema/type. Renaming a
            schema under /components/schemas also rewrites its $refs.
        value:
          description: set only; the replacement value.
        to:
          type: [string, "null"]
          description: rename only; the new key name.
        reason: { type: [string, "null"] }
    Destination:
      type: object
      description: Where regeneration pull requests land.
      properties:
        repo:
          type: [string, "null"]
          description: Defaults to the source repository when the source is a repo.
        directory:
          type: [string, "null"]
          description: Directory the generated package is written to.
    Project:
      type: object
      required: [id, object, name, source, platforms, auto_regen, created_at]
      properties:
        id: { type: string }
        object: { type: string, const: project }
        name: { type: string }
        spec_url:
          type: [string, "null"]
          description: The source URL when the source kind is url; null otherwise.
        source: { $ref: "#/components/schemas/Source" }
        destination:
          oneOf:
            - { $ref: "#/components/schemas/Destination" }
            - { type: "null" }
        languages:
          type: array
          description: >-
            Languages this project generates. Each is a separate package, a
            separate pull request, and a separate hosted generation. Defaults
            to typescript alone.
          items:
            type: string
            enum: [typescript, python, go]
        destinations:
          type: object
          description: >-
            Where each language's pull request lands, keyed by language. A
            repository each is the convention API vendors follow, and Go
            requires it since `go get` resolves a module to the repository
            root. Several languages may share a repository with different
            directories, producing one pull request.
          additionalProperties: { $ref: "#/components/schemas/Destination" }
        package_names:
          type: object
          description: >-
            Registry name per language. The ecosystems disagree about what a
            name is: npm takes an optional @scope, PyPI normalizes to
            lowercase-with-hyphens, and Go's name is the module path that
            `go get` resolves. Unset means the name is derived from the API's
            title.
          additionalProperties: { type: string }
        auto_regen:
          type: boolean
          description: >-
            Regenerate when the spec changes: on every push to the default
            branch for a repository source, every 30 minutes for a URL
            source. Off by default: the first generation is always one you
            asked for. Off means only "generate now" and
            POST /projects/{project_id}/generations regenerate.
        package_name:
          type: [string, "null"]
          description: npm name override for generated output; supports @scope/name.
        spec_patches:
          type: array
          items: { $ref: "#/components/schemas/SpecPatch" }
        config:
          oneOf:
            - { $ref: "#/components/schemas/Config" }
            - { type: "null" }
        mcp_enabled:
          type: boolean
          description: >-
            Whether the hosted MCP endpoint is on. Requires the mcp platform
            and Enterprise; turning the platform off turns this off.
        mcp_url:
          type: [string, "null"]
          description: Path of the hosted MCP endpoint while it is on; read-only.
        relay_enabled:
          type: boolean
          description: >-
            Whether the webhook relay is on, letting the generated CLI's
            webhooks listen command mint relay sessions. Requires the cli
            platform and Pro; turning the platform off turns this off.
        platforms:
          type: array
          items: { type: string, enum: [sdk, cli, mcp] }
          description: >-
            Artifacts this project builds from its spec. sdk is always
            present and stands for the SDK in each of `languages`; cli and
            mcp are built on the TypeScript SDK and ship in its package, so
            they require typescript among the languages. Each SDK language
            and each of cli and mcp is one platform for billing.
        created_at:
          type: string
          format: date-time
    Account:
      type: object
      description: >-
        The organization an API key belongs to. Members share its projects,
        keys, and plan; sign-in identity is not part of the API.
      required: [id, object, name, plan, created_at]
      properties:
        id: { type: string }
        object: { type: string, const: account }
        name: { type: string, description: The organization's display name. }
        plan:
          type: string
          enum: [free, pro, enterprise]
        created_at:
          type: string
          format: date-time
    CliBehavior:
      type: object
      description: How the generated CLI behaves. Part of Config.
      properties:
        whoami_operation:
          type: [string, "null"]
          description: >-
            resource.method of a zero-argument GET that the generated CLI's
            whoami command calls. Overrides auto-detection; a value that
            matches nothing is reported as a generation warning.
        oauth_client_id:
          type: [string, "null"]
          description: >-
            OAuth client id baked into the generated CLI for device-flow
            login. Without it, login prompts for a pasted credential.
        oauth_scopes:
          type: array
          items: { type: string }
          maxItems: 20
          description: >-
            Scopes requested during device-flow login. Include
            offline_access if the authorization server gates refresh
            tokens behind it.
        oauth_audience:
          type: [string, "null"]
          description: >-
            Audience sent with the device-authorization request, for
            authorization servers that require one to issue API-valid
            access tokens.
        update_notice:
          type: boolean
          description: >-
            Opt in to a once-a-day registry check that prints an upgrade
            hint. Off by default; generated code phones nobody unless this
            is enabled.
        support_url:
          type: [string, "null"]
          description: >-
            Where the generated CLI's feedback command sends users. GitHub
            issues/new URLs get a prefilled title and environment details.
    McpBehavior:
      type: object
      description: How the generated MCP server and the hosted endpoint behave. Part of Config.
      properties:
        tool_mode:
          type: string
          enum: [auto, operations, meta]
          description: >-
            MCP tool shape. meta collapses per-operation tools into
            search_docs, read_docs, and execute so large APIs don't flood an
            agent's context window; auto switches to meta above 100 operations.
        instructions:
          type: [string, "null"]
          maxLength: 2000
          description: >-
            Guidance appended to the MCP server's instructions, which agents
            read once when they connect (server/discover): what to call
            first, conventions the spec does not state, what not to do.
            Carried by the package's server and the hosted endpoint alike.
        tool_descriptions:
          type: object
          additionalProperties: { type: string, maxLength: 600 }
          maxProperties: 200
          description: >-
            Hand-written MCP tool descriptions keyed by operationId or
            "METHOD /path". Each replaces the text typeship derives for that
            operation (summary, first sentence, method and path, deprecation
            and auth notes). For flows the spec cannot describe, such as a
            multi-step upload. Keys that match no operation are reported as
            generation warnings.
    Config:
      type: object
      description: >-
        Everything typeship needs beyond the spec, in one object: generation
        customization (globals, retries, pagination) and how the generated
        tooling behaves (cli, mcp, docs_url). Plain configuration. typeship
        never requires vendor extensions inside the spec itself. The same
        shape is accepted on a project and on POST /generate.
      properties:
        globals:
          type: array
          items: { type: string }
          maxItems: 20
          description: >-
            Wire names of query/header parameters that become settable once
            on the generated client and auto-apply to every operation that
            accepts them; per-call values win. Names that match nothing are
            reported as generation warnings.
        retries: { $ref: "#/components/schemas/RetryTuning" }
        pagination:
          type: object
          maxProperties: 100
          additionalProperties:
            oneOf:
              - { $ref: "#/components/schemas/PaginationRule" }
              - type: boolean
                description: false disables pagination detection for the operation.
          description: >-
            Per-operation pagination control, keyed by operationId or
            "METHOD /path". Unmatched keys are reported as generation
            warnings.
        graphql: { $ref: "#/components/schemas/GraphqlSettings" }
        cli: { $ref: "#/components/schemas/CliBehavior" }
        mcp: { $ref: "#/components/schemas/McpBehavior" }
        docs_url:
          type: [string, "null"]
          description: >-
            The API's documentation site. Read through its llms.txt by the
            generated CLI's docs command, the MCP server's docs tools, and
            the package's AGENTS.md. Defaults to the spec's externalDocs URL.
    GraphqlSettings:
      type: object
      description: >-
        What a GraphQL schema cannot say about itself. Ignored for OpenAPI
        specs.
      properties:
        endpoint:
          type: string
          format: uri
          description: >-
            The URL every request is POSTed to; the generated client's
            default baseUrl. Defaults to the URL the schema was fetched from.
            Without either, baseUrl is a required client option.
        environments:
          type: array
          maxItems: 10
          items:
            type: object
            required: [name, url]
            properties:
              name: { type: string }
              url: { type: string, format: uri }
          description: >-
            Named endpoints (sandbox, production). Each becomes a client
            environment; the first is the default unless endpoint is set.
        auth:
          type: string
          enum: [bearer, basic, api_key, none]
          default: bearer
          description: >-
            How requests authenticate. bearer sends Authorization: Bearer;
            basic is for key-pair APIs (public key as username, private key as
            password); api_key sends a header named by api_key_header; none
            generates no auth option.
        api_key_header:
          type: string
          description: Header carrying the key when auth is api_key. Default X-API-Key.
        title:
          type: string
          maxLength: 80
          description: >-
            The API's name; drives the package and client names ("Braintree"
            gives braintree and BraintreeClient). Defaults to a name derived
            from the endpoint's host.
    RetryTuning:
      type: object
      description: >-
        Retry behavior. Top-level fields adjust every operation; operations
        maps operationId or "METHOD /path" keys to per-operation overrides.
      properties:
        max_retries: { type: integer, minimum: 0, maximum: 10 }
        statuses:
          type: array
          items: { type: integer }
          maxItems: 20
          description: Replaces the default retryable set (408, 429, 500, 502, 503, 504).
        initial_delay_ms: { type: integer, minimum: 1 }
        max_delay_ms: { type: integer, minimum: 1 }
        retry_non_idempotent:
          type: boolean
          description: Also retry non-idempotent methods (POST/PATCH).
        disabled:
          type: boolean
          description: Shorthand for max_retries 0.
        operations:
          type: object
          maxProperties: 100
          additionalProperties: { $ref: "#/components/schemas/RetryTuning" }
    PaginationRule:
      type: object
      required: [items_field]
      properties:
        style:
          type: string
          enum: [cursor, cursorFromLastId, page, offset]
          default: cursor
        items_field:
          type: string
          description: Response field holding the item array.
        cursor_param: { type: string }
        next_cursor_field: { type: string }
        has_more_field: { type: string }
        id_field: { type: string }
        page_param: { type: string }
        offset_param: { type: string }
        limit_param: { type: string }
    FileStub:
      type: object
      required: [path, bytes]
      properties:
        path: { type: string }
        bytes: { type: integer }
    Generation:
      type: object
      required: [id, object, status, trigger, created_at]
      properties:
        id: { type: string }
        object: { type: string, const: generation }
        files_omitted:
          type: boolean
          description: >-
            Present and true when the generated output was too large to
            inline; files_index lists paths, fetched one at a time via
            GET /generations/{generation_id}/file.
        files_index:
          type: array
          items: { $ref: "#/components/schemas/FileStub" }
        project_id: { type: [string, "null"] }
        status: { type: string, enum: [succeeded, failed] }
        trigger: { type: string, enum: [manual, webhook, poll, preview] }
        language:
          type: [string, "null"]
          enum: [typescript, python, go, null]
          description: >-
            Language this run generated. Null on generations recorded before
            projects had a language axis.
        meta: { $ref: "#/components/schemas/GenerationMeta" }
        warnings:
          type: array
          items: { type: string }
        files:
          type: array
          description: Present on retrieve and create; omitted in lists.
          items: { $ref: "#/components/schemas/GeneratedFile" }
        error: { type: [string, "null"] }
        created_at:
          type: string
          format: date-time
    GenerationFailure:
      type: object
      description: A language that did not generate in a multi-language run.
      required: [language, status, error]
      properties:
        language:
          type: string
          enum: [typescript, python, go]
        status: { type: string, const: failed }
        error: { type: string }
    Usage:
      type: object
      required: [object, hosted_generations, included_endpoints]
      properties:
        object: { type: string, const: usage }
        hosted_generations:
          type: object
          required: [used]
          properties:
            used: { type: integer }
            included:
              type: [integer, "null"]
              description: Null on paid plans, which meter rather than cap.
            remaining: { type: [integer, "null"] }
        included_endpoints:
          type: integer
          description: Endpoints included before per-endpoint billing applies.
        requests:
          type: object
          description: >-
            Who called the API in the last 30 days, read from the User-Agent
            the generated tooling sends: by surface (cli, mcp, sdk, http)
            and by agent harness (claude-code, codex, cursor, ...), and the
            share of requests that came through an agent.
          required: [days, requests, by_surface, by_harness, agent_share]
          properties:
            days: { type: integer }
            requests: { type: integer }
            by_surface:
              type: object
              additionalProperties: { type: integer }
            by_harness:
              type: object
              additionalProperties: { type: integer }
            agent_share:
              type: number
              description: 0 to 1.

    ApiKey:
      type: object
      required: [id, object, name, last4, revoked, created_at]
      properties:
        id: { type: string }
        object: { type: string, const: api_key }
        name: { type: string }
        last4:
          type: string
          description: Last four characters of the secret; the secret itself is never stored.
        revoked: { type: boolean }
        last_used_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }

    McpUsage:
      type: object
      required: [object, project_id, days, calls, errors, rate_limited, avg_duration_ms, by_tool]
      properties:
        object: { type: string, const: mcp_usage }
        project_id: { type: string }
        mcp_url:
          type: [string, "null"]
          description: The hosted endpoint URL, or null when it is off.
        days: { type: integer, description: The window these numbers cover. }
        calls: { type: integer, description: Tool calls served, including ones that returned an error. }
        errors: { type: integer, description: Calls whose result was a tool error (API failures, bad arguments). }
        rate_limited: { type: integer, description: Calls turned away by the per-caller or per-endpoint limit. }
        avg_duration_ms: { type: integer, description: Mean upstream request time across served calls. }
        by_tool:
          type: array
          items:
            type: object
            required: [tool, calls, errors]
            properties:
              tool: { type: string }
              calls: { type: integer }
              errors: { type: integer }

    SpecVersion:
      type: object
      required: [id, object, project_id, hash, created_at]
      properties:
        id: { type: string }
        object: { type: string, const: spec_version }
        project_id: { type: string }
        hash:
          type: string
          description: sha256 of the raw spec text; the version's identity.
        bytes: { type: integer }
        source:
          type: [object, "null"]
          description: Where this spec came from — a URL, or a repo and path.
        content:
          type: string
          description: >-
            The raw spec text. Present on retrieve, omitted from lists, and
            replaced by content_omitted when the spec is too large to inline.
        content_omitted:
          type: boolean
          description: >-
            Present and true when the spec was too large to inline; fetch it
            from /spec_versions/{spec_version_id}/content.
        created_at: { type: string, format: date-time }

    Error:
      type: object
      required: [errors, request_id]
      properties:
        errors:
          type: array
          items:
            type: object
            required: [code, message]
            properties:
              code:
                type: string
                enum:
                  - invalid_request
                  - unauthorized
                  - not_found
                  - spec_error
                  - fetch_error
                  - plan_limit_reached
                  - payload_too_large
                  - rate_limited
                  - internal_error
              message: { type: string }
        request_id:
          type: string
          description: Also sent as the x-request-id response header.

  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: No such resource in this account.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

paths:
  /generate:
    post:
      operationId: run
      tags: [generate]
      summary: Generate a package from a spec
      description: |
        Stateless generation: nothing is stored. Returns the full generated
        package as files. Works without an API key: anonymous calls generate
        the first 25 operations, rate limited per IP address, and the
        response's `limits` object says what was held back and where to lift
        it; anonymous calls from a spec URL also carry `claim.url`, a link
        that turns the run into a project once a person signs in. With a key, the free plan generates the first 25 operations and
        paid plans generate the whole spec. A present but invalid key is a
        401, not a downgrade to anonymous.
      security: [{}, { apiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [spec]
              properties:
                spec: { $ref: "#/components/schemas/SpecInput" }
                platforms:
                  type: array
                  items: { type: string, enum: [sdk, cli, mcp] }
                  description: >-
                    Artifacts to generate from the spec. Defaults to [sdk].
                    
                language:
                  type: string
                  enum: [typescript, python, go]
                  default: typescript
                  description: >-
                    Language to generate. Python and Go produce the SDK only;
                    the CLI and MCP server are TypeScript artifacts and are
                    skipped with a warning when requested alongside them.
                package_name:
                  type: string
                  description: npm name override for the generated package.
                config: { $ref: "#/components/schemas/Config" }
      responses:
        "200":
          description: The generated package.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GenerationResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422":
          description: The spec could not be understood.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "413":
          description: Spec exceeds the 10MB limit.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        default:
          description: Unexpected error.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

  /projects:
    get:
      operationId: list
      tags: [projects]
      summary: List projects
      security: [{ apiKey: [] }]
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of projects.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Project" }
                  next_cursor: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      operationId: create
      tags: [projects]
      summary: Create a project
      security: [{ apiKey: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 80 }
                spec_url:
                  type: string
                  description: >-
                    Spec location for a URL-sourced project. Provide this or
                    source; a project with neither has nothing to generate.
                source: { $ref: "#/components/schemas/Source" }
                platforms:
                  type: array
                  description: >-
                    Artifacts to build. sdk is implied; cli and mcp require
                    typescript among the languages. Free projects
                    run one platform in total (one SDK language); more is a
                    402 until the account is on Pro.
                  items:
                    type: string
                    enum: [sdk, cli, mcp]
                languages:
                  type: array
                  description: >-
                    Languages to generate. Each is a separate package, a
                    separate pull request, a separate hosted generation, and
                    one platform for billing. Defaults to typescript alone.
                  items:
                    type: string
                    enum: [typescript, python, go]
                destinations:
                  type: object
                  description: Per-language pull-request destination, keyed by language.
                  additionalProperties: { $ref: "#/components/schemas/Destination" }
                package_names:
                  type: object
                  description: Registry name per language; unset derives from the API title.
                  additionalProperties: { type: string }
                destination:
                  oneOf:
                    - { $ref: "#/components/schemas/Destination" }
                    - { type: "null" }
                auto_regen: { type: boolean }
                package_name: { type: [string, "null"] }
                spec_patches:
                  type: array
                  maxItems: 50
                  items: { $ref: "#/components/schemas/SpecPatch" }
                mcp_enabled:
                  type: boolean
                  description: Requires the mcp platform and Enterprise.
                relay_enabled:
                  type: boolean
                  description: Requires the cli platform and Pro.
                config:
                  oneOf:
                    - { $ref: "#/components/schemas/Config" }
                    - { type: "null" }
      responses:
        "201":
          description: Created.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Project" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400":
          description: Invalid name, spec source, or field value.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "402":
          description: The plan does not include this.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

  /projects/{project_id}:
    get:
      operationId: get
      tags: [projects]
      summary: Retrieve a project
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The project.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Project" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      operationId: update
      tags: [projects]
      summary: Update a project
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 80 }
                spec_url: { type: string }
                source: { $ref: "#/components/schemas/Source" }
                platforms:
                  type: array
                  description: >-
                    Artifacts to build; replaces the list. Dropping cli or
                    mcp turns off the hosted feature it serves. cli and mcp
                    require typescript among the languages.
                    Turning a platform off stops generating it; nothing
                    already delivered is removed.
                  items:
                    type: string
                    enum: [sdk, cli, mcp]
                destination:
                  oneOf:
                    - { $ref: "#/components/schemas/Destination" }
                    - { type: "null" }
                languages:
                  type: array
                  description: >-
                    Languages to generate; replaces the list. Each is its own
                    hosted generation and one platform for billing.
                  items:
                    type: string
                    enum: [typescript, python, go]
                destinations:
                  type: object
                  description: Per-language pull-request destination, keyed by language.
                  additionalProperties: { $ref: "#/components/schemas/Destination" }
                package_names:
                  type: object
                  description: Registry name per language; unset derives from the API title.
                  additionalProperties: { type: string }
                auto_regen: { type: boolean }
                package_name: { type: [string, "null"] }
                spec_patches:
                  type: array
                  maxItems: 50
                  items: { $ref: "#/components/schemas/SpecPatch" }
                mcp_enabled:
                  type: boolean
                  description: >-
                    Serve this project as a hosted remote MCP endpoint.
                    Requires the mcp platform and Enterprise.
                relay_enabled:
                  type: boolean
                  description: >-
                    Enable the webhook relay so the generated CLI's
                    webhooks listen command works for this API's users.
                    Requires the cli platform and Pro.
                config:
                  oneOf:
                    - { $ref: "#/components/schemas/Config" }
                    - { type: "null" }
                  description: >-
                    Replaces the whole config. Pass null to clear it.
      responses:
        "200":
          description: The updated project.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Project" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "400":
          description: Invalid field.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "402":
          description: The plan does not include this.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
    delete:
      operationId: delete
      tags: [projects]
      summary: Delete a project
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted.
          content:
            application/json:
              schema:
                type: object
                required: [deleted]
                properties:
                  deleted: { type: boolean, const: true }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /projects/{project_id}/generations:
    post:
      operationId: generate
      tags: [projects]
      summary: Run a hosted generation
      description: |
        Fetches the project's spec URL, generates every configured language,
        and stores each result in the project's history. Each language
        counts as one hosted generation. Does not open pull requests. Only
        URL-sourced projects can be regenerated this way; repository sources
        regenerate on push.
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "201":
          description: >-
            One entry per configured language: the stored generation
            including files, or a failure record for a language that did
            not generate.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      oneOf:
                        - { $ref: "#/components/schemas/Generation" }
                        - { $ref: "#/components/schemas/GenerationFailure" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "402":
          description: The account has used its hosted generation allowance.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "422":
          description: The spec could not be understood.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
    get:
      operationId: listGenerations
      tags: [projects]
      summary: List a project's generations
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
        - name: cursor
          in: query
          schema: { type: string }
        - name: language
          in: query
          schema: { type: string, enum: [typescript, python, go] }
          description: Only generations for this language.
      responses:
        "200":
          description: A page of generations, newest first, without files.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Generation" }
                  next_cursor: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /generations/{generation_id}:
    get:
      operationId: get
      tags: [generations]
      summary: Retrieve a generation
      description: Includes the generated files when the generation succeeded.
      security: [{ apiKey: [] }]
      parameters:
        - name: generation_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The generation.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Generation" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /generations/{generation_id}/file:
    get:
      operationId: getFile
      tags: [generations]
      summary: Fetch one file from a generation
      description: >-
        Raw file content, for generations whose output was too large to
        inline (files_omitted true). The generation's files_index lists
        valid paths.
      security: [{ apiKey: [] }]
      parameters:
        - name: generation_id
          in: path
          required: true
          schema: { type: string }
        - name: path
          in: query
          required: true
          schema: { type: string }
          description: Repo-relative path inside the generated package.
      responses:
        "200":
          description: The file content.
          content:
            text/plain:
              schema: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /projects/{project_id}/spec_versions:
    get:
      operationId: list
      tags: [spec_versions]
      summary: List the specs this project has generated from
      description: >-
        The audit trail behind a regeneration: which spec produced which SDK,
        and when it changed. Content is omitted from the list because specs
        run to megabytes.
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of spec versions.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/SpecVersion" }
                  next_cursor: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /projects/{project_id}/mcp_usage:
    get:
      operationId: mcp_usage
      tags: [projects]
      summary: Retrieve hosted MCP endpoint usage for a project
      description: >-
        What the project's hosted MCP endpoint has served over the last `days`
        (default 30, max 90): tool calls, calls that returned an error,
        calls turned away by the rate limit, mean upstream latency, and a
        per-tool breakdown. The same numbers the console shows next to the
        endpoint URL. Zeroes when the endpoint is off or unused.
      security: [{ apiKey: [] }]
      parameters:
        - name: project_id
          in: path
          required: true
          schema: { type: string }
        - name: days
          in: query
          description: Window in days, 1 to 90.
          schema: { type: integer, minimum: 1, maximum: 90, default: 30 }
      responses:
        "200":
          description: Usage over the window.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/McpUsage" }
        "400":
          description: days out of range.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /spec_versions/{spec_version_id}:
    get:
      operationId: get
      tags: [spec_versions]
      summary: Retrieve a spec version
      description: >-
        One recorded spec, with its content. Storing every spec a project has
        generated from is only an audit trail if you can read one back and
        diff it against what shipped.
      security: [{ apiKey: [] }]
      parameters:
        - name: spec_version_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The spec version.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SpecVersion" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /spec_versions/{spec_version_id}/content:
    get:
      operationId: getContent
      tags: [spec_versions]
      summary: Retrieve a spec version's raw text
      description: >-
        The escape hatch for specs too large to inline, and the endpoint to
        pipe straight into a diff.
      security: [{ apiKey: [] }]
      parameters:
        - name: spec_version_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The raw spec text.
          content:
            text/plain:
              schema: { type: string }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /me:
    get:
      operationId: me
      tags: [account]
      summary: The account behind the presented credentials
      description: |
        Returns the account that owns the presented API key. This is also the
        identity endpoint the generated typeship CLI's `whoami` calls.
      security: [{ apiKey: [] }]
      responses:
        "200":
          description: The authenticated account.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Account" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /usage:
    get:
      operationId: retrieve
      tags: [usage]
      summary: Retrieve usage for this account
      description: >-
        What this account has consumed and what remains. A hosted generation
        can cost more than one unit: a project generating TypeScript, Python,
        and Go consumes three, so callers that would otherwise discover the
        limit by receiving a 402 can check first.
      security: [{ apiKey: [] }]
      responses:
        "200":
          description: Current usage.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Usage" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api_keys:
    get:
      operationId: list
      tags: [api_keys]
      summary: List API keys
      description: >-
        Keys are never returned in full — only their identity and last four.
        Creation stays in the console deliberately: a leaked key that can
        mint more keys is a leaked account.
      security: [{ apiKey: [] }]
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: A page of API keys.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ApiKey" }
                  next_cursor: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api_keys/{api_key_id}:
    delete:
      operationId: revoke
      tags: [api_keys]
      summary: Revoke an API key
      description: >-
        Idempotent: revoking an already-revoked key returns the same body, so
        a rotation script that re-runs does not have to special-case having
        already succeeded.
      security: [{ apiKey: [] }]
      parameters:
        - name: api_key_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: The revoked key.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ApiKey" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
