> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seesaw.fun/llms.txt
> Use this file to discover all available pages before exploring further.

# List the key owner's coin ledger

> Every balance movement, newest first, plus any payments received but
not credited (`kind: uncredited`, balance unchanged).




## OpenAPI

````yaml /api-reference/openapi.yaml get /me/coin-ledger
openapi: 3.1.0
info:
  title: SeeSaw Open API
  version: 2.0.0
  description: >
    The **open API** of SeeSaw — a dedicated surface for AI agents and

    third-party integrations, exposed both as REST (`/open/v1/*`) and as an

    **MCP server** (`/mcp`, Streamable HTTP, stateless) in the same process.


    Every key can **read**; keys created with the `write` scope can also act

    on the owner's behalf: place a position, publish an opinion, back an

    opinion. Writes always run in two phases — a `preview_*` call returns the

    summary the user is meant to confirm plus a short-lived `confirm_token`,

    and the execute call accepts only that token.


    This contract is independent from the app-facing API
    (`docs/api/swagger.yaml`):

    it does not inherit app DTO shapes and follows its own conventions.


    ## Conventions

    - Field names are `snake_case`; enums are lowercase strings.

    - Decimals (coins, gems, prices) are **strings with 4 decimal places**
      (`"100.0000"`) — never floats. Rounding for display is the client's call.
      One deliberate exception: `avg_price` (coins per ruby, a ratio rather
      than a price) carries **6 decimal places**, and says so at the field.
    - Timestamps are RFC 3339 UTC (`2026-09-09T12:00:00Z`).

    - Every resource carries a human-visitable `url`.

    - Lists use **opaque cursor pagination**: `{data, next_cursor, has_more}`.
      `limit` defaults to 20, max 100. Reusing a cursor with different query
      params does not guarantee continuity.
    - Single resources are returned bare (no wrapper); errors use
      `{"error": {"code", "message", "details"}}`.
    - Vocabulary follows the 2.0 glossary: **opinion** (not comment), **back**,
      **position**, **stake**, **payout**.

    ## Authentication and scopes

    Every request (public data included) requires a **personal API key** (PAT):

    `Authorization: Bearer sspat_…`. Keys are issued per user (max 5 active)

    and can be revoked anytime. Key management lives on the app API

    (`/v1/users/me/api-keys`, JWT auth — see swagger.yaml), NOT on this surface.


    Two scope tiers, chosen when the key is created and **fixed for the life of

    the key** (there is no upgrade path — create a new key instead):


    | Scopes | Grants |

    | --- | --- |

    | `["read"]` | The whole read surface. The default. |

    | `["read","write"]` | The read surface **plus** the six write operations. |


    A read-only key hitting a write endpoint gets `403 FORBIDDEN_SCOPE`; over

    MCP it never sees the write tools at all.


    ## The write flow

    1. Call `POST …/order-preview`, `…/opinion-preview` or `…/back-preview`.
       Nothing is charged; the response carries a `summary` plus, when the
       action is currently possible, a `confirm_token` and its `expires_at`.
    2. **Show the summary to the user and get explicit confirmation.**

    3. Call `POST /orders`, `/opinions` or `/backs` with only that token.


    Tokens are valid **5 minutes**, are bound to the issuing user, key, action

    and parameters, and are single-use. `place_order` and `back_opinion` are

    idempotent on retry with the same token (`replayed: true`);

    `publish_opinion` is strictly single-use and answers a retry with

    `409 CONFIRM_TOKEN_USED`.


    ## Rate limits (per key)

    - **Read window**: 120 requests/min and 10,000 requests/day across the
      surface. The three `preview_*` operations count only against this window.
    - **Search**: additionally capped at 30 requests/min — a dedicated per-key
      budget shared by REST `/search` and the MCP `search_topics` tool.
    - **Write window**: the three executing operations (`/orders`, `/opinions`,
      `/backs`) are additionally capped at **10/min and 200/day**; they also
      consume the read window.
    - `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset`
      response headers reflect the binding minute window; 429 responses
      carry `Retry-After`. Over MCP, exceeding a quota surfaces as a tool
      error naming the retry delay.

    ## MCP server

    `POST https://api.seesaw.fun/mcp` (Streamable HTTP, stateless, same

    `Authorization` header). A read key sees **11 tools**; a read+write key

    sees **17**:


    - Read: `search_topics`, `list_topics`, `list_topic_categories`,
      `get_topic`, `list_topic_opinions`, `get_my_profile`,
      `list_my_positions`, `list_my_opinions`, `list_my_backed_opinions`,
      `list_my_watchlist`, `list_my_coin_ledger`.
    - Write: `preview_order` / `place_order`, `preview_opinion` /
      `publish_opinion`, `preview_back` / `back_opinion`.

    Read and preview tools are annotated `readOnlyHint: true`; the three

    executing tools carry `destructiveHint: true` and

    `_meta["anthropic/requiresUserInteraction"] = true`, which compatible

    clients turn into a mandatory confirmation prompt. Tool results carry the

    corresponding REST response JSON.


    Read tools take an optional `language` argument; REST reads the

    `Accept-Language` header instead. Supported: `en` (default), `zh-CN`,

    `zh-TW`, `ja`, `ko`, `th`, `vi`, `id`.


    ## Trading mechanisms

    2.0 topics run one of two mechanisms. `v21_lmsr` is the live one: a fixed

    **100-coin** stake, one order per topic, no selling, no fees; a correct

    option pays **rubies** equal to the order's `units` (the stake itself is

    not returned). `v2_share` topics are legacy share-pool markets — readable,

    but not tradable through this surface (`preview_order` answers

    `MECHANISM_UNSUPPORTED`). List and search endpoints default to `v21_lmsr`.
