openapi: 3.1.0
info:
  title: MockApiHub REST API
  version: 1.1.0
  description: "Free, deterministic mock REST API for testing and prototyping.\n\nTwo surfaces:\n- Generic datasets (/api/users, /api/posts, …) for traditional mock-API needs.\n- Stripe-shaped surface (/api/stripe/*) for testing Stripe integrations without a Stripe account — cursor pagination, Stripe-style error envelopes, HMAC-signed webhook delivery, and chaos query params (simulate / delay / fail_rate).\n\nWrites (POST/PATCH/DELETE) return realistic, shape-correct responses but are NOT persisted — the next GET returns the same deterministic data."
  license:
    name: MIT
servers:
  - url: "https://mockapihub.com"
      description: Production
  - url: "http://localhost:4000"
      description: Local dev
tags:
  - name: Users
  - name: Posts
  - name: Products
  - name: Recipes
  - name: Todos
  - name: Comments
  - name: Stripe — Customers
      description: Mock Stripe customer objects.
  - name: Stripe — Charges
      description: Mock Stripe charge objects.
  - name: Stripe — PaymentIntents
      description: Mock Stripe PaymentIntents including the confirm flow.
  - name: Stripe — Subscriptions
      description: Mock Stripe subscription objects.
  - name: Stripe — Invoices
      description: Mock Stripe invoice objects.
  - name: Stripe — Webhooks
      description: Sign and deliver Stripe-shaped events to a target URL. SSRF-hardened.
