Upload a Third-Party Contract and Send It for Signature
Goal
Upload a third-party paper contract and send it into SpotDraft's signing workflow in a single public API call.
Endpoints used
POST /api/v2.1/public/contracts/upload_sign_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 PDF file content available as a base64 string
- any creator organization entity id, counterparty details, signatory details, and intake-form answers required by your workflow
Overview
This recipe assumes the selected upload contract type is accessible in the target workspace and that your signatory data matches that workflow's creator-side entity model.
Request sequence
1. Fetch the intake-form questionnaire for the contract type
Use the questionnaire endpoint to discover the allowed variable names and the expected value types for intake_form_data.
- 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_response = requests.get(
f"{base_url}/api/v2.1/public/contract_types/{os.environ['SPOTDRAFT_CONTRACT_TYPE_ID']}/intake_form/questionnaire/",
headers=headers,
timeout=30,
)
questionnaire_response.raise_for_status()
questionnaire = questionnaire_response.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},
);
if (!questionnaireResponse.ok) {
throw new Error(`Questionnaire fetch failed: ${questionnaireResponse.status}`);
}
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"
Example response excerpt:
[
{
"display_label": "Contract term",
"variable": "term",
"type": "string",
"required": true
},
{
"display_label": "Payment method",
"variable": "payment_method",
"type": "enum",
"options": [
{
"label": "Annual invoice",
"value": "annual_invoice"
}
]
}
]
2. Upload the contract for signature
This endpoint takes the file content inline as file_base64. It does not use the workspace file upload flow.
- 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 Corporation NDA",
"contract_type_id": contract_type_id,
"file_name": "acme-nda.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",
},
"business_user_email": "owner@company.com",
"creator_signatories": [
{
"email": "signatory@company.com",
"title": "VP Legal",
"note": "Primary signer for the creator entity.",
}
],
"external_metadata": {
"id": "hubspot-deal-10024",
"integration_name": "HubSpot",
"record_type": "Deal",
},
"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",
},
}
upload_response = requests.post(
f"{base_url}/api/v2.1/public/contracts/upload_sign_contracts/",
headers=headers,
json=payload,
timeout=30,
)
upload_response.raise_for_status()
contract = upload_response.json()
const contractTypeId = Number(process.env.SPOTDRAFT_CONTRACT_TYPE_ID);
const creatorEntityId = Number(process.env.SPOTDRAFT_CREATOR_ENTITY_ID);
const payload = {
contract_name: 'Acme Corporation NDA',
contract_type_id: contractTypeId,
file_name: 'acme-nda.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',
},
business_user_email: 'owner@company.com',
creator_signatories: [
{
email: 'signatory@company.com',
title: 'VP Legal',
note: 'Primary signer for the creator entity.',
},
],
external_metadata: {
id: 'hubspot-deal-10024',
integration_name: 'HubSpot',
record_type: 'Deal',
},
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 uploadResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/upload_sign_contracts/`,
{
method: 'POST',
headers,
body: JSON.stringify(payload),
},
);
if (!uploadResponse.ok) {
throw new Error(`Upload-for-signature failed: ${uploadResponse.status}`);
}
const contract = await uploadResponse.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/upload_sign_contracts/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"contract_name": "Acme Corporation NDA",
"contract_type_id": '"$SPOTDRAFT_CONTRACT_TYPE_ID"',
"file_name": "acme-nda.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"
},
"business_user_email": "owner@company.com",
"creator_signatories": [
{
"email": "signatory@company.com",
"title": "VP Legal",
"note": "Primary signer for the creator entity."
}
],
"external_metadata": {
"id": "hubspot-deal-10024",
"integration_name": "HubSpot",
"record_type": "Deal"
},
"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:15:00Z",
"contract_name": "Acme Corporation NDA",
"contract_link": "https://app.spotdraft.com/contracts/v2/4322",
"contract_status": "SIGN"
}
Production notes
file_base64must contain a valid PDF.- Use questionnaire keys exactly as returned by the questionnaire endpoint.
- Creator entity and signatory requirements vary by upload workflow.
- If some intake-form answers are learned later, you can update the saved contract response after creation with
PATCH /api/v2.1/public/contracts/{composite_id}/intake_form/response.
Extract:
id: the SpotDraft composite contract id, such asT-1234contract_link: the UI link to the created contract
Common failure points
-
upload workflow not linked to the contract type The schema explicitly notes this can fail with
SD_CONTRACTS_00289whencontract_type_idis not linked to a published third-party paper upload workflow. -
invalid
intake_form_dataThe keys must match questionnairevariablenames, and the values must match the configured question types. -
wrong file encoding
file_base64must be the actual base64 file content, not a file path or a data URL wrapper. -
missing counterparty details The endpoint requires
counterparty_organization_nameand often a validcounterparty_poc_detailsblock for real workflows.
Related
- Read Update Intake Form Responses on a Contract if your integration needs to change intake-form answers after the contract has already been created.
Related
- Read Troubleshooting for the style of operational debugging we recommend across integrations.
- Read Errors, Redirects, and Retries for authentication and response conventions.