openapi: 3.0.3
info:
  title: Trupocket API - Assistant Activity
  description: |
    The assistant activity trail is the user-facing record of every write an AI
    assistant performed through Trupocket's MCP surface. It answers the question the
    server-side log cannot: **"what did my assistant change last Tuesday?"**

    ## What is recorded

    One entry per MCP **write** tool call — the eighteen `create_*` / `update_*` /
    `delete_*` tools across accounts, categories, hashtags, payees, transactions, and
    scheduled transactions. Read tools are never recorded; the no-payload policy for
    reads stays intact.

    Each entry carries the tool name, the acting user, the entity touched, the
    outcome, and — for writes that changed data — a compact `beforeState` /
    `afterState` projection plus the list of `changedFields`. A create has no
    `beforeState`; a delete and every failed call have no `afterState`.

    Failed calls are recorded too, including ones refused before they reached your
    data - a `delete_*` whose confirmation did not match, or a call with a malformed
    argument. *"My assistant tried to delete an account and was refused"* is exactly
    as interesting as a successful write, so those entries carry an `errorCode` and,
    where nothing was ever resolved, no `entityID` and no state snapshots. The one
    exception is a call through `/v1/mcp` that fails its
    per-call household check (membership, style, plan, or scope, including a missing or
    malformed `household_id`): it is refused before the tool runs and is not recorded.

    ## Financial payload snapshots

    **The state snapshots contain financial data.** `beforeState` and `afterState`
    hold a human-readable projection of the affected entity — names, amounts, dates,
    payee, category, and account — so a member can recognise what changed without
    looking the entity up. Treat this response as financial data at rest and in
    transit; do not cache it in an unprotected client store.

    ## Scope: household, not caller

    The trail is **household-scoped**. Every member of the household sees every
    member's assistant activity, including the financial snapshots above, exactly as
    every member already sees every member's transactions. It is deliberately not
    narrowed to the acting user: "who changed this?" is the question the trail
    exists to answer.

    ## Retention and paging

    Entries are kept for a fixed **12-month retention window** and are then deleted
    permanently by a scheduled sweep. Keeping the trail forever would contradict the
    data-minimization promise the feature is built on, so a write older than twelve
    months is simply gone — export anything you need to keep beyond that.

    Within the window the trail is append-only and grows with every assistant write,
    so it is never returned in full and is never counted in full. `count` is the size
    of the page you received, not a grand total. Page by walking `offset` forward
    until `count` is less than `limit`.

    ## Plan

    Requires a **Premium plan or above** — the same plan gate as MCP itself.
    Ineligible households receive `402 plan-upgrade-required`.

  version: 1.0.0
  contact:
    name: Trupocket Support
    email: support@trupocket.app

servers:
  - url: https://api.trupocket.app/v1
    description: Production API server (v1)

tags:
  - name: Assistant Activity
    description: The household's audit trail of AI assistant write activity

security:
  - BearerAuth: []