paths:
  "/api/users":
    get:
      tags:
        - Users
      summary: List users
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/User"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      tags:
        - Users
      summary: Create a user (not persisted)
      description: Writes return a realistic, shape-correct response but are NOT persisted. The next GET returns the same deterministic data as before.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                email:
                  type: string
                phone:
                  type: string
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/User"
        "400":
          description: Missing required fields
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/users/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Users
      summary: Get one user
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/User"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    patch:
      tags:
        - Users
      summary: Update a user (not persisted)
      description: Writes return a realistic, shape-correct response but are NOT persisted. The next GET returns the same deterministic data as before.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                phone:
                  type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/User"
    delete:
      tags:
        - Users
      summary: Delete a user (not persisted)
      description: Writes return a realistic, shape-correct response but are NOT persisted. The next GET returns the same deterministic data as before.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                      id:
                        type: integer
  "/api/users/{id}/posts":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Users
        - Posts
      summary: List posts authored by this user
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Post"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
  "/api/users/{id}/todos":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Users
        - Todos
      summary: List todos owned by this user
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Todo"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
  "/api/posts":
    get:
      tags:
        - Posts
      summary: List posts
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Post"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/posts/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Posts
      summary: Get one post
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/Post"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/posts/{id}/comments":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Posts
        - Comments
      summary: List comments on this post
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Comment"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
  "/api/products":
    get:
      tags:
        - Products
      summary: List products
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Product"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/products/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Products
      summary: Get one product
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/Product"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/recipes":
    get:
      tags:
        - Recipes
      summary: List recipes
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Recipe"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/recipes/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Recipes
      summary: Get one recipe
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/Recipe"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/todos":
    get:
      tags:
        - Todos
      summary: List todos
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Todo"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/todos/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Todos
      summary: Get one todo
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/Todo"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/comments":
    get:
      tags:
        - Comments
      summary: List comments
      parameters:
        - name: page
            in: query
            schema:
              type: integer
              minimum: 1
              default: 1
            description: 1-based page number.
        - name: limit
            in: query
            schema:
              type: integer
              minimum: 1
              maximum: 100
              default: 3
            description: Page size. Capped at 100.
        - name: q
            in: query
            schema:
              type: string
            description: Case-insensitive substring search across string fields.
        - name: sort
            in: query
            schema:
              type: string
            description: Field name to sort by.
        - name: order
            in: query
            schema:
              type: string
              enum:
                - asc
                - desc
              default: asc
            description: Sort direction.
        - name: fields
            in: query
            schema:
              type: string
            description: Comma-separated list of fields to include in each item.
        - name: seed
            in: query
            schema:
              type: string
            description: Override the seed used by the deterministic data generator.
        - name: delay
            in: query
            schema:
              type: integer
              minimum: 0
            description: Sleep this many ms before responding (chaos testing).
        - name: errorRate
            in: query
            schema:
              type: number
              minimum: 0
              maximum: 1
            description: Probability (0..1) of returning a simulated 503 server error.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Comment"
                  pagination:
                    type: object
                    required:
                      - page
                      - limit
                      - total
                      - hasNext
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      hasNext:
                        type: boolean
        "503":
          description: Simulated error (when errorRate triggers)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/comments/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: integer
    get:
      tags:
        - Comments
      summary: Get one comment
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    "$ref": "#/components/schemas/Comment"
        "404":
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/stripe/customers":
    get:
      tags:
        - Stripe — Customers
      summary: List customers
      parameters:
        - "$ref": "#/components/parameters/StripeLimit"
        - "$ref": "#/components/parameters/StripeStartingAfter"
        - "$ref": "#/components/parameters/StripeEndingBefore"
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeListCustomers"
        "400":
          description: Invalid cursor (resource_missing).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "402":
          description: Card error (when ?simulate=card_declined / insufficient_funds).
        "429":
          description: Rate limit (when ?simulate=rate_limited).
        "500":
          description: Simulated server error (when ?fail_rate triggers).
        "504":
          description: Timeout (when ?simulate=timeout).
    post:
      tags:
        - Stripe — Customers
      summary: Create a customer (not persisted)
      description: Writes return a Stripe-shaped object with a fresh id, but the data is NOT persisted. A follow-up GET for the same id returns 404.
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                name:
                  type: string
                description:
                  type: string
                phone:
                  type: string
                currency:
                  type: string
                metadata:
                  type: object
      responses:
        "200":
          description: Created (shape-correct response, not stored).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeCustomer"
  "/api/stripe/customers/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            description: Stripe-style id with the resource prefix.
    get:
      tags:
        - Stripe — Customers
      summary: Retrieve a customer
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeCustomer"
        "404":
          description: No such object.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/charges":
    get:
      tags:
        - Stripe — Charges
      summary: List charges
      parameters:
        - "$ref": "#/components/parameters/StripeLimit"
        - "$ref": "#/components/parameters/StripeStartingAfter"
        - "$ref": "#/components/parameters/StripeEndingBefore"
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeListCharges"
        "400":
          description: Invalid cursor (resource_missing).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "402":
          description: Card error (when ?simulate=card_declined / insufficient_funds).
        "429":
          description: Rate limit (when ?simulate=rate_limited).
        "500":
          description: Simulated server error (when ?fail_rate triggers).
        "504":
          description: Timeout (when ?simulate=timeout).
    post:
      tags:
        - Stripe — Charges
      summary: Create a charge (not persisted)
      description: Writes return a Stripe-shaped object with a fresh id, but the data is NOT persisted. A follow-up GET for the same id returns 404.
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amount
                - currency
              properties:
                amount:
                  type: integer
                  description: Smallest currency unit (cents).
                currency:
                  type: string
                customer:
                  type: string
                  pattern: ^cus_
                description:
                  type: string
                metadata:
                  type: object
      responses:
        "200":
          description: Created.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeCharge"
        "400":
          description: parameter_missing for amount or currency.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/charges/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            description: Stripe-style id with the resource prefix.
    get:
      tags:
        - Stripe — Charges
      summary: Retrieve a charge
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeCharge"
        "404":
          description: No such object.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/payment_intents":
    get:
      tags:
        - Stripe — PaymentIntents
      summary: List paymentintents
      parameters:
        - "$ref": "#/components/parameters/StripeLimit"
        - "$ref": "#/components/parameters/StripeStartingAfter"
        - "$ref": "#/components/parameters/StripeEndingBefore"
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeListPaymentIntents"
        "400":
          description: Invalid cursor (resource_missing).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "402":
          description: Card error (when ?simulate=card_declined / insufficient_funds).
        "429":
          description: Rate limit (when ?simulate=rate_limited).
        "500":
          description: Simulated server error (when ?fail_rate triggers).
        "504":
          description: Timeout (when ?simulate=timeout).
    post:
      tags:
        - Stripe — PaymentIntents
      summary: Create a PaymentIntent (not persisted)
      description: Writes return a Stripe-shaped object with a fresh id, but the data is NOT persisted. A follow-up GET for the same id returns 404. When body.confirm is true the returned status is succeeded; otherwise requires_payment_method.
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amount
                - currency
              properties:
                amount:
                  type: integer
                currency:
                  type: string
                customer:
                  type: string
                  pattern: ^cus_
                description:
                  type: string
                capture_method:
                  type: string
                  enum:
                    - automatic
                    - manual
                confirm:
                  type: boolean
                metadata:
                  type: object
      responses:
        "200":
          description: Created.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripePaymentIntent"
        "400":
          description: parameter_missing for amount or currency.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/payment_intents/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            description: Stripe-style id with the resource prefix.
    get:
      tags:
        - Stripe — PaymentIntents
      summary: Retrieve a paymentintent
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripePaymentIntent"
        "404":
          description: No such object.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/payment_intents/{id}/confirm":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            pattern: ^pi_
    post:
      tags:
        - Stripe — PaymentIntents
      summary: Confirm a PaymentIntent
      description: "Accepts any pi_* id (including ones from a previous POST that we never persisted).\n\n?simulate=requires_action returns the requires_action status with a next_action object — useful for testing 3DS challenge flows.\nOtherwise the PI is returned with status=succeeded."
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      requestBody:
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Confirmed.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripePaymentIntent"
        "404":
          description: id doesn't start with pi_.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/subscriptions":
    get:
      tags:
        - Stripe — Subscriptions
      summary: List subscriptions
      parameters:
        - "$ref": "#/components/parameters/StripeLimit"
        - "$ref": "#/components/parameters/StripeStartingAfter"
        - "$ref": "#/components/parameters/StripeEndingBefore"
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeListSubscriptions"
        "400":
          description: Invalid cursor (resource_missing).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "402":
          description: Card error (when ?simulate=card_declined / insufficient_funds).
        "429":
          description: Rate limit (when ?simulate=rate_limited).
        "500":
          description: Simulated server error (when ?fail_rate triggers).
        "504":
          description: Timeout (when ?simulate=timeout).
  "/api/stripe/subscriptions/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            description: Stripe-style id with the resource prefix.
    get:
      tags:
        - Stripe — Subscriptions
      summary: Retrieve a subscription
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeSubscription"
        "404":
          description: No such object.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/invoices":
    get:
      tags:
        - Stripe — Invoices
      summary: List invoices
      parameters:
        - "$ref": "#/components/parameters/StripeLimit"
        - "$ref": "#/components/parameters/StripeStartingAfter"
        - "$ref": "#/components/parameters/StripeEndingBefore"
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeListInvoices"
        "400":
          description: Invalid cursor (resource_missing).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "402":
          description: Card error (when ?simulate=card_declined / insufficient_funds).
        "429":
          description: Rate limit (when ?simulate=rate_limited).
        "500":
          description: Simulated server error (when ?fail_rate triggers).
        "504":
          description: Timeout (when ?simulate=timeout).
  "/api/stripe/invoices/{id}":
    parameters:
      - name: id
          in: path
          required: true
          schema:
            type: string
            description: Stripe-style id with the resource prefix.
    get:
      tags:
        - Stripe — Invoices
      summary: Retrieve a invoice
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeInvoice"
        "404":
          description: No such object.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
  "/api/stripe/webhooks/trigger":
    post:
      tags:
        - Stripe — Webhooks
      summary: Sign and POST a Stripe-shaped event to a public HTTPS URL
      description: "Defenses (see src/lib/stripe-webhook.ts for details):\n- target_url must be https://. http, file, ftp, etc. are rejected.\n- Refuses localhost, *.local, *.internal, private IP literals, and any hostname that DNS-resolves to a private/internal IP (including 169.254.169.254 cloud metadata).\n- redirect:'manual', 5s timeout, target's response body is discarded so this can't be used as a port-scan oracle.\n- Per-IP rate limit: 10 calls/min. count is clamped to 5 events per call.\n\nSigned with HMAC-SHA256(secret, `${t}.${payload}`). Stripe-Signature header format: t=<unix>,v1=<hex>. The public test secret is published in the response so devs can verify with stripe.js's webhooks.constructEvent()."
      parameters:
        - "$ref": "#/components/parameters/ChaosSimulate"
        - "$ref": "#/components/parameters/ChaosDelay"
        - "$ref": "#/components/parameters/ChaosFailRate"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/StripeWebhookTriggerRequest"
      responses:
        "200":
          description: Events delivered (or attempted).
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeWebhookTriggerResponse"
        "400":
          description: Missing field, unsupported event_type, or unsafe target_url.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
        "429":
          description: Per-IP rate limit. Retry-After header included.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StripeError"
