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

# Activity history overview

> Read the audit trail of a virtual card, gift card, spend account, or funding source: who created it, who changed what, who it was shared with, and which approval authorized each change.

Every virtual card, gift card, spend account, and funding source on a Fluz account keeps an **activity history**. It's an append-only list of the actions taken on that object. Each event tells you what changed, who made the change, who it was made for, which connected application it came through, and which approval authorized it.

You read it with the `objectChangeHistory` query. One query works for all four object types, and the event shape is the same for each. Only the events differ.

<Note>
  **Activity history records actions on the object, not reads or spend.**

  Revealing card details, viewing balances, and listing objects aren't recorded. Neither are card transactions, authorizations, or refunds. To read spend, use [Transaction activity](/features/transactions-details-overview).
</Note>

***

## Supported objects

| Object | `objectType` | `objectId` | Events |
| - | - | - | - |
| Virtual card | `VIRTUAL_CARDS` | `virtualCardId` | [Virtual card activity history](/features/virtual-card-activity-history) |
| Gift card | `GIFT_CARDS` | `giftCardId` | [Gift card activity history](/features/gift-card-activity-history) |
| Spend account | `SPEND_ACCOUNTS` | `userCashBalanceId` | [Spend account activity history](/features/spend-account-activity-history) |
| Funding source | `FUNDING_SOURCES` | `bankCardId`, `bankAccountId`, or `paypalVaultId` | [Funding source activity history](/features/funding-source-activity-history) |

***

## Query activity history

`objectChangeHistory`

Returns the newest events first, up to 50 per object.

**Required scope:** `VIEW_OBJECT_CHANGE_HISTORY`

<Info>
  **Prerequisites:** the `VIEW_OBJECT_CHANGE_HISTORY` scope enabled on your application. The query accepts both Bearer (a user access token that carries the scope) and Basic (`<API_KEY>`) authentication. The object must belong to the account the credentials act on.
</Info>

### Arguments

| Argument | Type | Description | Required |
| :- | :- | :- | :- |
| `objectType` | `ObjectOwnerObjectType!` | `VIRTUAL_CARDS`, `GIFT_CARDS`, `SPEND_ACCOUNTS`, or `FUNDING_SOURCES`. | Yes |
| `objectId` | `UUID!` | The ID of the object, as returned by the queries that list it. | Yes |

### Sample query

```graphql theme={null}
query {
  objectChangeHistory(
    objectType: VIRTUAL_CARDS
    objectId: "07df5653-43a8-4532-9881-3ab5857bbe11"
  ) {
    id
    changeType
    change
    createdAt
    subjectUserId
    approvalId
    actor {
      userId
      system
      applicationId
      applicationName
    }
  }
}
```

### cURL example

```curl theme={null}
curl -X POST \
  https://transactional-graph.staging.fluzapp.com/api/v1/graphql \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_USER_ACCESS_TOKEN' \
  -d '{
    "query": "query { objectChangeHistory(objectType: VIRTUAL_CARDS, objectId: \"07df5653-43a8-4532-9881-3ab5857bbe11\") { id changeType change createdAt subjectUserId approvalId actor { userId system applicationId applicationName } } }"
  }'
```

### Sample response

A card that an authorized user asked to unlock, and a manager approved through the API. The newest event comes first.

```json theme={null}
{
  "data": {
    "objectChangeHistory": [
      {
        "id": "4b1f7c2e-9d3a-4f61-8e2b-7a5c0d9e1f23",
        "changeType": "FIELD_CHANGED",
        "change": { "field": "status", "old": "LOCKED", "new": "UNLOCKED" },
        "createdAt": "2026-10-08T14:02:11.482Z",
        "subjectUserId": null,
        "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12",
        "actor": {
          "userId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
          "system": null,
          "applicationId": null,
          "applicationName": null
        }
      },
      {
        "id": "2d6e8a1b-3c5f-4e7a-9b0d-1f2e3a4b5c6d",
        "changeType": "APPROVAL_APPROVED",
        "change": { "approval_type": "VIRTUAL_CARD", "approval_code": "600005", "decision_channel": "API" },
        "createdAt": "2026-10-08T14:02:10.917Z",
        "subjectUserId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12",
        "actor": {
          "userId": "00a2ec0a-255f-4fe7-85ad-2958dc8d3c72",
          "system": null,
          "applicationId": "5f0c1d2e-3a4b-4c5d-8e6f-7a8b9c0d1e2f",
          "applicationName": "Acme Expenses"
        }
      },
      {
        "id": "8c9d0e1f-2a3b-4c5d-9e6f-0a1b2c3d4e5f",
        "changeType": "APPROVAL_REQUESTED",
        "change": { "approval_type": "VIRTUAL_CARD", "approval_code": "600005" },
        "createdAt": "2026-10-08T13:47:55.120Z",
        "subjectUserId": null,
        "approvalId": "07df5653-43a8-4532-9881-3ab5857bbe12",
        "actor": {
          "userId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
          "system": null,
          "applicationId": null,
          "applicationName": null
        }
      }
    ]
  }
}
```

***

## Response fields

`objectChangeHistory` returns a list of `ObjectChangeHistoryEvent`.

