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 merchantId and it carries your own reference, 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. 1

    Create the application, with the merchant’s company details

    POST /partner/api/onboarding/initiate

    status becomes created

  2. 2

    Set products and regions

    PUT /partner/api/merchants/{merchantId}/application

  3. 3

    Describe what the merchant sells

    PATCH /partner/api/merchants/{merchantId}/application

  4. 4

    Submit for review

    POST /partner/api/merchants/{merchantId}/application/submit

    status becomes submitted

Outpost

  1. 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.

On changes_requested you return to step 3: PATCH the application, then submit again. Poll GET /partner/api/merchants/{merchantId}/application on a slow schedule until the status leaves submitted.

The flow

  1. 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. 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. 3 Create the application. PUT /partner/api/merchants/{merchantId}/application with one product code and the regions you want.
  4. 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. 5 Submit for review. POST /partner/api/merchants/{merchantId}/application/submit. Outpost reviews the application.
  6. 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

GET /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

GET /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.

ParameterTypeRequiredDescription
idstring (uuid)The internal region id. It is a random UUID and differs between environments, so look it up rather than hard-coding it.
namestringHuman-readable name, for example "United Kingdom" or "California".
flagCodestringShort code, for example EU, GB or CA. Not unique on its own. CA is both Canada’s neighbour California and, in another row, Canada.
countryCodestringISO 3166-1 alpha-2 country the region sits in. Absent for regions that are not a single country, such as the European Union.
taxSupportedbooleanWhether Tax of Record is available in this region.
morSupportedbooleanWhether 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

POST /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

ParameterTypeRequiredDescription
merchantReferencestringYesYour own identifier for this merchant. Unique per partner, and permanent. Calling initiate again with the same reference returns the existing application.
productCodestringYestor or mor. Fixed once set. The application keeps this product.
modestring enumYesHOSTED or API. Send API to drive the application yourself. HOSTED is the default and returns a link for the merchant to complete.
companyobjectYesLegal entity details. See OnboardingCompany under Data models. This is the only endpoint that writes them.
returnUrlstringNoWhere the merchant returns after a hosted flow. Ignored in API mode.
complianceSummaryobjectNoChecks you have already run. See PartnerComplianceSummary under Data models. Stored and shown to the Outpost reviewer.
merchantContextobjectNoFree-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"
}
ParameterTypeRequiredDescription
applicationIdstringIdentifier for the application. Note this is not the merchantId. Look that up with GET /partner/api/merchants.
statusstringApplication status. DRAFT on creation. These are the raw internal values, not the lowercase ones the merchant-scoped endpoints return.
modestringHOSTED or API, as stored.
isExistingApplicationbooleantrue when an application already existed for this merchantReference. Company details in your request were discarded.
onboardingUrlstringHosted link for the merchant. In API mode this is returned without a token and is not usable in this mode, so ignore it.

Errors

HTTPCodeDescription
400invalid_argumentA required field is missing or blank. Each missing field is named, for example company.registeredAddress.city.
500internal_errorThe 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.
401Invalid 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

GET /partner/api/merchants/{merchantId}/application

Returns the current state. This is the call you poll after submitting. It returns 404 until the application exists.

