Update Intake Form Responses on a Contract
Goal
Patch intake-form data on an existing contract after the contract has already been created.
When to use this
Use this when your contract workflow exposes a contract-level intake form response that can be edited after creation.
This is a supported follow-up flow after contract creation. If you already know all intake-form answers up front, you can still send intake_form_data during upload_review_contracts or upload_sign_contracts to avoid a second API call.
Endpoints used
GET /api/v2.1/public/contract_types/{contract_type_id}/intake_form/questionnaire/GET /api/v2.1/public/contracts/{contract_id}/questionnaire/responses/?questionnaire_type=INTAKE_FORMPATCH /api/v2.1/public/contracts/{composite_id}/intake_form/response
Prerequisites
- a valid public API key
- the contract's
composite_id, such asT-1234 - the contract's numeric
contract_id - the
contract_type_idused for the contract workflow
Overview
This route is workspace- and workflow-dependent. Validate that the target contract type is accessible in the current workspace, then read the current saved intake-form response before patching it.
Two ids are involved:
contract_idis the numeric internal id used by the questionnaire-response read endpointcomposite_idis the public id shape likeT-1234orH-1234used by the patch endpoint
Request sequence
1. Fetch the questionnaire definition
Use the contract type questionnaire endpoint to understand the field names and expected value shapes.
- 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 = requests.get(
f"{base_url}/api/v2.1/public/contract_types/{os.environ['SPOTDRAFT_CONTRACT_TYPE_ID']}/intake_form/questionnaire/",
headers=headers,
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 questionnaireResponse = await fetch(
`${baseUrl}/api/v2.1/public/contract_types/${process.env.SPOTDRAFT_CONTRACT_TYPE_ID}/intake_form/questionnaire/`,
{headers},
);
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"
}
]
}
]
Use:
variableas the JSON key in your patch bodytype,parent_type, andoptionsto choose the right value shape
2. Read the current saved intake-form response
The patch route merges your payload into the latest saved contract intake-form response. Read the current response first if you need to preserve existing values intentionally.
- Python
- Node.js
- curl
current_response = requests.get(
f"{base_url}/api/v2.1/public/contracts/{os.environ['SPOTDRAFT_NUMERIC_CONTRACT_ID']}/questionnaire/responses/",
headers=headers,
params={"questionnaire_type": "INTAKE_FORM"},
timeout=30,
)
current_response.raise_for_status()
current_intake_form = current_response.json()
const currentResponse = await fetch(
`${baseUrl}/api/v2.1/public/contracts/${process.env.SPOTDRAFT_NUMERIC_CONTRACT_ID}/questionnaire/responses/?questionnaire_type=INTAKE_FORM`,
{headers},
);
if (!currentResponse.ok) {
throw new Error(`Current intake-form fetch failed: ${currentResponse.status}`);
}
const currentIntakeForm = await currentResponse.json();
curl --request GET \
--url "https://api.in.spotdraft.com/api/v2.1/public/contracts/$SPOTDRAFT_NUMERIC_CONTRACT_ID/questionnaire/responses/?questionnaire_type=INTAKE_FORM" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
Use this response as the current-state baseline. The patch route updates the stored response rather than replacing it from an empty object.
3. Patch the intake-form response
The schema describes this as a partial update. Only send the questionnaire keys you want to change.
- Python
- Node.js
- curl
response = requests.patch(
f"{base_url}/api/v2.1/public/contracts/{os.environ['SPOTDRAFT_CONTRACT_ID']}/intake_form/response",
headers=headers,
json={
"term": {"days": 365, "type": "DAYS", "value": 365},
"notes": "Updated after procurement review.",
"start_date": "2026-05-01",
"payment_method": "annual_invoice",
},
timeout=30,
)
response.raise_for_status()
intake_form_response = response.json()
const response = await fetch(
`${baseUrl}/api/v2.1/public/contracts/${process.env.SPOTDRAFT_CONTRACT_ID}/intake_form/response`,
{
method: 'PATCH',
headers,
body: JSON.stringify({
term: {days: 365, type: 'DAYS', value: 365},
notes: 'Updated after procurement review.',
start_date: '2026-05-01',
payment_method: 'annual_invoice',
}),
},
);
const intakeFormResponse = await response.json();
curl --request PATCH \
--url "https://api.in.spotdraft.com/api/v2.1/public/contracts/$SPOTDRAFT_CONTRACT_ID/intake_form/response" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"term": {
"days": 365,
"type": "DAYS",
"value": 365
},
"notes": "Updated after procurement review.",
"start_date": "2026-05-01",
"payment_method": "annual_invoice"
}'
Example response:
{
"data": {
"term": {
"days": 365,
"type": "DAYS",
"value": 365
},
"notes": "Updated after procurement review.",
"start_date": "2026-05-01",
"payment_method": "annual_invoice"
},
"created": "2026-04-17T11:45:00Z"
}
Production notes
- The backend merges your patch into the latest saved intake-form response for that contract.
- Treat the questionnaire definition as the source of truth and send only questionnaire field names.
- This is a supported post-create contract edit flow. Use it when your integration learns or corrects intake-form values after the contract already exists.
- This route can return
400when the workspace feature flag for intake-form editing is disabled, when the patch produces no effective change, or when approval-state constraints block the edit.
Common failure points
-
wrong contract id shape
composite_idmust look likeT-123orH-123, not a plain integer. -
wrong field types Match the questionnaire value shape exactly, especially for enums, dates, repeated structures, and typed objects such as duration or currency.
-
accidental full overwrite assumptions This route merges into the latest saved intake-form response, so read current state first if you need deterministic updates.
-
wrong id type on the read step
GET /contracts/{contract_id}/questionnaire/responses/uses the numeric internalcontract_id, not the public composite id.
Related
- Read Upload a Third-Party Contract and Send It for Signature if you want to provide
intake_form_dataduring the initial upload. - Read Upload a Third-Party Contract and Request Review for the review-first variant.