| Field | Type | Description |
| - | - | - |
| `id` | `UUID!` | The event ID. |
| `objectType` | `ObjectOwnerObjectType!` | The object type you queried. |
| `objectId` | `UUID!` | The object you queried. |
| `changeType` | `String!` | What happened. See [Change types](#change-types). |
| `change` | `JSON` | The details of the change. The shape depends on `changeType`. See [The change payload](#the-change-payload). |
| `actor` | `ObjectChangeHistoryActor!` | Who made the change. See [Who made the change](#who-made-the-change). |
| `subjectUserId` | `UUID` | The user the change was made for, when that's a different person from the actor. For example, the cardholder of a card an admin created. |
| `approvalId` | `UUID` | The approval linked to this event, if any. |
| `approval` | `ObjectChangeHistoryApproval` | The linked approval and its approvers. See [Approvals](#approvals). |
| `createdAt` | `DateTime!` | When the change happened. |

***

## Change types

| `changeType` | Meaning |
| - | - |
| `OBJECT_CREATED` | The object was created. |
| `FIELD_CHANGED` | One field of the object changed. A change to several fields at once produces one event per field. |
| `OWNER_ASSIGNED` | The object got its first owner. |
| `OWNER_TRANSFERRED` | Ownership moved to another user. |
| `ACCESS_GRANTED` | A user got access to the object. |
| `ACCESS_LEVEL_CHANGED` | A user's access level on the object changed. |
| `ACCESS_REVOKED` | A user lost access to the object. |
| `PENDING_ACCESS_GRANTED` | Access was granted to someone who was invited but hasn't registered yet. |
| `PENDING_ACCESS_LEVEL_CHANGED` | The pending access level of an invited person changed. |
| `PENDING_ACCESS_REVOKED` | The pending access of an invited person was revoked, for example because the invite was cancelled. |
| `ACCESS_APPLIED_ON_REGISTRATION` | An invited person registered, and their pending access took effect. |
| `APPROVAL_REQUESTED` | Someone asked for approval to act on the object. |
| `APPROVAL_APPROVED` | An approver approved the request. |
| `APPROVAL_DECLINED` | An approver declined the request. |
| `APPROVAL_EXPIRED` | The request expired before anyone decided it. |

Treat `changeType` as an open list: new values can appear as Fluz records more actions.

***

## The change payload

`change` is a JSON object. Its keys use `snake_case`.

| `changeType` | `change` |
| - | - |
| `OBJECT_CREATED` | Usually `{}`. A virtual card created by a schedule or a CSV import says so. See [Virtual card activity history](/features/virtual-card-activity-history#creation). |
| `FIELD_CHANGED` | `{ "field", "old", "new" }`, plus optional context keys such as `reason` or `transaction_log_id`. Each object page lists its fields and values. |
| `OWNER_ASSIGNED`, `OWNER_TRANSFERRED` | `{ "field": "user_id", "old", "new" }`. `old` and `new` are user IDs. `old` is `null` for a first owner. |
| `ACCESS_*`, `PENDING_ACCESS_*` | `{ "field": "role", "old", "new" }`. `old` and `new` are [access levels](#ownership-and-access); `null` means no access. |
| `APPROVAL_*` | `{ "approval_type", "approval_code", "decision_channel" }`. `decision_channel` is only on approve and decline events. |

Fluz never puts sensitive values in `change`. PINs, card numbers, wallet tokens, address text, and memo or receipt content are replaced by a marker such as `"updated"`, `"UPDATED"`, or `"ADDED"`, or by an ID.

***

## Who made the change

`actor` names the one party that performed the action.

| Field | Description |
| - | - |
| `userId` | The user who made the change. When someone acts for another person, this is the person acting, not the person it was done for, except where an object page notes otherwise. |
| `system` | Set when no user made the change: the Fluz service or job that did it, such as `virtual-card-service` or `approvals-service`. `cms` means Fluz support. The support agent's identity isn't returned. |
| `applicationId` | The connected application the change came through. `null` for changes made in the Fluz web or mobile app, by Fluz support, or by a Fluz system. |
| `applicationName` | The application's name at the time of the change. Renaming the application later doesn't change past events. |

Exactly one of `userId` and `system` is set. Each object page lists the `system` values you can see for that object. Treat `system` values as labels for display and auditing: new values can appear, and existing ones can change.

With API key (Basic) authentication, `userId` is the user who owns your developer account and `applicationId` is your application.

`subjectUserId` names the person the change was made for, when that's someone other than the actor. For example, an admin creates a card for an authorized user: `actor.userId` is the admin, and `subjectUserId` is the cardholder.

***

## Approvals

When a change goes through [Approvals & requests](/features/approvals-and-requests), the object's history shows the whole trail. Every event in it carries the same `approvalId`.

1. **`APPROVAL_REQUESTED`.** The actor is the requester, with the application the request came through.
2. **`APPROVAL_APPROVED` or `APPROVAL_DECLINED`.** One event per approver who decided. The actor is the user who submitted the decision, and `subjectUserId` is the requester. A decision made through an approval link that names no approver is attributed to `system: "approvals-service"`.
3. **The resulting action.** For example, a `FIELD_CHANGED` event that unlocks the card. A change to the object itself is usually attributed to the requester, because they asked for it. An access grant is attributed to the approver who granted it. Each object page notes the exceptions.

If nobody decides before the request expires, an `APPROVAL_EXPIRED` event is recorded instead, attributed to `system: "approvals-service"`.

For a request that creates a new object, such as a virtual card or gift card purchase, the new object gets the request and approval events once it exists. A declined or expired creation request doesn't create an object, so the new object has no history. A request to replace a virtual card records its decline or expiry on the original card.

The `approval` field returns the approval as it is now:

| Field | Description |
| - | - |
| `approvalId` | The approval ID. |
| `approvalType` | The approval type, such as `VIRTUAL_CARD`, `GIFT_CARD`, or `ACCOUNT_ACCESS`. |
| `status` | The approval's current status. |
| `requesterUserId` | The user who asked for approval. |
| `approvers` | Everyone asked to decide, in the order they were added. |
| `approvers.approverUserId` | The user who was asked to decide. |
| `approvers.decidedByUserId` | The user who actually submitted the decision. `null` when no user was named, for example a decision through an account-level link. |
| `approvers.status` | That approver's status. |
| `approvers.decidedAt` | When they decided. |
| `approvers.decisionChannel` | How the decision was submitted: `LINK` (an email or SMS approval link), `REST` (the approvals inbox in the Fluz app), or `API` (`approveApprovalRequest` or `declineApprovalRequest`). |

Fluz never asks a requester to approve their own request, and rejects the decision if they try.

***

## Ownership and access

Ownership and access events work the same way on every object type that supports them.

* **Ownership.** [`assignObjectOwner`](/api-reference/mutations/assign-object-owner) records `OWNER_ASSIGNED`. [`transferObjectOwner`](/api-reference/mutations/transfer-object-owner) records `OWNER_TRANSFERRED`. New virtual cards and gift cards also record `OWNER_ASSIGNED`. Spend accounts created in the Fluz app record it for the owner picked, or for the creator.
* **The owner's access.** An owner always gets at least `SPENDER` access. When that access is granted, raised, or removed, the access event carries `"reason": "owner-floor"`. For example, a transfer revokes the previous owner's `SPENDER` access.
* **Sharing.** Sharing an object with a member of the account in the Fluz app records `ACCESS_GRANTED`, `ACCESS_LEVEL_CHANGED`, or `ACCESS_REVOKED`. `subjectUserId` is the member.
* **Invites.** Sharing with someone who hasn't registered yet records `PENDING_ACCESS_*` events. Those events have no `subjectUserId`; `change.subject_non_registered_user_id` identifies the invite. When the person registers, `ACCESS_APPLIED_ON_REGISTRATION` is recorded and attributed to the person who invited them.
* **Access requests.** A member who asks for access to an object goes through an approval. The object records `APPROVAL_REQUESTED`, then `APPROVAL_APPROVED` or `APPROVAL_DECLINED`. If the request is approved, an `ACCESS_GRANTED` event follows, or `ACCESS_LEVEL_CHANGED` if the requester already had lower access. It's attributed to the approver, with the requester as `subjectUserId`. If the requester already had that level or higher, no access event is recorded.

Access levels in `change.old` and `change.new`:

| Level | Value |
| - | - |
| `GUEST` | `0` |
| `VIEWER` | `1` |
| `SPENDER` | `10` |
| `MANAGER` | `100` |
| `ADMIN` | `1000` |

Access a user has through a group isn't recorded on the object.

***

## Good to know

* **History starts when recording started.** Changes made before Fluz began recording an action don't appear, and nothing is backfilled, with one exception. Virtual cards that existed when ownership launched have an `OWNER_ASSIGNED` event dated when ownership launched, not when the card was created.
* **Only real changes are recorded.** An update that sets a field to its current value records nothing.
* **Recording doesn't block most actions.** If an event can't be written, the action still succeeds. Most ownership and access events are the exception: they're written together with the change. Use the object's current state, not its history, as the source of truth for its current values.
* **The newest 50 events.** The query returns at most 50 events, newest first, with no pagination.
* **Changes from every channel.** The history includes changes made through the API, in the Fluz web and mobile apps, by Fluz support, and by Fluz systems.

***

## Access rules

The object must belong to the account the credentials act on:

| Authentication | Account |
| - | - |
| Bearer (user access token) | The account the token was issued for. |
| Basic (`<API_KEY>`) | The application's configured operator account. |

For an OAuth grant restricted to specific spend accounts, `SPEND_ACCOUNTS` history is only available for those spend accounts.

Asking for an object on another account returns the same error as asking for one that doesn't exist, so the API never reveals whether the object exists.

## Errors

| Code | When it happens |
| - | - |
| `AUTH-0031` | The Bearer token doesn't carry `VIEW_OBJECT_CHANGE_HISTORY`. |
| `AUTH-0043` | The object doesn't exist, or it isn't on the account the credentials act on. |

See [General API error codes](/get-started/general-api-error-codes) for authentication errors shared by every operation.

## Next steps

<CardGroup cols={2}>
  <Card title="Virtual card activity history" icon="credit-card" href="/features/virtual-card-activity-history">
    Creation, settings, locks, PINs, wallet tokens, and expense details.
  </Card>

  <Card title="Gift card activity history" icon="gift" href="/features/gift-card-activity-history">
    Purchase, archive, used status, balance, and expense details.
  </Card>

  <Card title="Spend account activity history" icon="wallet" href="/features/spend-account-activity-history">
    Creation, renames, default changes, images, and closure.
  </Card>

  <Card title="Funding source activity history" icon="coins" href="/features/funding-source-activity-history">
    Bank cards, bank accounts, PayPal, and primary or backup changes.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.