Upload a Third-Party Contract for Review
Goal
Upload a third-party paper contract and create it directly in a review workflow instead of sending it for signature immediately.
Endpoints used
POST /api/v2.1/public/contracts/upload_review_contracts/GET /api/v2.1/public/contract_types/{contract_type_id}/intake_form/questionnaire/
Prerequisites
- a valid public API key
- a
contract_type_idlinked to a published third-party paper upload workflow - the file content available as base64
- either a
counterparty_workspace_idor acounterparty_organization_name - an optional reviewer email or reviewer role id
Overview
This flow is only valid when the selected contract_type_id is accessible in the target workspace. Validate the questionnaire first, then map intake_form_data to the exact keys returned by that endpoint.
Request sequence
1. Inspect the contract type questionnaire
If your upload workflow uses intake-form questions, fetch them first.
- Python
- Node.js
- curl
import os
import requests
base_url = "https://api.in.spotdraft.com" # Replace with your workspace region.
headers = {
"client-id": SPOTDRAFT_CLIENT_ID,
"client-secret": SPOTDRAFT_CLIENT_SECRET,
"Content-Type": "application/json",
}
questionnaire = requests.get(
f"{base_url}/api/v2.1/public/contract_types/{os.environ['SPOTDRAFT_CONTRACT_TYPE_ID']}/intake_form/questionnaire/",
headers=headers,
timeout=30,
).json()
const baseUrl = 'https://api.in.spotdraft.com'; // Replace with your workspace region.
const headers = {
'client-id': process.env.SPOTDRAFT_CLIENT_ID,
'client-secret': process.env.SPOTDRAFT_CLIENT_SECRET,
'Content-Type': 'application/json',
};
const questionnaireResponse = await fetch(
`${baseUrl}/api/v2.1/public/contract_types/${process.env.SPOTDRAFT_CONTRACT_TYPE_ID}/intake_form/questionnaire/`,
{headers},
);
const questionnaire = await questionnaireResponse.json();
curl --request GET \
--url "https://api.in.spotdraft.com/api/v2.1/public/contract_types/$SPOTDRAFT_CONTRACT_TYPE_ID/intake_form/questionnaire/" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
2. Upload the contract and assign a reviewer
- Python
- Node.js
- curl
contract_type_id = int(os.environ["SPOTDRAFT_CONTRACT_TYPE_ID"])
creator_entity_id = int(os.environ["SPOTDRAFT_CREATOR_ENTITY_ID"])
payload = {
"contract_name": "Acme Vendor Paper",
"contract_type_id": contract_type_id,
"file_name": "vendor-paper.pdf",
"file_base64": PDF_FILE_BASE64,
"creator_organization_entity_id": creator_entity_id,
"counterparty_organization_name": "Acme Corporation",
"counterparty_is_individual": False,
"counterparty_poc_details": {
"first_name": "Avery",
"last_name": "Stone",
"email": "legal@acme.example",
},
"reviewer": {
"org_user_email": "reviewer@company.com",
"description": "Please review the indemnity and payment terms.",
},
"intake_form_data": {
"term": {"days": 180, "type": "MONTHS", "value": 6},
"notes": "Customer requested revised indemnity language.",
"start_date": "2026-04-01",
"payment_method": "annual_invoice",
},
}
contract = requests.post(
f"{base_url}/api/v2.1/public/contracts/upload_review_contracts/",
headers=headers,
json=payload,
timeout=30,
).json()
const contractTypeId = Number(process.env.SPOTDRAFT_CONTRACT_TYPE_ID);
const creatorEntityId = Number(process.env.SPOTDRAFT_CREATOR_ENTITY_ID);
const payload = {
contract_name: 'Acme Vendor Paper',
contract_type_id: contractTypeId,
file_name: 'vendor-paper.pdf',
file_base64: process.env.PDF_FILE_BASE64,
creator_organization_entity_id: creatorEntityId,
counterparty_organization_name: 'Acme Corporation',
counterparty_is_individual: false,
counterparty_poc_details: {
first_name: 'Avery',
last_name: 'Stone',
email: 'legal@acme.example',
},
reviewer: {
org_user_email: 'reviewer@company.com',
description: 'Please review the indemnity and payment terms.',
},
intake_form_data: {
term: {days: 180, type: 'MONTHS', value: 6},
notes: 'Customer requested revised indemnity language.',
start_date: '2026-04-01',
payment_method: 'annual_invoice',
},
};
const response = await fetch(
`${baseUrl}/api/v2.1/public/contracts/upload_review_contracts/`,
{
method: 'POST',
headers,
body: JSON.stringify(payload),
},
);
const contract = await response.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/upload_review_contracts/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"contract_name": "Acme Vendor Paper",
"contract_type_id": '"$SPOTDRAFT_CONTRACT_TYPE_ID"',
"file_name": "vendor-paper.pdf",
"file_base64": "'"$PDF_FILE_BASE64"'",
"creator_organization_entity_id": '"$SPOTDRAFT_CREATOR_ENTITY_ID"',
"counterparty_organization_name": "Acme Corporation",
"counterparty_is_individual": false,
"counterparty_poc_details": {
"first_name": "Avery",
"last_name": "Stone",
"email": "legal@acme.example"
},
"reviewer": {
"org_user_email": "reviewer@company.com",
"description": "Please review the indemnity and payment terms."
},
"intake_form_data": {
"term": {
"days": 180,
"type": "MONTHS",
"value": 6
},
"notes": "Customer requested revised indemnity language.",
"start_date": "2026-04-01",
"payment_method": "annual_invoice"
}
}'
Example response excerpt:
{
"id": "T-1234",
"created": "2026-04-17T11:30:00Z",
"contract_name": "Acme Vendor Paper",
"contract_link": "https://app.spotdraft.com/contracts/v2/4322",
"contract_status": "REVIEW"
}
Notes on reviewer assignment
The public schema allows the reviewer object to contain either:
org_user_emailrole_id
Use whichever matches how review assignment is modeled in your workspace.
Production notes
- The API validates
file_base64as a real PDF, not just any base64 string. - Contract type access is enforced per workspace.
intake_form_datamust use the exact questionnaire keys returned by the questionnaire endpoint.- If your integration needs to update contract-level intake answers later, use
PATCH /api/v2.1/public/contracts/{composite_id}/intake_form/responseafter creation.
Common failure points
-
missing counterparty identity For review uploads, provide either
counterparty_workspace_idorcounterparty_organization_name. -
invalid reviewer block Keep the reviewer payload minimal and valid for your workspace model. Do not send both email and unrelated user metadata.
-
invalid questionnaire keys
intake_form_datashould be keyed only by questionnaire field names returned by the contract type questionnaire endpoint. -
wrong upload workflow If the contract type is not linked to the right published upload workflow, the create request can fail before the review is created.
Related
- Read Update Intake Form Responses on a Contract if review starts before all intake-form data is final.
- Read Troubleshooting if your downstream automations depend on review-stage transitions.