Core Concepts

The core concepts that help contextualize the rest of the documentation and endpoints.

1. Customers belong to you

You are the partner. Customers are the businesses or people you onboard, and they sit underneath you.

Your API key identifies you, so no call takes a partner identifier. POST /v1/customers creates a customer under you. GET /v1/customers with no parameters lists the customers you have onboarded. Another partner's customers are never included in that list.

Creation is deliberately thin. Only type is required. It decides whether the customer runs KYC or KYB and can't be changed afterwards. The fields that classify a customer are set afterwards with PUT /v1/customers/{customerId}. Those are the taxonomy fields, the legal identity, the four address roles and the declared activity.

A customer can't do anything until it's ACTIVE.

2. Endorsements gate everything

Every instrument and every movement type is behind an endorsement. The order is always the same: request it, wait for approval, then call the endpoint.

Calling the endpoint first fails with endorsement_not_enabled. Check the endorsement before you call rather than catching the error.

There are two gates on each entry and both have to clear:

  • endorsement_status is Alfred's own product and rail eligibility. Its values are INCOMPLETE, APPROVED, PAUSED and REVOKED.
  • provider_endorsements[] is the provider-side approval, which Alfred doesn't control.

A customer can read APPROVED on our side and still not provision, because a provider endorsement is outstanding. That's what reason_code: provider_processing means. There's nothing for you or the customer to do except wait.

PAUSED and REVOKED both arrive on customer.endorsement.updated with no other warning, and both stop new movement on instruments that were working the day before. Existing balances aren't affected. A revoked endorsement has to be requested again from scratch instead of being resumed.

3. Routes decide what's possible

A route is a combination of four things:

country + currency + destinationType + rail

GET /v1/countries/{country} is the authoritative answer on whether a route is supported. Each route in the registry carries the operations it supports and the identifier fields to collect. It also says whether a payout on it needs a quote, and gives a status of LIVE, SANDBOX or TARGET.

Anything that isn't LIVE is refused with route_unsupported. The refusal happens before anything is locked, at registration, at quote and at payout.

A rail is a scheme family and does not name a country. The rails are:

ACH · WIRE · SPEI · PIX · COELSA · FPS · CHATS · WALLET

The country lives on the destination and the currency lives on the quote. So (ACH, BOB) and (ACH, CLP) are different routes and price differently. (CHATS, USD) means USD delivered in Hong Kong.

You don't choose the rail when you register a destination. You send country, currency and identifiers, and Alfred derives supportedRails[] from them. A CLABE gets [SPEI], a US routing and account number gets [ACH, WIRE], and a Hong Kong account gets [FPS, CHATS].

Read each country once when you integrate, then again when country.route.updated fires.

4. Four independent state machines

Every money object carries four independent statuses. None of them implies any of the others, and inferring one from another is the most common integration mistake.

MachineAnswersExample values
transaction_statusWhere the instruction is in its own lifecycle. This is the one you show your end user.PENDING_FUNDS, PROCESSING, COMPLETE, FAILED, CANCELLED
compliance_statusHave the controls cleared.PENDING_SCREENING, CLEARED, ALERTED, RFI_REQUESTED, BLOCKED
settlement_statusHave the funds actually landed. This one is leg-level and financial.FUNDS_EXPECTED, FUNDS_HELD, SETTLED, RETURNED
reconciliation_summaryDo the records match. This one is read-only and never blocks anything.ALL_MATCHED, PARTIAL_MATCH, HAS_BREAK

An example shows why this matters. A payout that is owed reads PROCESSING with FUNDS_EXPECTED and CLEARED. The money has left and delivery is pending. There's nothing to investigate and nothing for you to do. A payout on a compliance hold reads PENDING_COMPLIANCE with ALERTED and settlement untouched. Nothing has moved and the outcome is undecided.

A single status field would make those two cases look identical. With four statuses you can tell them apart without reading a reason code.

Full detail in Statuses and lifecycle.

5. Transactions are independent

A pay-in, the conversion it funds and the payout that follows are three separate transactions. They're related through payment_flow_id, which groups them without chaining their lifecycles.

One completing never completes another. A deposit reaching COMPLETE means that deposit settled. It says nothing about the conversion you're planning to run against it.

This matters most at attribution. A deposit being matched to an intent you announced records which intent it belongs to and consumes that intent. It doesn't credit a balance, prove that the funds settled, or complete anything. If you release goods on the match rather than on deposit.credited, you're releasing against money that hasn't landed.

A pay-in also never consumes a quote. You don't need to price anything to receive a deposit into a virtual account. The deposit intent carries an indicativeRate for display, and it is an indication rather than a lock.

6. First-party and third-party movement

Some partners only move money belonging to the party they verified. Others move it on behalf of somebody else. Alfred supports both, and which one you are doing changes what you have to set up.

A first-party movement involves the customer whose identity was verified. A business converting USD to USDC for its own treasury is first-party, and so is an individual converting USDC into ARS and receiving it in their own bank account.

A third-party movement sends or receives on behalf of somebody else. A payroll platform paying a worker, a marketplace collecting from consumers on behalf of its merchants, and a platform paying its vendors are all third-party.

Three parts of the API respond to this, and each works differently.

You declare who the customer is. customerType on the profile is CUSTOMER, THIRD_PARTY_CUSTOMER or SUB_CUSTOMER, and you set it with PUT /v1/customers/{customerId} alongside customerArchetypes and useCases. The archetype list includes PAYROLL_PLATFORM, MARKETPLACE and GIG_PLATFORM, so the pattern you run is something you state rather than something we infer from your traffic.

Alfred verifies who owns the destination. When you register a linked account, Alfred decides whether it belongs to the customer or to somebody else from the provider's account validation and a name match against the customer's verified identity, not from the holder name you send. The answer comes back as relationship.relationship_type, with values of BANK_ACCOUNT_OWNERSHIP, WALLET_OWNERSHIP or THIRD_PARTY_BENEFICIARY. There is no field for you to set here, and asserting in your request that a destination belongs to the customer will not change the result.

Third-party payouts are allowed. Nesting is not. A customer paying its own supplier or contractor from its own balance is an ordinary third-party payout. What is not permitted is using an Alfred balance to hold or move funds that belong to someone who is not onboarded with Alfred, such as a customer's own customers.

Paying a third party can need its own endorsement. Where the derived relationship is THIRD_PARTY_BENEFICIARY, a stablecoin payout requires the stablecoin_payout.third_party endorsement. Without it the request is refused with third_party_not_permitted, and it will be refused the same way on every attempt until the endorsement is approved. Check provider_endorsements[] as well as endorsement_status, because both gates apply here as they do everywhere else.

The two directions are not symmetric. All of the above is about where money is going. On the way in there is no equivalent. You do not register a payer, there is no relationship to derive and no endorsement keys off who they are. What the rail tells us about them arrives on the deposit, in sender, after the money does. So a marketplace collecting on behalf of its merchants sets nothing up per payer, while the same marketplace paying those merchants registers every destination first. See Collecting into a balance.

The practical consequence is that a third-party flow needs setting up well before your first payout. Set customerType and the archetypes during onboarding. Register the destination early enough to see what relationship we derive from it. Request the endorsement while you still have time to wait for approval.

What follows from all this

Most flows have the same shape:

  1. Check the route is LIVE.
  2. Check the endorsement is APPROVED.
  3. Make the API call.
  4. Wait for the event.
  5. Read all four statuses before acting.

Skipping step 1 or 2 turns a refusal that would have come before anything was locked into a failure later in the flow. Skipping step 5 is how integrations end up treating a compliance hold as a settlement delay.


Did this page help you?