components:
  schemas:
    User:
      type: object
      required:
        - id
        - name
        - email
        - phone
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
    Post:
      type: object
      required:
        - id
        - userId
        - title
        - body
      properties:
        id:
          type: integer
        userId:
          type: integer
        title:
          type: string
        body:
          type: string
    Product:
      type: object
      required:
        - id
        - name
        - price
        - inStock
      properties:
        id:
          type: integer
        name:
          type: string
        price:
          type: number
        inStock:
          type: boolean
    Recipe:
      type: object
      required:
        - id
        - title
        - servings
        - ingredients
        - steps
      properties:
        id:
          type: integer
        title:
          type: string
        servings:
          type: integer
        ingredients:
          type: array
          items:
            type: string
        steps:
          type: array
          items:
            type: string
    Todo:
      type: object
      required:
        - id
        - userId
        - title
        - completed
      properties:
        id:
          type: integer
        userId:
          type: integer
        title:
          type: string
        completed:
          type: boolean
    Comment:
      type: object
      required:
        - id
        - postId
        - author
        - body
      properties:
        id:
          type: integer
        postId:
          type: integer
        author:
          type: string
        body:
          type: string
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - message
          properties:
            type:
              type: string
            message:
              type: string
    StripeCustomer:
      type: object
      required:
        - id
        - object
        - created
        - livemode
      properties:
        id:
          type: string
          pattern: ^cus_
        object:
          type: string
          enum:
            - customer
        created:
          type: integer
          description: Unix epoch seconds (NOT ISO 8601).
        email:
          type:
            - string
            - null
          format: email
        name:
          type:
            - string
            - null
        description:
          type:
            - string
            - null
        phone:
          type:
            - string
            - null
        currency:
          type: string
          description: Three-letter ISO currency code (lowercase).
        delinquent:
          type: boolean
        livemode:
          type: boolean
          enum:
            - false
        metadata:
          type: object
          additionalProperties:
            type: string
    StripeCharge:
      type: object
      required:
        - id
        - object
        - amount
        - currency
        - status
        - livemode
      properties:
        id:
          type: string
          pattern: ^ch_
        object:
          type: string
          enum:
            - charge
        amount:
          type: integer
          description: Amount in the smallest currency unit (e.g. cents). 1000 = $10.00.
        amount_captured:
          type: integer
        amount_refunded:
          type: integer
        captured:
          type: boolean
        created:
          type: integer
        currency:
          type: string
        customer:
          type:
            - string
            - null
          pattern: ^cus_
        description:
          type:
            - string
            - null
        paid:
          type: boolean
        status:
          type: string
          enum:
            - succeeded
            - pending
            - failed
        livemode:
          type: boolean
          enum:
            - false
        metadata:
          type: object
          additionalProperties:
            type: string
        payment_method_details:
          type: object
          properties:
            type:
              type: string
              enum:
                - card
            card:
              type: object
              properties:
                brand:
                  type: string
                last4:
                  type: string
                exp_month:
                  type: integer
                exp_year:
                  type: integer
    StripePaymentIntent:
      type: object
      required:
        - id
        - object
        - amount
        - currency
        - status
        - livemode
      properties:
        id:
          type: string
          pattern: ^pi_
        object:
          type: string
          enum:
            - payment_intent
        amount:
          type: integer
        amount_capturable:
          type: integer
        amount_received:
          type: integer
        capture_method:
          type: string
          enum:
            - automatic
            - manual
        client_secret:
          type: string
        confirmation_method:
          type: string
          enum:
            - automatic
            - manual
        created:
          type: integer
        currency:
          type: string
        customer:
          type:
            - string
            - null
          pattern: ^cus_
        description:
          type:
            - string
            - null
        status:
          type: string
          enum:
            - requires_payment_method
            - requires_confirmation
            - requires_action
            - processing
            - succeeded
            - canceled
        livemode:
          type: boolean
          enum:
            - false
        metadata:
          type: object
          additionalProperties:
            type: string
        next_action:
          type:
            - object
            - null
          description: Present when status=requires_action. Mirrors Stripe's next_action shape (use_stripe_sdk → three_d_secure_redirect).
          properties:
            type:
              type: string
            use_stripe_sdk:
              type: object
    StripeSubscription:
      type: object
      required:
        - id
        - object
        - status
        - livemode
      properties:
        id:
          type: string
          pattern: ^sub_
        object:
          type: string
          enum:
            - subscription
        billing_cycle_anchor:
          type: integer
        cancel_at_period_end:
          type: boolean
        canceled_at:
          type:
            - integer
            - null
        collection_method:
          type: string
          enum:
            - charge_automatically
            - send_invoice
        created:
          type: integer
        currency:
          type: string
        current_period_start:
          type: integer
        current_period_end:
          type: integer
        customer:
          type: string
          pattern: ^cus_
        description:
          type:
            - string
            - null
        livemode:
          type: boolean
          enum:
            - false
        metadata:
          type: object
          additionalProperties:
            type: string
        status:
          type: string
          enum:
            - trialing
            - active
            - past_due
            - canceled
            - unpaid
            - incomplete
            - incomplete_expired
    StripeInvoice:
      type: object
      required:
        - id
        - object
        - status
        - livemode
      properties:
        id:
          type: string
          pattern: ^in_
        object:
          type: string
          enum:
            - invoice
        account_country:
          type: string
        account_name:
          type: string
        amount_due:
          type: integer
        amount_paid:
          type: integer
        amount_remaining:
          type: integer
        created:
          type: integer
        currency:
          type: string
        customer:
          type: string
          pattern: ^cus_
        subscription:
          type:
            - string
            - null
          pattern: ^sub_
        status:
          type: string
          enum:
            - draft
            - open
            - paid
            - uncollectible
            - void
        livemode:
          type: boolean
          enum:
            - false
        metadata:
          type: object
          additionalProperties:
            type: string
    StripeError:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - message
          properties:
            type:
              type: string
              description: "One of: invalid_request_error, card_error, rate_limit_error, api_error."
            code:
              type: string
            decline_code:
              type: string
              description: Present on card_error responses (e.g. generic_decline, insufficient_funds).
            message:
              type: string
            param:
              type: string
    StripeListCustomers:
      type: object
      required:
        - object
        - data
        - has_more
        - url
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            "$ref": "#/components/schemas/StripeCustomer"
        has_more:
          type: boolean
        url:
          type: string
          description: Stripe's path for this resource (e.g. /v1/customers).
    StripeListCharges:
      type: object
      required:
        - object
        - data
        - has_more
        - url
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            "$ref": "#/components/schemas/StripeCharge"
        has_more:
          type: boolean
        url:
          type: string
          description: Stripe's path for this resource (e.g. /v1/customers).
    StripeListPaymentIntents:
      type: object
      required:
        - object
        - data
        - has_more
        - url
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            "$ref": "#/components/schemas/StripePaymentIntent"
        has_more:
          type: boolean
        url:
          type: string
          description: Stripe's path for this resource (e.g. /v1/customers).
    StripeListSubscriptions:
      type: object
      required:
        - object
        - data
        - has_more
        - url
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            "$ref": "#/components/schemas/StripeSubscription"
        has_more:
          type: boolean
        url:
          type: string
          description: Stripe's path for this resource (e.g. /v1/customers).
    StripeListInvoices:
      type: object
      required:
        - object
        - data
        - has_more
        - url
      properties:
        object:
          type: string
          enum:
            - list
        data:
          type: array
          items:
            "$ref": "#/components/schemas/StripeInvoice"
        has_more:
          type: boolean
        url:
          type: string
          description: Stripe's path for this resource (e.g. /v1/customers).
    StripeWebhookTriggerRequest:
      type: object
      required:
        - event_type
        - target_url
      properties:
        event_type:
          type: string
          enum:
            - charge.succeeded
            - charge.failed
            - customer.created
            - payment_intent.succeeded
            - payment_intent.payment_failed
            - invoice.paid
            - customer.subscription.created
            - customer.subscription.deleted
        target_url:
          type: string
          format: uri
          description: Public HTTPS URL to POST the signed event to. Private/internal addresses are refused at request time AND after DNS resolution.
        count:
          type: integer
          minimum: 1
          maximum: 5
          default: 1
          description: Events to send. Clamped to 5.
    StripeWebhookTriggerResponse:
      type: object
      required:
        - object
        - event_type
        - delivered
        - results
        - signature
      properties:
        object:
          type: string
          enum:
            - webhook.trigger_result
        event_type:
          type: string
        delivered:
          type: integer
        results:
          type: array
          items:
            type: object
            required:
              - event_id
              - delivery_status
              - duration_ms
            properties:
              event_id:
                type: string
                pattern: ^evt_
              delivery_status:
                type: integer
                description: HTTP status returned by the target. 0 if the request errored before a response.
              duration_ms:
                type: integer
              error:
                type: string
        signature:
          type: object
          properties:
            algorithm:
              type: string
              enum:
                - HMAC-SHA256
            header_format:
              type: string
            secret_hint:
              type: string
            test_secret:
              type: string
            example_header:
              type: string
  parameters:
    StripeLimit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 10
      description: Page size, capped at 100. Matches Stripe's pagination.
    StripeStartingAfter:
      name: starting_after
      in: query
      schema:
        type: string
      description: "Cursor: ID of the last item from a previous page. Returns items strictly AFTER this id."
    StripeEndingBefore:
      name: ending_before
      in: query
      schema:
        type: string
      description: "Cursor: ID of the first item from a previous page. Returns items strictly BEFORE this id."
    ChaosSimulate:
      name: simulate
      in: query
      schema:
        type: string
        enum:
          - card_declined
          - insufficient_funds
          - rate_limited
          - timeout
          - requires_action
          - succeeded
      description: "Failure-simulation hook.\n- card_declined → 402 card_error / generic_decline\n- insufficient_funds → 402 card_error / insufficient_funds\n- rate_limited → 429 rate_limit_error\n- timeout → 504 api_error after a 5s delay\n\nOn /payment_intents/{id}/confirm, the non-chaos values requires_action / succeeded steer the confirm result."
    ChaosDelay:
      name: delay
      in: query
      schema:
        type: integer
        minimum: 0
        maximum: 10000
      description: Sleep this many ms before responding. Clamped to 10000.
    ChaosFailRate:
      name: fail_rate
      in: query
      schema:
        type: number
        minimum: 0
        maximum: 1
      description: Probability (0..1) of returning a 500 api_error. Useful for testing retry logic.