openapi: 3.1.0
info:
  title: Travelmode Platform API
  version: 1.0.0
  summary: "Cross-cutting developer endpoints: webhooks and usage/quota."
  description: |
    The Platform API exposes the cross-cutting developer-account endpoints that are not specific to any single product surface: managing webhook endpoints (subscribe to signed events and inspect delivery attempts) and reading your current-period usage and remaining quota.

    These endpoints share the same response envelope, error format, API key authentication scheme, and rate-limit headers as the rest of the Travelmode developer platform. See the accompanying markdown guides in `docs/platform-api/` for narrative guidance and worked examples.
  contact:
    name: Travelmode Developer Support
    url: https://travelmode.ai
  license:
    name: Proprietary
    url: https://travelmode.ai
servers:
  - url: https://api.travelmode.ai
    description: Production
  - url: https://{host}
    description: Self-hosted / Replit deployment. Replace `{host}` with your deployed domain.
    variables:
      host:
        default: your-app-domain.replit.app
tags:
  - name: Webhooks
    description: Manage developer-owned webhook endpoints and inspect deliveries.
  - name: Usage
    description: Read your current-period quota, usage, and remaining capacity.
security:
  - BearerApiKey: []
  - HeaderApiKey: []
paths:
  /v1/webhooks:
    post:
      tags:
        - Webhooks
      summary: Create a webhook endpoint.
      description: |
        Registers a developer-managed HTTPS endpoint that receives
        signed `weather.*` events. Returns the freshly-minted secret
        exactly once — only its SHA-256 hash is persisted. Use the
        `secret` to verify the `X-Travelmode-Signature` header on
        every delivery.

        **Billing:** 1 unit per call. **Required scope:**
        `weather:webhooks`.
      operationId: createWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookCreateRequest"
            examples:
              all-events:
                $ref: "#/components/examples/WebhookCreateRequest"
      responses:
        "201":
          description: Webhook endpoint created. Secret returned once.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookCreateEnvelope"
              examples:
                created:
                  $ref: "#/components/examples/WebhookCreateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/BillingLimitReached"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    get:
      tags:
        - Webhooks
      summary: List the calling developer's webhook endpoints.
      description: |
        Returns every endpoint, including disabled ones (so devs can
        see history). Secrets are never returned — only the public
        `secret_prefix`.

        **Billing:** 0 units (metadata read). **Required scope:**
        `weather:webhooks`.
      operationId: listWebhooks
      responses:
        "200":
          description: Webhook list.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookListEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /v1/webhooks/{webhook_id}:
    parameters:
      - name: webhook_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: ID returned by `POST /v1/webhooks`.
    get:
      tags:
        - Webhooks
      summary: Get a single webhook endpoint.
      description: |
        Returns the metadata for a single endpoint owned by the
        calling developer. Secrets are never returned — only the
        public `secret_prefix`.

        **Billing:** 0 units. **Required scope:** `weather:webhooks`.
      operationId: getWebhook
      responses:
        "200":
          description: Endpoint found.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookGetEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      tags:
        - Webhooks
      summary: Disable a webhook endpoint.
      description: |
        Soft-deactivates an endpoint. The delivery worker stops
        targeting it; the row itself is retained so historical
        delivery attempts remain queryable.

        **Billing:** 1 unit per call. **Required scope:**
        `weather:webhooks`.
      operationId: deleteWebhook
      responses:
        "200":
          description: Endpoint disabled.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeleteEnvelope"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /v1/webhooks/{webhook_id}/attempts:
    parameters:
      - name: webhook_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Webhooks
      summary: List recent webhook delivery attempts.
      description: |
        Returns the most-recent delivery attempts for a webhook
        endpoint. Useful for debugging why a webhook isn't being
        received.

        **Billing:** 0 units (debug surface). **Required scope:**
        `weather:webhooks`.
      operationId: listWebhookAttempts
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        "200":
          description: Attempt list.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookAttemptsEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /v1/usage:
    get:
      tags:
        - Usage
      summary: Read your current-period usage and remaining quota.
      description: |
        Returns the calling key's current-period usage, plan limits,
        and remaining quota across the four enforced quota
        dimensions: `requests`, `timeline_items`, `route_calls`, and
        `active_watches`. Calling this endpoint never burns request
        quota — it logs `billing_units = 0`.

        **Billing:** 0 units. **No additional scope required** beyond
        a valid API key.
      operationId: getUsage
      responses:
        "200":
          description: Usage snapshot.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/XRequestId"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageEnvelope"
              examples:
                free-tier:
                  $ref: "#/components/examples/UsageResponseFreeTier"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/InternalError"