ParameterTypeRequiredDescription
merchantIdstring (uuid)The merchant this application belongs to.
statusstringOne of created, submitted, changes_requested, approved, rejected. See the status lifecycle below.
productsobject[]Always one entry, with code and regions. Omitted while no product is set.
requirements.currentlyDuestring[]Requirement keys you still have to satisfy before you can submit. Omitted when nothing is due.
requirements.errorsobject[]Reviewer comments, each with field and message. Only filled while the status is changes_requested or rejected. Omitted otherwise.
reviewMessagestringFree text from the Outpost reviewer. Set when a review has happened.
submittedAtstring (ISO 8601)When the application was last submitted.
reviewedAtstring (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

PUT /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.

ParameterTypeRequiredDescription
productsobject[]RequiredMust contain exactly one entry. Zero or more than one is rejected with 400.
products[0].codestringRequiredThe product to activate: tor or mor. Not validated, and fixed after the first call. Later calls must send the identical string.
products[0].regionsstring[]RequiredWhere 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

PATCH /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.

ParameterTypeRequiredDescription
businessDescriptionstringWhat the merchant sells. Satisfies the businessDescription requirement.
storeUrlsstring[]The merchant’s storefront or checkout URLs. Satisfies the checkoutUrl requirement.
categorystringFree-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

POST /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

GET /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

POST /partner/api/merchants
ParameterTypeRequiredDescription
referencestringRequiredYour 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

GET /partner/api/merchants/{merchantId}
ParameterTypeRequiredDescription
idstring (uuid)The Outpost merchant id. Use it in every other path on this page.
referencestringThe reference you sent when you created the merchant.
companyobjectCompany record. Populated from the company object you sent to initiate; empty strings if the merchant was created with POST /partner/api/merchants instead.
businessDescriptionstringWhat you last sent as businessDescription on the application.
storeUrlsstring[]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 keyEndpointRequest fieldNotes
productsPUT /applicationproductsOnly appears if the application has no product at all. A successful PUT always sets one, so you should not see it.
selectedRegionsPUT /applicationproducts[0].regionsSatisfied as soon as the list is not empty. PUT already rejects an empty list with 400.
businessDescriptionPATCH /applicationbusinessDescriptionSatisfied by any non-blank string.
checkoutUrlPATCH /applicationstoreUrlsThe 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.

StatusWhat it means
createdThe application exists and you can still change it. This is the status right after the first PUT.
submittedYou have submitted it and Outpost is reviewing. The application is read-only.
changes_requestedA reviewer wants something changed. You can edit again, then submit again.
approvedOutpost accepted the application. The application is read-only.
rejectedOutpost declined the application. The application is read-only.

What each call does in each status

StatusPUTPATCHPOST /submit
created201 or 200200200, or 400 if requirements are outstanding
submitted409 invalid_status409 invalid_status409 invalid_status
changes_requested200200200, or 400 if requirements are outstanding
approved409 invalid_status409 invalid_status409 invalid_status
rejected409 invalid_status409 invalid_status409 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 statusWhat you see
DRAFTcreated
IN_REVIEWsubmitted
CHANGES_REQUESTEDchanges_requested
APPROVEDapproved
SIGNED_BY_MERCHANTapproved
SIGNED_BY_OUTPOSTapproved
REJECTEDrejected

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. submitted means 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 is changes_requested or rejected.

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.

ParameterTypeRequiredDescription
legalNamestringYesRegistered legal name.
tradingNamestringNoTrading or brand name, if it differs from the legal name.
websitestringYesThe business's website.
taxIdstringYesTax identifier in the jurisdiction of registration.
registrationNumberstringYesCompany registration number.
jurisdictionstringYesCountry of incorporation, ISO 3166-1 alpha-2.
gmvTierstring enumNoExpected annual gross merchandise value band. See OnboardingGmvTier below.
registeredAddressOnboardingAddressYesRegistered address. See below.

OnboardingAddress

ParameterTypeRequiredDescription
line1stringYesPrimary address line.
line2stringNoSecondary address line.
citystringYesCity.
statestringNoState or province.
postalCodestringYesPostal code.
countrystringYesISO 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.

ParameterTypeRequiredDescription
verificationStatusstringNoFree text. Your own verification outcome for this business.
riskLevelstringNoFree text. Your own risk rating.
countryOfIncorporationstringNoISO 3166-1 alpha-2 country code.
hasOpenComplianceFlagsbooleanNoWhether 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.

ParameterTypeRequiredDescription
currentlyDuestring[]What still blocks submit: products, selectedRegions, businessDescription, checkoutUrl. Empty once nothing is outstanding.
errorsobject[]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.

HTTPCodeDescription
401Missing, expired or invalid access token, or a merchant credential used on a partner endpoint. Refresh the token and retry once.
400validation_errorThe 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.
400incomplete_applicationSubmit was called while requirements are outstanding. One entry per missing requirement key.
404not_foundThe 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.
409duplicate_referenceCreating a merchant with a reference you already used. The body carries existingMerchantId instead of an errors array.
409invalid_statusA write or a submit was attempted while the application is not in created or changes_requested.

Which endpoint returns what

EndpointErrors
GET /partner/api/whoami401
GET /partner/api/regions401
GET /partner/api/merchants401
POST /partner/api/merchants400 validation_error, 409 duplicate_reference, 401
GET /partner/api/merchants/{merchantId}404 not_found, 401
GET /partner/api/merchants/{merchantId}/application404 not_found, 401
PUT /partner/api/merchants/{merchantId}/application400 validation_error, 404 not_found, 409 invalid_status, 401
PATCH /partner/api/merchants/{merchantId}/application400 validation_error, 404 not_found, 409 invalid_status, 401
POST /partner/api/merchants/{merchantId}/application/submit400 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.