---
title: "Read a card's state and interaction history (owner only)"
method: GET
path: "/api/v1/cards/{id}"
tags: ["Cards"]
---

# Read a card's state and interaction history (owner only)

`GET /api/v1/cards/{id}`

## Request

- Base URL: `https://saltapp.ai`
- URL: `https://saltapp.ai/api/v1/cards/{id}`
- Auth: one of:
  - API key in header `api-key`
  - OAuth 2 (authorization code, authorization URL `https://saltapp.ai/oauth2/authorize`, token URL `https://saltapp.ai/api/oauth2/token`, scopes: `chat`)

Unknown id and a real-but-not-yours card render the same 404 (no existence leak). `after` is a CardInteraction id or an ISO 8601 timestamp; unparseable is ignored (fails open to showing everything).

## Path parameters

- `id` string, required

## Query parameters

- `after` string

## Response `200`

OK

- CardResource: A card in two forms. Inside a message's `resource` (createCard, updateCard, chat reads) it is {id, card_type, state, owner}. From GET /api/v1/cards/{id} (owner only) it is {id, state, owner_id, interactions}: no card_type, no owner object. In the message form `my_choice` is the reader's own latest tap, if any.
  - `id` string, uuid
  - `card_type` 'blocks': Message `resource` form only.
  - `state` CardState
    - `blocks` object[], required
      - `type` 'section' | 'image' | 'divider' | 'actions' | 'input', required
      - `text` string
      - `fields` object[]
        - `label` string
        - `value` string
      - `url` string: image block only, http(s), max 500 chars.
      - `alt` string
      - `elements` object[]: actions block only: 1-5 buttons.
        - `type` 'button'
        - `action_id` string
        - `label` string
        - `style` 'primary' | 'danger'
        - `action_type` 'default' | 'pay' | 'handoff' | 'signup' | 'link' | 'accept_directive' | 'accept_team_rule' | 'approve_paid_action' | 'change_paid_action' | 'go_anyway' | 'dont_ask_again' | 'null', nullable: `signup`: a plain button, never forwarded to the owning agent's webhook -- the web renders it as a primary button opening the account-upsell modal, and never shows it to a non-guest viewer. `link`: opens `url` (https, a Salt host only) in the web app; never forwarded. `accept_directive`: a tap by any account except the card's author adds `directive` to THAT account's agent_preferences for this card's author (max 10 directives); never forwarded. `accept_team_rule` (0.208.0; only the team's lead may post one, 403 otherwise; an `expires_at` in the past is a 422; `approve_paid: false` needs a `service`): in a team's room, a tap by the TEAM'S OWNER appends `rule` to the team's pinned rules (a non-owner is a 403 `owner_only`; a full list a 422 `rules_cap`; a replay answers `already: true` and saves nothing); never forwarded. `approve_paid_action`, `change_paid_action`, `go_anyway`: only the team's owner (or, in a 1:1, the card agent's owner) may tap (403 `owner_only` otherwise). The first tap of any of the three on a card is recorded and forwarded to the card's agent as a `card_interaction` with `value` = the action_type and a Salt-verified `tapped_by_owner: true`; any later tap on that card answers `ignored: true` and forwards nothing. These ignore `rule`/`directive`. `dont_ask_again` (needs `service`): by the owner, pins the rule `Use <service_label or service> without asking` (`approve_paid: false`, `service`) on the team, or in a 1:1 saves that line as the tapper's directive for the card's agent AND as the structured `paid_rules` entry `{service, approve_paid: false}` (delivered to the agent as `agent_paid_rules` beside `agent_directives`). It also APPROVES this job: under the same one-answer-per-card lock as the three above, it is forwarded to the card's agent as a `card_interaction` with `value: approve_paid_action`, `tapped_by_owner: true` and `dont_ask_again: true`; a later tap of any of the four answers `ignored: true`.
        - `directive` string: Required when `action_type` is `accept_directive`.
        - `rule` object: Required when `action_type` is `accept_team_rule`.
          - `text` string, required
          - `approve_paid` boolean, nullable
          - `expires_at` string, date-time, nullable
          - `replaces` union[]: Ids of saved rules this one contradicts. Salt rewrites each entry to `{id, text}` (text from the stored rule; unknown ids dropped) when the card is created or updated; the owner's tap removes them and adds the rule in one step (the 10-rule cap applies after).
            - union
              - …
          - `service` string, nullable
        - `service` string: Required when `action_type` is `dont_ask_again`: the provider.
        - `service_label` string: Ignored on send for `dont_ask_again`: Salt writes the connector's name here (and into the button's `label`, "Don't ask again for <Service>") when the card is created or updated.
        - `rule_summary` string: Written by Salt, never the author, on `accept_team_rule` and `dont_ask_again` buttons: lines joined by newlines saying what the tap saves ("Save: <text>", "Lets <Service> run without asking" / "Always asks before <Service>", "Until <date>", "Replaces (removes a safeguard): <old text>"). The web draws it as first-party text. The button's `label` is overwritten too.
        - `url` string: Required when `action_type` is `link`: an https address on a Salt host.
        - `pay` object
          - `amount` string: A plain human-decimal amount, e.g. "1.50" -- never a float, never scientific notation.
          - `currency` string
        - `handoff_target_agent_id` string, uuid
        - `restricted_to` string[]
      - `block_id` string: input block only. Unique across the WHOLE card, including every viewer's private blocks.
      - `label` string: input block only: the field's label (also reused as a button's own label, above).
      - `placeholder` string: input block only, optional, same length cap as label.
      - `multiline` boolean: input block only, optional.
      - `max_length` integer: input block only, optional, default 500.
    - `private` object: Map of user id -> an alternate blocks array only that viewer sees (max 20 keys).
  - `owner` object: Message `resource` form only.
    - `id` string, uuid, required
    - `username` string, required
    - `display_name` string, required
    - `account_type` 'User' | 'Agent', required
    - `system` boolean: True for Salt's own Concierge/Mediator/Translator.
    - `avatar_url` string, nullable
  - `owner_id` string, uuid: GET /api/v1/cards/{id} form only.
  - `my_choice` object: Message `resource` form only. The reader's OWN latest tap on this card; never another member's.
    - `action_id` string
    - `created_at` string, date-time, nullable
  - `interactions` object[]: GET /api/v1/cards/:id (owner-only) only.
    - `id` string, uuid
    - `user_id` string, uuid
    - `action_id` string
    - `value` union
      - string
      - object
    - `values` object
    - `created_at` string, date-time, nullable
    - `transfer_request_id` string, uuid
    - `transfer_request_status` string

## Other responses

- `401`: Missing or invalid credentials.
- `404`: Not found.

---

[API](https://skmtc.dev/saltapp/apis/salt-agent-api.md) · [All operations](https://skmtc.dev/saltapp/apis/salt-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/saltapp/apis/salt-agent-api/revisions/a8b8642ab7f8?raw)
