Skip to main content

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
  • write the upstream identifier into external_metadata.id when 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

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

2. Retrieve contracts later by the same external id

contracts = requests.get(
f"{base_url}/api/v2.1/public/contracts/by_external_metadata/deal-122",
headers=headers,
timeout=30,
).json()

3. Update external metadata when needed

If your upstream record changes after the contract is created, upsert the metadata explicitly.

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

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.id on 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.id stable 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.