Skip to content

Integration  ·  Finance

How to build an expense and card feed sync

Month end arrives and forty card transactions have no receipt, all belonging to people who are travelling. Here is how a sync that chases them earlier actually works, the prompts that build it, and what running it demands.

Built with Tray Headless

  1. System Brex
  2. Step Match to receipt
  3. Step Derive coding
  4. Step Policy check
  5. System NetSuite
Also Slack

Coding is derived from the merchant rather than asked of the claimant, and chasing starts days after the transaction rather than at month end.

The short answer

What is an expense and card feed sync?

An expense and card feed sync comes down to four things: matching transactions to receipts on amount, merchant and date rather than waiting for somebody to submit, coding derived from the merchant rather than asked of the claimant, a policy check that flags instead of blocks, and chasing that starts days after the spend instead of at month end. Most of these come apart on the timing. Asking somebody in the last week of the month to remember a coffee from three weeks ago produces a guess, and the guess gets posted.

What matters here

  • Match the receipt to the transaction, do not wait for a claim. The card feed already knows what was spent.
  • Derive the coding from the merchant. Asking a claimant to pick a GL code produces a guess that finance corrects later.
  • Chase within days, not at month end. A receipt is findable the week it happens and lost by the fourth week.
  • Flag policy exceptions, do not block them. Somebody stuck at an airport needs the hotel more than they need the policy.
  • Report the unreceipted total in real time. That number is a tax liability, and it is only ever discovered late.

Who this is for

You run finance operations. Card spend arrives as a feed, receipts arrive whenever, and reconciling the two is a week of somebody chasing people who are travelling.

How it works in practice

The path from a card being tapped to the spend being coded.

  1. 1

    The card feed lands daily and is matched to receipts

    On amount, merchant and date, with a tolerance, rather than waiting for a claim to be submitted.

  2. 2

    Coding is derived from the merchant

    A mapping table finance owns. The claimant confirms, not chooses.

  3. 3

    A policy check flags rather than blocks

    Over a limit, out of hours, a restricted category. Flagged for review, not refused.

  4. 4

    Anything without a receipt is chased within days

    With the merchant and amount in the message, because that is what makes it findable.

  5. 5

    The claimant confirms rather than fills in a form

    One tap for correct, or a change if the derived coding is wrong.

  6. 6

    Matched and coded spend posts to the ledger

    Only the exceptions reach a human, which is the whole point.

What the sync is made of

Four parts, and the last is what changes month end.

Transaction matching

Amount, merchant and date within a tolerance. The feed already knows the spend, so the receipt is the missing half instead of the starting point.

Merchant-derived coding

A mapping from merchant to GL code and cost centre, owned by finance. Asking the claimant produces guesses that finance then corrects.

A policy check that flags

Exceptions surfaced with context, never refused automatically. A blocked expense at an airport is a person stranded.

Early chasing

Within days of the transaction, with the merchant and amount quoted. A receipt is findable that week and gone by month end.

The Tray Headless prompts

Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.

