> ## 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.

# Add Expense Details

You can enrich any transaction with a **memo**, a **category**, and/or an **attachment** (e.g. receipts, invoices, purchase orders). Annotations can be added at the time of the original transaction or updated afterward using the `updateTransactionMetadata` mutation.

Annotations are supported on:

* Deposits (`depositCashBalance1`)
* Gift card purchases (`purchaseGiftCard`)
* Wallet transfers (`createTransfer`, `transferInternalBalance`)
* Virtual card creation (`createVirtualCard`)
  * `attachment` is excluded

***

## How it works

1. **Optional:** If you want to attach a file, uplaod it first via the REST upload endpoint. You'll get back an `attachmentId`.
2. Pass `memo`, `transactionCategory`, and/or `attachmentId` into your mutation input — either at transaction time or later via `updateTransactionMetadata`.
3. Read annotations back on `getTransactions`, `getUserPurchases`, or the mutation response. The `attachmentUrl` field returns a short-lived signed URL for file access.

***

## Step 1: Upload an attachment (optional)

> This is a REST endpoint, not a GraphQL mutation

### Endpoint

`POST /api/v1/file-upload/transaction-memo-attachment`

### Authentication

`Authorization: Bearer <YOUR_USER_ACCESS_TOKEN>`

### Request

Send the file as `multipart/form-data` with the field name `file`.

**Accepted types:** `application/pdf`, `image/png`

> ⚠️ JPEG is not accepted for transaction attachments.

### Sample Request

```bash theme={null}
curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/transaction-memo-attachment \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -F "file=@receipt.pdf"
```

### Response

```json theme={null}
{
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

Copy the `attachmentId` — you'll pass it into your mutation input in the next step.

> The `attachmentId` is scoped to your account. The system verifies the file exists in your account's storage when you submit the mutation. An ID from a different account will be rejected.

### Upload Errors

| Cause                             | Status | Details             |
| --------------------------------- | ------ | ------------------- |
| No file included in request       | 400    | Missing file        |
| File type not allowed (e.g. JPEG) | 400    | `INVALID_ARGUMENTS` |

***

## Step 2: Annotate the transaction

You can provide annotations at the time of the original transaction **or** update them afterward.

### Option A — At transaction time

The following mutations accept `memo`, `transactionCategory`, and `attachmentId` as optional fields in their input:

* `depositCashBalance` → `DepositCashBalanceInput`
* `purchaseGiftCard` → `PurchaseGiftCardInput`
* `createTransfer` → `CreateTransferInput`
* `transferInternalBalance` → `TransferInternalBalanceInput`

#### Annotation Fields

| Field                 | Type   | Description                                                                                                                |
| --------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `memo`                | String | Free-text note. Max 255 characters.                                                                                        |
| `transactionCategory` | String | Category label. Free-form — categories are created automatically on first use and reused if the same name is passed again. |
| `attachmentId`        | String | The ID returned by the upload endpoint. The file must be uploaded before submitting the mutation.                          |

#### Sample — Purchase Gift Card with Annotation

```graphql theme={null}
mutation purchaseGiftCard($input: PurchaseGiftCardInput!) {
  purchaseGiftCard(input: $input) {
    purchaseDisplayId
    purchaseAmount
    memo
    transactionCategory
    attachmentUrl
    giftCard {
      giftCardId
      status
    }
  }
}
```

```json theme={null}
{
  "idempotencyKey": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
  "offerId": "0284be6f-1a69-44f7-9da0-5b5edaf45d19",
  "amount": 50.00,
  "balanceAmount": 50.00,
  "memo": "Team lunch — Q2",
  "transactionCategory": "Meals & Entertainment",
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

***

### Option B — After the transaction (`updateTransactionMetadata`)

Use this mutation to add or update annotations on any existing transaction.

> **Partial update semantics:** Only the fields you include are updated. Omitted fields are left unchanged. Pass `null` to clear a field.

#### Mutation

```graphql theme={null}
mutation updateTransactionMetadata($input: UpdateTransactionMetadataInput!) {
  updateTransactionMetadata(input: $input) {
    record_id
    memo
    transactionCategory
    attachmentUrl
  }
}
```

#### UpdateTransactionMetadataInput

| Field                 | Type   | Required | Description                                                                        |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| `recordId`            | UUID!  | Yes      | The `record_id` of the transaction to update.                                      |
| `memo`                | String | No       | Free-text note. Max 255 characters. Omit to leave unchanged; pass `null` to clear. |
| `transactionCategory` | String | No       | Category label. Omit to leave unchanged; pass `null` to clear.                     |
| `attachmentId`        | String | No       | ID from the upload endpoint. Omit to leave unchanged; pass `null` to clear.        |

#### Required Scopes

`LIST_PAYMENT` and `LIST_PURCHASES`

#### Sample — Add a memo and category

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "memo": "Q1 vendor payment",
  "transactionCategory": "Operating Expenses"
}
```

#### Sample — Attach a file to an existing transaction

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "attachmentId": "3f2a1b4c-e5d6-7890-abcd-ef1234567890"
}
```

#### Sample — Clear a memo

```json theme={null}
{
  "recordId": "550e8400-e29b-41d4-a716-446655440000",
  "memo": null
}
```

#### Sample Response

```json theme={null}
{
  "data": {
    "updateTransactionMetadata": {
      "record_id": "550e8400-e29b-41d4-a716-446655440000",
      "memo": "Q1 vendor payment",
      "transactionCategory": "Operating Expenses",
      "attachmentUrl": "https://storage.googleapis.com/..."
    }
  }
}
```

#### Errors

| Cause                                                      | Error                                                                          |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `recordId` not found or belongs to a different account     | `INVALID_ARGUMENTS` — transaction not found                                    |
| Transaction type does not support metadata                 | `INVALID_ARGUMENTS` — transaction does not support metadata                    |
| `attachmentId` is not a valid UUID                         | `INVALID_ARGUMENTS` — Invalid attachment ID                                    |
| File not found in storage (not uploaded, or wrong account) | `INVALID_ARGUMENTS` — Attachment file not found. Please upload the file first. |

***

## Reading annotations

Annotations are returned on the following:

| Query / Mutation            | Type                 | Fields                                         |
| --------------------------- | -------------------- | ---------------------------------------------- |
| `getTransactions`           | `Transaction`        | `memo`, `transactionCategory`, `attachmentUrl` |
| `getUserPurchases`          | `UserPurchase`       | `memo`, `transactionCategory`, `attachmentUrl` |
| `updateTransactionMetadata` | `Transaction`        | `memo`, `transactionCategory`, `attachmentUrl` |
| `depositCashBalance`        | `CashBalanceDeposit` | `attachmentUrl`                                |

> ⚠️ **`attachmentUrl` is a signed URL.** It expires shortly after being generated. Do not store it — re-fetch the transaction when you need to display or access the file.
