openapi: 3.1.0
info:
  title: Stepes Public API
  version: 2.0.0
  summary: Create and manage translation projects from your applications.
  description: >-
    Use a Stepes API key to upload files or send JSON/text, create translation
    projects, monitor progress, retrieve completed translations, optionally
    request quotes, and receive webhook updates. Every response includes
    Stepes-Request-Id and Stepes-API-Version. Authenticated responses also
    include RateLimit-Policy and RateLimit; replayed requests include
    Idempotency-Replayed: true.
servers:
  - url: https://apis.stepes.com/v2
    description: Production
x-stepes-response-header-policy:
  all_responses:
    Stepes-Request-Id: {$ref: '#/components/headers/StepesRequestId'}
    Stepes-API-Version: {$ref: '#/components/headers/StepesApiVersion'}
  authenticated_responses:
    RateLimit-Policy: {$ref: '#/components/headers/RateLimitPolicy'}
    RateLimit: {$ref: '#/components/headers/RateLimit'}
  idempotency_replays:
    Idempotency-Replayed: {$ref: '#/components/headers/IdempotencyReplayed'}
tags:
  - name: System
    description: Public API identity and release discovery.
  - name: Account
    description: Account details, API key permissions, limits, and capabilities.
  - name: Discovery
    description: Available languages, services, and turnaround options.
  - name: Files
    description: Private source uploads, metadata, and retained inline content.
  - name: Projects
    description: Create and track translation projects and retrieve completed translations.
  - name: Quotes
    description: Review pricing before creating a translation project.
  - name: Webhooks
    description: Signed endpoint registration, rotation, delivery history, and retry.
