Create a Template Contract and Send It for Signature
Goal
Start a signature flow from your own product or workflow engine without requiring an operator to open SpotDraft and send the contract manually.
When to use this
Use this when your system already has the commercial inputs and you want SpotDraft to handle drafting and signature without an operator manually advancing the contract in the UI.
Endpoints used
POST /api/v2.1/public/contracts/POST /api/v2.1/public/contracts/{composite_id}/send_to_counterpartiesGET /api/v2.1/public/contracts/{composite_id}/statusGET /api/v2.1/public/contracts/{composite_id}/recipients/
Prerequisites
- a valid public API key
- a SpotDraft template flow that supports signature
- the
contract_template_idfor that flow - validated counterparty and signatory data from your source system
- webhook handling for signature and execution events
Overview
This flow depends on workspace-specific template configuration. Resolve a template id that the target workspace can access, then adapt the payload fields to that template before copying the example directly.
Treat organization_type as a workspace-specific value, not a universal enum. If your workspace uses a different label, send that exact value instead of copying "company" literally.
1. Create the contract
- Python
- Node.js
- curl
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",
}
payload = {
"contract_template_id": 1204, # Replace with a template your workspace can access.
"contract_name": "Acme NDA",
"contract_data": {
"agreement_date": "2026-04-20",
"customer_name": "Acme Inc.",
},
"counter_party_details": [
{
"is_individual": False,
"organization_type": "company",
"organization_name": "Acme Inc.",
"poc_details": {
"first_name": "Avery",
"last_name": "Stone",
"email": "legal@acme.example",
},
"organization_details": {
"jurisdiction_iso_code": "US",
"address": {
"line_one": "350 Fifth Avenue",
"city": "New York",
"state": "NY",
"country_iso_code": "US",
"zipcode": "10118",
},
},
}
],
"external_metadata": {
"id": "crm-opportunity-2841",
"integration_name": "Salesforce",
"record_type": "Opportunity",
},
}
create_response = requests.post(
f"{base_url}/api/v2.1/public/contracts/",
headers=headers,
json=payload,
timeout=30,
)
create_response.raise_for_status()
contract = create_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 payload = {
contract_template_id: 1204, // Replace with a template your workspace can access.
contract_name: 'Acme NDA',
contract_data: {
agreement_date: '2026-04-20',
customer_name: 'Acme Inc.',
},
counter_party_details: [
{
is_individual: false,
organization_type: 'company',
organization_name: 'Acme Inc.',
poc_details: {
first_name: 'Avery',
last_name: 'Stone',
email: 'legal@acme.example',
},
organization_details: {
jurisdiction_iso_code: 'US',
address: {
line_one: '350 Fifth Avenue',
city: 'New York',
state: 'NY',
country_iso_code: 'US',
zipcode: '10118',
},
},
},
],
external_metadata: {
id: 'crm-opportunity-2841',
integration_name: 'Salesforce',
record_type: 'Opportunity',
},
};
const createResponse = await fetch(`${baseUrl}/api/v2.1/public/contracts/`, {
method: 'POST',
headers,
body: JSON.stringify(payload),
});
if (!createResponse.ok) {
throw new Error(`Create contract failed: ${createResponse.status}`);
}
const contract = await createResponse.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"contract_template_id": 1204,
"contract_name": "Acme NDA",
"contract_data": {
"agreement_date": "2026-04-20",
"customer_name": "Acme Inc."
},
"counter_party_details": [
{
"is_individual": false,
"organization_type": "company",
"organization_name": "Acme Inc.",
"poc_details": {
"first_name": "Avery",
"last_name": "Stone",
"email": "legal@acme.example"
},
"organization_details": {
"jurisdiction_iso_code": "US",
"address": {
"line_one": "350 Fifth Avenue",
"city": "New York",
"state": "NY",
"country_iso_code": "US",
"zipcode": "10118"
}
}
}
],
"external_metadata": {
"id": "crm-opportunity-2841",
"integration_name": "Salesforce",
"record_type": "Opportunity"
}
}'
2. Send the contract for signature
- Python
- Node.js
- curl
requests.post(
f"{base_url}/api/v2.1/public/contracts/T-1234/send_to_counterparties",
headers=headers,
json={},
timeout=30,
).raise_for_status()
const sendResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/T-1234/send_to_counterparties`,
{
method: 'POST',
headers,
body: JSON.stringify({}),
},
);
if (!sendResponse.ok) {
throw new Error(`Send to counterparties failed: ${sendResponse.status}`);
}
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/T-1234/send_to_counterparties \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{}'
3. Inspect the recipients that SpotDraft has configured
Use this once after setup or during incident debugging. It is the fastest way to confirm that the template flow created the expected signers.
- Python
- Node.js
- curl
recipient_response = requests.get(
f"{base_url}/api/v2.1/public/contracts/T-1234/recipients/",
headers=headers,
timeout=30,
)
recipient_response.raise_for_status()
recipients = recipient_response.json()
const recipientResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/T-1234/recipients/`,
{headers},
);
if (!recipientResponse.ok) {
throw new Error(`Recipient fetch failed: ${recipientResponse.status}`);
}
const recipients = await recipientResponse.json();
curl --request GET \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/T-1234/recipients/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
4. Reconcile status only when webhooks are not enough
Your normal production flow should rely on webhook events for signature and execution changes. Use the status endpoint below only when:
- a webhook delivery failed downstream and you need to repair state
- you are validating a newly deployed receiver
- you are running a bounded reconciliation job
- Python
- Node.js
- curl
status_response = requests.get(
f"{base_url}/api/v2.1/public/contracts/T-1234/status",
headers=headers,
timeout=30,
)
status_response.raise_for_status()
status_payload = status_response.json()
const statusResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/T-1234/status`,
{headers},
);
if (!statusResponse.ok) {
throw new Error(`Status fetch failed: ${statusResponse.status}`);
}
const statusPayload = await statusResponse.json();
curl --request GET \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/T-1234/status \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
Production checklist
- keep your own source-system id in
external_metadata.id - treat
send_to_counterpartiesas the explicit boundary between draft creation and outward signature dispatch - consume webhooks for real-time updates and use status polling only for reconciliation
- persist both the SpotDraft composite id and your source-system id on your side
Production notes
- Persist the contract
idreturned from the create step and reuse it in the send, recipients, and status calls. POST /api/v2.1/public/contracts/requirescontract_data, even if the template metadata is minimal.
Common failure points
-
inaccessible template id A template that exists in another workspace or environment is not enough. The current workspace must be able to access it.
-
missing creator-side signer setup The selected template flow must support the creator-side signatory model your payload expects.
-
polling as the primary integration Use
GET /statusas a diagnostic or fallback path, not your only production sync mechanism.