Application Submit via API
Use this guide when you want to submit a merchant application by API, instead of asking the merchant to submit in hosted UI.
Before You Start
- Feature enablement is required: API submit is an entitlement and is not enabled for every integration. You must be enabled for this feature before
PUT /applications/{id}/submitcan be used. - You need a draft application id from Create Application.
- The application must still be in
DRAFTstatus.
Quick Navigation
- Step 0: Create the draft application
- Step 1: Get terms and submit requirements
- Step 2: Upload required documents
- Step 3: Submit the application
- After submit
End-to-End Submit Flow (3 Steps)
Step 0: Create the draft application
Before the 3 submit steps, create a draft application using Create Application (POST /applications).
Use the returned application id as {id} in every endpoint in this guide.
If you want a more complete walkthrough for draft creation fields and examples, see Boarding API Step by Step.
How data flows between steps
This is the exact handoff between calls:
| Step | Call | What you get back | What you use next |
|---|---|---|---|
| 0 | POST /applications | New application id, DRAFT status | Path param {id} for all steps below |
| 1a | GET /applications/{id}/terms | terms_version, display fields (terms_and_conditions, etc.) | Show terms in your UI; send terms_version on submit |
| 1b | GET /applications/{id} | Full draft + requires_bank_account_file_ids_on_submit | Base payload for submit; decide if uploads are required |
| 2 | initiate_upload → PUT presigned URLs → complete_upload | complete_upload returns id (stored file id), message (e.g. file_success) | Build file_ids: ["<id>", ...] on submit when the flag is true |
| 3 | PUT /applications/{id}/submit | Updated application (no longer draft; moves into underwriting) | Poll Get Application or use webhooks |
If GET /applications/{id}/terms returns not found, you are likely not enabled for API submit (see API reference note on that endpoint).
Step 1: Get terms and submit requirements
Call these endpoints:
- Get Application Terms (
GET /applications/{id}/terms) - Get Application (
GET /applications/{id})
Then prepare these submit fields:
terms_version: fromGET /applications/{id}/terms. Must match exactly on submit.terms_accepted: trueandattest_verified: true: you set these in the submit body.requires_bank_account_file_ids_on_submit: fromGET /applications/{id}. Iftrue, you must upload docs and sendfile_idson submit.
Use GET /applications/{id} as the source of truth for your current draft payload.
If requires_bank_account_file_ids_on_submit is true, complete Step 2 before submit.
Step 2: Upload required documents (3-step file upload)
When documents are required, complete this flow:
- Initiate upload using Initiate Application File Upload (
POST /applications/{id}/files/initiate_upload). - Upload each part with
PUTto each returnedpresigned_urland store each partETag. - Complete upload using Complete Application File Upload (
POST /applications/{id}/files/complete_upload) withupload_id,key, andparts(part_number,etag).
For multiple files: repeat the same 3 calls for each file. Add each returned file id to file_ids.
For multiple parts: if initiate_upload returns multiple parts, upload all parts and include all of them in complete_upload.
Complete the file upload
To complete the upload, call POST /applications/{id}/files/complete_upload after you upload bytes to each presigned_url.
Use the request fields exactly as follows:
upload_idandkey: use the exact values returned byinitiate_upload(do not construct these manually).parts: include every uploaded part withpart_numberand its correspondingetagfrom the upload response headers.filename,mimetype,size, andpurpose: must match the file you uploaded.
After complete_upload succeeds, read the id field in the response (ApplicationFileCompleteUploadResponseDto). That string is one entry in file_ids on submit. Repeat for each uploaded file.
Complete one-file example (requires jq):
mimetype is an enum. Allowed values are:
application/pdfimage/png
Set your variables first:
API_HOST="https://<api-host>"
API_KEY="key_123456789"
APPLICATION_ID="<application_id>"
FILE_PATH="./voided-check.pdf"
FILENAME="voided-check.pdf"
MIMETYPE="application/pdf"
SIZE=$(wc -c < "$FILE_PATH" | tr -d ' ')
Initiate upload and capture upload_id, key, and the first part URL:
INITIATE_RESPONSE=$(curl --silent --request POST \
"$API_HOST/applications/$APPLICATION_ID/files/initiate_upload" \
--header "x-api-key: $API_KEY" \
--header "Content-Type: application/json" \
--data "{
\"filename\": \"$FILENAME\",
\"mimetype\": \"$MIMETYPE\",
\"size\": $SIZE,
\"purpose\": \"underwriting_document\"
}")
UPLOAD_ID=$(echo "$INITIATE_RESPONSE" | jq -r '.upload_id')
KEY=$(echo "$INITIATE_RESPONSE" | jq -r '.key')
PRESIGNED_URL=$(echo "$INITIATE_RESPONSE" | jq -r '.parts[0].presigned_url')
PART_NUMBER=$(echo "$INITIATE_RESPONSE" | jq -r '.parts[0].part_number')
Upload file bytes to the presigned URL (no x-api-key header):
curl --silent --show-error --request PUT "$PRESIGNED_URL" \
--upload-file "$FILE_PATH" \
-D /tmp/upload-headers.txt \
-o /dev/null
ETAG=$(awk 'tolower($1)=="etag:" {print $2}' /tmp/upload-headers.txt | tr -d '\r')
ETAG_JSON=$(printf '%s' "$ETAG" | jq -R .)
Complete upload and capture the returned file id for submit:
COMPLETE_RESPONSE=$(curl --silent --request POST \
"$API_HOST/applications/$APPLICATION_ID/files/complete_upload" \
--header "x-api-key: $API_KEY" \
--header "Content-Type: application/json" \
--data "{
\"filename\": \"$FILENAME\",
\"mimetype\": \"$MIMETYPE\",
\"size\": $SIZE,
\"purpose\": \"underwriting_document\",
\"upload_id\": \"$UPLOAD_ID\",
\"key\": \"$KEY\",
\"parts\": [
{
\"part_number\": $PART_NUMBER,
\"etag\": $ETAG_JSON
}
]
}")
FILE_ID=$(echo "$COMPLETE_RESPONSE" | jq -r '.id')
echo "Use this file id in submit file_ids: $FILE_ID"
Step 3: Submit the application
Call Submit Application (PUT /applications/{id}/submit).
Send the full application payload (same shape as your draft), plus these submit fields:
| Field | Source |
|---|---|
terms_version | GET /applications/{id}/terms |
terms_accepted | Set to true after the merchant agrees |
attest_verified | Set to true (your attestation) |
file_ids | Array of id values from each complete_upload when requires_bank_account_file_ids_on_submit is true; omit or empty otherwise |
bank_account | Optional; US only when Vault is configured (see API reference) |
Important submit checks:
- Include
terms_accepted,attest_verified, and the correctterms_version. - Include
file_idswhenrequires_bank_account_file_ids_on_submitistrue. - Set exactly one owner with
signer: true. - You can update most application fields at submit time, but
processing_plan_datais not changed on submit (draft value is always used).
Response: the API returns the updated application. Status moves out of DRAFT into underwriting states.
What to do after submit
After successful submit, the application moves from DRAFT into underwriting statuses. Use:
- Get Application for point checks
- List Applications for monitoring
- Boarding API guide — webhooks and event-driven status updates