servers:
  - url: https://api.seesaw.fun/open/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: Topics
    description: 2.0 prediction topics — browse, search, detail, opinions
  - name: Me
    description: Data of the authenticated key owner
  - name: Trading
    description: Place a position (preview then execute, `write` scope)
  - name: Opinions
    description: Publish and back opinions (preview then execute, `write` scope)
paths:
  /me/coin-ledger:
    get:
      tags:
        - Me
      summary: List the key owner's coin ledger
      description: |
        Every balance movement, newest first, plus any payments received but
        not credited (`kind: uncredited`, balance unchanged).
      operationId: listMyCoinLedger
      parameters:
        - name: flow
          in: query
          schema:
            type: string
            enum:
              - all
              - in
              - out
            default: all
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Ledger entries
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LedgerEntryList'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    Cursor:
      name: cursor
      in: query
      description: Opaque pagination cursor from a previous response's `next_cursor`.
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Page size (max 100).
      schema:
        type: integer
        default: 20
        minimum: 1
        maximum: 100
  schemas:
    LedgerEntryList:
      allOf:
        - $ref: '#/components/schemas/ListEnvelope'
        - type: object
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/LedgerEntry'
    ListEnvelope:
      type: object
      description: Uniform list shape. `next_cursor` is null on the last page.
      required:
        - data
        - next_cursor
        - has_more
      properties:
        data:
          type: array
          items: {}
        next_cursor:
          type:
            - string
            - 'null'
        has_more:
          type: boolean
    LedgerEntry:
      type: object
      description: |
        `kind: ledger` rows moved the balance (`amount` always positive, with a
        `direction`). `kind: uncredited` rows are payments received but not
        credited — the balance is unchanged and the amount is in the gateway's
        own currency, not coins.
      required:
        - kind
        - id
        - amount
        - created_at
      properties:
        kind:
          type: string
          enum:
            - ledger
            - uncredited
        id:
          type: string
        type:
          type: string
          description: Ledger rows only, e.g. `topic_bet`, `opinion_back`, `coin_recharge`.
        direction:
          type: string
          enum:
            - in
            - out
          description: Ledger rows only.
        amount:
          type: string
        balance_after:
          type: string
          description: Ledger rows only.
        reference_id:
          type:
            - string
            - 'null'
          format: uuid
          description: Ledger rows only — the order / backing that caused the movement.
        currency:
          type: string
          description: Uncredited rows only.
        provider:
          type: string
          description: Uncredited rows only.
        reason:
          type: string
          description: Uncredited rows only.
        created_at:
          type: string
          format: date-time
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - UNAUTHORIZED
                - FORBIDDEN
                - FORBIDDEN_SCOPE
                - NOT_FOUND
                - OPINION_NOT_FOUND
                - INVALID_ARGUMENT
                - RATE_LIMITED
                - SERVICE_DISABLED
                - INTERNAL
                - CONFIRM_TOKEN_INVALID
                - CONFIRM_TOKEN_EXPIRED
                - CONFIRM_TOKEN_USED
                - CONFIRM_UNAVAILABLE
                - MECHANISM_UNSUPPORTED
                - PREVIEW_UNAVAILABLE
                - TOPIC_ALREADY_STAKED
                - TOPIC_CLOSED
                - TOPIC_QUOTE_EXPIRED
                - TOPIC_QUOTE_INVALID
                - TOPIC_MECHANISM_MISMATCH
                - RETRY_IDEMPOTENCY_KEY
                - OPINION_ALREADY_BACKED
                - OPINION_BACK_PRICE_MOVED
                - OPINION_BACK_RETRY
                - OPINION_BACK_FULL
                - OPINION_BACK_CHAIN_BROKEN
                - OPINION_NEEDS_POSITION
                - OPINION_TOPIC_CLOSED
                - OPINION_TOPIC_HIDDEN
                - OPINIONS_UNAVAILABLE
                - INSUFFICIENT_BALANCE
                - CONTENT_INVALID
                - CONTENT_MODERATED
                - USER_PUNISHED
              description: |
                Stable contract values. `SERVICE_DISABLED` (503) is the
                emergency kill switch; `FORBIDDEN_SCOPE` (403) means the key
                lacks the `write` scope. The `CONFIRM_TOKEN_*` and rule codes
                only occur on the write surface.
            message:
              type: string
            details:
              type: object
              description: >-
                Optional context, e.g. `{"field": "limit"}`, `{"retry_after":
                12}` or `{"required_scope": "write"}`.
              additionalProperties: true
  responses:
    BadRequest:
      description: Invalid argument (`details.field` names the offending parameter)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: >-
        Missing, invalid, revoked, or expired API key (carries
        `WWW-Authenticate`)
      headers:
        WWW-Authenticate:
          schema:
            type: string
          description: Bearer realm="seesaw-open-api"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: >-
        Rate limit exceeded (see `Retry-After` and `details.retry_after`
        seconds)
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: sspat_ personal API key
      description: |
        Personal API key issued per SeeSaw user account:
        `Authorization: Bearer sspat_…`. Manage keys on the app API
        (`/v1/users/me/api-keys`). Revocation propagates within ~60 seconds
        (validation cache window).

        The key's **scopes** decide what it may do: every key has `read`; a key
        created with `["read","write"]` may also use the six write operations
        (marked `x-required-scope: write` in this document). Scopes are fixed at
        creation — to gain write access, create a new key. A read-only key
        calling a write operation gets `403 FORBIDDEN_SCOPE`.

````