Skip to main content

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_counterparties
  • GET /api/v2.1/public/contracts/{composite_id}/status
  • GET /api/v2.1/public/contracts/{composite_id}/recipients/

Prerequisites

  • a valid public API key
  • a SpotDraft template flow that supports signature
  • the contract_template_id for 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

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()

2. Send the contract for signature

requests.post(
f"{base_url}/api/v2.1/public/contracts/T-1234/send_to_counterparties",
headers=headers,
json={},
timeout=30,
).raise_for_status()

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.

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()

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
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()

Production checklist

  • keep your own source-system id in external_metadata.id
  • treat send_to_counterparties as 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 id returned from the create step and reuse it in the send, recipients, and status calls.
  • POST /api/v2.1/public/contracts/ requires contract_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 /status as a diagnostic or fallback path, not your only production sync mechanism.