Once per project, run /tray-workflows:set-workspace to pick the workspace these build in. Point it at a sandbox first.

  1. 1

    Set up and read the feed and the ledger

    The feed arrives in one shape and the ledger wants another.

    Headless skills build-workflow

    Use build-workflow. The systems in play are Brex and NetSuite, or
    whatever we run in those seats.
    
    I need the card transaction feed with merchant name, merchant category
    code, amount, currency, cardholder and timestamp, and from the ledger the
    expense categories, cost centres and how tax is handled per category.
    
    Show me the merchant category codes actually appearing in our feed. That
    list is what the coding mapping gets built from, instead of a taxonomy
    somebody invented.
  2. 2

    Match transactions to receipts, without waiting for a claim

    The feed already knows the spend. Only the receipt is missing.

    Headless skills build-workflow tray-patterns

    Use build-workflow. Pull the card feed daily and match each transaction to
    any receipt already uploaded:
    
      Exact on amount and merchant within a few days
      Near on amount within a small tolerance, for tips added after the fact
      Fuzzy on merchant name, because the feed name and the receipt name
      rarely agree, and neither matches the trading name
    
    Do not wait for a claim to be submitted. The transaction is the record;
    the receipt is the evidence attached to it. Building it the other way
    round means finance only sees spend that somebody remembered to claim.
  3. 3

    Derive the coding, then ask for confirmation

    A claimant choosing a GL code is a guess finance corrects later.

    Build a mapping from merchant and merchant category code to GL code, cost
    centre and tax treatment, held as a table finance owns.
    
    Default the cost centre from the cardholder instead of asking. Apply the
    mapping, then ask the claimant only to confirm, with a way to change it
    if the default is wrong.
    
    Learn from corrections: if the same merchant is recoded the same way
    three times, surface it as a suggested mapping change instead of 
    silently learning it. Finance owns the chart of accounts and should
    approve a change to it.
  4. 4

    Flag policy, never block it

    A refused expense at an airport is a person stranded.

    Headless skills tray-gotchas

    Use tray-gotchas, then run a policy check that flags rather than refuses:
    
      Above the category limit
      A restricted category
      Out of hours or at a weekend, where that matters
      Duplicate amount and merchant on the same day
      Missing receipt above the threshold that requires one
    
    Flag with context and route to the approver. Never block a transaction
    automatically. Somebody stuck overnight needs the hotel more than they
    need the policy, and the conversation belongs after the spend.
    
    Handle foreign currency: store the original amount and currency, the
    converted amount, and the rate used with its date. A month-end rate
    applied to a mid-month transaction is a discrepancy nobody can explain
    later.
  5. 5

    Chase early, and report the exposure

    A receipt is findable that week and lost by the fourth.

    Chase missing receipts three days after the transaction, not at month end.
    
    Put the merchant, amount and date in the message. Somebody can find a
    receipt for a named restaurant on a named day; they cannot find one for
    an unspecified expense last month.
    
    Escalate to the manager after a week and report to finance after two.
    
    Then report continuously: unreceipted total by age and by person, which
    is a tax liability instead of an inconvenience, policy exceptions by
    type, average days from transaction to coded, and the merchants most
    often miscoded, because that is a mapping fix instead of a training
    problem.
  6. 6

    Validate it, then hand the mapping to finance

    The chart of accounts is theirs.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production.
    
    Then open the same workflow in Tray Build so finance can maintain the
    merchant mapping, the policy limits and the chase timings in the visual
    canvas. New merchants appear weekly.

What it connects to

The card feed, the receipts and the ledger, plus the people who need chasing.

Brex

Read the card transaction feed with merchant, category code, amount and cardholder.

Reads

Expensify

Read submitted receipts and write back the derived coding, so reviewers work in one place.

Reads and writes

NetSuite

Post coded expenses with their tax treatment, and read the chart of accounts and cost centres.

Reads and writes

Slack

Chase the cardholder within days with the merchant and amount quoted, and escalate to the manager.

Writes

Okta

Resolve the cardholder to a named person and their manager. The cost centre comes from the HR record, which is where it is actually maintained.

Reads

Snowflake

Land the unreceipted exposure over time, so the tax liability is visible before an audit finds it.

Writes

Same build, other stacks

The design does not change if you run something else in one of these seats. The same prompts build it against SAP S/4HANA, Google BigQuery, Microsoft Teams, Azure Active Directory, Oracle or Databricks.

Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.

Running it in production

This posts to the ledger and touches everybody with a card. Both matter.

The platform runs it, not a laptop under a desk

A daily feed pull, matching and chasing run on the same engine, with a history per transaction rather than a spreadsheet somebody maintains.

Every coding decision is traceable

Which mapping produced which code, and every correction a person made. That pairing is what makes an audit answerable.

Credentials live in the workspace, never in the repo

The card feed credential exposes every transaction the company makes. It lives in your workspace, read-only and separately rotatable.

Finance owns the mapping

Merchant to GL mapping, policy limits and chase timings open in Tray Build, maintained by the team that owns the chart of accounts.

Nothing is blocked automatically

Policy flags, a person decides. An automated refusal at the wrong moment costs more than the expense it prevented.

Questions people ask

Why match transactions, not wait for claims?

Because the card feed already knows the spend. Building it claim-first means finance only ever sees the expenses somebody remembered to submit, and the rest surface at month end.

Why derive the coding instead of asking?

Because a claimant picking a GL code from a long list produces a guess that finance corrects later. Deriving from the merchant and asking only for confirmation gets a better answer with less effort.

Should a policy breach block the transaction?

No. Flag it and route it to an approver. Somebody stranded overnight needs the hotel more than they need the policy, and that conversation belongs after the spend instead of instead of it.

Why chase within days?

Because a receipt is findable the week it happens and effectively gone by the fourth. Quoting the merchant and amount in the message is what makes it findable at all.

What is the number worth reporting?

Unreceipted total by age. It is a tax liability instead of an inconvenience, and it is almost always discovered late because nobody produces it continuously.

Vibe-coding app guides

Vibe-code an entire app with Helix

This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.

How to build an expense policy checker (opens helix.tray.ai in a new tab)

Last reviewed September 2026.