paths:
  /households/{householdID}/detailed-tracking/mcp/activity:
    get:
      tags: [Assistant Activity]
      summary: List assistant write activity
      description: |
        Returns the household's recorded MCP write activity, **newest first**.

        Every filter is validated before the query runs: an unrecognized `tool`,
        `entityType`, or `outcome` is a `400` naming the parameter, never a silently
        empty list — an empty list has to mean "your assistant did nothing".

        The `from`/`to` window is expressed in **Unix seconds**, matching the
        `createdAt` field in the response. `from` is **inclusive** and `to` is
        **exclusive**, so consecutive windows can be walked without double-counting
        a boundary entry.

        Only entries inside the trail's fixed **12-month retention window** are
        returned. A `from` reaching further back than twelve months is accepted but
        will not surface older activity — it has already been deleted. This is a
        retention limit, not a plan limit: no plan extends it.

        The response body contains financial payload snapshots and is
        household-scoped — see this spec's overview.
      operationId: listMCPActivity
      parameters:
        - $ref: "components.yaml#/components/parameters/HouseholdID"
        - name: from
          in: query
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
          description: Only entries recorded at or after this Unix second (inclusive)
          example: 1767225600
        - name: to
          in: query
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
          description: Only entries recorded before this Unix second (exclusive). Must be greater than `from`.
          example: 1767312000
        - name: tool
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/MCPActivityTool"
          description: Only entries produced by this write tool
          example: update_transaction
        - name: entityType
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/MCPActivityEntityType"
          description: Only entries touching this kind of entity
          example: transaction
        - name: entityId
          in: query
          required: false
          schema:
            type: string
            pattern: "^[0-9A-Za-z]{27}$"
          description: Only entries touching this specific entity - "everything my assistant did to this transaction"
          example: 2BKHLmNpqRsTuvWxYzAbCdEfGhI
        - name: outcome
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/MCPActivityOutcome"
          description: Only successful or only failed calls
          example: failure
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 50
          description: |
            Page size. Defaults to 50. A value above the maximum of **200** is
            **clamped to 200** rather than refused - the `limit` echoed in the
            response tells you what was actually applied. A non-numeric or
            non-positive value is a `400 invalid-limit`.
          example: 100
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
          description: Number of entries to skip, for paging
          example: 50
      responses:
        "200":
          description: |
            The requested page of assistant activity, newest first. Contains financial
            payload snapshots for every member of the household.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MCPActivityList"
              example:
                activity:
                  - activityID: 2BKHActivityAbCdEfGhIjKlMnO
                    userID: 2BKHUserAbCdEfGhIjKlMnOpQrS
                    tool: update_transaction
                    action: update
                    entityType: transaction
                    entityID: 2BKHLmNpqRsTuvWxYzAbCdEfGhI
                    outcome: success
                    beforeState:
                      date: "2026-08-04"
                      amount: "-42.10"
                      payee: Kroger
                      category: Groceries
                      account: Everyday Checking
                    afterState:
                      date: "2026-08-04"
                      amount: "-42.10"
                      payee: Kroger
                      category: Dining Out
                      account: Everyday Checking
                    changedFields: [category]
                    createdAt: 1767283200
                  - activityID: 2BKHActivityZyXwVuTsRqPoNmL
                    userID: 2BKHUserAbCdEfGhIjKlMnOpQrS
                    tool: delete_account
                    action: delete
                    entityType: account
                    entityID: 2BKHAcctAbCdEfGhIjKlMnOpQrS
                    outcome: failure
                    errorCode: in_use
                    beforeState:
                      name: Everyday Checking
                      amount: "1204.55"
                    createdAt: 1767281100
                count: 2
                limit: 50
                offset: 0
        "400":
          $ref: "#/components/responses/MCPActivityBadRequest"
        "401":
          $ref: "components.yaml#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/MCPActivityPlanUpgradeRequired"
        "403":
          $ref: "components.yaml#/components/responses/InsufficientScope"
        "404":
          description: The household does not exist, the caller is not a member, or it is not a Detailed Tracking household
          content:
            application/json:
              schema:
                $ref: "components.yaml#/components/schemas/Error"
              examples:
                householdNotFound:
                  $ref: "components.yaml#/components/examples/HouseholdNotFound"
                householdNotDetailedTracking:
                  $ref: "components.yaml#/components/examples/HouseholdNotDetailedTracking"
        "429":
          $ref: "components.yaml#/components/responses/UserRateLimitExceeded"
        "500":
          $ref: "#/components/responses/MCPActivityServerError"

