curl --request POST \
--url https://api-sandbox.finogates.com/v1/platform/merchant-onboarding \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'payload=<string>' \
--form voided_check='@example-file' \
--form bank_statements='@example-file' \
--form 'processing_statements=<string>' \
--form processing_statements.0='@example-file'import requests
url = "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding"
files = {
"voided_check": ("example-file", open("example-file", "rb")),
"bank_statements": ("example-file", open("example-file", "rb")),
"processing_statements.0": ("example-file", open("example-file", "rb"))
}
payload = {
"payload": "<string>",
"processing_statements": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('payload', '<string>');
form.append('voided_check', '<string>');
form.append('bank_statements', '<string>');
form.append('processing_statements', '<string>');
form.append('processing_statements.0', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api-sandbox.finogates.com/v1/platform/merchant-onboarding', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.finogates.com/v1/platform/merchant-onboarding")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.finogates.com/v1/platform/merchant-onboarding")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"status_code": 201,
"data": {
"user_id": "228e4b03-6831-4b63-ad8e-2250d01f6d64",
"kyb_id": "69b336da-331c-4ddf-8ac5-0a0557c7f279",
"card_acquiring_id": "07f78e86-b3c6-4761-84ae-22176ca015c1"
},
"query_generated_time": 1712847600000
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Onboard a Merchant for Card Processing
Onboard a business for card processing in one call.
This is the combined form of several separate calls — create the user, record their identity check, record the business check, submit the card-processing application, and optionally attach its underwriting packet. Use it when you already hold everything needed to onboard the merchant.
The request is multipart/form-data. The structured half goes in the payload form field as JSON text; the supporting documents are separate binary file parts.
Identity — supply exactly one of these. Sending both is rejected.
inquiry_id— an identity-verification inquiry id you already hold for this individual. The verified details are pulled from the verification provider. The inquiry must belong to the same environment you are calling in; one created in sandbox cannot be read in production, or vice versa.kyc— the identity details keyed in directly, when no inquiry exists. Every field is required, because this arm must carry everything the inquiry arm would have returned from the provider:first_name,last_name,email,dob(YYYY-MM-DD),phone_country_code,phone_no,ssn,address,city,state,zip,country(ISO alpha-2),id_type,id_no, and the image linksselfie,id_front_img,id_back_img. If a value does not exist for this individual, useinquiry_idinstead.
Business — kyb is required. Same shape as POST /v1/platform/users/kyb/start/{user_id}, with one difference: kyb.business_data.business_email is required here. It is the merchant’s email; there is no separate field to override it.
kyb.business_data.website is required here too — an application cannot be underwritten without the site the payments originate from.
kyb.business_data.business_documents must carry 2 to 5 links to the business-verification documents.
Underwriting — merchant_info is required, together with its document file parts:
merchant_info.average_ticket_minor— average transaction amount, minor units (cents).merchant_info.monthly_volume_minor— expected monthly processing volume, minor units.merchant_info.max_ticket_minor— maximum expected transaction amount, minor units. Cannot be lower thanaverage_ticket_minor.voided_checkfile part — required.bank_statementsfile part — required, last 3 months, one file.processing_statementsfile part — optional; only applies when the business already processes with another provider.
There is no merchant_info.website: it is read from kyb.business_data.website, so one merchant states one site.
File size limits, per file:
- Uploaded supporting documents (
voided_check,bank_statements,processing_statements) — 10 MB. - Linked identity images and business documents — 7 MB.
Links are downloaded and re-hosted, not just recorded. Every document and identity-image link you send is fetched once while the request is being handled, stored on Finogate’s own storage, and it is that stored copy which is kept against the verification record. Your original URL is not retained, so it can expire or rotate afterwards without affecting the record.
Because of that, links must actually be fetchable at the moment you call:
https://only. Plainhttp://is rejected.- The host must be publicly reachable. Private, loopback and link-local addresses are refused.
- The file must be a PNG, JPG, JPEG or PDF — the only types this endpoint accepts, for uploads and links alike. HEIC/HEIF are not supported, so convert iPhone photos before sending.
- An
s3://URI already on Finogate storage is kept as-is and not re-fetched.
If any link cannot be fetched the whole call fails with 422 and nothing is created — a verification record is never approved against a document we could not retrieve. To upload files directly instead, use POST /v1/platform/users/kyb/start/{user_id}.
The merchant is created with a Primary USD wallet, the same as POST /v1/platform/users.
Everything this call files goes to review. One call replaces four requests; it does not replace the decision. The identity check, the business check and the card-processing application are all created awaiting review, exactly as the per-step endpoints leave them, and each is approved or declined by Finogate afterwards. The merchant cannot process cards, and no MID is issued, until the application is approved — which requires the identity and business checks to be approved first.
If your platform runs its own KYC/KYB programme, that identity and business review can be waived for you: Finogate then reads the verdict you already hold for the merchant instead of repeating the check, and approves both on your APPROVED. This is arranged with Finogate per platform and is off unless it has been set up for you; the card-processing application is still decided by Finogate either way. Contact support to have your programme assessed.
Poll the verification and card-processing endpoints for status, or subscribe to the user_kyc.*, user_kyb.* and card_acquiring.application.* webhooks.
Repeat submissions for the same merchant email. What happens depends on what that merchant already has:
- An application still awaiting review — rejected with
409. Only one may be in flight at a time; wait for it to be approved or declined. - A previously approved application for the same business (same legal name and EIN) — that application is returned unchanged and nothing is written, so retrying after a timeout cannot create a duplicate merchant.
- A previously approved application for a different business — a new business check and a new application are created. One email can hold several companies.
Declined and suspended applications never block a new submission.
The response is the three ids this call produced. The records themselves are read from the endpoints that own them — see the response schema.
curl --request POST \
--url https://api-sandbox.finogates.com/v1/platform/merchant-onboarding \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'payload=<string>' \
--form voided_check='@example-file' \
--form bank_statements='@example-file' \
--form 'processing_statements=<string>' \
--form processing_statements.0='@example-file'import requests
url = "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding"
files = {
"voided_check": ("example-file", open("example-file", "rb")),
"bank_statements": ("example-file", open("example-file", "rb")),
"processing_statements.0": ("example-file", open("example-file", "rb"))
}
payload = {
"payload": "<string>",
"processing_statements": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('payload', '<string>');
form.append('voided_check', '<string>');
form.append('bank_statements', '<string>');
form.append('processing_statements', '<string>');
form.append('processing_statements.0', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api-sandbox.finogates.com/v1/platform/merchant-onboarding', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.finogates.com/v1/platform/merchant-onboarding"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.finogates.com/v1/platform/merchant-onboarding")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.finogates.com/v1/platform/merchant-onboarding")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"payload\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"voided_check\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"bank_statements\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"processing_statements.0\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"status_code": 201,
"data": {
"user_id": "228e4b03-6831-4b63-ad8e-2250d01f6d64",
"kyb_id": "69b336da-331c-4ddf-8ac5-0a0557c7f279",
"card_acquiring_id": "07f78e86-b3c6-4761-84ae-22176ca015c1"
},
"query_generated_time": 1712847600000
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Authorizations
The access token received from the authorization server in the OAuth 2.0 flow.
Body
The onboarding details, sent as JSON text. See the endpoint description for the object's shape.
Voided check for the account settlements should land in.
Last 3 months of business bank statements, one file.
Last 3 months of merchant processing statements. Optional — it only applies when the business already processes with another provider.

