Gifting API
API reference
Every endpoint, field and webhook event in the Backer Gifting API: 26 endpoints, version 2026-11-06.
Download the OpenAPI 3.1 document this page is built from · Overview · Request access
Basics
| Base URL | https://backer.com/restapi/v2. A sandbox host comes with your credentials. |
| Authentication | OAuth2 client credentials: POST /v2/oauth/token, then Authorization: Bearer <accessToken> on every other call. |
| Format | JSON request and response bodies with camelCase keys. Money in integer cents. |
| Rate limits | 300 requests per minute and 20 requests per second per partner; over either, 429 Too Many Requests. |
| Version | 2026-11-06 |
| Examples | The request and response bodies below are illustrative, built from the schemas: real IDs, amounts and dates differ. |
REST API for integrating Backer's 529 gifting platform into a partner product. Partners manage customers, accounts, payment methods, contributions, and transactions via this API, and redirect customers to Backer-hosted pages (payment-method collection) via short-lived signed URLs.
All requests and responses are JSON with camelCase keys.
Authentication uses OAuth2 client_credentials. Partners exchange a clientId + clientSecret at POST /v2/oauth/token for a short-lived bearer access token, then pass it via Authorization: Bearer <accessToken> on every subsequent call. The access-token TTL is configured server-side; when expiresIn elapses or the API returns invalid_token, partners reauthenticate by calling POST /v2/oauth/token again with their clientId + clientSecret.
Rate limiting: 300 requests per minute and 20 requests per second per partner. Exceeding either limit returns 429 Too Many Requests.
Authentication
Exchange credentials for a bearer access token
Implements RFC 6749 §4.4 (client_credentials). Returns a short-lived JWT access token. When the token expires, call this endpoint again with the same clientId + clientSecret to obtain a new one.
grantType=client_credentials: supplyclientId+clientSecret(provisioned out-of-band by Backer).
Errors follow RFC 6749 §5.2 and use HTTP 400:
invalid_client: unknownclientIdor wrongclientSecret.unsupported_grant_type:grantTypeis not one of the supported values.invalid_request:grantTypemissing or otherwise malformed.
This endpoint is unauthenticated – do not send the Authorization header.
Request body – OAuthTokenRequest
| Field | Type | Description |
|---|---|---|
grantType Required | string | OAuth2 grant type.
One of client_credentials |
clientId Required | string | Partner's public client id. |
clientSecret Required | string | Partner's secret. |
{
"grantType": "client_credentials",
"clientId": "string",
"clientSecret": "string"
} Responses
- 200 Token issued. – OAuthTokenResponse
- 400 OAuth2 error envelope (RFC 6749 §5.2). – OAuthErrorEnvelope
{
"accessToken": "string",
"tokenType": "Bearer",
"expiresIn": 0
} Customers
Register a customer with Backer
Registers a partner customer (registry owner or gift giver) with Backer. Must be called before creating an account or submitting a payment on behalf of the customer.
clientCustomerId is optional. When provided, it must be unique within the partner's namespace.
Request body – CreateCustomerRequest
| Field | Type | Description |
|---|---|---|
clientCustomerId | string | Partner's unique identifier for the customer. Optional; when provided, it must be unique within the partner's namespace. |
email Required | string (email) | |
firstName | string | |
lastName | string |
{
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string"
} Responses
- 200 Customer created. – Customer
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string",
"status": "active"
} Get a customer by clientCustomerId
Looks up a customer by the partner-supplied clientCustomerId, scoped to the calling partner. To fetch by the Backer customer ID instead, use GET /v2/customers/{customerId}.
Parameters
clientCustomerId | query, string , required – Partner-supplied customer identifier (clientCustomerId on CreateCustomerRequest). |
Responses
- 200 Customer found. – Customer
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string",
"status": "active"
} Get a customer by Backer customer id
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Responses
- 200 Customer found. – Customer
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string",
"status": "active"
} Update a customer's details
Updates mutable fields on an existing customer. All fields are optional; at least one must be provided.
Supported updates:
clientCustomerId– partner's unique identifier for the customeremailfirstNamelastName
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Request body – UpdateCustomerRequest
| Field | Type | Description |
|---|---|---|
clientCustomerId | string | Updated partner-unique identifier for the customer. |
email | string (email) | |
firstName | string | |
lastName | string |
null Responses
- 200 Customer updated. – Customer
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 409 Resource already exists (e.g. duplicate
clientCustomerIdorclientContributionId). – ActionErrorEnvelope - 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string",
"status": "active"
} Activate, deactivate, or put a customer on hold
Sets a customer's status.
active– reactivates a customer that is inactive or on hold. Reactivates their accounts (when coming from inactive) or their pending contributions (when coming from on hold).inactive– deactivates the customer: cancels their contributions and deactivates their accounts. The customer is not emailed.hold– places the customer on hold and opens an internal Backer support ticket for review.
statusReason is required when setting inactive or hold; it is ignored when setting active. A missing reason returns 422 with a statusReason field error (errorCode=missing_value); an unsupported status returns 422 with errorCode=invalid_value.
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Request body – SetCustomerStatusRequest
| Field | Type | Description |
|---|---|---|
status Required | string | Target status for the customer.
One of active inactive hold |
statusReason | string | Reason for the status change. Required when status is inactive
or hold.
|
{
"status": "active",
"statusReason": "string"
} Responses
- 200 Customer status updated. – CustomerWithStatus
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clientCustomerId": "string",
"email": "giver@example.com",
"firstName": "string",
"lastName": "string",
"status": "active",
"statusReason": "string"
} Payment methods
Create a signed URL to the hosted payment-method page
Returns a short-lived signed URL that authenticates the customer's session on the Backer-hosted payment-method page. Redirect the customer to this URL to collect a card or bank account.
Once collected, the payment method appears in GET /v2/customers/{customerId}/payment-methods.
Customer eligibility:
- Returns
422witherror.errorCode=customer_on_holdwhen the customer is on hold. - Returns
422witherror.errorCode=customer_inactivewhen the customer is inactive.
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Request body – CreatePaymentMethodSessionRequest
| Field | Type | Description |
|---|---|---|
successUrl Required | string (uri) | Absolute http(s) URL the hosted page redirects to on completion. |
cancelUrl Required | string (uri) | Absolute http(s) URL the hosted page redirects to on exit/cancel. |
type Required | string | Payment method type to collect. Locks the hosted page to a single rail –
ACH shows only the bank-account form; card shows only the card form.
Switching between rails is not permitted once the session is created.
One of ach card |
metadata | string | Optional opaque partner-defined string echoed back to
successUrl as the metadata query parameter when the customer
finishes adding a payment method. Useful for correlating the
resulting payment method with a partner-side order, cart, or
wishlist. The value is passed through verbatim – partners are
free to choose its format (e.g. JSON, base64, opaque token)
and parse it on successUrl.
|
platformFeeInCents | integer | Platform fee in cents shown on the hosted payment-method page
(e.g. "$1.99 convenience fee"). Omit or set to 0 to hide the
convenience fee line on ACH; card pages still show the 3% processing
fee disclaimer.
|
{
"successUrl": "https://example.com",
"cancelUrl": "https://example.com",
"type": "ach",
"metadata": "string",
"platformFeeInCents": 0
} Responses
- 200 Session created. – HostedSession
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"url": "https://example.com"
} List saved payment methods for a customer
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Responses
- 200 Payment methods on file. – PaymentMethod[]
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
[
{
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "ach",
"bankAccount": {
"lastFour": "string",
"bankInstitutionName": "string",
"bankAccountType": "string",
"accountHolderName": "string"
}
}
] Get a saved payment method by id
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
paymentMethodId | path, string (uuid) , required – Backer payment method UUID. |
Responses
- 200 Payment method on file. – PaymentMethod
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "ach",
"bankAccount": {
"lastFour": "string",
"bankInstitutionName": "string",
"bankAccountType": "string",
"accountHolderName": "string"
}
} Delete a saved payment method
Removes a saved payment method (card or bank account). Returns the deleted payment method – same shape as GET /v2/customers/{customerId}/payment-methods/{paymentMethodId}.
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
paymentMethodId | path, string (uuid) , required – Backer payment method UUID. |
Responses
- 200 Payment method deleted. – PaymentMethod
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"type": "ach",
"bankAccount": {
"lastFour": "string",
"bankInstitutionName": "string",
"bankAccountType": "string",
"accountHolderName": "string"
}
} Plans and brokers
List 529 plans
By default returns plans currently supported for direct linking (isSupported=true). Pass includeUnsupported=true to also include unsupported plans (isSupported=false). Soft-deleted plans are never returned.
Parameters
includeUnsupported | query, boolean – When true, include plans that are not supported for direct
linking. Defaults to false.
|
Responses
- 200 Plans (supported only by default; all alive when includeUnsupported=true). – Plan[]
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
[
{
"planId": "string",
"stateName": "string",
"displayName": "string",
"isSupported": true,
"programManager": "string",
"programDistributor": "string"
}
] List brokerage partners
By default returns brokerage partners currently supported and enabled for partner linking (isSupported=true). Each broker includes the plans associated with that broker for informational use during broker link flows.
Pass includeUnsupported=true to also include unsupported brokers (isSupported=false), such as partners that are configured but not yet enabled, and catalog-only brokers that are not yet launched. Unsupported brokers cannot be used with validate-account-number / link-account until launched.
Broker-held accounts validate and link against broker-level account number rules. An optional planId on validate-account-number and link-account identifies which underlying 529 plan the customer holds; the plan may have isSupported: false as long as it is in the selected broker's plan catalog and the broker itself has isSupported: true.
Parameters
includeUnsupported | query, boolean – When true, also include unsupported brokers. Defaults to false.
|
Responses
- 200 Brokers (supported/enabled only by default; full catalog when includeUnsupported=true). – Broker[]
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
[
{
"brokerId": "string",
"name": "string",
"isSupported": true,
"plans": [
{
"planId": "string",
"stateName": "string",
"displayName": "string",
"isSupported": true,
"programManager": "string",
"programDistributor": "string"
}
]
}
] Accounts
Validate a 529 account number
Checks whether an account number matches the format expected for a direct 529 plan link and/or a brokerage-held 529 account before calling POST /v2/customers/{customerId}/link-account. Uses the same account-number validation rules as link-account.
At least one of brokerId or planId is required:
- Plan only –
planId+accountNumber(the plan must haveisSupported: trueinGET /v2/plans). - Broker only –
brokerId+accountNumber(the broker must haveisSupported: trueinGET /v2/brokers). - Broker + plan –
brokerId, optionalplanId, andaccountNumber.planIdmay be any plan in the broker's catalog fromGET /v2/brokers, regardless of the plan's ownisSupportedvalue; the broker must haveisSupported: true.
Returns 422 with error.errorCode=broker_not_found when brokerId is unknown.
Returns 422 with error.errorCode=broker_not_available when the broker is not currently enabled.
Returns 422 with error.errorCode=broker_plan_mismatch when both brokerId and planId are sent but the plan is not serviced by the broker.
Returns 422 with error.errorCode=plan_not_found when planId is sent but does not match a known plan, or when a plan-only request references a plan whose isSupported value is false.
Returns 422 with error.errorCode=invalid_account_number when the account number does not match the expected format.
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Request body – ValidateAccountNumberRequest
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID (from GET /v2/brokers). |
planId | string | Backer plan ID. Required for plan-only validation. Optional with
brokerId to identify the underlying 529 plan.
|
accountNumber Required | string | 529 account number to validate. |
null Responses
- 200 Account number is valid. – ValidateAccountNumberResponse
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"valid": true
} Create an account backed by an existing 529 account
Creates a new account for the registry owner identified by customerId and links it to an existing 529 account that the owner has already opened. Use this when the partner has collected the account number out-of-band.
At least one of brokerId or planId is required (same rules as POST /v2/customers/{customerId}/validate-account-number).
Returns 422 with error.errorCode=broker_not_found, broker_not_available, or broker_plan_mismatch for broker validation failures.
Returns 422 with error.errorCode=invalid_account_number when the account number does not match the expected format.
Returns 422 with error.errorCode=plan_not_found when a plan-only link references an unknown or unsupported plan.
If the account number turns out to be invalid (delivered later via a transaction.failed webhook whose failureDetails.code indicates an invalid 529 account number), correct it via POST /v2/accounts/{accountId} once the registry owner provides the right number.
Customer eligibility:
- Returns
422witherror.errorCode=customer_on_holdwhen the customer is on hold. - Returns
422witherror.errorCode=customer_inactivewhen the customer is inactive.
Parameters
customerId | path, string (uuid) , required – Backer customer UUID. |
Request body – LinkAccountRequest
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID with isSupported: true from GET /v2/brokers.
|
planId | string | Backer plan ID. Required with isSupported: true for direct plan
linking. Optional with brokerId for brokerage-held accounts, where
the plan's own isSupported value does not restrict broker linking.
|
accountNumber Required | string | Customer's existing 529 account number. |
beneficiary | Beneficiary | |
accountOwner | AccountOwnerInput | |
isAccountOwnerBeneficiary | boolean | When true, the registry owner is also the beneficiary of the
529. beneficiary must be omitted in that case (422 if both are
set). The owner's first/last name (resolved from existing customer
data or the supplied accountOwner) becomes the account owner's
first/last name.
|
null Responses
- 200 Account linked. – LinkAccountResponse
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"broker": {
"brokerId": "string",
"name": "string",
"isSupported": true
},
"plan": {
"planId": "string",
"name": "string",
"stateName": "string"
},
"beneficiary": {
"firstName": "string",
"lastName": "string"
},
"accountOwner": {
"email": "giver@example.com",
"firstName": "string",
"lastName": "string"
},
"isAccountOwnerBeneficiary": true,
"status": "active",
"userName": "string"
} Get a linked 529 account by id
Parameters
accountId | path, string (uuid) , required – Backer linked-account UUID. |
Responses
- 200 Account found. – LinkAccountResponse
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"broker": {
"brokerId": "string",
"name": "string",
"isSupported": true
},
"plan": {
"planId": "string",
"name": "string",
"stateName": "string"
},
"beneficiary": {
"firstName": "string",
"lastName": "string"
},
"accountOwner": {
"email": "giver@example.com",
"firstName": "string",
"lastName": "string"
},
"isAccountOwnerBeneficiary": true,
"status": "active",
"userName": "string"
} Update linked 529 account details
Updates mutable details on a linked 529 account.
Supported updates:
planId(Backer plan ID withisSupported: truefromGET /v2/plans; re-validates the stored account number against the new plan)accountNumber(re-validates against the current or updated plan)beneficiary.firstNameandbeneficiary.lastNameaccountOwner.firstName,accountOwner.lastName, andaccountOwner.email(overwrites existing values)
At least one update field must be provided.
Returns 422 with error.errorCode=transactions_still_processing when the request changes planId or accountNumber while a contribution push to the 529 plan is scheduled or processing.
Returns 422 with error.errorCode=invalid_account_number when accountNumber or planId fails account-number validation against the target plan.
Parameters
accountId | path, string (uuid) , required – Backer linked-account UUID. |
Request body – UpdateAccountRequest
| Field | Type | Description |
|---|---|---|
planId | string | Backer plan ID with isSupported: true from GET /v2/plans.
|
accountNumber | string | Updated 529 account number. |
beneficiary | Beneficiary | |
accountOwner | AccountOwnerInput |
null Responses
- 200 Account updated. – LinkAccountResponse
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"broker": {
"brokerId": "string",
"name": "string",
"isSupported": true
},
"plan": {
"planId": "string",
"name": "string",
"stateName": "string"
},
"beneficiary": {
"firstName": "string",
"lastName": "string"
},
"accountOwner": {
"email": "giver@example.com",
"firstName": "string",
"lastName": "string"
},
"isAccountOwnerBeneficiary": true,
"status": "active",
"userName": "string"
} Activate or deactivate a linked 529 account
Sets an account's status.
active– reactivates a deactivated account.inactive– deactivates the account: cancels its contributions. The customer is not emailed.
Deactivating the customer's last account also deactivates the customer.
statusReason is required when setting inactive; it is ignored when setting active. A missing reason returns 422 with a statusReason field error (errorCode=missing_value); an unsupported status returns 422 with errorCode=invalid_value.
Parameters
accountId | path, string (uuid) , required – Backer linked-account UUID. |
Request body – SetAccountStatusRequest
| Field | Type | Description |
|---|---|---|
status Required | string | Target status for the account.
One of active inactive |
statusReason | string | Reason for the status change. Required when status is inactive.
|
{
"status": "active",
"statusReason": "string"
} Responses
- 200 Account status updated. – AccountWithStatus
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"broker": {
"brokerId": "string",
"name": "string",
"isSupported": true
},
"plan": {
"planId": "string",
"name": "string",
"stateName": "string"
},
"beneficiary": {
"firstName": "string",
"lastName": "string"
},
"accountOwner": {
"email": "giver@example.com",
"firstName": "string",
"lastName": "string"
},
"isAccountOwnerBeneficiary": true,
"status": "active",
"userName": "string",
"statusReason": "string"
} Get 529 contribution limits for a linked account
Returns the minimum, one-time maximum, and recurring (monthly, annually) maximum contribution amounts (in cents) accepted by the linked 529 plan, for both ACH and card payment methods. Use these to validate amounts client-side before calling POST /v2/payments.
Card maximums are capped at 50000 cents ($500) to reflect the per-transaction card limit; ACH entries return the plan's native limits unchanged.
Parameters
accountId | query, string (uuid) , required – Backer linked-account UUID. |
Responses
- 200 Contribution limits for the account. – ContributionLimitsResponse
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"limits": [
{
"paymentMethodType": "ach",
"minimumInCents": 2500,
"maximumInCents": 2500,
"maximumMonthlyInCents": 2500,
"maximumAnnuallyInCents": 2500
}
]
} Contributions and transactions
Quote fees for a prospective contribution
Returns the platform and processing fees that would be charged for a contribution with the given method and amount, plus the total amount the contributor would be charged (amountInCents + platformFeeInCents + processingFeeInCents).
Parameters
paymentMethodType | query, string , required – How the contributor would pay; drives processingFeeInCents (card only). |
amountInCents | query, integer , required – Contribution amount in cents. |
platformFeeInCents | query, integer , required – Platform fee in cents to apply to this contribution. |
Responses
- 200 Quoted fees. – ContributionFees
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"platformFeeInCents": 2500,
"processingFeeInCents": 2500,
"totalAmountInCents": 2500
} Quote fees for a contribution, including optional extra items
Returns the platform and processing fees that would be charged for a contribution with the given method and amount, plus the total amount the contributor would be charged. Optional extraItems (amount + tax) are added to totalAmountInCents. Extra-item processing is returned on each item and is not included in totalAmountInCents.
Request body – QuoteContributionRequest
| Field | Type | Description |
|---|---|---|
frequency | string | Optional, as on POST /v2/contributions; defaults to onetime.
Send the frequency the contribution will be created with: the quote
applies the same rule, so extraItems with monthly, annually,
or other return 422 here instead of at creation.
One of onetime monthly annually other |
paymentMethodType Required | string | How the contributor would pay; drives processing fees (card only).
One of ach card |
amountInCents Required | integer | Contribution amount in cents (sent to the 529 plan). |
platformFeeInCents Required | integer | Platform fee in cents to apply to this contribution. |
extraItems | ExtraItemRequest[] | Optional add-on items charged to the giver and not sent to the 529. At most one item. An empty array is treated as omitted. Extra-item processing is computed from the payment rail and returned on each item; it is not charged to the giver. |
{
"frequency": "onetime",
"paymentMethodType": "ach",
"amountInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500
}
]
} Responses
- 200 Quoted fees. This is a calculation, not a created resource. – ContributionQuote
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"amountInCents": 2500,
"platformFeeInCents": 2500,
"processingFeeInCents": 2500,
"totalAmountInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
]
} Submit a contribution
Charges the contributor via the selected payment method and transfers the contribution to the 529 plan.
Response behavior depends on payment method:
- Card: the response immediately indicates success (
status: processing) or failure (4xx error envelope). - ACH: the response confirms the contribution was accepted (
status: processing). Collection failures arrive later via atransaction.failedwebhook.
Contribution fees:
- Platform Fee is set by the
platformFeeInCentsparameter.
- Processing Fee is 3% of the
amountInCentswhen the payment method is a card or a digital wallet.
Simulation support (non-production environments only):
- Pass
X-Backer-Simulateor use a magicamountInCentsvalue to force deterministic outcomes for partner QA. When both are provided, the amount takes precedence. - Magic amounts:
4200successful contribution,4210card declined (credit card),4211funding insufficient funds (bank account),4221provider invalid account number,4222provider plan not available,4230refund succeeded (any payment method; payout fails, refund succeeds async –refund_processing,refunded),4231refund card expired (credit card; payout fails, refund fails async –refund_processing,refunded,refund_failed, codecard_expired),4232refund account closed (bank account; payout fails, ACH return –refund_processing,refund_failed, codeaccount_closed). Settled, invalid account number, and plan not available work with any payment method. - Supported header values (same scenarios as magic amounts, plus immediate card decline):
card_declined: returns an immediate 422 error witherror.errorCode=card_declined.ach_insufficient_funds: returns a normal 200processingresponse, then transitions to failed a few seconds later and emitstransaction.failedwebhook.settled: returns a normal 200processingresponse, then a few seconds later simulates settling collecting the funds successfully, sending the funds to the 529 plan successfully and settle the transaction, which emits atransaction.settledwebhook.invalid_account_number: returns a normal 200processingresponse, then a few seconds later simulates collecting the funds successfully but the transfer to the 529 plan fails, which emits atransaction.failedwebhook withfailureDetails.code=invalid_account_number.plan_not_available: returns a normal 200processingresponse, then a few seconds later simulates a provider payout rejection withfailureDetails.code=plan_not_available.refund_succeeded: same as4230.refund_card_expired: same as4231.refund_account_closed: same as4232.- In production environments, simulation triggers return 403.
Account/owner eligibility:
- Returns
422witherror.errorCode=account_inactivewhen the linked account is inactive. - Returns
422witherror.errorCode=account_owner_on_holdwhen the account owner is on hold. - Returns
422witherror.errorCode=account_owner_inactivewhen the account owner is inactive. - Returns
422witherror.errorCode=customer_on_holdwhen the contributing customer (the funding customer) is on hold. - Returns
422witherror.errorCode=customer_inactivewhen the contributing customer (the funding customer) is inactive.
Parameters
X-Backer-Simulate | header, string – Optional non-production simulation scenario for partner testing.
Allowed values: card_declined, ach_insufficient_funds,
settled, invalid_account_number, plan_not_available,
refund_succeeded, refund_card_expired, refund_account_closed.
Alternatively, pass a magic amountInCents; amount takes precedence
when both are set.
|
Request body – CreateContributionRequest
| Field | Type | Description |
|---|---|---|
accountId Required | string (uuid) | Backer linked-account UUID the contribution is sent to. |
amountInCents Required | integer | Contribution amount in cents (sent to the 529 plan). |
platformFeeInCents Required | integer | Platform fee in cents charged to the contributor on top of
amountInCents. Obtain the correct value for the contributor
via GET /v2/contribution-fees before submitting.
|
funding Required | ContributionFundingRequest | |
clientContributionId | string | Optional partner-supplied identifier for this contribution. Must be
unique across the partner's contributions; a duplicate returns 409.
Echoed back on the transaction and usable with
GET /v2/transactions?clientContributionId=....
|
frequency | string | Optional partner-declared schedule intent. Omit or set to onetime
for a one-time contribution (default). Set to monthly, annually,
or other when the partner considers this part of a recurring
schedule they manage externally. extraItems are rejected when
frequency is monthly, annually, or other.
One of onetime monthly annually other |
extraItems | ExtraItemRequest[] | Optional add-on items charged to the giver and not sent to the 529. At most one item. An empty array is treated as omitted. Extra-item processing is computed from the payment rail and returned on each item; it is not charged to the giver. |
{
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"platformFeeInCents": 2500,
"funding": {
"type": "CUSTOMER",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"clientContributionId": "string",
"frequency": "onetime",
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500
}
]
} Responses
- 200 Contribution initiated. – Contribution
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 409 Resource already exists (e.g. duplicate
clientCustomerIdorclientContributionId). – ActionErrorEnvelope - 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"status": "processing",
"type": "contribution",
"createdAt": "2026-11-06T17:00:00Z",
"updatedAt": "2026-11-06T17:00:00Z",
"contribution": {
"clientContributionId": "string",
"frequency": "onetime",
"funding": {
"type": "customer",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"processingFeeInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
],
"totalAmountInCents": 2500
}
} Get a transaction by id
Returns a single transaction by its UUID. The response shape is identical to an item in GET /v2/accounts/{accountId}/transactions.
Parameters
transactionId | path, string (uuid) , required – Backer transaction UUID. |
Responses
- 200 Transaction found. – Transaction
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"status": "processing",
"type": "contribution",
"createdAt": "2026-11-06T17:00:00Z",
"updatedAt": "2026-11-06T17:00:00Z",
"cancellableUntil": "2026-11-06T17:00:00Z",
"contribution": {
"clientContributionId": "string",
"funding": {
"type": "customer",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"processingFeeInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
],
"totalAmountInCents": 2500
},
"failureDetails": {
"phase": "funding",
"category": "funding_card_failure",
"code": "string",
"message": "string",
"failedAt": "2026-11-06T17:00:00Z"
}
} Get a transaction by clientContributionId
Looks up a transaction by the partner-supplied clientContributionId provided when the contribution was created. To fetch by the Backer transaction ID instead, use GET /v2/transactions/{transactionId}.
Parameters
clientContributionId | query, string , required – Partner-supplied contribution identifier (clientContributionId on CreateContributionRequest). |
Responses
- 200 Transaction found. – Transaction
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"status": "processing",
"type": "contribution",
"createdAt": "2026-11-06T17:00:00Z",
"updatedAt": "2026-11-06T17:00:00Z",
"cancellableUntil": "2026-11-06T17:00:00Z",
"contribution": {
"clientContributionId": "string",
"funding": {
"type": "customer",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"processingFeeInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
],
"totalAmountInCents": 2500
},
"failureDetails": {
"phase": "funding",
"category": "funding_card_failure",
"code": "string",
"message": "string",
"failedAt": "2026-11-06T17:00:00Z"
}
} List transactions for an account
Returns the transaction history for a linked 529 account, ordered by descending createdAt. Results are paginated.
Parameters
accountId | path, string (uuid) , required – Backer linked-account UUID. |
page | query, integer – Page number (1-based). Defaults to 1. |
pageSize | query, integer – Number of transactions per page. Defaults to 10. |
Responses
- 200 Paginated transactions for the account. – PaginatedTransactions
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"items": [
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"status": "processing",
"type": "contribution",
"createdAt": "2026-11-06T17:00:00Z",
"updatedAt": "2026-11-06T17:00:00Z",
"cancellableUntil": "2026-11-06T17:00:00Z",
"contribution": {
"clientContributionId": "string",
"funding": {
"type": "customer",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"processingFeeInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
],
"totalAmountInCents": 2500
},
"failureDetails": {
"phase": "funding",
"category": "funding_card_failure",
"code": "string",
"message": "string",
"failedAt": "2026-11-06T17:00:00Z"
}
}
],
"total": 0,
"page": 0,
"pageSize": 0
} Cancel a contribution transaction
Cancels a contribution transaction within the cancellation window.
- ACH: cancels the scheduled bank pull; no refund is needed because funds have not been collected.
- Card: initiates a refund because funds were collected synchronously.
A transaction is cancellable when:
- The current time is within 1 hour 5 minutes of
createdAt(cancellableUntilplus a 5-minute backend grace period).
Error codes:
409not_cancellable: transaction is outside the window or otherwise not eligible.
Parameters
transactionId | path, string (uuid) , required – Backer transaction UUID. |
Responses
- 200 Transaction cancelled or refund initiated. – Transaction
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 403 Authenticated, but the partner is not allowed to access this resource. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 409 Resource already exists (e.g. duplicate
clientCustomerIdorclientContributionId). – ActionErrorEnvelope - 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"amountInCents": 2500,
"status": "processing",
"type": "contribution",
"createdAt": "2026-11-06T17:00:00Z",
"updatedAt": "2026-11-06T17:00:00Z",
"cancellableUntil": "2026-11-06T17:00:00Z",
"contribution": {
"clientContributionId": "string",
"funding": {
"type": "customer",
"customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"paymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"processingFeeInCents": 2500,
"platformFeeInCents": 2500,
"extraItems": [
{
"sku": "string",
"amountInCents": 2500,
"taxAmountInCents": 2500,
"processingFeeInCents": 2500
}
],
"totalAmountInCents": 2500
},
"failureDetails": {
"phase": "funding",
"category": "funding_card_failure",
"code": "string",
"message": "string",
"failedAt": "2026-11-06T17:00:00Z"
}
} Webhooks
Update the partner's webhook subscription
Updates the webhook subscription for the authenticated partner. If the partner has no subscription, returns 404.
All fields are optional; at least one must be provided.
Request body – UpdateWebhookSubscriptionRequest
| Field | Type | Description |
|---|---|---|
url | string (uri) | Delivery endpoint URL (must be http or https). |
eventTypes | string[] | Non-empty list of event types to subscribe to.
One of transaction.settled transaction.failed transaction.refund_initiated transaction.refunded transaction.refund_failed |
secret | string | Signing secret used to verify webhook payloads. |
description | string | Human-readable description for the subscription. |
null Responses
- 200 Subscription updated. – WebhookSubscription
- 401 Missing or invalid bearer token. – ActionErrorEnvelope
- 404 Resource not found, or not owned by the calling partner. – ActionErrorEnvelope
- 422 Field-level validation failed (one or more request fields). – FieldErrorsEnvelope
- 429 Rate limit exceeded (300 requests/minute and 20 requests/second per partner). – ActionErrorEnvelope
{
"url": "https://example.com",
"eventTypes": [
"string"
],
"secret": "string",
"description": "string",
"status": "enabled"
} Webhook events
Backer POSTs each event to the URL set with POST /v2/webhook-subscription, signed in the Webhook-Signature header.
Contribution settled to the 529 plan
Emitted when a contribution reaches its terminal success state: funds collected from the contributor's payment method and dispatched to the 529 plan. data.object.failureDetails is always null for this event. Payload is signed via the Webhook-Signature header (HMAC-SHA256). Delivery is retried with exponential backoff up to 20 attempts.
Payload – TransactionSettledEvent
Contribution failed
Emitted when a contribution transitions to failed (ACH return, plan rejection, card decline, etc.). data.object.failureDetails describes failure phase, category, and reason.
Payload – TransactionFailedEvent
Refund initiated for a contribution
Emitted when a refund is requested for a contribution – for both card and bank (ACH) refunds. This is followed by either transaction.refunded (success) or transaction.refund_failed (failure): card refunds resolve synchronously, while ACH refunds resolve a few business days later once the refund transaction settles or is returned. Payload is signed via the Webhook-Signature header (HMAC-SHA256). Delivery is retried with exponential backoff up to 20 attempts.
Payload – TransactionRefundInitiatedEvent
Refund settled for a contribution
Emitted when a refund completes successfully and the contribution is refunded. Card refunds emit this as soon as the refund succeeds; ACH refunds emit it when the refund transaction settles (a few business days later). data.status is refunded and data.failureDetails is absent.
Payload – TransactionRefundedEvent
Refund failed for a contribution
Emitted when a refund attempt fails – a card refund error, or an ACH refund returned by the bank. data.status is always refund_failed. data.failureDetails describes the refund failure (phase=refund).
Payload – TransactionRefundFailedEvent
Schemas
Request schemas mark required fields; response schemas mark fields that can be null.
OAuthTokenRequest
OAuth2 token request. Supply clientId + clientSecret with grantType=client_credentials.
| Field | Type | Description |
|---|---|---|
grantType Required | string | OAuth2 grant type.
One of client_credentials |
clientId Required | string | Partner's public client id. |
clientSecret Required | string | Partner's secret. |
OAuthTokenResponse
| Field | Type | Description |
|---|---|---|
accessToken | string | Signed JWT bearer token. Pass as Authorization: Bearer <accessToken>. |
tokenType | string | |
expiresIn | integer | Lifetime of accessToken in seconds. The server's TTL is configurable; do not hard-code. |
OAuthErrorEnvelope
OAuth2 error envelope per RFC 6749 §5.2. Distinct from the ActionErrorEnvelope / FieldErrorsEnvelope returned by every non-OAuth endpoint.
| Field | Type | Description |
|---|---|---|
error | string |
One of invalid_request invalid_client unsupported_grant_type |
CreateCustomerRequest
| Field | Type | Description |
|---|---|---|
clientCustomerId | string | Partner's unique identifier for the customer. Optional; when provided, it must be unique within the partner's namespace. |
email Required | string (email) | |
firstName | string | |
lastName | string |
Customer
| Field | Type | Description |
|---|---|---|
customerId | string (uuid) | Backer's UUID for the customer. |
clientCustomerId | string Nullable | Partner's unique identifier for the customer (echoed back). |
email | string (email) | |
firstName | string Nullable | |
lastName | string Nullable | |
status | string | Customer status. active when the customer has no status or is
active; inactive when deactivated; hold when placed on hold.
One of active inactive hold |
UpdateCustomerRequest
Fields that can be updated on an existing customer. At least one must be provided.
| Field | Type | Description |
|---|---|---|
clientCustomerId | string | Updated partner-unique identifier for the customer. |
email | string (email) | |
firstName | string | |
lastName | string |
SetCustomerStatusRequest
Sets the customer's status. statusReason is required when status is inactive or hold, and is ignored when status is active.
| Field | Type | Description |
|---|---|---|
status Required | string | Target status for the customer.
One of active inactive hold |
statusReason | string | Reason for the status change. Required when status is inactive
or hold.
|
CustomerWithStatus
SetAccountStatusRequest
Sets the account's status. statusReason is required when status is inactive, and is ignored when status is active.
| Field | Type | Description |
|---|---|---|
status Required | string | Target status for the account.
One of active inactive |
statusReason | string | Reason for the status change. Required when status is inactive.
|
AccountWithStatus
UpdateAccountRequest
Fields that can be updated on a linked 529 account.
At least one of planId, accountNumber, beneficiary, or accountOwner must be provided.
Changing planId re-validates the stored account number against the new plan. Changing accountNumber re-validates against the current or updated plan.
| Field | Type | Description |
|---|---|---|
planId | string | Backer plan ID with isSupported: true from GET /v2/plans.
|
accountNumber | string | Updated 529 account number. |
beneficiary | Beneficiary | |
accountOwner | AccountOwnerInput |
LinkAccountRequest
beneficiary is required when the registry owner is gifting on behalf of someone else (the default). When the registry owner is the beneficiary of their own 529, set isAccountOwnerBeneficiary: true and omit beneficiary – Backer copies the owner's first and last name onto the account instead.
accountOwner may be supplied to set the customer's first and last name during linking. Supplied values overwrite any existing names on the customer.
At least one of brokerId or planId is required. For brokerage-held accounts, send brokerId and optionally planId from the broker's catalog (GET /v2/brokers). The selected broker must have isSupported: true.
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID with isSupported: true from GET /v2/brokers.
|
planId | string | Backer plan ID. Required with isSupported: true for direct plan
linking. Optional with brokerId for brokerage-held accounts, where
the plan's own isSupported value does not restrict broker linking.
|
accountNumber Required | string | Customer's existing 529 account number. |
beneficiary | Beneficiary | |
accountOwner | AccountOwnerInput | |
isAccountOwnerBeneficiary | boolean | When true, the registry owner is also the beneficiary of the
529. beneficiary must be omitted in that case (422 if both are
set). The owner's first/last name (resolved from existing customer
data or the supplied accountOwner) becomes the account owner's
first/last name.
|
ValidateAccountNumberRequest
At least one of brokerId or planId is required. See POST /v2/customers/{customerId}/validate-account-number.
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID (from GET /v2/brokers). |
planId | string | Backer plan ID. Required for plan-only validation. Optional with
brokerId to identify the underlying 529 plan.
|
accountNumber Required | string | 529 account number to validate. |
ValidateAccountNumberResponse
| Field | Type | Description |
|---|---|---|
valid | boolean | Always true on success. |
Beneficiary
| Field | Type | Description |
|---|---|---|
firstName | string | |
lastName | string |
AccountOwnerInput
Optional first/last name and email for the customer. Any field may be omitted. Supplied values overwrite existing ones.
| Field | Type | Description |
|---|---|---|
firstName | string Nullable | |
lastName | string Nullable | |
email | string (email) Nullable |
AccountOwner
Customer who owns the linked 529 account.
| Field | Type | Description |
|---|---|---|
email | string (email) | |
firstName | string Nullable | |
lastName | string Nullable |
LinkAccountResponse
| Field | Type | Description |
|---|---|---|
accountId | string (uuid) | Backer's UUID for the linked account. |
broker | LinkedAccountBroker | |
plan | LinkedAccountPlan | |
beneficiary | Beneficiary | |
accountOwner | AccountOwner | |
isAccountOwnerBeneficiary | boolean | true when the registry owner is also the beneficiary of the
529 (the account's beneficiary first/last name mirrors the account
owner's). false otherwise.
|
status | string | Account status. active when the account is active or pending;
inactive when deactivated.
One of active inactive |
userName | string Nullable | 529 plan login username, present when the account was opened through a Backer-hosted flow; omitted for link-account-only accounts. |
LinkedAccountBroker
Brokerage partner for a linked account. Omitted when the account is not linked via a broker.
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID (e.g. br_1). |
name | string | Broker display name. |
isSupported | boolean | Whether the broker is currently available for account linking. |
LinkedAccountPlan
Underlying 529 plan for a linked account. Omitted when the account is not linked to a plan (broker-only links).
| Field | Type | Description |
|---|---|---|
planId | string | |
name | string | |
stateName | string |
Broker
| Field | Type | Description |
|---|---|---|
brokerId | string | Backer broker ID (e.g. br_1). |
name | string | Broker display name. |
isSupported | boolean | Whether the broker is currently available for account linking
(has account-number validation configured and is enabled).
Catalog-only brokers returned with includeUnsupported=true are
always false until launched.
|
plans | Plan[] | Plans associated with this broker (informational). |
Plan
| Field | Type | Description |
|---|---|---|
planId | string | Backer's plan ID. |
stateName | string | |
displayName | string | |
isSupported | boolean | Whether the plan is currently available for direct account linking. |
programManager | string Nullable | Name of the 529 program manager (e.g. "Vanguard"). |
programDistributor | string Nullable | Name of the 529 program distributor. |
ContributionLimitsResponse
Contribution amount limits for the account's 529 plan, in cents. Returns one entry per supported payment method (ach, card). minimumInCents and maximumInCents are the one-time contribution bounds; maximumMonthlyInCents and maximumAnnuallyInCents are the recurring caps for the matching frequency.
| Field | Type | Description |
|---|---|---|
limits | ContributionLimitsEntry[] |
ContributionLimitsEntry
| Field | Type | Description |
|---|---|---|
paymentMethodType | string |
One of ach card |
minimumInCents | integer | Minimum one-time contribution amount in cents. |
maximumInCents | integer | Maximum one-time contribution amount in cents. Capped at 50000 ($500) for card. |
maximumMonthlyInCents | integer | Maximum recurring monthly contribution amount in cents. Capped at 50000 ($500) for card. |
maximumAnnuallyInCents | integer | Maximum recurring annual contribution amount in cents. Capped at 50000 ($500) for card. |
PaymentMethod
Saved payment method. type discriminates the shape: ACH methods carry a bankAccount object; card methods carry a creditCard object.
AchPaymentMethod
| Field | Type | Description |
|---|---|---|
paymentMethodId | string (uuid) | |
type | string | |
bankAccount | object |
CardPaymentMethod
| Field | Type | Description |
|---|---|---|
paymentMethodId | string (uuid) | |
type | string | |
creditCard | object |
CreatePaymentMethodSessionRequest
| Field | Type | Description |
|---|---|---|
successUrl Required | string (uri) | Absolute http(s) URL the hosted page redirects to on completion. |
cancelUrl Required | string (uri) | Absolute http(s) URL the hosted page redirects to on exit/cancel. |
type Required | string | Payment method type to collect. Locks the hosted page to a single rail –
ACH shows only the bank-account form; card shows only the card form.
Switching between rails is not permitted once the session is created.
One of ach card |
metadata | string | Optional opaque partner-defined string echoed back to
successUrl as the metadata query parameter when the customer
finishes adding a payment method. Useful for correlating the
resulting payment method with a partner-side order, cart, or
wishlist. The value is passed through verbatim – partners are
free to choose its format (e.g. JSON, base64, opaque token)
and parse it on successUrl.
|
platformFeeInCents | integer | Platform fee in cents shown on the hosted payment-method page
(e.g. "$1.99 convenience fee"). Omit or set to 0 to hide the
convenience fee line on ACH; card pages still show the 3% processing
fee disclaimer.
|
HostedSession
| Field | Type | Description |
|---|---|---|
url | string (uri) | Signed, short-lived URL to redirect the customer to. |
CreateContributionRequest
| Field | Type | Description |
|---|---|---|
accountId Required | string (uuid) | Backer linked-account UUID the contribution is sent to. |
amountInCents Required | integer | Contribution amount in cents (sent to the 529 plan). |
platformFeeInCents Required | integer | Platform fee in cents charged to the contributor on top of
amountInCents. Obtain the correct value for the contributor
via GET /v2/contribution-fees before submitting.
|
funding Required | ContributionFundingRequest | |
clientContributionId | string | Optional partner-supplied identifier for this contribution. Must be
unique across the partner's contributions; a duplicate returns 409.
Echoed back on the transaction and usable with
GET /v2/transactions?clientContributionId=....
|
frequency | string | Optional partner-declared schedule intent. Omit or set to onetime
for a one-time contribution (default). Set to monthly, annually,
or other when the partner considers this part of a recurring
schedule they manage externally. extraItems are rejected when
frequency is monthly, annually, or other.
One of onetime monthly annually other |
extraItems | ExtraItemRequest[] | Optional add-on items charged to the giver and not sent to the 529. At most one item. An empty array is treated as omitted. Extra-item processing is computed from the payment rail and returned on each item; it is not charged to the giver. |
ContributionFundingRequest
Identifies who is funding the contribution and which saved payment method to charge.
| Field | Type | Description |
|---|---|---|
type Required | string | Discriminator for the funding source. Only the customer funding
source is supported.
One of CUSTOMER |
customerId Required | string (uuid) | Contributor's Backer customer UUID. |
paymentMethodId Required | string (uuid) | Backer payment-method UUID to charge. |
Contribution
| Field | Type | Description |
|---|---|---|
transactionId | string (uuid) | Backer's UUID for the underlying transaction. |
accountId | string (uuid) | Backer linked-account UUID the contribution was sent to. |
amountInCents | integer | Contribution amount sent to the 529 plan. |
status | TransactionStatus | |
type | string | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
contribution | ContributionDetails |
ContributionDetails
| Field | Type | Description |
|---|---|---|
clientContributionId | string Nullable | Partner-supplied identifier for this contribution, if one was provided on creation; null otherwise. |
frequency | string | Partner-declared schedule intent. onetime for one-time
contributions; monthly, annually, or other when the partner
marked the contribution as part of a recurring schedule.
One of onetime monthly annually other |
funding | ContributionFunding | |
processingFeeInCents | integer | Card processing fee charged to the contributor (0 for ACH). |
platformFeeInCents | integer | Platform fee charged to the contributor (0 for owner self-contributions). |
extraItems | ExtraItem[] | Extra items persisted on this contribution, including the recomputed extra-item processing fee. Omitted when none were sent. |
totalAmountInCents | integer | Total amount charged to the contributor (amountInCents +
platformFeeInCents + processingFeeInCents + extra item amounts +
tax). Extra-item processing is not included.
|
ContributionFunding
Identifies who funded the contribution and which saved payment method was charged. Mirrors ContributionFundingRequest on the response side; type is returned lowercase (customer). paymentMethodId may be null if the payment method has been removed since the contribution was created.
| Field | Type | Description |
|---|---|---|
type | string | |
customerId | string (uuid) | Contributor's Backer customer UUID. |
paymentMethodId | string (uuid) Nullable |
TransactionContributionFunding
Funding identity recorded against a past transaction. Both customerId and paymentMethodId may be null if the contributor or the payment method have been removed since the transaction was recorded.
| Field | Type | Description |
|---|---|---|
type | string | |
customerId | string (uuid) Nullable | Contributor's Backer customer UUID. |
paymentMethodId | string (uuid) Nullable |
TransactionStatus
REST transaction status values.
processing: contribution accepted and in progress – collecting funds from the contributor and/or transferring to the 529 plan.settled: funds dispatched to the 529 plan. Most plans do not confirm receipt, sosettledis the terminal success state.cancelled: contribution was cancelled before completion; nofailureDetails.failed: contribution failed during funding or provider payout; seefailureDetails.refunded: refund completed successfully.refund_processing: refund is initiated and in flight.refund_failed: refund attempt failed; seefailureDetails.
PaginatedTransactions
| Field | Type | Description |
|---|---|---|
items | Transaction[] | |
total | integer | Total number of transactions for the account. |
page | integer | Current page number (1-based). |
pageSize | integer | Number of transactions per page. |
Transaction
A single transaction (currently always a contribution) recorded against a linked 529 account.
| Field | Type | Description |
|---|---|---|
transactionId | string (uuid) | Backer's UUID for the underlying transaction. |
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
amountInCents | integer | Transaction amount sent to the 529 plan, in cents. |
status | TransactionStatus | |
type | string | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
cancellableUntil | string (date-time) Nullable | Nominal deadline for cancelling this transaction (createdAt + 1
hour). Null for non-cancellable transaction types. Cancel requests
are accepted until 5 minutes after this time (backend grace).
|
contribution | TransactionContributionDetails | |
failureDetails | TransactionFailureDetails | null | Populated when status is failed or refund_failed; null otherwise. |
TransactionContributionDetails
| Field | Type | Description |
|---|---|---|
clientContributionId | string Nullable | Partner-supplied identifier for this contribution, if one was provided on creation; null otherwise. |
funding | TransactionContributionFunding | |
processingFeeInCents | integer | Card processing fee charged to the contributor (0 for ACH). |
platformFeeInCents | integer | Platform fee charged to the contributor (0 for owner self-contributions). |
extraItems | ExtraItem[] | Extra items persisted on this contribution, including the recomputed extra-item processing fee. Omitted when none were sent. |
totalAmountInCents | integer | Total amount charged to the contributor (amountInCents +
platformFeeInCents + processingFeeInCents + extra item amounts +
tax). Extra-item processing is not included.
|
TransactionFailureDetails
| Field | Type | Description |
|---|---|---|
phase | string | Stage of processing in which the transaction failed.
- funding: failure while collecting funds from the contributor.
- provider_payout: failure while sending funds to the 529 provider.
- refund: failure while refunding funds back to the contributor.
One of funding provider_payout refund |
category | string | Failure category used to scope allowed code values.
One of funding_card_failure funding_ach_failure provider_payout_failure refund_card_failure refund_ach_failure |
code | string | Stable machine-readable failure code, category-dependent:
- funding_card_failure: card_declined, insufficient_funds, card_expired, card_other_failure
- funding_ach_failure: insufficient_funds, account_closed, invalid_account_number, other_ach_return
- provider_payout_failure: invalid_account_number, account_closed, plan_not_available, unknown_error
- refund_card_failure: card_expired, card_other_failure
- refund_ach_failure: account_closed, other_ach_return
|
message | string Nullable | Human-readable failure reason. |
failedAt | string (date-time) Nullable |
UpdateWebhookSubscriptionRequest
Fields to update on the partner's webhook subscription. At least one must be provided.
| Field | Type | Description |
|---|---|---|
url | string (uri) | Delivery endpoint URL (must be http or https). |
eventTypes | string[] | Non-empty list of event types to subscribe to.
One of transaction.settled transaction.failed transaction.refund_initiated transaction.refunded transaction.refund_failed |
secret | string | Signing secret used to verify webhook payloads. |
description | string | Human-readable description for the subscription. |
WebhookSubscription
| Field | Type | Description |
|---|---|---|
url | string (uri) | Delivery endpoint URL. |
eventTypes | string[] | List of subscribed event types. |
secret | string | Signing secret for verifying webhook payloads. |
description | string Nullable | Human-readable description for the subscription. |
status | string | Current status of the subscription.
One of enabled disabled |
ContributionFees
| Field | Type | Description |
|---|---|---|
platformFeeInCents | integer | Platform fee in cents (0 for owner self-contributions). |
processingFeeInCents | integer | Card processing fee in cents (0 for ACH). |
totalAmountInCents | integer | Total the contributor will be charged in cents (amountInCents + platformFeeInCents + processingFeeInCents). |
QuoteContributionRequest
| Field | Type | Description |
|---|---|---|
frequency | string | Optional, as on POST /v2/contributions; defaults to onetime.
Send the frequency the contribution will be created with: the quote
applies the same rule, so extraItems with monthly, annually,
or other return 422 here instead of at creation.
One of onetime monthly annually other |
paymentMethodType Required | string | How the contributor would pay; drives processing fees (card only).
One of ach card |
amountInCents Required | integer | Contribution amount in cents (sent to the 529 plan). |
platformFeeInCents Required | integer | Platform fee in cents to apply to this contribution. |
extraItems | ExtraItemRequest[] | Optional add-on items charged to the giver and not sent to the 529. At most one item. An empty array is treated as omitted. Extra-item processing is computed from the payment rail and returned on each item; it is not charged to the giver. |
ExtraItemRequest
| Field | Type | Description |
|---|---|---|
sku Required | string | Partner-defined extra-item SKU. Echoed on the quote and transaction. |
amountInCents Required | integer | Extra-item amount in cents charged to the giver. |
taxAmountInCents | integer | Tax in cents charged to the giver. Defaults to 0 when omitted. |
ExtraItem
| Field | Type | Description |
|---|---|---|
sku | string | Partner-defined extra-item SKU. |
amountInCents | integer | Extra-item amount in cents charged to the giver. |
taxAmountInCents | integer | Tax in cents charged to the giver. |
processingFeeInCents | integer | Extra-item card processing fee: 3% of (amountInCents +
taxAmountInCents), rounded half up. 0 for ACH. Not charged to the
giver; netted from the partner payout.
|
ContributionQuote
| Field | Type | Description |
|---|---|---|
amountInCents | integer | Contribution amount in cents (sent to the 529 plan). Echoed from the request. |
platformFeeInCents | integer | Platform fee in cents. |
processingFeeInCents | integer | Card processing fee on the 529 contribution amount (0 for ACH). |
totalAmountInCents | integer | Total the contributor will be charged (amountInCents +
platformFeeInCents + processingFeeInCents + extra item amounts +
tax). Extra-item processing is not included.
|
extraItems | ExtraItem[] | Enriched extra items when they were sent on the request. |
ActionErrorEnvelope
Envelope for non-validation 4xx and 5xx responses on every non-OAuth endpoint (auth, not-found, rate limit, business-rule failure). Carries a single error with a stable machine-readable errorCode and a partner-displayable message. Action errorCode values include but are not limited to: unauthorized, forbidden, not_found, conflict, rate_limited, internal_error, account_not_established, account_inactive, account_owner_on_hold, account_owner_inactive, customer_not_found, plan_not_found, payment_method_not_found, payment_method_mismatch, payment_not_created, ach_transaction_failed, card_payment_failed, refund_failed, stripe_unavailable, customer_resolution_failed, payment_method_session_failed, account_update_failed, invalid_status, transactions_still_processing, invalid_account_number, broker_not_found, broker_not_available, broker_plan_mismatch, invalid_request. New codes may be introduced over time without a breaking spec change. OAuth2 errors at POST /v2/oauth/token use OAuthErrorEnvelope instead.
| Field | Type | Description |
|---|---|---|
error | object |
FieldErrorsEnvelope
Envelope for 422 validation failures. Each entry names the failing field, a stable errorCode, and a human-readable message. The field value mirrors the camelCase request key.
| Field | Type | Description |
|---|---|---|
fieldErrors | object[] |
WebhookEnvelope
Outer envelope for all webhook deliveries. The event-specific payload lives at data.
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Unique id for this webhook event (also sent as the Webhook-Id HTTP header for idempotency). |
type | string | Event type, e.g. transaction.settled. |
createdAt | string (date-time) | ISO 8601 UTC datetime when the event occurred. |
resourceId | string (uuid) | UUID of the affected resource, e.g. the transactionId. |
resourceType | string | Type of the affected resource, e.g. transaction. |
data | object | Event-specific payload. See per-event schemas. |
TransactionSettledEventData
data payload for transaction.settled events.
| Field | Type | Description |
|---|---|---|
status | TransactionStatus | |
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
TransactionFailedEventData
data payload for transaction.failed events.
| Field | Type | Description |
|---|---|---|
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
status | TransactionStatus | |
failureDetails | WebhookFailureDetails |
WebhookFailureDetails
Failure details delivered on transaction.failed. Slimmer than the REST TransactionFailureDetails – no metadata field.
| Field | Type | Description |
|---|---|---|
phase | string | - funding: failure occurred while collecting funds from the
contributor's payment method.
- provider_payout: funds were collected; failure occurred while
sending money to the 529 provider.
- refund: failure occurred while refunding funds to the
contributor.
One of funding provider_payout refund |
category | string | Failure category used to scope allowed code values.
One of funding_card_failure funding_ach_failure provider_payout_failure refund_card_failure refund_ach_failure |
code | string | Stable machine-readable failure code, category-dependent:
- funding_card_failure: card_declined, insufficient_funds, card_expired, card_other_failure
- funding_ach_failure: insufficient_funds, account_closed, invalid_account_number, other_ach_return
- provider_payout_failure: invalid_account_number, account_closed, plan_not_available, unknown_error
- refund_card_failure: card_expired, card_other_failure
- refund_ach_failure: account_closed, other_ach_return
|
message | string Nullable | Human-readable failure reason. |
failedAt | string (date-time) Nullable |
TransactionSettledEvent
TransactionFailedEvent
TransactionRefundInitiatedEventData
data payload for transaction.refund_initiated events. status is typically refund_processing, refunded for synchronous card refunds.
| Field | Type | Description |
|---|---|---|
status | TransactionStatus | Current transaction status at event time. Typically
refund_processing; card refunds may already show refunded.
|
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
TransactionRefundInitiatedEvent
TransactionRefundedEventData
data payload for transaction.refunded events.
| Field | Type | Description |
|---|---|---|
status | string | Always refunded for this event. |
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
TransactionRefundedEvent
TransactionRefundFailedEventData
data payload for transaction.refund_failed events.
| Field | Type | Description |
|---|---|---|
accountId | string (uuid) | Backer linked-account UUID the transaction belongs to. |
status | string | Always refund_failed for this event. |
failureDetails | WebhookFailureDetails |