Skip to main content

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}/submit can be used.
  • You need a draft application id from Create Application.
  • The application must still be in DRAFT status.

Quick Navigation

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:

StepCallWhat you get backWhat you use next
0POST /applicationsNew application id, DRAFT statusPath param {id} for all steps below
1aGET /applications/{id}/termsterms_version, display fields (terms_and_conditions, etc.)Show terms in your UI; send terms_version on submit
1bGET /applications/{id}Full draft + requires_bank_account_file_ids_on_submitBase payload for submit; decide if uploads are required
2initiate_uploadPUT presigned URLs → complete_uploadcomplete_upload returns id (stored file id), message (e.g. file_success)Build file_ids: ["<id>", ...] on submit when the flag is true
3PUT /applications/{id}/submitUpdated 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:

Then prepare these submit fields:

  • terms_version: from GET /applications/{id}/terms. Must match exactly on submit.
  • terms_accepted: true and attest_verified: true: you set these in the submit body.
  • requires_bank_account_file_ids_on_submit: from GET /applications/{id}. If true, you must upload docs and send file_ids on 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:

  1. Initiate upload using Initiate Application File Upload (POST /applications/{id}/files/initiate_upload).
  2. Upload each part with PUT to each returned presigned_url and store each part ETag.
  3. Complete upload using Complete Application File Upload (POST /applications/{id}/files/complete_upload) with upload_id, key, and parts (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_id and key: use the exact values returned by initiate_upload (do not construct these manually).
  • parts: include every uploaded part with part_number and its corresponding etag from the upload response headers.
  • filename, mimetype, size, and purpose: 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/pdf
  • image/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:

FieldSource
terms_versionGET /applications/{id}/terms
terms_acceptedSet to true after the merchant agrees
attest_verifiedSet to true (your attestation)
file_idsArray of id values from each complete_upload when requires_bank_account_file_ids_on_submit is true; omit or empty otherwise
bank_accountOptional; US only when Vault is configured (see API reference)

Important submit checks:

  • Include terms_accepted, attest_verified, and the correct terms_version.
  • Include file_ids when requires_bank_account_file_ids_on_submit is true.
  • Set exactly one owner with signer: true.
  • You can update most application fields at submit time, but processing_plan_data is 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: