Create an Intake and Add an Intake Task
Goal
Create a legal intake, add an intake task to that intake, update the intake assignee list, and finally delete the intake.
Endpoints used
POST /api/v1/public/legal_intake/PATCH /api/v1/public/legal_intake/{legal_intake_id}/DELETE /api/v1/public/legal_intake/{legal_intake_id}/POST /api/v2.1/public/user_tasks/
Prerequisites
- a valid public API key
- a workflow id for a published
INTAKE_WORKFLOW - org user ids for anyone you want to assign to the intake or the task
Overview
Every id in this recipe is workspace-specific. Resolve the workflow id and org user ids first, then substitute them into the requests below.
Request sequence
1. Create the intake
- 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",
}
workflow_id = int(os.environ["SPOTDRAFT_WORKFLOW_ID"])
org_user_id = os.environ["SPOTDRAFT_ORG_USER_ID"]
intake = requests.post(
f"{base_url}/api/v1/public/legal_intake/",
headers=headers,
json={
"title": "Route new vendor request",
"description": "Initial request for legal review.",
"priority": "MEDIUM",
"workflow_id": workflow_id,
"due_date": "2026-05-05T17:00:00Z",
"assignees": [
{
"entity_id": org_user_id,
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO",
}
],
"metadata": {"source": "api", "system": "crm"},
},
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 intakeResponse = await fetch(`${baseUrl}/api/v1/public/legal_intake/`, {
method: 'POST',
headers,
body: JSON.stringify({
title: 'Route new vendor request',
description: 'Initial request for legal review.',
priority: 'MEDIUM',
workflow_id: Number(process.env.SPOTDRAFT_WORKFLOW_ID),
due_date: '2026-05-05T17:00:00Z',
assignees: [
{entity_id: process.env.SPOTDRAFT_ORG_USER_ID, entity_type: 'ORG_USER', assignee_type: 'ASSIGNED_TO'},
],
metadata: {source: 'api', system: 'crm'},
}),
});
const intake = await intakeResponse.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v1/public/legal_intake/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"title": "Route new vendor request",
"description": "Initial request for legal review.",
"priority": "MEDIUM",
"workflow_id": '"$SPOTDRAFT_WORKFLOW_ID"',
"due_date": "2026-05-05T17:00:00Z",
"assignees": [
{
"entity_id": "'"$SPOTDRAFT_ORG_USER_ID"'",
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO"
}
],
"metadata": {
"source": "api",
"system": "crm"
}
}'
Example response excerpt:
{
"id": 81,
"public_id": "aaf55a1f-0ef1-4bfc-8b0f-3d15b7ed5f02",
"reference_id": "I-0081",
"status": "PENDING",
"priority": "MEDIUM"
}
Extract:
id: use this aslegal_intake_idfor the next steps
2. Create an intake task associated with the intake
Create the task with task_category set to INTAKE_TASK, then associate it to the intake through task_associations.
- Python
- Node.js
- curl
legal_intake_id = intake["id"]
task = requests.post(
f"{base_url}/api/v2.1/public/user_tasks/",
headers=headers,
json={
"title": "Collect business justification",
"notes": "Follow up with procurement before legal review starts.",
"assignees": [{"org_user_id": int(org_user_id)}],
"task_category": "INTAKE_TASK",
"priority": "HIGH",
"intake_task_status": "PENDING",
"due_date": "2026-05-01T17:00:00Z",
"task_associations": [
{
"entity_id": legal_intake_id,
"entity_type": "LEGAL_INTAKE",
"association_type": "PRIMARY",
}
],
},
timeout=30,
).json()
const taskResponse = await fetch(`${baseUrl}/api/v2.1/public/user_tasks/`, {
method: 'POST',
headers,
body: JSON.stringify({
title: 'Collect business justification',
notes: 'Follow up with procurement before legal review starts.',
assignees: [{org_user_id: Number(process.env.SPOTDRAFT_ORG_USER_ID)}],
task_category: 'INTAKE_TASK',
priority: 'HIGH',
intake_task_status: 'PENDING',
due_date: '2026-05-01T17:00:00Z',
task_associations: [
{
entity_id: intake.id,
entity_type: 'LEGAL_INTAKE',
association_type: 'PRIMARY',
},
],
}),
});
const task = await taskResponse.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/user_tasks/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"title": "Collect business justification",
"notes": "Follow up with procurement before legal review starts.",
"assignees": [
{
"org_user_id": '"$SPOTDRAFT_ORG_USER_ID"'
}
],
"task_category": "INTAKE_TASK",
"priority": "HIGH",
"intake_task_status": "PENDING",
"due_date": "2026-05-01T17:00:00Z",
"task_associations": [
{
"entity_id": '"$SPOTDRAFT_LEGAL_INTAKE_ID"',
"entity_type": "LEGAL_INTAKE",
"association_type": "PRIMARY"
}
]
}'
Example response excerpt:
{
"id": 501,
"title": "Collect business justification",
"status": "UPCOMING",
"task_category": "INTAKE_TASK",
"task_associations": [
{
"entity_id": "81",
"entity_type": "LEGAL_INTAKE",
"association_type": "PRIMARY"
}
]
}
Extract:
id: the task id for your own tracking
Production notes
- Persist the intake
idfrom the create response and reuse it in the task, update, and delete calls. - Updating assignees can also change the collaborator list returned by the API.
3. Update the intake assignees
The intake API supports partial updates. Send only the fields you want to change. When you send assignees, treat it as the desired current list.
- Python
- Node.js
- curl
updated_intake = requests.patch(
f"{base_url}/api/v1/public/legal_intake/{legal_intake_id}/",
headers=headers,
json={
"assignees": [
{
"entity_id": org_user_id,
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO",
},
{
"entity_id": os.environ["SPOTDRAFT_COLLABORATOR_ID"],
"entity_type": "ORG_USER",
"assignee_type": "COLLABORATOR",
},
],
"priority": "HIGH",
"status": "IN_PROGRESS",
},
timeout=30,
).json()
const updateResponse = await fetch(
`${baseUrl}/api/v1/public/legal_intake/${process.env.SPOTDRAFT_LEGAL_INTAKE_ID}/`,
{
method: 'PATCH',
headers,
body: JSON.stringify({
assignees: [
{entity_id: process.env.SPOTDRAFT_ORG_USER_ID, entity_type: 'ORG_USER', assignee_type: 'ASSIGNED_TO'},
{entity_id: process.env.SPOTDRAFT_COLLABORATOR_ID, entity_type: 'ORG_USER', assignee_type: 'COLLABORATOR'},
],
priority: 'HIGH',
status: 'IN_PROGRESS',
}),
},
);
const updatedIntake = await updateResponse.json();
curl --request PATCH \
--url "https://api.in.spotdraft.com/api/v1/public/legal_intake/$SPOTDRAFT_LEGAL_INTAKE_ID/" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"assignees": [
{
"entity_id": "'"$SPOTDRAFT_ORG_USER_ID"'",
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO"
},
{
"entity_id": "'"$SPOTDRAFT_COLLABORATOR_ID"'",
"entity_type": "ORG_USER",
"assignee_type": "COLLABORATOR"
}
],
"priority": "HIGH",
"status": "IN_PROGRESS"
}'
Example response excerpt:
{
"id": 81,
"status": "IN_PROGRESS",
"priority": "HIGH",
"assignees": [
{
"entity_id": "789",
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO"
}
],
"collaborators": [
{
"entity_id": "999",
"entity_type": "ORG_USER",
"assignee_type": "COLLABORATOR"
}
]
}
4. Delete the intake when it is no longer needed
- Python
- Node.js
- curl
requests.delete(
f"{base_url}/api/v1/public/legal_intake/{legal_intake_id}/",
headers=headers,
timeout=30,
).raise_for_status()
await fetch(`${baseUrl}/api/v1/public/legal_intake/${process.env.SPOTDRAFT_LEGAL_INTAKE_ID}/`, {
method: 'DELETE',
headers,
});
curl --request DELETE \
--url "https://api.in.spotdraft.com/api/v1/public/legal_intake/$SPOTDRAFT_LEGAL_INTAKE_ID/" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
Successful deletion returns 204 No Content.
Important limitation
The current public API exposes POST /api/v2.1/public/user_tasks/ for task creation, but it does not expose public PATCH, PUT, or DELETE routes for user_tasks.
That means:
- set task assignees at creation time
- update intake assignees through the legal intake
PATCHendpoint - do not document task reassignment or task deletion as public API operations unless those routes are added to the public schema later
Common failure points
-
invalid intake workflow
workflow_idmust resolve to a published frozen workflow of typeINTAKE_WORKFLOW. -
malformed intake assignee rows Each intake assignee row must include
entity_id,entity_type, andassignee_type. -
malformed task assignee rows Each task assignee must set exactly one of
org_user_idorrole_id. -
invalid intake task payload
priorityandintake_task_statusare only valid forINTAKE_TASK. -
403on intake update or delete The public intake routes intentionally return403when the intake is not visible in the current workspace or the caller lacks access.
Related
- Read Developer Settings for how teams typically discover org users and validate workflow setup.
- Read Platform 101 for broader public API conventions.