components:
  securitySchemes:
    BearerAuth:
      $ref: "components.yaml#/components/securitySchemes/BearerAuth"

  schemas:
    MCPActivityTool:
      type: string
      description: |
        The MCP write tool that produced the entry, named `{action}_{entity}`. The
        eighteen values below are the whole write surface; read tools are never
        recorded.
      enum:
        - create_account
        - update_account
        - delete_account
        - create_category
        - update_category
        - delete_category
        - create_hashtag
        - update_hashtag
        - delete_hashtag
        - create_payee
        - update_payee
        - delete_payee
        - create_transaction
        - update_transaction
        - delete_transaction
        - create_scheduled_transaction
        - update_scheduled_transaction
        - delete_scheduled_transaction

    MCPActivityAction:
      type: string
      description: What the tool did to the entity
      enum: [create, update, delete]

    MCPActivityEntityType:
      type: string
      description: The Detailed Tracking entity the tool acted on
      enum: [account, category, hashtag, payee, transaction, scheduledTransaction]

    MCPActivityOutcome:
      type: string
      description: Whether the tool call succeeded. Failed calls are recorded too.
      enum: [success, failure]

    MCPActivityState:
      type: object
      description: |
        A compact, human-meaningful projection of the entity - **financial data**.
        Keys present depend on the entity: accounts, categories, hashtags and payees
        project `name` (plus `amount` for a budgeted category or an account balance);
        transactions and scheduled transactions project `date`, `amount`, `payee`,
        `category` and `account`. Resolved names are stored rather than IDs so the
        trail stays readable after the referenced entity is renamed or removed.

        Every value is a **string**. `amount` is a fixed two-decimal string in the
        household's currency (not the `{currency, amount}` object used elsewhere in
        this API), and `date` is `YYYY-MM-DD` in the household timezone.
      additionalProperties: true
      example:
        date: "2026-08-04"
        amount: "-42.10"
        payee: Kroger
        category: Groceries

    MCPActivity:
      type: object
      description: One recorded assistant write.
      required: [activityID, userID, tool, action, entityType, outcome, createdAt]
      properties:
        activityID:
          type: string
          description: KSUID of the activity entry
          example: 2BKHActivityAbCdEfGhIjKlMnO
        userID:
          type: string
          description: |
            The member whose credentials the assistant used. This is intentionally not
            a foreign key - entries survive the member's removal from the household,
            so a `userID` may no longer resolve to a current member.
          example: 2BKHUserAbCdEfGhIjKlMnOpQrS
        tool:
          $ref: "#/components/schemas/MCPActivityTool"
        action:
          $ref: "#/components/schemas/MCPActivityAction"
        entityType:
          $ref: "#/components/schemas/MCPActivityEntityType"
        entityID:
          type: string
          description: |
            The entity acted on. Absent whenever the call failed before an entity was
            resolved - a create that never got one, and an update or delete that was
            refused before the entity was loaded.
          example: 2BKHLmNpqRsTuvWxYzAbCdEfGhI
        outcome:
          $ref: "#/components/schemas/MCPActivityOutcome"
        errorCode:
          type: string
          description: |
            Why the call failed. Present only when `outcome` is `failure`.

            A tool a token's scope does not reach is never registered, so it can never
            be called and never produces a row - refused-for-scope is not one of these
            values.
          enum:
            - plan_restricted
            - not_found
            - name_conflict
            - in_use
            - invalid_request
            - internal_error
          example: in_use
        beforeState:
          allOf:
            - $ref: "#/components/schemas/MCPActivityState"
          description: The entity as it stood before the write. Absent on a create.
        afterState:
          allOf:
            - $ref: "#/components/schemas/MCPActivityState"
          description: The entity as it stands after the write. Absent on a delete and on every failure.
        changedFields:
          type: array
          description: |
            The projection keys that differ between `beforeState` and `afterState`, so
            a client can highlight the change without diffing two objects. Absent on a
            create, a delete, and a failure - each has only one side - and also on an
            update that left every projected value as it was.
          items:
            type: string
          example: [category]
        source:
          type: string
          description: |
            Reserved. The client or token label the call arrived under. The credential
            label is not yet threaded onto the write path, so this field is currently
            absent from every response - do not build on it.
          example: Claude Desktop
        createdAt:
          type: integer
          format: int64
          description: When the write was recorded, as a Unix second
          example: 1767283200

    MCPActivityList:
      type: object
      required: [activity, count, limit, offset]
      properties:
        activity:
          type: array
          description: The page of entries, newest first
          items:
            $ref: "#/components/schemas/MCPActivity"
        count:
          type: integer
          description: |
            The number of entries in **this page** - not a grand total. The trail is
            append-only and is never counted in full. When `count` is less than
            `limit` you have reached the end of the filtered trail.
          example: 2
        limit:
          type: integer
          description: The page size actually applied, after clamping to the maximum of 200
          example: 50
        offset:
          type: integer
          description: The offset the page was read from
          example: 0

  responses:
    MCPActivityPlanUpgradeRequired:
      description: |
        Payment required - the household's plan does not include MCP. The assistant
        activity trail is gated on the same plan feature as the MCP server itself.
      content:
        application/json:
          schema:
            $ref: "components.yaml#/components/schemas/Error"
          example:
            errorCode: plan-upgrade-required
            errorMessage: MCP access requires a Premium, Pro, Developer, or Enterprise plan. Please upgrade to use AI features.
            errorField: null

    MCPActivityBadRequest:
      description: |
        A filter value was not recognized. Every filter is validated before the query
        runs so a typo can never masquerade as an empty trail. Also returned when the
        household ID in the path is malformed.
      content:
        application/json:
          schema:
            $ref: "components.yaml#/components/schemas/Error"
          examples:
            invalidHousehold:
              $ref: "components.yaml#/components/examples/InvalidHousehold"
            invalidTool:
              summary: The tool name is not an assistant write tool
              value:
                errorCode: invalid-tool
                errorMessage: "The tool you specified is not an assistant write tool; supported tools are: create_account, create_category, create_hashtag, create_payee, create_scheduled_transaction, create_transaction, delete_account, delete_category, delete_hashtag, delete_payee, delete_scheduled_transaction, delete_transaction, update_account, update_category, update_hashtag, update_payee, update_scheduled_transaction, update_transaction"
                errorField: tool
            invalidEntityType:
              summary: The entity type is not a Detailed Tracking entity
              value:
                errorCode: invalid-entity-type
                errorMessage: "The entityType you specified is not supported; supported types are: account, category, hashtag, payee, transaction, scheduledTransaction"
                errorField: entityType
            invalidOutcome:
              summary: The outcome is neither success nor failure
              value:
                errorCode: invalid-outcome
                errorMessage: The outcome you specified must be either success or failure
                errorField: outcome
            invalidEntityID:
              summary: The entity ID is not a valid KSUID
              value:
                errorCode: invalid-entity-id
                errorMessage: The entityId you specified is not a valid identifier
                errorField: entityId
            invalidFrom:
              summary: The from bound is not a Unix second
              value:
                errorCode: invalid-from
                errorMessage: The from you specified must be a Unix timestamp in seconds
                errorField: from
            invalidTo:
              summary: The to bound is not a Unix second
              value:
                errorCode: invalid-to
                errorMessage: The to you specified must be a Unix timestamp in seconds
                errorField: to
            invalidRange:
              summary: The window is empty or inverted
              value:
                errorCode: invalid-range
                errorMessage: The from you specified must be earlier than the to
                errorField: from
            invalidLimit:
              summary: The limit is not a whole number of 1 or more
              value:
                errorCode: invalid-limit
                errorMessage: The limit you specified must be a whole number of 1 or more; values above 200 are reduced to 200
                errorField: limit
            invalidOffset:
              summary: The offset is not a whole number of 0 or more
              value:
                errorCode: invalid-offset
                errorMessage: The offset you specified must be a whole number of 0 or more
                errorField: offset

    MCPActivityServerError:
      description: An error occurred reading the assistant activity trail
      content:
        application/json:
          schema:
            $ref: "components.yaml#/components/schemas/Error"
          example:
            errorCode: mcp-activity-failed
            errorMessage: An error occurred retrieving your assistant activity; please contact customer support
            errorField: null
