Annotate a Transaction (memo, category & attachment)

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

curl -X POST https://transactional-graph.staging.fluzapp.com/api/v1/file-upload/transaction-memo-attachment \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -F "[email protected]"

Response

{
  "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

CauseStatusDetails
No file included in request400Missing file
File type not allowed (e.g. JPEG)400INVALID_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:

  • depositCashBalanceDepositCashBalanceInput
  • purchaseGiftCardPurchaseGiftCardInput
  • createTransferCreateTransferInput
  • transferInternalBalanceTransferInternalBalanceInput

Annotation Fields

FieldTypeDescription
memoStringFree-text note. Max 255 characters.
transactionCategoryStringCategory label. Free-form — categories are created automatically on first use and reused if the same name is passed again.
attachmentIdStringThe ID returned by the upload endpoint. The file must be uploaded before submitting the mutation.

Sample — Purchase Gift Card with Annotation

mutation purchaseGiftCard($input: PurchaseGiftCardInput!) {
  purchaseGiftCard(input: $input) {
    purchaseDisplayId
    purchaseAmount
    memo
    transactionCategory
    attachmentUrl
    giftCard {
      giftCardId
      status
    }
  }
}
{
  "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

mutation updateTransactionMetadata($input: UpdateTransactionMetadataInput!) {
  updateTransactionMetadata(input: $input) {
    record_id
    memo
    transactionCategory
    attachmentUrl
  }
}

UpdateTransactionMetadataInput

FieldTypeRequiredDescription
recordIdUUID!YesThe record_id of the transaction to update.
memoStringNoFree-text note. Max 255 characters. Omit to leave unchanged; pass null to clear.
transactionCategoryStringNoCategory label. Omit to leave unchanged; pass null to clear.
attachmentIdStringNoID 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

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

Sample — Attach a file to an existing transaction

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

Sample — Clear a memo

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

Sample Response

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

Errors

CauseError
recordId not found or belongs to a different accountINVALID_ARGUMENTS — transaction not found
Transaction type does not support metadataINVALID_ARGUMENTS — transaction does not support metadata
attachmentId is not a valid UUIDINVALID_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 / MutationTypeFields
getTransactionsTransactionmemo, transactionCategory, attachmentUrl
getUserPurchasesUserPurchasememo, transactionCategory, attachmentUrl
updateTransactionMetadataTransactionmemo, transactionCategory, attachmentUrl
depositCashBalanceCashBalanceDepositattachmentUrl
⚠️

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.