Upload documents
Supply documents to an extraction step in an existing pipeline run. SeaUpload resolves the step from its unique label and manages this upload workflow inside the widgets. The HTTP API below is for server integrations.
Connection
Use Bash, curl and jq. Set the API host root and an access token or API key, then run the following commands in the same shell:
sh
export API_BASE="https://YOUR_API_HOST"
export TOKEN="YOUR_ACCESS_TOKEN_OR_API_KEY"sh
set -euo pipefail
: "${API_BASE:?Set the API host root without /v2}"
: "${TOKEN:?Set the access token or API key}"
request() {
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "$@"
}Select the run step
Use the existing run's canonical runId. Read its status through GET /v2/pipelines/runs/{runId} and select the extraction step's runStepId. Match its pipelineStepId to the configured step's id when resolving a label from the pipeline list.
Run status includes steps[].attempt, a required positive integer for each step. Capture the selected step's attempt once before uploading and keep it with the batch. SeaUpload reads and sends this value internally; its host still supplies only the run ID and step label.
sh
RUN_ID="YOUR_EXISTING_RUN_ID"
RUN_STEP_ID="YOUR_EXTRACTION_RUN_STEP_ID"
ATTEMPT=$(request "$API_BASE/v2/pipelines/runs/$RUN_ID" \
| jq -er --arg step "$RUN_STEP_ID" \
'.steps[] | select(.runStepId == $step) | .attempt')The target must have role extraction, status pending or blocked, and an unresolved missing_upload blocker. Other blockers, such as parent_incomplete, may remain after documents are supplied. A pipeline step definition ID cannot replace a run-step ID.
Upload the batch
Send multipart files, a unique uploadId and the captured attempt to POST /v2/pipelines/runs/{runId}/steps/{runStepId}/documents. A nanoid or UUID-compatible value of up to 128 characters identifies the batch. Retain it and the attempt with the original files for retries. Put document.pdf in the current directory before running this request.
sh
UPLOAD_ID="YOUR_UNIQUE_BATCH_ID"
curl --fail-with-body --silent --show-error \
"$API_BASE/v2/pipelines/runs/$RUN_ID/steps/$RUN_STEP_ID/documents" \
-H "Authorization: Bearer $TOKEN" \
-F "uploadId=$UPLOAD_ID" \
-F "attempt=$ATTEMPT" \
-F 'files=@./document.pdf;type=application/pdf' > upload.json
jq . upload.jsonRepeat the files field to send several files to the same step. The request needs no form, submission or extraction IDs.
Receipts and retries
The response is a PipelineRunDocumentUpload receipt:
json
{
"runId": "run-1",
"runStepId": "run-step-1",
"uploadId": "batch-1",
"attempt": 1,
"outcome": "committed",
"documents": [
{ "fileIndex": 0, "fileName": "document.pdf", "documentId": "document-1" }
],
"fileFailures": [],
"dispatchAttempt": { "outcome": "accepted" }
}committed means the complete batch is linked to that exact step and its missing_upload blocker is resolved. It does not mean extraction has finished. partial means verified files remain staged and unlinked; the step's upload blocker remains unresolved.
fileFailures identifies each failed file by fileIndex and fileName, with code, message, acceptance rejected or unknown, and an optional documentId. These receipts contain no submission or extraction IDs.
The receipt's required positive-integer attempt identifies the upload's step generation. Match it with the captured run, step and batch ID before applying the receipt.
After a partial receipt or lost response, repeat the upload request with the same attempt, uploadId and entire original batch in the same order, with the same filenames and bytes. Include files already listed in documents. Keep the captured attempt unchanged on retries. Staging resumes safely; changing a batch under the same ID returns 409.
The first staged document reserves the selected step's upload batch for its current attempt. A competing uploadId for that attempt is rejected with 409 before storage. Keep using the original batch ID for retries. A later step attempt with a new missing_upload dependency can accept a new batch.
A stale attempt returns 409 before reading files or reserving a batch, including for a previously committed batch. After a reset, read the new status and begin a new batch only for that attempt's missing_upload dependency. Keep the old batch's identity unchanged.
An expired uncommitted batch also returns 409. Retain its receipt for recovery. An exact committed replay remains valid after storage expiry or the run becomes terminal while the step's attempt is unchanged.
The first complete commit links the full batch before resolving the selected upload blocker and attempting to resume the run once. An exact committed replay for the same attempt returns the same document IDs without another wake-up.
dispatchAttempt describes the first commit's dispatch attempt. accepted confirms acknowledgement; unknown with DISPATCH_UNCONFIRMED leaves delivery uncertain. A committed replay omits this field. Read the existing run's status to follow progress; repeating the committed batch does not trigger another dispatch.
HTTP responses
| Status | Meaning |
|---|---|
200 | A committed or partial receipt, including an uncertain dispatch acknowledgement |
400 | Invalid multipart fields, batch ID, attempt or files |
404 | The run or executable step is unavailable |
409 | A stale attempt, ineligible target, competing or changed batch, or expired batch |
413 | The multipart upload exceeds the API limit |
500 | An unexpected acknowledgement failure; the body contains an error description |
503 | Storage configuration is unavailable before staging |
Check the receipt's outcome on 200, including when attachment fails after staging or dispatch is uncertain. Retain partial receipts and uncertain outcomes. Retry acknowledgement failures with the original complete batch and its ID, following the receipt rules. Error responses covers the common error envelope.
Read the outputs
Read the existing run's status and outputs as processing advances. Run results identify each linked output submission and its data URL. Read results covers corrections and original documents.
Existing submission uploads
Submission-based integrations can still upload directly into a known submission. This compatibility path creates or uses a submission and tracks extraction IDs. It does not select a run step by label.
Create a deal
sh
DEAL_ID=$(request -X POST "$API_BASE/v2/deals" \
-d '{"name":"Sample case"}' | jq -er '.id')Store DEAL_ID against the case in your application.
Choose a form
List the forms, then set FORM_ID to the required form's id:
sh
request "$API_BASE/v2/forms/" | jq '.[] | {id, name}'
FORM_ID="YOUR_FORM_ID"Create a submission
sh
SUBMISSION_ID=$(request -X POST "$API_BASE/v2/submissions" \
-d "$(jq -n --arg f "$FORM_ID" --arg d "$DEAL_ID" '{formId:$f,dealId:$d}')" | jq -er '.id')Upload the file
Put document.pdf in the current directory. Send it as multipart field files with model=general:
sh
curl --fail-with-body --silent --show-error \
"$API_BASE/v2/submissions/$SUBMISSION_ID/extractions" \
-H "Authorization: Bearer $TOKEN" \
-F 'files=@./document.pdf;type=application/pdf' \
-F 'model=general' > upload.json
jq . upload.jsonThe receipt identifies the accepted document and extraction:
json
{
"submissionId": "submission-1",
"extractions": [
{ "documentId": "document-1", "extractionId": "extraction-1" }
],
"failures": 0,
"fileFailures": []
}Per-file failures appear in fileFailures with fileIndex, fileName, code, message, acceptance and optional documentId and extractionId. The widget reports these through upload-failed; code identifies a per-file upload failure.
Read processing results
Poll the returned extraction ID until its status is done or failed, then read the submission's values:
sh
EXTRACTION_ID=$(jq -er '.extractions[0].extractionId' upload.json)
while :; do
request "$API_BASE/v2/submissions/$SUBMISSION_ID/extractions/$EXTRACTION_ID" > extraction.json
case "$(jq -r '.status' extraction.json)" in
done) break ;;
failed) jq . extraction.json; exit 1 ;;
esac
sleep 2
done
request "$API_BASE/v2/submissions/$SUBMISSION_ID/data" > submission.json
jq '.groups[] | .items[] | {id, field: .formField.name, value}' submission.jsonRead results explains the submission data response and source documents.