API onboarding for partners
Overview
This page describes how a partner (a payment provider or a platform) onboards its merchants to Outpost through the API. Your systems hold the merchant data and call Outpost server to server. The merchant never leaves your product. If you would rather hand the merchant to an Outpost-hosted form, use the Hosted Onboarding API instead.
Merchants and applications
The API splits the merchant from the application, and the split matters because the two have different lifetimes.
- A merchant is the business you are onboarding. You create it once.
It is identified by a
merchantIdand it carries your ownreference, so you can match it back to your records. Every other call in this document is scoped to a merchant you created. - An application is a request to activate one Outpost product for that merchant in a set of regions. It has a status, a set of requirements and a review outcome. A merchant has at most one application.
Who does what
Every call is yours. The merchant never sees Outpost, and Outpost only appears at review. Nothing is sent back to you. You poll the application for the outcome.
Your platform
- 1
Create the application, with the merchant’s company details
POST /partner/api/onboarding/initiate
status becomes
created - 2
Set products and regions
PUT /partner/api/merchants/{merchantId}/application
- 3
Describe what the merchant sells
PATCH /partner/api/merchants/{merchantId}/application
- 4
Submit for review
POST /partner/api/merchants/{merchantId}/application/submit
status becomes
submitted
Outpost
- 5
A person reviews the application. There is no fixed turnaround, and nothing is sent back to you. Poll for the result.
approved The merchant is live for that product. Nothing more to do.
changes_requested Read reviewMessage and requirements.errors, PATCH the application, then submit again.
rejected Final. The application cannot be resubmitted.
The flow
- 1 Create the application. POST /partner/api/onboarding/initiate with mode set to API, your own reference and the company details. This creates the merchant and the application together. To start without company details, POST /partner/api/merchants instead. See Limits to know about.
- 2 Look up the merchantId. GET /partner/api/merchants and match on your reference. Every later call is addressed by merchantId, and initiate returns the applicationId rather than the merchantId.
- 3 Create the application. PUT /partner/api/merchants/{merchantId}/application with one product code and the regions you want.
- 4 Describe what the merchant sells. PATCH the same path with the trading description, store URLs and category. This is the merchant’s business model, not its legal details. Those were sent at initiate. Repeat as often as you need.
- 5 Submit for review. POST /partner/api/merchants/{merchantId}/application/submit. Outpost reviews the application.
- 6 Poll for the outcome. GET /partner/api/merchants/{merchantId}/application and read status, reviewMessage and requirements.errors.
Base URL
All paths on this page are relative to https://api.outpostanywhere.com. Responses omit null fields rather than returning them as null, so treat a missing key as "not set".
Authentication
Every endpoint on this page uses the same authentication as the other partner APIs:
OAuth2 client_credentials.
Your backend exchanges a client ID and secret for an access token, then sends that
token as a Bearer token on each call. Your credentials come from the Partner Portal.
Ask your Outpost contact if you do not have access yet.
Get a token
# Step 1: Obtain an access token
curl -X POST \
"https://access.outpostanywhere.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=$OUTPOST_CLIENT_SECRET" | jq
# Export the token (example)
# export OUTPOST_ACCESS_TOKEN="eyJhbGciOi..."
# Sample response:
# {
# "access_token": "<JWT>",
# "expires_in": 86400,
# "token_type": "Bearer"
# }Call the API
curl "https://api.outpostanywhere.com/partner/api/whoami" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN"Cache the token server side until it expires, with a small buffer. Refresh it and retry once on a 401. Run all of this from your server — never put the client secret in a browser or a mobile app.
Scope
A token is scoped to one partner. You can only see and change merchants that were
created under that partner. A merchant that belongs to another partner, or a
merchant id that does not exist, returns 404 — never 403. That is
deliberate: it stops anyone from probing merchant ids.
Reference data
Two read-only endpoints help you set up. Neither is scoped to a merchant.
Who am I
/partner/api/whoami Returns the partner your token belongs to. Use it to check credentials after a rotation, and to tell your staging and production keys apart.
{
"partnerId": "3d7c2e19-84af-4c0b-9f61-5b8e2a7d1c03",
"name": "Example Payments"
}Regions
/partner/api/regions Returns every region where Outpost supports at least one product. Regions that support neither Tax of Record nor Merchant of Record are left out.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (uuid) | The internal region id. It is a random UUID and differs between environments, so look it up rather than hard-coding it. | |
| name | string | Human-readable name, for example "United Kingdom" or "California". | |
| flagCode | string | Short code, for example EU, GB or CA. Not unique on its own. CA is both Canada’s neighbour California and, in another row, Canada. | |
| countryCode | string | ISO 3166-1 alpha-2 country the region sits in. Absent for regions that are not a single country, such as the European Union. | |
| taxSupported | boolean | Whether Tax of Record is available in this region. | |
| morSupported | boolean | Whether Merchant of Record is available in this region. |
200 OK
[
{
"id": "5f4e3d2c-1b0a-4c9d-8e7f-6a5b4c3d2e1f",
"name": "European Union",
"flagCode": "EU",
"taxSupported": true,
"morSupported": true
},
{
"id": "2a9c8b7d-6e5f-4a3b-9c8d-7e6f5a4b3c2d",
"name": "United Kingdom",
"flagCode": "GB",
"countryCode": "GB",
"taxSupported": true,
"morSupported": true
}
]
The European Union row has no countryCode because it is not one country, so the field is left out of the response entirely.
Product codes
An application activates one product. Two codes are meaningful today, both lowercase:
-
tor— Tax of Record. Outpost calculates, files and remits the merchant’s sales tax and VAT. -
mor— Merchant of Record. Outpost becomes the seller of record for the merchant’s transactions.
The API does not reject an unknown product code today. It stores whatever you send,
and an unrecognised code behaves like mor : no product requirements are added. Send tor or mor, exactly as
written.
Applications
An application is a request to activate one product for one merchant in one or more regions. Creating it is the first call you make: it creates the merchant, links it to your partner account and opens the application in a single step.
Create a merchant with company details
/partner/api/onboarding/initiate
This is the same endpoint the Hosted Onboarding API uses. Set mode to API and it creates the merchant, the link to your partner account and the application in
one call, with the company details attached. No hosted link is generated, so the onboardingUrl in the response is not usable, so ignore it.
company is required, and this
is the only endpoint that writes it. Field-by-field requirements are documented on the
Hosted Onboarding page; they are identical in both modes.
curl -X POST \
"https://api.outpostanywhere.com/partner/api/onboarding/initiate" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"merchantReference": "psp-merchant-4821",
"productCode": "tor",
"mode": "API",
"company": {
"legalName": "Northwind Digital Ltd",
"tradingName": "Northwind",
"registrationNumber": "09123456",
"taxId": "GB123456789",
"jurisdiction": "GB",
"website": "https://northwind.example",
"gmvTier": "FROM_1M_TO_10M",
"registeredAddress": {
"line1": "12 Example Street",
"line2": "Floor 3",
"city": "London",
"state": "Greater London",
"postalCode": "EC1A 1AA",
"country": "GB"
}
},
"complianceSummary": {
"verificationStatus": "verified",
"riskLevel": "low",
"countryOfIncorporation": "GB",
"hasOpenComplianceFlags": false
},
"merchantContext": {
"accountManager": "avery.diaz@acme-psp.example",
"segment": "mid-market"
}
}'Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantReference | string | Yes | Your own identifier for this merchant. Unique per partner, and permanent. Calling initiate again with the same reference returns the existing application. |
| productCode | string | Yes | tor or mor. Fixed once set. The application keeps this product. |
| mode | string enum | Yes | HOSTED or API. Send API to drive the application yourself. HOSTED is the default and returns a link for the merchant to complete. |
| company | object | Yes | Legal entity details. See OnboardingCompany under Data models. This is the only endpoint that writes them. |
| returnUrl | string | No | Where the merchant returns after a hosted flow. Ignored in API mode. |
| complianceSummary | object | No | Checks you have already run. See PartnerComplianceSummary under Data models. Stored and shown to the Outpost reviewer. |
| merchantContext | object | No | Free-form string map carried through to the Outpost reviewer. |
Response 200 OK
{
"applicationId": "3f8c1e07-5a44-4b91-9d2e-77a0c6b41e58",
"status": "DRAFT",
"mode": "API",
"isExistingApplication": false,
"onboardingUrl": "https://onboarding.outpostanywhere.com/acme-psp/onboarding?app=3f8c1e07-5a44-4b91-9d2e-77a0c6b41e58"
}| Parameter | Type | Required | Description |
|---|---|---|---|
| applicationId | string | Identifier for the application. Note this is not the merchantId. Look that up with GET /partner/api/merchants. | |
| status | string | Application status. DRAFT on creation. These are the raw internal values, not the lowercase ones the merchant-scoped endpoints return. | |
| mode | string | HOSTED or API, as stored. | |
| isExistingApplication | boolean | true when an application already existed for this merchantReference. Company details in your request were discarded. | |
| onboardingUrl | string | Hosted link for the merchant. In API mode this is returned without a token and is not usable in this mode, so ignore it. |
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_argument | A required field is missing or blank. Each missing field is named, for example company.registeredAddress.city. |
| 500 | internal_error | The body could not be parsed, for example an unrecognised gmvTier or mode. Validate those values before sending; retrying the same body gets the same result. |
| 401 | — | Invalid or missing Authorization token. |
Call it before anything else
If an application already exists for that merchantReference, initiate returns the existing one with isExistingApplication: true and keeps the company details it already has, so send yours on the first call.
Get the application
/partner/api/merchants/{merchantId}/application
Returns the current state. This is the call you poll after submitting. It returns
404 until the application
exists.
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string (uuid) | The merchant this application belongs to. | |
| status | string | One of created, submitted, changes_requested, approved, rejected. See the status lifecycle below. | |
| products | object[] | Always one entry, with code and regions. Omitted while no product is set. | |
| requirements.currentlyDue | string[] | Requirement keys you still have to satisfy before you can submit. Omitted when nothing is due. | |
| requirements.errors | object[] | Reviewer comments, each with field and message. Only filled while the status is changes_requested or rejected. Omitted otherwise. | |
| reviewMessage | string | Free text from the Outpost reviewer. Set when a review has happened. | |
| submittedAt | string (ISO 8601) | When the application was last submitted. | |
| reviewedAt | string (ISO 8601) | When Outpost last completed a review. |
200 OK
{
"merchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"status": "submitted",
"products": [
{ "code": "tor", "regions": ["EU"] }
],
"requirements": {},
"submittedAt": "2026-07-21T09:14:52.118Z"
}
Fields holding their default value are left out of the response, so an empty requirements object means nothing is due and there are no reviewer comments.
Create or replace the product selection
/partner/api/merchants/{merchantId}/application
Accept any 2xx. You get 200 OK when the application already exists, which is the case in the flow above because initiate created it. You get 201 Created only when there is no application yet, which happens if you started from POST /partner/api/merchants. Either way the call replaces the region list with what you send. The product code
is fixed by the first call: sending a different code afterwards returns 400.
| Parameter | Type | Required | Description |
|---|---|---|---|
| products | object[] | Required | Must contain exactly one entry. Zero or more than one is rejected with 400. |
| products[0].code | string | Required | The product to activate: tor or mor. Not validated, and fixed after the first call. Later calls must send the identical string. |
| products[0].regions | string[] | Required | Where the merchant wants the product. Must not be empty, but the values themselves are not validated. See Requirements. Later calls replace the whole list. |
curl -X PUT \
"https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/application" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"products": [
{ "code": "tor", "regions": ["EU"] }
]
}'200 OK or 201 Created
{
"merchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"status": "created",
"products": [
{ "code": "tor", "regions": ["EU"] }
],
"requirements": {
"currentlyDue": ["businessDescription", "checkoutUrl"]
}
}
PUT only works while the status is created or changes_requested.
In any other status it returns 409 invalid_status.
Describe what the merchant sells
/partner/api/merchants/{merchantId}/application These fields describe how the merchant trades: what it sells, where it sells it and under which category. The legal details are separate: legal name, tax id, registration number and registered address are sent once at initiate and stay as sent.
PATCH keeps what you do not send. A field you leave out keeps its current value, and
sending null also keeps it.
To change a value, send the new one. PATCH leaves the product and the regions alone —
use PUT for those.
| Parameter | Type | Required | Description |
|---|---|---|---|
| businessDescription | string | What the merchant sells. Satisfies the businessDescription requirement. | |
| storeUrls | string[] | The merchant’s storefront or checkout URLs. Satisfies the checkoutUrl requirement. | |
| category | string | Free-text business category. Not part of any requirement today. |
curl -X PATCH \
"https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/application" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"businessDescription": "Subscription meal kits sold to consumers in the EU",
"storeUrls": ["https://shop.example.com"],
"category": "retail"
}'200 OK
{
"merchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"status": "created",
"products": [
{ "code": "tor", "regions": ["EU"] }
],
"requirements": {}
}
Like PUT, PATCH only works in status created or changes_requested,
and returns 404 if no application
exists yet.
Submit for review
/partner/api/merchants/{merchantId}/application/submit
Hands the application to Outpost for review. There is no request body. On success
the status becomes submitted and the response carries submittedAt.
curl -X POST \
"https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/application/submit" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN"
If anything in requirements.currentlyDue is still outstanding, the call fails with 400 incomplete_application and lists what is missing:
{
"code": "incomplete_application",
"errors": [
{ "field": "checkoutUrl", "message": "checkoutUrl is required" }
]
}
Submit is not idempotent. A second call while the application is already submitted returns 409 invalid_status. Treat that as "already submitted", not as an error to retry.
Merchants
A merchant is the business you are onboarding. Creating the application creates it for
you, so these endpoints are mainly how you find its id — every application and tax call is addressed by it. Use POST /partner/api/merchants only when you want a merchant without company details.
List merchants
/partner/api/merchants
Returns every merchant linked to your partner account, as an array of id and reference. There
is no pagination and no filter: the whole list comes back in one response. The list
is scoped to your partner account.
[
{
"id": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"reference": "psp-merchant-4821"
},
{
"id": "5c2a7f10-3b94-4de6-a1c8-7f60d3e91b25",
"reference": "psp-merchant-4822"
}
]Create a merchant
/partner/api/merchants | Parameter | Type | Required | Description |
|---|---|---|---|
| reference | string | Required | Your own identifier for this merchant. Must not be blank, and must be unique inside your partner account. Another partner can use the same value. |
curl -X POST "https://api.outpostanywhere.com/partner/api/merchants" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reference": "psp-merchant-4821"}'201 Created
{
"id": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"reference": "psp-merchant-4821"
}This call is not idempotent
Sending the same reference twice does not return the first merchant. It returns 409 duplicate_reference, and the body carries the id of the merchant that already holds that reference:
{
"code": "duplicate_reference",
"existingMerchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94"
}
To recover, take existingMerchantId and carry on with the flow. Do not generate a new reference — you would end up with
two merchants for one business.
Get a merchant
/partner/api/merchants/{merchantId} | Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (uuid) | The Outpost merchant id. Use it in every other path on this page. | |
| reference | string | The reference you sent when you created the merchant. | |
| company | object | Company record. Populated from the company object you sent to initiate; empty strings if the merchant was created with POST /partner/api/merchants instead. | |
| businessDescription | string | What you last sent as businessDescription on the application. | |
| storeUrls | string[] | What you last sent as storeUrls on the application. |
200 OK
{
"id": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"reference": "psp-merchant-4821",
"company": {
"legalName": "",
"registrationNumber": "",
"taxId": "",
"jurisdiction": "",
"website": "",
"registeredAddress": {
"line1": "",
"city": "",
"postalCode": "",
"country": ""
}
},
"businessDescription": "Subscription meal kits sold to consumers in the EU",
"storeUrls": ["https://shop.example.com"]
}
Before an application exists, only id and reference come back.
The company object above shows
the empty case: every field is an empty string because this merchant was created with
POST /partner/api/merchants, which takes no company data. To populate it, create the merchant with initiate in API mode instead. Nothing later in this flow can fill it in.
Requirements
requirements.currentlyDue lists
what is still missing before the application can be submitted. Read it after every write
and stop calling submit until the list is gone.
{
"merchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"status": "created",
"products": [
{ "code": "tor", "regions": ["EU"] }
],
"requirements": {
"currentlyDue": ["businessDescription", "checkoutUrl"]
}
}The keys and what satisfies them
| Requirement key | Endpoint | Request field | Notes |
|---|---|---|---|
| products | PUT /application | products | Only appears if the application has no product at all. A successful PUT always sets one, so you should not see it. |
| selectedRegions | PUT /application | products[0].regions | Satisfied as soon as the list is not empty. PUT already rejects an empty list with 400. |
| businessDescription | PATCH /application | businessDescription | Satisfied by any non-blank string. |
| checkoutUrl | PATCH /application | storeUrls | The names do not match. The requirement is called checkoutUrl; you satisfy it by sending a non-empty storeUrls array. |
The key checkoutUrl is the
one that catches people out. There is no field called checkoutUrl anywhere in the request. Send storeUrls on the PATCH and the requirement clears.
Requirements depend on the product
Only tor has product requirements.
An application for mor adds
none, so currentlyDue is empty
from the moment the application exists.
That does not mean submit always succeeds. Submit still fails with 409 invalid_status if the application is not in created or changes_requested,
and with 404 if the merchant
is not linked to your partner account. Handle both cases.
Open question: what to put in regions
products[0].regions is a list
of free-form strings. The only check is that the list is not empty. Nothing compares the
values against GET /partner/api/regions, so a typo is accepted, stored and echoed back to you unchanged.
Outpost’s own tests for this API send short codes that match the flagCode field — for example "EU",
"GB", "DE". Other parts of the platform put the region id UUID in the same field. Both are accepted because neither is checked.
This is a known gap, not a documented rule. Agree the exact values with your Outpost contact before you go live, and keep them in one place in your code so they are easy to change.
Status lifecycle
An application has exactly five statuses. They are lowercase, and they are the only
values status can hold on this
API.
| Status | What it means |
|---|---|
| created | The application exists and you can still change it. This is the status right after the first PUT. |
| submitted | You have submitted it and Outpost is reviewing. The application is read-only. |
| changes_requested | A reviewer wants something changed. You can edit again, then submit again. |
| approved | Outpost accepted the application. The application is read-only. |
| rejected | Outpost declined the application. The application is read-only. |
What each call does in each status
| Status | PUT | PATCH | POST /submit |
|---|---|---|---|
| created | 201 or 200 | 200 | 200, or 400 if requirements are outstanding |
| submitted | 409 invalid_status | 409 invalid_status | 409 invalid_status |
| changes_requested | 200 | 200 | 200, or 400 if requirements are outstanding |
| approved | 409 invalid_status | 409 invalid_status | 409 invalid_status |
| rejected | 409 invalid_status | 409 invalid_status | 409 invalid_status |
In short: you can only write while the application is created or changes_requested.
Everything else is read-only and answers 409.
Five statuses are the complete set
Outpost's review process has more stages than this API reports. Several of them,
including the contract signing steps, all report as approved. Treat approved as the end
state and do not wait for anything more granular. The mapping below is stable, so you
can switch on these five values safely.
| Internal status | What you see |
|---|---|
| DRAFT | created |
| IN_REVIEW | submitted |
| CHANGES_REQUESTED | changes_requested |
| APPROVED | approved |
| SIGNED_BY_MERCHANT | approved |
| SIGNED_BY_OUTPOST | approved |
| REJECTED | rejected |
The Hosted Onboarding API is different
The endpoints under /partner/api/onboarding return the raw internal values — DRAFT, IN_REVIEW, SIGNED_BY_OUTPOST and the rest, in upper case. If you integrate both flows, keep the two status parsers
separate. Sharing one is the most common mistake here.
After you submit
Poll for the outcome: there are no webhooks
This API does not call you back, for onboarding or for anything else. Once you submit, the application goes to Outpost for review and the result appears on the application itself. Plan for a scheduled poll rather than a callback.
Poll the application
curl "https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/application" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN"
A person reviews each application, so the outcome takes as long as it takes. Poll on
a slow schedule — minutes rather than seconds — and stop once the status is approved or rejected. Watch
three fields:
-
status— the outcome.submittedmeans the review is still open. -
reviewMessage— free text from the reviewer, meant to be read by a person. Show it to whoever manages the merchant. -
requirements.errors— the specific problems, one entry per open reviewer comment. Only filled while the status ischanges_requestedorrejected.
Handling changes_requested
{
"merchantId": "9b1f4c8e-6d2a-4f7b-8e3c-1a5d0f2b7c94",
"status": "changes_requested",
"products": [
{ "code": "tor", "regions": ["EU"] }
],
"requirements": {
"errors": [
{
"field": "businessDescription",
"message": "Describe what the merchant sells, not just the industry."
},
{
"field": "BUSINESS_MODEL",
"message": "The store URL returns a holding page."
}
]
},
"reviewMessage": "We need a little more detail before we can approve this.",
"submittedAt": "2026-07-21T09:14:52.118Z",
"reviewedAt": "2026-07-22T14:03:07.442Z"
}
Each entry in requirements.errors has a field and a message. When the reviewer commented on one field, field is that field’s name, such as businessDescription. When the comment is about a whole section, it is the section name in upper case,
such as BUSINESS_MODEL.
Treat the value as a label to display, not as a key to switch on.
The application is writable again in this status. Fix what was raised with PATCH (or
PUT for the regions), then call submit again. The status goes back to submitted and submittedAt is updated.
Comments a reviewer has resolved drop out of the list, so an empty errors list means nothing is outstanding.
Data models
Objects shared across more than one request or response on this page.
OnboardingCompany
Sent on POST /partner/api/onboarding/initiate and returned on GET /partner/api/merchants/{merchantId}. Required fields are required at initiate; on the way back, unset fields come back
as empty strings rather than being omitted.
| Parameter | Type | Required | Description |
|---|---|---|---|
| legalName | string | Yes | Registered legal name. |
| tradingName | string | No | Trading or brand name, if it differs from the legal name. |
| website | string | Yes | The business's website. |
| taxId | string | Yes | Tax identifier in the jurisdiction of registration. |
| registrationNumber | string | Yes | Company registration number. |
| jurisdiction | string | Yes | Country of incorporation, ISO 3166-1 alpha-2. |
| gmvTier | string enum | No | Expected annual gross merchandise value band. See OnboardingGmvTier below. |
| registeredAddress | OnboardingAddress | Yes | Registered address. See below. |
OnboardingAddress
| Parameter | Type | Required | Description |
|---|---|---|---|
| line1 | string | Yes | Primary address line. |
| line2 | string | No | Secondary address line. |
| city | string | Yes | City. |
| state | string | No | State or province. |
| postalCode | string | Yes | Postal code. |
| country | string | Yes | ISO 3166-1 alpha-2 country code. |
OnboardingGmvTier
UP_TO_100KFROM_100K_TO_250KFROM_250K_TO_500KFROM_500K_TO_1MFROM_1M_TO_10MFROM_10M_TO_50MFROM_50M_TO_100MFROM_100M_TO_250MFROM_250M_TO_500MFROM_500M_TO_1B Any other value is rejected as a malformed body, not as a field error.
PartnerComplianceSummary
Optional. Checks you have already run, stored against the application and shown to the Outpost reviewer. Every field is free text — Outpost does not parse or act on them automatically.
| Parameter | Type | Required | Description |
|---|---|---|---|
| verificationStatus | string | No | Free text. Your own verification outcome for this business. |
| riskLevel | string | No | Free text. Your own risk rating. |
| countryOfIncorporation | string | No | ISO 3166-1 alpha-2 country code. |
| hasOpenComplianceFlags | boolean | No | Whether you have unresolved compliance flags on this business. |
ApplicationRequirements
Returned as requirements
on every application response. When nothing is outstanding it serialises as an empty object,
so read it defensively.
| Parameter | Type | Required | Description |
|---|---|---|---|
| currentlyDue | string[] | — | What still blocks submit: products, selectedRegions, businessDescription, checkoutUrl. Empty once nothing is outstanding. |
| errors | object[] | — | Reviewer comments, present only in changes_requested and rejected. Each entry has field and message. |
Errors
Errors come back as a code and
a list of field-level messages. Switch on code, and use errors[].field to
point your own user at the right input.
{
"code": "validation_error",
"errors": [
{ "field": "products[0].regions", "message": "Must not be empty" }
]
}
The errors array can be empty
— the ownership check that produces a 404 does not name a field. duplicate_reference is the one response that has no errors array at all; it carries existingMerchantId instead.
| HTTP | Code | Description |
|---|---|---|
| 401 | — | Missing, expired or invalid access token, or a merchant credential used on a partner endpoint. Refresh the token and retry once. |
| 400 | validation_error | The request failed a check. errors[].field names the offending field: reference, products, products[0].code, products[0].regions, or body for a payload that does not parse. |
| 400 | incomplete_application | Submit was called while requirements are outstanding. One entry per missing requirement key. |
| 404 | not_found | The merchant does not exist, is not linked to your partner, or the merchantId is not a valid UUID. Also returned when the application does not exist yet. |
| 409 | duplicate_reference | Creating a merchant with a reference you already used. The body carries existingMerchantId instead of an errors array. |
| 409 | invalid_status | A write or a submit was attempted while the application is not in created or changes_requested. |
Which endpoint returns what
| Endpoint | Errors |
|---|---|
| GET /partner/api/whoami | 401 |
| GET /partner/api/regions | 401 |
| GET /partner/api/merchants | 401 |
| POST /partner/api/merchants | 400 validation_error, 409 duplicate_reference, 401 |
| GET /partner/api/merchants/{merchantId} | 404 not_found, 401 |
| GET /partner/api/merchants/{merchantId}/application | 404 not_found, 401 |
| PUT /partner/api/merchants/{merchantId}/application | 400 validation_error, 404 not_found, 409 invalid_status, 401 |
| PATCH /partner/api/merchants/{merchantId}/application | 400 validation_error, 404 not_found, 409 invalid_status, 401 |
| POST /partner/api/merchants/{merchantId}/application/submit | 400 incomplete_application, 404 not_found, 409 invalid_status, 401 |
404, never 403
Every path under /partner/api/merchants/{merchantId} answers 404 when the merchant
is unknown to you — whether it does not exist, belongs to another partner, or the id is
not a UUID at all. You never get a 403, so a 404 is not proof that the merchant is missing. This is deliberate: it stops
anyone from discovering merchant ids by trying them. The two collection endpoints, GET and POST /partner/api/merchants, are scoped to your partner account and are not affected.
Limits to know about
Three things are fixed by your first few calls. Everything else can be corrected as you go.
- Send company details on the first call. Company details are supplied by POST /partner/api/onboarding/initiate with mode set to API — the same endpoint the Hosted Onboarding API uses, and the only one that writes them. Start there and the merchant, the application and the company details are created together.
- One application per merchant, one product per application. The product code is set by the first call and fixed from then on. To onboard the same business for a second product, create a second merchant with a different reference.
- Merchant references are permanent. The reference you send when you create a merchant stays with it for good. Use an identifier that is already stable in your own system.
Ready to integrate?
Copy the complete integration plan for your AI coding assistant, or contact us for white-glove onboarding.
One-click brief for your AI agent.