Find Contracts Using Your Own System's IDs
Goal
Use your CRM, ERP, procurement, or ticketing ids as the stable lookup key for SpotDraft contracts instead of relying on contract titles or user-entered text.
When to use this
Use this when your CRM, ERP, procurement, or ticketing system already has a canonical record id and you want that id to be the stable lookup key for SpotDraft contracts.
Endpoints used
POST /api/v2.1/public/contracts/GET /api/v2.1/public/contracts/by_external_metadata/{external_metadata_id}POST /api/v2.1/public/contracts/{contract_id}/external_metadata
Overview
The stable pattern is to write the upstream id during contract creation, then read it back through by_external_metadata later. The sample ids below are placeholders for your own source-system identifiers.
Prerequisites
- a valid public API key
- a template id your workspace can access
- a stable upstream identifier, such as a CRM deal id or ERP request id
Recommended pattern
- write the upstream identifier into
external_metadata.idwhen the contract is created - keep that identifier stable across environments
- query by that id when you need to re-open or reconcile a contract
- patch external metadata later only when your source-of-truth record changed after creation
1. Create the contract with external metadata
- 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,
"contract_name": "Acme MSA",
"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",
},
}
],
"external_metadata": {
"id": "deal-122",
"integration_name": "HubSpot",
"record_type": "Deal",
"record_data": {"owner_email": "owner@acme.example"},
},
}
contract = requests.post(
f"{base_url}/api/v2.1/public/contracts/",
headers=headers,
json=payload,
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 payload = {
contract_template_id: 1204,
contract_name: 'Acme MSA',
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',
},
},
],
external_metadata: {
id: 'deal-122',
integration_name: 'HubSpot',
record_type: 'Deal',
record_data: {owner_email: 'owner@acme.example'},
},
};
const response = await fetch(`${baseUrl}/api/v2.1/public/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/ \
--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 MSA",
"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"
}
}
],
"external_metadata": {
"id": "deal-122",
"integration_name": "HubSpot",
"record_type": "Deal",
"record_data": {
"owner_email": "owner@acme.example"
}
}
}'
2. Retrieve contracts later by the same external id
- Python
- Node.js
- curl
contracts = requests.get(
f"{base_url}/api/v2.1/public/contracts/by_external_metadata/deal-122",
headers=headers,
timeout=30,
).json()
const contractsResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/by_external_metadata/deal-122`,
{headers},
);
const contracts = await contractsResponse.json();
curl --request GET \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/by_external_metadata/deal-122 \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
3. Update external metadata when needed
If your upstream record changes after the contract is created, upsert the metadata explicitly.
- Python
- Node.js
- curl
metadata_response = requests.post(
f"{base_url}/api/v2.1/public/contracts/T-1234/external_metadata",
headers=headers,
json={
"external_metadata": {
"id": "deal-122",
"integration_name": "HubSpot",
"record_type": "Deal",
"record_data": {
"deal_stage": "closedwon",
"owner_email": "owner@acme.example",
},
}
},
timeout=30,
)
metadata_response.raise_for_status()
await fetch(`${baseUrl}/api/v2.1/public/contracts/T-1234/external_metadata`, {
method: 'POST',
headers,
body: JSON.stringify({
external_metadata: {
id: 'deal-122',
integration_name: 'HubSpot',
record_type: 'Deal',
record_data: {
deal_stage: 'closedwon',
owner_email: 'owner@acme.example',
},
},
}),
});
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/contracts/T-1234/external_metadata \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"external_metadata": {
"id": "deal-122",
"integration_name": "HubSpot",
"record_type": "Deal",
"record_data": {
"deal_stage": "closedwon",
"owner_email": "owner@acme.example"
}
}
}'
Good external ids
- CRM opportunity ids
- procurement request ids
- ERP purchase order ids
- internal workflow or request ids
Avoid
- parsing identifiers from
contract_name - changing the meaning of the same external id field across environments
- storing only human-readable labels without a stable machine identifier
Production checklist
- write
external_metadata.idon create, not later if you can avoid it - keep the value stable across the life of the source record
- use lookup by external metadata as the default re-entry path for downstream sync
- do not rely on contract titles for reconciliation
Production notes
- Keep
external_metadata.idstable across retries and reconciliation jobs. - Query by external metadata for support tooling and background sync, not by contract title.
Common failure points
-
non-stable upstream ids If the source record id changes across environments or retries, the lookup pattern becomes unreliable.
-
missing metadata at create time Backfilling later works, but it makes webhook-driven reconciliation harder.