Skip to content

Publish the rebuilt fiat account surface (UHT-520) - #65

Merged
hallstain merged 8 commits into
mainfrom
refactor/fiat-accounts-docs
Sep 21, 2026
Merged

hallstain merged 8 commits into
mainfrom
refactor/fiat-accounts-docs

Conversation

@hallstain

@hallstain hallstain commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Publishes UHT-520: one fiat account resource, addressed by fiat_account_id.

What a partner reads now

  • Fiat Accounts is its own reference section, a sibling of User Management, Recipients and Orders rather than a heading inside User Management.
  • Five endpoints: config, list, open, read one, read its transactions. The identity and identification endpoints are gone from the docs because they are gone from the API.
  • The guide follows the flow a partner actually has: check the product, check the customer is ready, open the account, wait for active, show the pay-in details, read the credits.
  • Account status is documented as something to act on (pending, active, failed, closed), and a pending account is explicitly one you must not send a customer to.
  • Two webhooks: fiat_account.updated and fiat_account.deposit.received. retail.settlement.completed is no longer described as a partner event.
  • On-ramp funding from the customer's own account is own_account, and the order names the fiat_account_id it is funded from.
  • Errors: KYC_NOT_CLEARED and ISSUANCE_NOT_READY (with missing_fields in KYC's own names) replace the holder and identification codes.

Also in this release

Merge last

After account (Unigox/account#631), trades (Unigox/trades#495) and the gateway (Unigox/api#62) are deployed. Merging earlier publishes a surface the API does not serve yet.

Checks

swagger-cli validate openapi/swagger.yaml passes.

Still open

The guide page at /tutorials/fiat-accounts is edited in GitBook by hand rather than synced from this repo, so it needs one manual paste after this merges.

Rollout

# Step How it runs Waits on Blocks trading?
1 agent-scripts#322 — vendor agent reads both spellings deploy nothing no
2 account#631 — migrations: new tables, trades.fiat_account_id, CHECK widened to both spellings (NOT VALID), then the covering index CONCURRENTLY in its own file account migrate, with the deploy nothing no
3 api#62 — gateway routes + its swagger copy deploy step 2 no
4 trades#495 — reads both spellings, writes the new one, stamps fiat_account_id deploy step 2 (the widened CHECK must exist before the first own_account row) no
5 Backfill — rewrites stored rows, 5000 at a time, each batch its own transaction by hand: DB_CONNECTION_STRING=… make migrate-seed file=sql/seeds/uht548_own_account_backfill.sql step 4 fully rolled out no
6 api-guides#65 — the published docs merge step 4 no
7 Contract — validate the constraint, narrow it and the index to one value by hand, same way count(*) WHERE fiat_funding_source = 'own_iban' = 0 no

Everything in steps 1–4 ships with a normal deploy. The only hand-run step is the backfill, and it is hand-run for a reason: it commits in batches, so it cannot be a migration, and it must not run until every pod reads both spellings.

The one hard edge is 2 before 4. Everything else can slip: until trades ships, partners simply keep seeing own_iban and orders carry no fiat_account_id.

At no point does a running pod meet a value it does not understand — the agent (step 1) and trades (step 4) both accept the old and the new spelling, and only step 7 takes the old one away.

hallstain and others added 5 commits September 15, 2026 19:23
The catalog has reported institution_required:false for interac-e-transfer
while creating a destination without one was refused. The backend now resolves
an omitted institution to interac-e-transfers; the changelog says so, and both
omission lists in the reference name every rail that accepts it — they had
stopped at iban-sepa and nip-nigeria, missing faster-payments and the Chinese
wallets as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The payments route was documented as the incoming payments that credited
the account. It lists bank transfers in both directions, the way the bank
returns them, so `kind` is `incoming` or `outgoing` and `recipient`, `iban`
and `bic` name the other side of the transfer.

- Ledger: every balance change, not only transfers. Its `reference` is the
  id of the operation behind the entry, which for a transfer is the payment
  `id`. That is how the two lists connect.
- Publish the `kind` and `status` values, and say that ledger `type` and
  `category` are open lists.
- Payments can answer 422 ACCOUNT_NOT_PROVISIONED like the ledger does.
- Name both operations as fiat account reads, so they no longer read as
  the customer's whole transaction history on the User Management page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…at the API returns

Read against the handlers, endpoint by endpoint. The names did not say these
were fiat account routes, several descriptions argued for the design rather
than describing the answer, and the error list was missing codes a partner can
actually receive.

- Summaries name the resource: "What fiat accounts you can offer", "Fiat
  account holder identity", "Fiat account holder verification status",
  "Submit a fiat account holder's identity", "This customer's fiat accounts",
  "Issue a fiat account", "One fiat account, with balances".
- Dropped the justifications ("A GET on purpose", "deliberately", "the one
  route outside the customer tree"). What the endpoint answers is the
  documentation; why it is shaped that way is not.
- Errors: added the codes the handlers reach and the docs never listed, in a
  second table for the ones that are about us rather than the request
  (`CUSTOMER_LOOKUP_UNAVAILABLE`, `IDENTITY_UNAVAILABLE`, `KYC_UNAVAILABLE`,
  `PRICING_UNAVAILABLE`, `PROVISION_UNAVAILABLE`, `DB_NOT_CONFIGURED`,
  `LOAD_FAILED`, `CREATE_FAILED`, `PROVISION_FAILED`, `LINK_WRITE_FAILED`,
  `STATUS_WRITE_FAILED`), plus `NO_OPERATING_ACCOUNT` and `CUSTOMER_UNKNOWN`.
- A repeat identity submission answers `already_linked: true`, which was
  returned but never documented.
- The payments and ledger schemas follow account#631: RFC 3339 times, a closed
  list of ledger types, a signed ledger amount, and the counterparty's
  `account_number` / `sort_code` on Faster Payments beside `iban` / `bic`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two routes were two views of the same money: the ledger accounted for
the balance but never said who paid, the payments list said who paid but
knew nothing of the conversions, fees and internal transfers around it.
Reading an account meant fetching both and joining them by `reference`.

`GET …/fiat-accounts/{account_id}/transactions` replaces both. One row is
one movement: `type` says what moved it, `direction` and a signed `amount`
say which way, and a bank transfer carries the other side of itself under
`counterparty` with the identifiers that transfer was addressed by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One resource, addressed by `fiat_account_id`, opened with
`POST /fiat-accounts` and `{user_uuid, currency}`. The identity and
identification endpoints are gone from the docs because they are gone from
the API: they were the banking vendor's KYC push, and a partner has one
identity for a person already.

- Fiat Accounts is its own reference section, a sibling of User Management
  rather than a heading inside it.
- The guide follows the flow a partner actually has: config, readiness on
  the customer, open, wait for `active`, show the pay-in details, read the
  credits.
- Two webhooks documented: `fiat_account.updated` and
  `fiat_account.deposit.received`. `retail.settlement.completed` is not a
  partner event and is no longer mentioned as one.
- On-ramp funding from the customer's own account is `own_account`, and the
  order names the `fiat_account_id` it is funded from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hallstain hallstain changed the title Name the fiat account endpoints for what they read, and document what they answer Publish the rebuilt fiat account surface (UHT-520) Sep 18, 2026
@notion-workspace

Copy link
Copy Markdown

hallstain and others added 3 commits September 18, 2026 20:10
The fiat account tag was already right; the two places that reference it
from outside were not.

- An on-ramp order's `fiat_funding_source` is `own_account`, not
  `own_iban`, and the order names the `fiat_account_id` it is funded from.
  The three prose references to the old value follow.
- `GET /users/{user_uuid}` documents `fiat_account_issuance`
  (`ready`, `missing_fields`), which is the only thing this rebuild adds to
  User Management.
- The guide says up front that it is the how-to and the reference is the
  Fiat Accounts tag, so the two are not read as alternatives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verified against account before taking it: an omitted institution_id on
interac-e-transfer resolves to interac-e-transfers, on alipay-wallet and
wechat-wallet to the rail's own institution, and on iban-sepa, nip-nigeria
and faster-payments to other-bank — the table in
internal/service/partner_accounts.go says exactly that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The USD rails to China, Hong Kong and Singapore can show up in the catalog,
but quote and initiate refuse them with provider_confirmation_pending. Say so
in the payouts guide and the changelog instead of publishing the retail
bill-payment flow (supersedes #64 and #66).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant