Initialize a File Upload and Attach It to an Intake
Goal
Upload a file into your SpotDraft workspace through the public API, then use the returned file_id while creating a legal intake.
Endpoints used
POST /api/v2.1/public/workspace/files/uploads/POST /api/v1/public/legal_intake/GET /api/v2.1/public/workspace/files/{file_id}/
Prerequisites
- a valid public API key for the target workspace
- a workflow id for an
INTAKE_WORKFLOW - the local file you want to upload
- a content type that matches the file, such as
application/pdf
Overview
This flow has three steps:
- initialize the upload with SpotDraft
- upload the raw bytes to the returned storage URL
- create the intake using the returned
file_id
Request sequence
1. Initialize the upload
Call the workspace file upload API with the file metadata. SpotDraft returns a file_id, a provider-signed upload_url, and an expires_at timestamp.
- 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",
}
upload_init = requests.post(
f"{base_url}/api/v2.1/public/workspace/files/uploads/",
headers=headers,
json={
"files": [
{
"filename": "vendor-nda.pdf",
"content_type": "application/pdf",
}
]
},
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 uploadInitResponse = await fetch(
`${baseUrl}/api/v2.1/public/workspace/files/uploads/`,
{
method: 'POST',
headers,
body: JSON.stringify({
files: [
{
filename: 'vendor-nda.pdf',
content_type: 'application/pdf',
},
],
}),
},
);
const uploadInit = await uploadInitResponse.json();
curl --request POST \
--url https://api.in.spotdraft.com/api/v2.1/public/workspace/files/uploads/ \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET" \
--header "Content-Type: application/json" \
--data '{
"files": [
{
"filename": "vendor-nda.pdf",
"content_type": "application/pdf"
}
]
}'
Example response:
{
"files": [
{
"file_id": "10f9a299-9e9a-4bc9-9299-5b89bc06d370",
"filename": "vendor-nda.pdf",
"upload_url": "https://storage.example.com/...",
"expires_at": "2026-04-17T14:30:00Z"
}
]
}
Extract:
file_id: the SpotDraft workspace file id you will later attach to the intakeupload_url: the signed storage URL that receives the raw bytes
2. Upload the file bytes to the returned URL
The upload_url is not another SpotDraft API endpoint. It is a time-limited signed storage URL. Send the file directly to that URL and do not attach your SpotDraft API headers.
curl --request PUT \
--url "$UPLOAD_URL" \
--header "Content-Type: application/pdf" \
--upload-file ./vendor-nda.pdf
Practical rules:
- upload before
expires_at - send the same content type you used while initializing the upload
- do not add
client-id,client-secret, or other SpotDraft API headers to the signed URL request
Production notes
- The signed
upload_urlis storage-provider specific and expires quickly. - A successful intake response includes the uploaded file under
attachments.
3. Create the legal intake with the uploaded file attached
Pass the returned file_id in file_ids when creating the intake.
- Python
- Node.js
- curl
workflow_id = int(os.environ["SPOTDRAFT_WORKFLOW_ID"])
primary_assignee_id = os.environ["SPOTDRAFT_PRIMARY_ASSIGNEE_ID"]
collaborator_id = os.environ["SPOTDRAFT_COLLABORATOR_ID"]
intake = requests.post(
f"{base_url}/api/v1/public/legal_intake/",
headers=headers,
json={
"title": "Review vendor NDA",
"description": "Upload and route a third-party NDA for legal review.",
"priority": "HIGH",
"workflow_id": workflow_id,
"due_date": "2026-04-30T17:00:00Z",
"file_ids": ["10f9a299-9e9a-4bc9-9299-5b89bc06d370"],
"assignees": [
{
"entity_id": primary_assignee_id,
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO",
},
{
"entity_id": collaborator_id,
"entity_type": "ORG_USER",
"assignee_type": "COLLABORATOR",
},
],
"metadata": {
"source": "developer-portal-recipe",
"request_type": "nda-review",
},
},
timeout=30,
).json()
const intakeResponse = await fetch(`${baseUrl}/api/v1/public/legal_intake/`, {
method: 'POST',
headers,
body: JSON.stringify({
title: 'Review vendor NDA',
description: 'Upload and route a third-party NDA for legal review.',
priority: 'HIGH',
workflow_id: Number(process.env.SPOTDRAFT_WORKFLOW_ID),
due_date: '2026-04-30T17:00:00Z',
file_ids: ['10f9a299-9e9a-4bc9-9299-5b89bc06d370'],
assignees: [
{entity_id: process.env.SPOTDRAFT_PRIMARY_ASSIGNEE_ID, entity_type: 'ORG_USER', assignee_type: 'ASSIGNED_TO'},
{entity_id: process.env.SPOTDRAFT_COLLABORATOR_ID, entity_type: 'ORG_USER', assignee_type: 'COLLABORATOR'},
],
metadata: {
source: 'developer-portal-recipe',
request_type: 'nda-review',
},
}),
});
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": "Review vendor NDA",
"description": "Upload and route a third-party NDA for legal review.",
"priority": "HIGH",
"workflow_id": '"$SPOTDRAFT_WORKFLOW_ID"',
"due_date": "2026-04-30T17:00:00Z",
"file_ids": [
"10f9a299-9e9a-4bc9-9299-5b89bc06d370"
],
"assignees": [
{
"entity_id": "'"$SPOTDRAFT_PRIMARY_ASSIGNEE_ID"'",
"entity_type": "ORG_USER",
"assignee_type": "ASSIGNED_TO"
},
{
"entity_id": "'"$SPOTDRAFT_COLLABORATOR_ID"'",
"entity_type": "ORG_USER",
"assignee_type": "COLLABORATOR"
}
],
"metadata": {
"source": "developer-portal-recipe",
"request_type": "nda-review"
}
}'
Example response excerpt:
{
"id": 1,
"public_id": "dc676f34-0c1a-4f89-8d24-5264d4f8b735",
"reference_id": "I-001",
"title": "Review vendor NDA",
"attachments": [
{
"name": "vendor-nda.pdf",
"uuid": "10f9a299-9e9a-4bc9-9299-5b89bc06d370"
}
]
}
4. Optionally fetch a signed download URL for the uploaded file
After the file is attached, you can request a signed download URL using the same file_id.
- Python
- Node.js
- curl
signed_url = requests.get(
f"{base_url}/api/v2.1/public/workspace/files/10f9a299-9e9a-4bc9-9299-5b89bc06d370/",
headers=headers,
timeout=30,
).json()
const signedUrlResponse = await fetch(
`${baseUrl}/api/v2.1/public/workspace/files/10f9a299-9e9a-4bc9-9299-5b89bc06d370/`,
{headers},
);
const signedUrl = await signedUrlResponse.json();
curl --request GET \
--url "https://api.in.spotdraft.com/api/v2.1/public/workspace/files/$FILE_ID/" \
--header "client-id: $SPOTDRAFT_CLIENT_ID" \
--header "client-secret: $SPOTDRAFT_CLIENT_SECRET"
Example response:
{
"signed_url": "https://storage.example.com/download/..."
}
Common failure points
-
400 One or more file_ids not found in workspaceThis usually means the upload was initialized in a different workspace, or you used the wrongfile_id. -
400 This workflow has no published version for the workspaceTheworkflow_idmust resolve to a published frozen version in the current workspace. -
400 This workflow is not an INTAKE_WORKFLOWFile attachment during intake creation only works with intake workflows. -
upload URL expired Re-run the upload initialization step to get a fresh
upload_url. -
wrong content type Use the same MIME type in the upload request that you used during initialization.
Related
- Read Developer Settings for workspace-side setup.
- Read Troubleshooting if you also trigger downstream automation after intake creation.