openapi: 3.1.0

info:
  title: TokenTip API
  version: 0.1.0
  summary: The synchronous (request/response) surface of the TokenTip API.
  description: |
    Programmatic access to a TokenTip account. The **event** surface — the tip
    lifecycle as it happens — is described separately in `asyncapi.yaml`; both
    share the models in `components/schemas.yaml`, so a `Tip` is the same shape
    whether you read it here or receive it in an event.

    **Auth.** Every request carries `Authorization: Bearer tt_…`, an API token
    minted in the dashboard. A token is **creator**-scoped (its own account) or
    **operator**-scoped (system-wide). Operator endpoints (`/v1/operator/*`)
    return `403` for a creator token.

    **Money** is always an exact decimal string. **Pagination** is cursor-based:
    pass the previous response's `next_cursor` as `cursor`. Errors are RFC 7807
    `application/problem+json`.

    **Rate limits.** Requests are limited per token (per client address when
    unauthenticated): a sustained 4 requests/second with a burst of 120. Exceeding
    it returns `429` with a `Retry-After` header. Concurrent event streams are
    capped at 5 per creator; opening a sixth returns `429`.
  contact:
    name: TokenTip
    url: https://tokentip.to/
    email: dj@codetestcode.io
  license:
    name: Proprietary — see Terms of Service
    url: https://tokentip.to/terms

servers:
  - url: https://tokentip.to
    description: Production

security:
  - bearerToken: []

tags:
  - name: Creator
    description: Your own account — balance, tips, key, stats.
  - name: Webhooks
    description: Register endpoints to receive signed events.
  - name: Operator
    description: System-wide read + privileged actions. Requires an operator token.

paths:
  /v1/me:
    get:
      tags: [Creator]
      operationId: getMe
      summary: Get the authenticated creator's profile + balance summary.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Me' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/balance:
    get:
      tags: [Creator]
      operationId: getBalance
      summary: Get the current credit balance and live key usage.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Balance' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/tips:
    get:
      tags: [Creator]
      operationId: listTips
      summary: List your tips, newest first.
      parameters:
        - $ref: './components/parameters.yaml#/Limit'
        - $ref: './components/parameters.yaml#/Cursor'
        - $ref: './components/parameters.yaml#/StatusFilter'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/TipList' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/tips/{id}:
    get:
      tags: [Creator]
      operationId: getTip
      summary: Get one of your tips.
      parameters:
        - $ref: './components/parameters.yaml#/TipId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Tip' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '404': { $ref: './components/responses.yaml#/NotFound' }

  /v1/key:
    get:
      tags: [Creator]
      operationId: getKey
      summary: Get the active key's non-secret fingerprint and live usage.
      description: Never returns the secret. Use `POST /v1/key/rotate` to obtain a new secret.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Key' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '404':
          $ref: './components/responses.yaml#/NotFound'

  /v1/key/rotate:
    post:
      tags: [Creator]
      operationId: rotateKey
      summary: Rotate the standing key; returns the new secret exactly once.
      description: >-
        Disables the current key and mints a replacement with the same balance.
        The secret in the response is shown once and never stored — save it.
      parameters:
        - $ref: './components/parameters.yaml#/IdempotencyKey'
      responses:
        '200':
          description: New key minted.
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/MintedKey' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '409':
          $ref: './components/responses.yaml#/Conflict'
        '502':
          $ref: './components/responses.yaml#/BadGateway'

  /v1/stats:
    get:
      tags: [Creator]
      operationId: getStats
      summary: Lifetime aggregates for your account.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Stats' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/tokens:
    get:
      tags: [Creator]
      operationId: listTokens
      summary: List your API tokens (never the token itself).
      description: >-
        Creator-scoped tokens see only creator-scoped tokens; an operator token
        sees every token on the account.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: './components/schemas.yaml#/ApiToken' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/tokens/{id}:
    delete:
      tags: [Creator]
      operationId: revokeToken
      summary: Revoke an API token immediately.
      description: >-
        Takes effect at once. A token may revoke itself. A creator-scoped token
        cannot revoke an operator-scoped one.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '204': { description: Revoked. }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '404': { $ref: './components/responses.yaml#/NotFound' }

  /v1/webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List your registered webhook endpoints.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: './components/schemas.yaml#/WebhookEndpoint' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Register a webhook endpoint; returns its signing secret once.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: './components/schemas.yaml#/NewWebhook' }
      responses:
        '201':
          description: Created.
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/WebhookEndpointCreated' }
        '400': { $ref: './components/responses.yaml#/BadRequest' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }

  /v1/webhooks/{id}:
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Remove a webhook endpoint.
      parameters:
        - $ref: './components/parameters.yaml#/WebhookId'
      responses:
        '204': { description: Deleted. }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '404': { $ref: './components/responses.yaml#/NotFound' }

  # ------------------------------------------------------------- operator

  /v1/operator/overview:
    get:
      tags: [Operator]
      operationId: operatorOverview
      summary: System-wide snapshot — solvency pool, money, vitals.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Overview' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '403': { $ref: './components/responses.yaml#/Forbidden' }

  /v1/operator/tips:
    get:
      tags: [Operator]
      operationId: operatorListTips
      summary: List all tips across every creator, newest first.
      parameters:
        - $ref: './components/parameters.yaml#/Limit'
        - $ref: './components/parameters.yaml#/Cursor'
        - $ref: './components/parameters.yaml#/StatusFilter'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/TipList' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '403': { $ref: './components/responses.yaml#/Forbidden' }

  /v1/operator/creators:
    get:
      tags: [Operator]
      operationId: operatorListCreators
      summary: List creators with balances and live key usage.
      parameters:
        - $ref: './components/parameters.yaml#/Limit'
        - $ref: './components/parameters.yaml#/Cursor'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/CreatorList' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '403': { $ref: './components/responses.yaml#/Forbidden' }

  /v1/operator/tips/{id}/settle:
    post:
      tags: [Operator]
      operationId: operatorForceSettle
      summary: Force-settle a held tip now, bypassing the time hold.
      description: >-
        The solvency guard still applies — if the grant would over-commit the
        pool, the tip stays `held` and the response says so.
      parameters:
        - $ref: './components/parameters.yaml#/TipId'
        - $ref: './components/parameters.yaml#/IdempotencyKey'
      responses:
        '200':
          description: Processed (see `status` for the outcome).
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/ForceSettleResult' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '403': { $ref: './components/responses.yaml#/Forbidden' }
        '409': { $ref: './components/responses.yaml#/Conflict' }
        '502': { $ref: './components/responses.yaml#/BadGateway' }

  /v1/operator/creators/{handle}/disable-key:
    post:
      tags: [Operator]
      operationId: operatorDisableKey
      summary: Kill switch — disable a creator's live key (fraud response).
      description: Remote disable only; the creator can rotate to restore access.
      parameters:
        - $ref: './components/parameters.yaml#/Handle'
      responses:
        '200':
          description: Disabled.
          content:
            application/json:
              schema: { $ref: './components/schemas.yaml#/Ack' }
        '401': { $ref: './components/responses.yaml#/Unauthorized' }
        '403': { $ref: './components/responses.yaml#/Forbidden' }
        '404': { $ref: './components/responses.yaml#/NotFound' }
        '502': { $ref: './components/responses.yaml#/BadGateway' }

components:
  securitySchemes:
    bearerToken:
      $ref: './components/security.yaml#/bearerToken'