components:
  securitySchemes:
    BearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: tm_weather_*****
      description: |
        Send your API key as `Authorization: Bearer tm_weather_...`.
        Keys are minted via internal admin tooling for the MVP; the
        plaintext is shown exactly once on creation. See the
        Authentication doc.
    HeaderApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Alternative to the bearer header. Send the same `tm_weather_*`
        token as the value of `X-API-Key`.
  headers:
    XRequestId:
      description: Unique ID for this request, also surfaced in `meta.request_id`.
      schema:
        type: string
        format: uuid
    XRateLimitLimit:
      description: Cap for the primary "requests/month" quota dimension.
      schema:
        type: integer
        minimum: 0
    XRateLimitRemaining:
      description: Requests remaining in the current monthly window.
      schema:
        type: integer
        minimum: 0
    XRateLimitReset:
      description: Unix-seconds timestamp when the monthly quota resets.
      schema:
        type: integer
        minimum: 0
  responses:
    BadRequest:
      description: Malformed request — missing or invalid fields.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: bad_request
              message: lat is required and must be a finite number
              details:
                field: lat
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    Unauthorized:
      description: |
        Missing, invalid, expired, or revoked API key. Look at
        `error.code` for the specific reason: `api_key_missing`,
        `api_key_invalid`, `api_key_expired`, `api_key_revoked`.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: api_key_missing
              message: API key is required
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    Forbidden:
      description: |
        API key valid but missing the scope this endpoint requires
        (`scope_required`) or the developer account is suspended
        (`account_suspended`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: scope_required
              message: API key is missing a required scope
              details:
                required: weather:timeline
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    NotFound:
      description: Resource not found, or not owned by the calling developer account.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: not_found
              message: Resource not found
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    RateLimited:
      description: |
        Rate-limit exceeded for the calling key. The `Retry-After`
        header is not currently emitted; use the `X-RateLimit-Reset`
        header to know when the monthly quota resets.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: rate_limit_exceeded
              message: Monthly request quota exceeded
              details:
                dimension: requests
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    BillingLimitReached:
      description: |
        The calling plan flatly excludes this feature (e.g. Free tier
        calling `/v1/weather/route`). Distinct from `rate_limited`
        because no amount of waiting unblocks the request — the caller
        has to upgrade plan.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: billing_limit_reached
              message: Plan 'free' does not include route intelligence
              details:
                dimension: route_calls
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
    InternalError:
      description: Unhandled internal error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
          example:
            error:
              code: internal_error
              message: Internal server error
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
            meta:
              request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
              generated_at: 2026-04-25T18:34:00.000Z
  schemas:
    ApiMeta:
      type: object
      required:
        - request_id
        - generated_at
      properties:
        request_id:
          type: string
          format: uuid
          description: Per-request UUID, also surfaced as the `X-Request-Id` header.
        generated_at:
          type: string
          format: date-time
          description: Server-side timestamp when the response was constructed.
        cache_status:
          type: string
          enum:
            - HIT
            - MISS
            - STALE
            - MIXED
        snapshot_id:
          type:
            - string
            - "null"
          format: uuid
        billing_units:
          type: integer
          minimum: 0
          description: Billing units accrued by this single request.
        items_analyzed:
          type: integer
          minimum: 0
        segments_analyzed:
          type: integer
          minimum: 0
        units:
          type: string
          enum:
            - metric
            - imperial
            - standard
        language:
          type: string
        environment:
          type: string
          enum:
            - test
            - production
      additionalProperties: true
    ErrorEnvelope:
      type: object
      required:
        - error
        - meta
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              $ref: "#/components/schemas/ErrorCode"
            message:
              type: string
            details:
              description: Code-specific structured detail (e.g. `field`, `dimension`, provider error code).
            request_id:
              type: string
              format: uuid
        meta:
          $ref: "#/components/schemas/ApiMeta"
    ErrorCode:
      type: string
      description: |
        Machine-readable error code. Exact set is pinned to PRD section 25.
      enum:
        - api_key_missing
        - api_key_invalid
        - api_key_revoked
        - api_key_expired
        - scope_required
        - account_suspended
        - rate_limited
        - rate_limit_exceeded
        - billing_limit_reached
        - bad_request
        - route_invalid
        - timeline_item_invalid
        - not_found
        - provider_unavailable
        - weather_refresh_timeout
        - internal_error
    WebhookEventType:
      type: string
      description: |
        MVP webhook event types (PRD section 27).
      enum:
        - weather.change_detected
        - weather.severe_alert_added
        - weather.watch_expired
        - weather.watch_failed
        - weather.snapshot_refreshed
        - weather.webhook_delivery_failed
        - weather.webhook_endpoint_disabled
    WebhookCreateRequest:
      type: object
      required:
        - url
        - events
      additionalProperties: false
      properties:
        url:
          type: string
          format: uri
          description: HTTPS endpoint that receives signed `weather.*` events.
          pattern: ^https://
          maxLength: 2048
        events:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/WebhookEventType"
        description:
          type:
            - string
            - "null"
          minLength: 1
          maxLength: 500
        metadata:
          type: object
          additionalProperties: true
          nullable: true
    WebhookEndpoint:
      type: object
      required:
        - webhook_id
        - url
        - events
        - status
        - secret_prefix
        - created_at
      properties:
        webhook_id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        description:
          type:
            - string
            - "null"
        status:
          type: string
          enum:
            - active
            - disabled
        secret_prefix:
          type: string
        failure_count:
          type: integer
          minimum: 0
        last_delivery_at:
          type:
            - string
            - "null"
          format: date-time
        last_success_at:
          type:
            - string
            - "null"
          format: date-time
        last_failure_at:
          type:
            - string
            - "null"
          format: date-time
        disabled_at:
          type:
            - string
            - "null"
          format: date-time
        disabled_reason:
          type:
            - string
            - "null"
          description: |
            Free-form reason populated when the auto-disable safeguard
            transitions the endpoint to `status='disabled'`. NULL when
            the developer disabled the endpoint themselves.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WebhookCreateEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          allOf:
            - $ref: "#/components/schemas/WebhookEndpoint"
            - type: object
              required:
                - secret
              properties:
                secret:
                  type: string
                  description: |
                    Plaintext secret for signing verification. Returned
                    exactly once on creation; only its SHA-256 hash is
                    persisted.
    WebhookListEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          type: object
          required:
            - webhooks
          properties:
            webhooks:
              type: array
              items:
                $ref: "#/components/schemas/WebhookEndpoint"
    WebhookGetEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          $ref: "#/components/schemas/WebhookEndpoint"
    WebhookDeleteEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          type: object
          required:
            - webhook_id
            - status
          properties:
            webhook_id:
              type: string
              format: uuid
            status:
              type: string
              enum:
                - active
                - disabled
            disabled_at:
              type:
                - string
                - "null"
              format: date-time
    WebhookAttempt:
      type: object
      required:
        - attempt_id
        - event_id
        - event_type
        - attempt_number
        - status
        - scheduled_at
      properties:
        attempt_id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        event_type:
          $ref: "#/components/schemas/WebhookEventType"
        attempt_number:
          type: integer
          minimum: 1
        status:
          type: string
          enum:
            - pending
            - delivered
            - failed
            - retrying
            - abandoned
        response_status:
          type:
            - integer
            - "null"
        response_time_ms:
          type:
            - integer
            - "null"
        response_body_excerpt:
          type:
            - string
            - "null"
        error_message:
          type:
            - string
            - "null"
        request_signature:
          type:
            - string
            - "null"
        request_timestamp:
          type:
            - string
            - "null"
          format: date-time
        scheduled_at:
          type: string
          format: date-time
        attempted_at:
          type:
            - string
            - "null"
          format: date-time
        completed_at:
          type:
            - string
            - "null"
          format: date-time
        next_retry_at:
          type:
            - string
            - "null"
          format: date-time
    WebhookAttemptsEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          type: object
          required:
            - webhook_id
            - attempts
          properties:
            webhook_id:
              type: string
              format: uuid
            attempts:
              type: array
              items:
                $ref: "#/components/schemas/WebhookAttempt"
    QuotaUsage:
      type: object
      required:
        - limit
        - used
        - remaining
      properties:
        limit:
          type: integer
          minimum: 0
        used:
          type: integer
          minimum: 0
        remaining:
          type: integer
          minimum: 0
    UsageEnvelope:
      type: object
      required:
        - data
        - meta
      properties:
        meta:
          $ref: "#/components/schemas/ApiMeta"
        data:
          type: object
          required:
            - plan
            - environment
            - is_internal
            - period
            - limits
            - usage
          properties:
            plan:
              type: string
              enum:
                - free
                - starter
                - pro
                - enterprise
            environment:
              type: string
              enum:
                - test
                - production
            is_internal:
              type: boolean
            period:
              type: object
              required:
                - start
                - end
              properties:
                start:
                  type: string
                  format: date-time
                end:
                  type: string
                  format: date-time
            limits:
              type: object
              required:
                - requests_per_month
                - timeline_items_per_month
                - active_watches
                - route_calls_per_month
              properties:
                requests_per_month:
                  type: integer
                  minimum: 0
                timeline_items_per_month:
                  type: integer
                  minimum: 0
                active_watches:
                  type: integer
                  minimum: 0
                route_calls_per_month:
                  type: integer
                  minimum: 0
            usage:
              type: object
              required:
                - requests
                - timeline_items
                - route_calls
                - active_watches
              properties:
                requests:
                  $ref: "#/components/schemas/QuotaUsage"
                timeline_items:
                  $ref: "#/components/schemas/QuotaUsage"
                route_calls:
                  $ref: "#/components/schemas/QuotaUsage"
                active_watches:
                  $ref: "#/components/schemas/QuotaUsage"
  examples:
    WebhookCreateRequest:
      summary: Subscribe to all MVP weather events.
      value:
        url: https://example.com/hooks/weather
        events:
          - weather.change_detected
          - weather.severe_alert_added
          - weather.watch_expired
          - weather.watch_failed
          - weather.snapshot_refreshed
          - weather.webhook_delivery_failed
          - weather.webhook_endpoint_disabled
        description: Production weather notifier
    WebhookCreateResponse:
      summary: Webhook created — `secret` returned exactly once.
      value:
        data:
          webhook_id: 9f8df5f0-8d9f-4a37-9c1a-9b2b3c4d5e6b
          url: https://example.com/hooks/weather
          events:
            - weather.change_detected
            - weather.severe_alert_added
            - weather.watch_expired
            - weather.watch_failed
            - weather.snapshot_refreshed
            - weather.webhook_delivery_failed
            - weather.webhook_endpoint_disabled
          description: Production weather notifier
          status: active
          secret: whsec_2c5b1f7e8b3d4f5a6c7d8e9f0a1b2c3d
          secret_prefix: whsec_2c5b1f7e8b3d
          created_at: 2026-04-25T18:01:13.500Z
        meta:
          request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
          generated_at: 2026-04-25T18:01:13.500Z
          environment: production
          billing_units: 1
    UsageResponseFreeTier:
      summary: A Free-tier key with light usage so far this month.
      value:
        data:
          plan: free
          environment: production
          is_internal: false
          period:
            start: 2026-04-01T00:00:00.000Z
            end: 2026-05-01T00:00:00.000Z
          limits:
            requests_per_month: 1000
            timeline_items_per_month: 100
            active_watches: 0
            route_calls_per_month: 0
          usage:
            requests:
              limit: 1000
              used: 42
              remaining: 958
            timeline_items:
              limit: 100
              used: 6
              remaining: 94
            route_calls:
              limit: 0
              used: 0
              remaining: 0
            active_watches:
              limit: 0
              used: 0
              remaining: 0
        meta:
          request_id: 7c0f3c2c-9e4a-4d29-b4a2-9c2b4e9f5c11
          generated_at: 2026-04-25T18:01:13.500Z
          environment: production
          billing_units: 0