paths:
  /:
    get:
      tags: [System]
      operationId: getApiInfo
      summary: Get API identity and version
      description: Confirm the public API base URL, semantic version, and direct API-key authentication mode.
      security: []
      responses:
        '200':
          description: API information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiInfo'
  /account:
    get:
      tags: [Account]
      operationId: getAccount
      summary: Get account details
      description: Return the account profile, currency, API key environment, permissions, and whether project creation is enabled.
      x-stepes-required-scopes: [account:read]
      responses:
        '200':
          description: Account and API access details
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Account'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
  /capabilities:
    get:
      tags: [Account]
      operationId: getCapabilities
      summary: Get key scopes, limits, and enabled capabilities
      description: Discover effective key scopes, project and quote availability, webhook events, request limits, content limits, and idempotency policy.
      x-stepes-required-scopes: [account:read]
      responses:
        '200':
          description: Capabilities
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Capabilities'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /languages:
    get:
      tags: [Discovery]
      operationId: listLanguages
      summary: List active source and target languages
      description: List source and target languages available to your account, optionally filtered by search text.
      x-stepes-required-scopes: [languages:read]
      parameters:
        - name: search
          in: query
          schema: {type: string, maxLength: 100}
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 200, default: 100}
      responses:
        '200':
          description: Language list
          content:
            application/json:
              schema:
                type: object
                required: [object, data]
                properties:
                  object: {const: list}
                  data:
                    type: array
                    items: {$ref: '#/components/schemas/Language'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /service-options:
    get:
      tags: [Discovery]
      operationId: listServiceOptions
      summary: List supported project services and turnaround options
      description: List the service codes and turnaround values accepted when creating projects or quotes.
      x-stepes-required-scopes: [account:read]
      responses:
        '200':
          description: Service options
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more, next_cursor]
                properties:
                  object: {const: list}
                  data:
                    type: array
                    items: {$ref: '#/components/schemas/ServiceOption'}
                  has_more: {const: false}
                  next_cursor: {type: 'null'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /files:
    post:
      tags: [Files]
      operationId: uploadFile
      summary: Upload a source file
      description: Upload one source file and receive a file ID that can be used to create a project or quote.
      x-stepes-required-scopes: [files:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: {type: string, format: binary, description: One private source file within the current max_file_bytes capability limit.}
      responses:
        '201':
          description: File created
          headers:
            Location: {$ref: '#/components/headers/Location'}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/File'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /files/{fileId}:
    parameters:
      - $ref: '#/components/parameters/FileId'
    get:
      tags: [Files]
      operationId: getFile
      summary: Get source file metadata and analysis state
      description: Return file metadata and whether the uploaded source is ready to use.
      x-stepes-required-scopes: [files:read]
      responses:
        '200':
          description: File
          content:
            application/json:
              schema: {$ref: '#/components/schemas/File'}
        '404': {$ref: '#/components/responses/NotFound'}
    delete:
      tags: [Files]
      operationId: deleteFile
      summary: Delete an unused source file
      description: Delete an uploaded source file before it has been attached to a quote or project.
      x-stepes-required-scopes: [files:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204': {description: File deleted}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /files/{fileId}/download:
    get:
      tags: [Files]
      operationId: downloadSourceFile
      summary: Download a source file
      description: Download the original bytes of a source file available to your API key.
      x-stepes-required-scopes: [files:read]
      parameters:
        - $ref: '#/components/parameters/FileId'
      responses:
        '200':
          description: Source file bytes
          content:
            application/octet-stream:
              schema: {type: string, format: binary}
        '404': {$ref: '#/components/responses/NotFound'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /files/{fileId}/content:
    get:
      tags: [Files]
      operationId: getSourceContent
      summary: Retrieve exact inline JSON or text source content
      description: Return the exact retained JSON structure or text submitted through an inline project or quote request.
      x-stepes-required-scopes: [files:read]
      parameters:
        - $ref: '#/components/parameters/FileId'
      responses:
        '200':
          description: Structured source content
          content:
            application/json:
              schema: {$ref: '#/components/schemas/StructuredContent'}
        '404': {$ref: '#/components/responses/NotFound'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /projects:
    post:
      tags: [Projects]
      operationId: createProject
      summary: Create a translation project
      description: >-
        Start a translation project from uploaded files or inline JSON/text.
        Provide source and target languages and optional project or billing
        references. The response includes a project ID that can be used to
        track progress and retrieve completed translations.
      x-stepes-required-scopes: [projects:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ProjectInput'}
      responses:
        '201':
          description: Translation project created
          headers:
            Location: {$ref: '#/components/headers/Location'}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
    get:
      tags: [Projects]
      operationId: listProjects
      summary: List translation projects
      description: List projects created with your API key, using cursor pagination and the documented filters.
      x-stepes-required-scopes: [projects:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Status'
        - $ref: '#/components/parameters/ExternalId'
        - $ref: '#/components/parameters/CreatedAfter'
        - $ref: '#/components/parameters/CreatedBefore'
      responses:
        '200':
          description: Project page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: {$ref: '#/components/schemas/Project'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /projects/{projectId}:
    get:
      tags: [Projects]
      operationId: getProject
      summary: Get project status and details
      description: Return current status, progress, source files, target-language work, delivery readiness, and billing summary.
      x-stepes-required-scopes: [projects:read]
      parameters:
        - $ref: '#/components/parameters/ProjectId'
      responses:
        '200':
          description: Project
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
        '404': {$ref: '#/components/responses/NotFound'}
  /projects/{projectId}/cancel:
    post:
      tags: [Projects]
      operationId: cancelProject
      summary: Cancel a translation project
      description: Request cancellation of an eligible project. Completed work and available deliveries are preserved.
      x-stepes-required-scopes: [projects:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/ProjectId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: {type: string, minLength: 1, maxLength: 2000}
                reason_code: {type: [string, 'null'], maxLength: 100}
      responses:
        '200':
          description: Cancelled project
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /projects/{projectId}/deliveries:
    get:
      tags: [Projects]
      operationId: listProjectDeliveries
      summary: List completed translations
      description: List translated files or structured JSON/text results currently available for a project.
      x-stepes-required-scopes: [deliveries:read]
      parameters:
        - $ref: '#/components/parameters/ProjectId'
      responses:
        '200':
          description: Delivery list
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: {$ref: '#/components/schemas/Delivery'}
        '404': {$ref: '#/components/responses/NotFound'}
  /projects/{projectId}/deliveries/{deliveryId}/download:
    get:
      tags: [Projects]
      operationId: downloadDelivery
      summary: Download a translated file
      description: Download the completed translated file for a project delivery.
      x-stepes-required-scopes: [deliveries:read]
      parameters:
        - $ref: '#/components/parameters/ProjectId'
        - $ref: '#/components/parameters/DeliveryId'
      responses:
        '200':
          description: Target file bytes
          content:
            application/octet-stream:
              schema: {type: string, format: binary}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
  /projects/{projectId}/deliveries/{deliveryId}/content:
    get:
      tags: [Projects]
      operationId: getDeliveryContent
      summary: Get structure-preserving JSON or text delivery
      description: Reconstruct translated JSON or text only when completed target segments align exactly with the retained source manifest.
      x-stepes-required-scopes: [deliveries:read]
      parameters:
        - $ref: '#/components/parameters/ProjectId'
        - $ref: '#/components/parameters/DeliveryId'
      responses:
        '200':
          description: Structured delivery
          content:
            application/json:
              schema: {$ref: '#/components/schemas/StructuredContent'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
  /quotes:
    post:
      tags: [Quotes]
      operationId: createQuote
      summary: Create a quote
      description: Calculate an editable price estimate from uploaded files or inline JSON/text without starting translation.
      x-stepes-required-scopes: [quotes:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ProjectInput'}
      responses:
        '201':
          description: Quote created
          headers:
            Location: {$ref: '#/components/headers/Location'}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Quote'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
    get:
      tags: [Quotes]
      operationId: listQuotes
      summary: List quotes
      description: List quotes created with your API key, using cursor pagination and the documented filters.
      x-stepes-required-scopes: [quotes:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Status'
        - $ref: '#/components/parameters/ExternalId'
        - $ref: '#/components/parameters/CreatedAfter'
        - $ref: '#/components/parameters/CreatedBefore'
      responses:
        '200':
          description: Quote page
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: {$ref: '#/components/schemas/Quote'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /quotes/{quoteId}:
    parameters:
      - $ref: '#/components/parameters/QuoteId'
    get:
      tags: [Quotes]
      operationId: getQuote
      summary: Get a quote
      description: Return current quote inputs, source files, lifecycle state, currency, totals, expiry, and converted project ID when present.
      x-stepes-required-scopes: [quotes:read]
      responses:
        '200':
          description: Quote
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Quote'}
        '404': {$ref: '#/components/responses/NotFound'}
    patch:
      tags: [Quotes]
      operationId: updateQuote
      summary: Recalculate an editable quote with revised inputs
      description: Update an editable quote and recalculate its price before submission or acceptance.
      x-stepes-required-scopes: [quotes:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/QuoteUpdate'}
      responses:
        '200':
          description: Revised quote
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Quote'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /quotes/{quoteId}/submit:
    post:
      tags: [Quotes]
      operationId: submitQuote
      summary: Submit and freeze a quote revision
      description: Submit the current quote for approval and prevent further draft edits.
      x-stepes-required-scopes: [quotes:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/QuoteId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                submission_notes: {type: [string, 'null'], maxLength: 5000}
      responses:
        '200':
          description: Submitted quote
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Quote'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /quotes/{quoteId}/accept:
    post:
      tags: [Quotes]
      operationId: acceptQuote
      summary: Accept a quote and create a project
      description: Accept an eligible quote and start the translation project. The response contains the newly created project.
      x-stepes-required-scopes: [quotes:write, projects:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/QuoteId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BillingMetadata'}
      responses:
        '201':
          description: Translation project created
          headers:
            Location: {$ref: '#/components/headers/Location'}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Project'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhook endpoints
      description: List signed HTTPS webhook endpoints registered for your account and API environment.
      x-stepes-required-scopes: [webhooks:read]
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Webhook list
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: {$ref: '#/components/schemas/WebhookEndpoint'}
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Create a signed HTTPS webhook endpoint
      description: The signing secret is returned only in this response.
      x-stepes-required-scopes: [webhooks:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/WebhookCreateInput'}
      responses:
        '201':
          description: Webhook created; copy the secret now
          headers:
            Location: {$ref: '#/components/headers/Location'}
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/WebhookEndpoint'
                  - type: object
                    required: [secret]
                    properties:
                      secret: {type: string, writeOnly: true}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
  /webhooks/{webhookId}:
    parameters:
      - $ref: '#/components/parameters/WebhookId'
    get:
      tags: [Webhooks]
      operationId: getWebhook
      summary: Get a webhook endpoint without its secret
      description: Return one webhook endpoint and its subscriptions without returning the signing secret.
      x-stepes-required-scopes: [webhooks:read]
      responses:
        '200':
          description: Webhook
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WebhookEndpoint'}
        '404': {$ref: '#/components/responses/NotFound'}
    patch:
      tags: [Webhooks]
      operationId: updateWebhook
      summary: Update URL, events, description, or status
      description: Update the HTTPS destination, event subscriptions, display name, or active state.
      x-stepes-required-scopes: [webhooks:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/WebhookInput'}
      responses:
        '200':
          description: Updated webhook
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WebhookEndpoint'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '422': {$ref: '#/components/responses/ValidationError'}
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Revoke a webhook endpoint
      description: Revoke an endpoint so no additional events are scheduled for delivery to it.
      x-stepes-required-scopes: [webhooks:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204': {description: Webhook revoked}
        '404': {$ref: '#/components/responses/NotFound'}
  /webhooks/{webhookId}/rotate-secret:
    post:
      tags: [Webhooks]
      operationId: rotateWebhookSecret
      summary: Rotate a signing secret and show it once
      description: Generate a replacement signing secret, return it once, and retain the previous secret only for the configured verification grace period.
      x-stepes-required-scopes: [webhooks:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/WebhookId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Replacement secret
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/WebhookEndpoint'
                  - type: object
                    required: [secret]
                    properties:
                      secret: {type: string, writeOnly: true}
        '404': {$ref: '#/components/responses/NotFound'}
  /webhook-deliveries:
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List webhook delivery history
      description: List delivery status and attempt timing for your webhook endpoints.
      x-stepes-required-scopes: [webhooks:read]
      parameters:
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 25}
        - $ref: '#/components/parameters/Cursor'
        - name: status
          in: query
          schema: {type: string}
      responses:
        '200':
          description: Delivery history
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/CursorPage'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: {$ref: '#/components/schemas/WebhookDelivery'}
  /webhook-deliveries/{deliveryId}/retry:
    post:
      tags: [Webhooks]
      operationId: retryWebhookDelivery
      summary: Redeliver the original persisted event
      description: Queue another attempt for the same persisted event and endpoint without creating a duplicate event record.
      x-stepes-required-scopes: [webhooks:write]
      x-stepes-idempotent: true
      parameters:
        - $ref: '#/components/parameters/WebhookDeliveryId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          description: Redelivery queued
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WebhookDelivery'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Stepes API key
      description: Opaque stp_live_* or stp_test_* key issued in the Stepes client portal.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Unique operation key. Reuse it only with the same method, path, and request body.
      schema: {type: string, minLength: 8, maxLength: 255}
    Limit:
      name: limit
      in: query
      schema: {type: integer, minimum: 1, maximum: 100, default: 25}
    Cursor:
      name: cursor
      in: query
      description: Opaque signed cursor from next_cursor.
      schema: {type: string}
    Status:
      name: status
      in: query
      schema: {type: string}
    ExternalId:
      name: external_id
      in: query
      schema: {type: string, maxLength: 255}
    CreatedAfter:
      name: created_after
      in: query
      schema: {type: string, format: date-time}
    CreatedBefore:
      name: created_before
      in: query
      schema: {type: string, format: date-time}
    FileId:
      name: fileId
      in: path
      required: true
      schema: {type: string, pattern: '^file_[A-Za-z0-9_-]{10,64}$'}
    ProjectId:
      name: projectId
      in: path
      required: true
      schema: {type: string, pattern: '^proj_[A-Za-z0-9_-]{10,64}$'}
    QuoteId:
      name: quoteId
      in: path
      required: true
      schema: {type: string, pattern: '^quote_[A-Za-z0-9_-]{10,64}$'}
    DeliveryId:
      name: deliveryId
      in: path
      required: true
      schema: {type: string, pattern: '^delivery_[A-Za-z0-9_-]{10,64}$'}
    WebhookId:
      name: webhookId
      in: path
      required: true
      schema: {type: string, pattern: '^wh_[A-Za-z0-9_-]{10,64}$'}
    WebhookDeliveryId:
      name: deliveryId
      in: path
      required: true
      schema: {type: string, pattern: '^whd_[A-Za-z0-9_-]{10,64}$'}
  headers:
    Location:
      description: Canonical URL of the created resource.
      schema: {type: string, format: uri}
    StepesRequestId:
      description: Server-generated request identifier for support and audit correlation.
      schema: {type: string, format: uuid}
    StepesApiVersion:
      description: Effective Stepes API semantic version.
      schema: {type: string, example: 2.0.0}
    RateLimitPolicy:
      description: Effective request limit and window for the authenticated key.
      schema: {type: string, example: '120;w=60'}
    RateLimit:
      description: Current limit, remaining requests, and reset interval.
      schema: {type: string, example: 'limit=120, remaining=119, reset=60'}
    IdempotencyReplayed:
      description: Present with value true when a stored mutation response is replayed.
      schema: {type: string, enum: ['true']}
  responses:
    BadRequest:
      description: Invalid cursor or malformed request
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
    Unauthorized:
      description: Missing, malformed, expired, revoked, or disallowed API key
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
    Forbidden:
      description: Missing API key permission or account access
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
    NotFound:
      description: Resource was not found or is not available to this API key
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
    Conflict:
      description: Idempotency conflict or invalid lifecycle transition
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
    ValidationError:
      description: Request validation failed
      content:
        application/problem+json:
          schema: {$ref: '#/components/schemas/Problem'}
  schemas:
    ApiInfo:
      type: object
      required: [object, name, version, base_url, authentication]
      properties:
        object: {const: api}
        name: {const: Stepes Public API}
        version: {type: string}
        base_url: {const: 'https://apis.stepes.com/v2'}
        authentication: {const: bearer_api_key}
    Account:
      type: object
      additionalProperties: false
      required: [object, id, name, environment, currency, invoice_terms, project_submission_enabled, scopes]
      properties:
        object: {const: account}
        id: {type: string, pattern: '^pac_[A-Za-z0-9_-]{10,64}$'}
        name: {type: string}
        environment: {type: string, enum: [live, test]}
        currency: {type: string, minLength: 3, maxLength: 3, examples: [USD]}
        invoice_terms: {type: boolean}
        project_submission_enabled: {type: boolean}
        scopes:
          type: array
          uniqueItems: true
          items: {$ref: '#/components/schemas/ApiScope'}
    Capabilities:
      type: object
      additionalProperties: false
      required: [object, key, project_submission, quotes, webhooks, limits, idempotency, authentication]
      properties:
        object: {const: capabilities}
        key:
          type: object
          additionalProperties: false
          required: [environment, scopes]
          properties:
            environment: {type: string, enum: [live, test]}
            scopes:
              type: array
              uniqueItems: true
              items: {$ref: '#/components/schemas/ApiScope'}
        project_submission:
          type: object
          additionalProperties: false
          required: [enabled, payment_mode, input_modes, delivery_modes]
          properties:
            enabled: {type: boolean}
            payment_mode: {const: invoice_terms}
            input_modes:
              type: array
              items: {type: string, enum: [file, json, text]}
            delivery_modes:
              type: array
              items: {type: string, enum: [file, json, text]}
        quotes:
          type: object
          additionalProperties: false
          required: [enabled, optional]
          properties:
            enabled: {type: boolean}
            optional: {const: true}
        webhooks:
          type: object
          additionalProperties: false
          required: [enabled, signature_version, events]
          properties:
            enabled: {type: boolean}
            signature_version: {const: v1}
            events:
              type: array
              items: {$ref: '#/components/schemas/WebhookEventType'}
        limits:
          type: object
          additionalProperties: false
          required: [requests_per_minute, max_file_bytes, max_inline_content_bytes, max_files_per_project, max_target_languages_per_project]
          properties:
            requests_per_minute: {type: integer, minimum: 1}
            max_file_bytes: {type: integer, minimum: 1}
            max_inline_content_bytes: {type: integer, minimum: 1}
            max_files_per_project: {type: integer, minimum: 1}
            max_target_languages_per_project: {type: integer, minimum: 1}
        idempotency:
          type: object
          additionalProperties: false
          required: [required_for_mutations, replay_ttl_hours]
          properties:
            required_for_mutations: {const: true}
            replay_ttl_hours: {type: integer, minimum: 1}
        authentication:
          type: object
          additionalProperties: false
          required: [type, token_exchange_required]
          properties:
            type: {const: bearer_api_key}
            token_exchange_required: {const: false}
    ApiScope:
      type: string
      enum:
        - account:read
        - languages:read
        - files:read
        - files:write
        - projects:read
        - projects:write
        - deliveries:read
        - quotes:read
        - quotes:write
        - webhooks:read
        - webhooks:write
    ServiceOption:
      type: object
      additionalProperties: false
      required: [object, code, name, unit_type]
      properties:
        object: {const: service_option}
        code: {type: string}
        name: {type: string}
        unit_type: {type: string}
    Language:
      type: object
      required: [code, name]
      properties:
        code: {type: string}
        name: {type: string}
        locale: {type: [string, 'null']}
        script: {type: [string, 'null']}
    InlineContent:
      description: Structure-preserving JSON or plain text submitted directly instead of uploaded file IDs.
      oneOf:
        - type: object
          required: [format, value]
          properties:
            format: {const: json, description: Preserve the submitted object or array shape in the source and completed delivery.}
            value:
              description: JSON object or array whose non-empty string leaves are translated.
              oneOf:
                - {type: object}
                - {type: array}
        - type: object
          required: [format, value]
          properties:
            format: {const: text, description: Retain the source and completed delivery as plain text.}
            value: {type: string, minLength: 1, description: Non-empty UTF-8 text to translate.}
    BillingMetadata:
      type: object
      additionalProperties: false
      properties:
        po_number: {type: [string, 'null'], maxLength: 191}
        cost_center: {type: [string, 'null'], maxLength: 191}
        invoice_notes: {type: [string, 'null'], maxLength: 5000}
    ProjectInput:
      type: object
      additionalProperties: false
      required: [name, source_language, target_languages]
      example:
        name: Catalog SKU 1042
        external_id: SKU-1042-ES
        source_language: EN-US
        target_languages: [ES-ES]
        service: translation
        turnaround: standard
        instructions: Preserve product codes and JSON property names.
        content:
          format: json
          value:
            title: Waterproof hiking jacket
            description: Lightweight shell for wet weather.
            sku: 1042
            active: true
            metadata: null
        po_number: PO-2026-1042
        cost_center: LOCALIZATION
      oneOf:
        - required: [file_ids]
        - required: [content]
      properties:
        name: {type: string, minLength: 1, maxLength: 255, description: Project or quote name displayed in API responses and the Stepes client portal.}
        external_id: {type: [string, 'null'], maxLength: 255, description: "Optional correlation ID from your application, scoped to the API key environment."}
        source_language: {type: string, maxLength: 20, examples: [EN-US], description: Active source language code returned by GET /languages.}
        target_languages:
          type: array
          minItems: 1
          maxItems: 50
          uniqueItems: true
          description: Distinct active target language codes, each different from the source language.
          items: {type: string, maxLength: 20}
        service: {type: string, default: translation, maxLength: 64, description: Service code returned by GET /service-options.}
        turnaround: {type: string, enum: [standard, rush, custom], default: standard, description: Requested delivery speed.}
        due_at: {type: [string, 'null'], format: date-time, description: Optional future requested due time; required by configurations using custom turnaround.}
        instructions: {type: [string, 'null'], maxLength: 10000, description: Instructions for the translation team.}
        file_ids:
          type: array
          minItems: 1
          maxItems: 20
          uniqueItems: true
          description: Ready file IDs returned by POST /files. Use this or content, never both.
          items: {type: string, pattern: '^file_[A-Za-z0-9_-]{10,64}$'}
        content:
          allOf:
            - {$ref: '#/components/schemas/InlineContent'}
          description: Inline JSON or text. Use this or file_ids, never both.
        po_number: {type: [string, 'null'], maxLength: 191, description: Optional purchase-order reference for billing.}
        cost_center: {type: [string, 'null'], maxLength: 191, description: Optional cost center for billing.}
        invoice_notes: {type: [string, 'null'], maxLength: 5000, description: Optional billing notes.}
    QuoteUpdate:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name: {type: string, minLength: 1, maxLength: 255}
        external_id: {type: [string, 'null'], maxLength: 255}
        source_language: {type: string, maxLength: 20}
        target_languages:
          type: array
          minItems: 1
          maxItems: 50
          uniqueItems: true
          items: {type: string, maxLength: 20}
        service: {type: string, maxLength: 64}
        turnaround: {type: string, enum: [standard, rush, custom]}
        due_at: {type: [string, 'null'], format: date-time}
        instructions: {type: [string, 'null'], maxLength: 10000}
    File:
      type: object
      required: [id, object, filename, status, analysis, created_at]
      properties:
        id: {type: string, pattern: '^file_'}
        object: {const: file}
        filename: {type: string}
        mime_type: {type: [string, 'null']}
        size: {type: integer, minimum: 0}
        checksum_sha256: {type: [string, 'null']}
        status: {type: string, enum: [ready, manual_review]}
        analysis:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
    SourceFile:
      type: object
      required: [id, object, filename, status, download_url]
      properties:
        id: {type: string, pattern: '^file_'}
        object: {const: file}
        filename: {type: string}
        mime_type: {type: [string, 'null']}
        size: {type: integer, minimum: 0}
        status: {type: string, enum: [ready, manual_review]}
        download_url: {type: string, format: uri}
        content_url: {type: [string, 'null'], format: uri}
    Job:
      type: object
      required: [id, object, status, progress, delivery_ready]
      properties:
        id: {type: string, pattern: '^job_'}
        object: {const: job}
        status: {type: string}
        service: {type: string}
        source_language: {type: [string, 'null']}
        target_language: {type: [string, 'null']}
        progress: {type: number, minimum: 0, maximum: 100}
        word_count: {type: integer, minimum: 0}
        delivery_ready: {type: boolean}
    InvoiceSummary:
      type: object
      required: [status, currency, total]
      properties:
        status: {type: string}
        currency: {type: string, minLength: 3, maxLength: 3}
        total: {type: number, minimum: 0}
        due_at: {type: [string, 'null'], format: date-time}
    Project:
      type: object
      required: [id, object, name, status, progress, jobs, source_files, delivery_ready, created_at]
      example:
        id: proj_01JZ8K6N2W6RX4J9P7Y8K3M2Q1
        object: project
        external_id: SKU-1042-ES
        name: Catalog SKU 1042
        status: new
        progress: 0
        source_language: EN-US
        target_languages: [ES-ES]
        source_files:
          - id: file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2
            object: file
            filename: catalog-sku-1042.json
            mime_type: application/json
            size: 142
            status: ready
            download_url: https://apis.stepes.com/v2/files/file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2/download
            content_url: https://apis.stepes.com/v2/files/file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2/content
        due_at: null
        jobs: []
        delivery_ready: false
        invoice:
          status: Unpaid
          currency: USD
          total: 25
          due_at: '2026-09-08T00:00:00+00:00'
        created_at: '2026-08-10T12:00:00+00:00'
        updated_at: '2026-08-10T12:00:00+00:00'
      properties:
        id: {type: string, pattern: '^proj_'}
        object: {const: project}
        external_id: {type: [string, 'null']}
        name: {type: string}
        status: {type: string}
        progress: {type: number, minimum: 0, maximum: 100}
        source_language: {type: [string, 'null']}
        target_languages:
          type: array
          items: {type: string}
        source_files:
          type: array
          items: {$ref: '#/components/schemas/SourceFile'}
        due_at: {type: [string, 'null'], format: date-time}
        jobs:
          type: array
          items: {$ref: '#/components/schemas/Job'}
        delivery_ready: {type: boolean}
        invoice:
          oneOf:
            - {$ref: '#/components/schemas/InvoiceSummary'}
            - {type: 'null'}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    Quote:
      type: object
      required: [id, object, status, source_files, currency, subtotal, total, created_at]
      example:
        id: quote_01JZ8K6N2W6RX4J9P7Y8K3M2Q3
        object: quote
        external_id: SKU-1042-ES
        status: draft
        name: Catalog SKU 1042
        source_language: EN-US
        target_languages: [ES-ES]
        source_files:
          - id: file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2
            object: file
            filename: catalog-sku-1042.json
            mime_type: application/json
            size: 142
            status: ready
            download_url: https://apis.stepes.com/v2/files/file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2/download
            content_url: https://apis.stepes.com/v2/files/file_01JZ8K6N2W6RX4J9P7Y8K3M2Q2/content
        currency: USD
        subtotal: 25
        total: 25
        expires_at: '2026-09-09T12:00:00+00:00'
        project_id: null
        created_at: '2026-08-10T12:00:00+00:00'
        updated_at: '2026-08-10T12:00:00+00:00'
      properties:
        id: {type: string, pattern: '^quote_'}
        object: {const: quote}
        external_id: {type: [string, 'null']}
        status: {type: string, enum: [draft, sent, accepted, cancelled]}
        name: {type: string}
        source_language: {type: [string, 'null']}
        target_languages:
          type: array
          items: {type: string}
        source_files:
          type: array
          items: {$ref: '#/components/schemas/SourceFile'}
        currency: {type: string}
        subtotal: {type: number}
        total: {type: number}
        expires_at: {type: [string, 'null'], format: date-time}
        project_id: {type: [string, 'null'], pattern: '^proj_'}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    Delivery:
      type: object
      additionalProperties: true
      required: [id, object, status]
      properties:
        id: {type: string, pattern: '^delivery_'}
        object: {const: delivery}
        status: {type: string}
        format: {type: [string, 'null']}
        filename: {type: [string, 'null']}
        download_url: {type: [string, 'null'], format: uri}
        content_url: {type: [string, 'null'], format: uri}
    StructuredContent:
      type: object
      required: [object, format, content]
      properties:
        id: {type: string}
        object: {type: string, enum: [source_content, delivery_content]}
        format: {type: string, enum: [json, text]}
        content: true
        created_at: {type: [string, 'null'], format: date-time}
    WebhookInput:
      type: object
      additionalProperties: false
      properties:
        name: {type: string, minLength: 1, maxLength: 100}
        url: {type: string, format: uri, pattern: '^https://'}
        events:
          type: array
          minItems: 1
          uniqueItems: true
          items: {$ref: '#/components/schemas/WebhookEventType'}
        status: {type: string, enum: [active, disabled]}
    WebhookCreateInput:
      allOf:
        - $ref: '#/components/schemas/WebhookInput'
        - type: object
          required: [name, url, events]
    WebhookEventType:
      type: string
      enum:
        - quote.created
        - quote.updated
        - quote.submitted
        - quote.accepted
        - project.created
        - project.updated
        - project.cancelled
        - project.completed
        - delivery.ready
    WebhookEndpoint:
      type: object
      required: [id, object, url, events, status, created_at]
      properties:
        id: {type: string, pattern: '^wh_'}
        object: {const: webhook_endpoint}
        url: {type: string, format: uri}
        name: {type: string}
        events:
          type: array
          items: {$ref: '#/components/schemas/WebhookEventType'}
        status: {type: string, enum: [active, disabled, revoked]}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    WebhookDelivery:
      type: object
      additionalProperties: true
      required: [id, object, status, event_type]
      properties:
        id: {type: string, pattern: '^whd_'}
        object: {const: webhook_delivery}
        event_type: {$ref: '#/components/schemas/WebhookEventType'}
        status: {type: string}
        attempt_count: {type: integer, minimum: 0}
        next_attempt_at: {type: [string, 'null'], format: date-time}
        delivered_at: {type: [string, 'null'], format: date-time}
    CursorPage:
      type: object
      required: [object, data, has_more, next_cursor]
      properties:
        object: {const: list}
        data: {type: array, items: true}
        has_more: {type: boolean}
        next_cursor: {type: [string, 'null']}
    Problem:
      type: object
      required: [type, title, status, code, detail, instance, request_id, retryable]
      properties:
        type: {type: string, format: uri-reference}
        title: {type: string}
        status: {type: integer, minimum: 400, maximum: 599}
        code: {type: string}
        detail: {type: string}
        instance: {type: string, pattern: '^/v2(?:/|$)'}
        request_id: {type: string, format: uuid}
        retryable: {type: boolean}
        errors:
          type: object
          additionalProperties:
            type: array
            items: {type: string}
security:
  - bearerAuth: []
