End-to-End Example
This guide walks through the complete workflow of creating a case, uploading evidence, and generating a technical report using the Silent Witness REST API.
Overview
1. POST /api/organizations → Create organization (optional)
2. POST /api/cases → Create case with all data
3. POST /api/files/upload → Upload damage photos (auto-linked via vehicle_role)
4. POST /api/reports → Create report (starts async processing)
5. GET /api/reports/:id → Poll until status is "completed"
6. Download PDF/DOCX → Use signed URLs from output
Prerequisites
- An API key from the Silent Witness dashboard
- Vehicle damage photos (JPEG or PNG)
- Case information (accident details, vehicle info)
Step 1: Create an Organization
Organizations help you group cases by law firm or client.
- curl
- Python
- TypeScript
- Go
curl -X POST "https://api.silentwitness.ai/api/organizations" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Smith & Associates Law Firm",
"phone_number": "555-123-4567",
"street_address": "123 Main Street, Suite 400",
"city": "San Francisco",
"state": "CA",
"zip_code": "94102",
"country": "United States"
}'
import requests
BASE_URL = "https://api.silentwitness.ai"
API_KEY = "sk-..."
def api(method, path, **kwargs):
headers = kwargs.pop("headers", {})
headers["X-API-Key"] = API_KEY
resp = requests.request(method, f"{BASE_URL}{path}", headers=headers, **kwargs)
resp.raise_for_status()
return resp.json()
org = api("POST", "/api/organizations", json={
"name": "Smith & Associates Law Firm",
"phone_number": "555-123-4567",
"street_address": "123 Main Street, Suite 400",
"city": "San Francisco",
"state": "CA",
"zip_code": "94102",
"country": "United States",
})
org_id = org["data"]["id"]
const BASE_URL = "https://api.silentwitness.ai";
const API_KEY = "sk-...";
async function api(method: string, path: string, body?: object) {
const resp = await fetch(`${BASE_URL}${path}`, {
method,
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
return resp.json();
}
const org = await api("POST", "/api/organizations", {
name: "Smith & Associates Law Firm",
phone_number: "555-123-4567",
street_address: "123 Main Street, Suite 400",
city: "San Francisco",
state: "CA",
zip_code: "94102",
country: "United States",
});
const orgId = org.data.id;
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const baseURL = "https://api.silentwitness.ai"
var apiKey = "sk-..."
func api(method, path string, body any) (map[string]any, error) {
var reqBody io.Reader
if body != nil {
b, _ := json.Marshal(body)
reqBody = bytes.NewReader(b)
}
req, _ := http.NewRequest(method, baseURL+path, reqBody)
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func main() {
org, _ := api("POST", "/api/organizations", map[string]any{
"name": "Smith & Associates Law Firm",
"phone_number": "555-123-4567",
"street_address": "123 Main Street, Suite 400",
"city": "San Francisco",
"state": "CA",
"zip_code": "94102",
"country": "United States",
})
orgID := org["data"].(map[string]any)["id"].(string)
Response:
{
"success": true,
"data": {
"id": "org_abc123def456",
"name": "Smith & Associates Law Firm"
}
}
Step 2: Create a Case
Create a case with vehicles, accident information, and occupants in a single request. This example uses accident_injury which includes both crash analysis and biomechanics. For crash-only analysis, use accident_only and omit the occupants array.
- curl
- Python
- TypeScript
- Go
curl -X POST "https://api.silentwitness.ai/api/cases" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"plaintiff_name": "John Smith",
"defendant_name": "Bob Johnson",
"attorney_name": "Jane Attorney, Esq.",
"organization_id": "org_abc123def456",
"analysis_type": "accident_injury",
"accident": {
"description": "Rear-end collision at red light",
"date": "2024-01-10",
"time": "14:30",
"location": "123 Main St at Oak Ave, Los Angeles, CA"
},
"vehicles": [
{
"role": "plaintiff",
"vehicle_maker": "Toyota",
"vehicle_model": "Camry",
"vehicle_year": "2020"
},
{
"role": "defendant",
"vehicle_maker": "Ford",
"vehicle_model": "F-150",
"vehicle_year": "2019"
}
],
"occupants": [
{
"name": "John Smith",
"age": 42,
"gender": "male",
"height_inches": 70,
"weight_lbs": 180,
"position": "driver",
"seatbelt_worn": true,
"alleged_injuries": ["cervical_spine", "lumbar_spine", "shoulder"],
"injury_severity": "moderate"
}
]
}'
case = api("POST", "/api/cases", json={
"plaintiff_name": "John Smith",
"defendant_name": "Bob Johnson",
"attorney_name": "Jane Attorney, Esq.",
"organization_id": org_id,
"analysis_type": "accident_injury",
"accident": {
"description": "Rear-end collision at red light",
"date": "2024-01-10",
"time": "14:30",
"location": "123 Main St at Oak Ave, Los Angeles, CA",
},
"vehicles": [
{"role": "plaintiff", "vehicle_maker": "Toyota", "vehicle_model": "Camry", "vehicle_year": "2020"},
{"role": "defendant", "vehicle_maker": "Ford", "vehicle_model": "F-150", "vehicle_year": "2019"},
],
"occupants": [
{
"name": "John Smith",
"age": 42,
"gender": "male",
"height_inches": 70,
"weight_lbs": 180,
"position": "driver",
"seatbelt_worn": True,
"alleged_injuries": ["cervical_spine", "lumbar_spine", "shoulder"],
"injury_severity": "moderate",
},
],
})
case_id = case["data"]["case"]["id"]
const caseResp = await api("POST", "/api/cases", {
plaintiff_name: "John Smith",
defendant_name: "Bob Johnson",
attorney_name: "Jane Attorney, Esq.",
organization_id: orgId,
analysis_type: "accident_injury",
accident: {
description: "Rear-end collision at red light",
date: "2024-01-10",
time: "14:30",
location: "123 Main St at Oak Ave, Los Angeles, CA",
},
vehicles: [
{ role: "plaintiff", vehicle_maker: "Toyota", vehicle_model: "Camry", vehicle_year: "2020" },
{ role: "defendant", vehicle_maker: "Ford", vehicle_model: "F-150", vehicle_year: "2019" },
],
occupants: [
{
name: "John Smith",
age: 42,
gender: "male",
height_inches: 70,
weight_lbs: 180,
position: "driver",
seatbelt_worn: true,
alleged_injuries: ["cervical_spine", "lumbar_spine", "shoulder"],
injury_severity: "moderate",
},
],
});
const caseId = caseResp.data.case.id;
caseResp, _ := api("POST", "/api/cases", map[string]any{
"plaintiff_name": "John Smith",
"defendant_name": "Bob Johnson",
"attorney_name": "Jane Attorney, Esq.",
"organization_id": orgID,
"analysis_type": "accident_injury",
"accident": map[string]any{
"description": "Rear-end collision at red light",
"date": "2024-01-10",
"time": "14:30",
"location": "123 Main St at Oak Ave, Los Angeles, CA",
},
"vehicles": []map[string]any{
{"role": "plaintiff", "vehicle_maker": "Toyota", "vehicle_model": "Camry", "vehicle_year": "2020"},
{"role": "defendant", "vehicle_maker": "Ford", "vehicle_model": "F-150", "vehicle_year": "2019"},
},
"occupants": []map[string]any{
{
"name": "John Smith", "age": 42, "gender": "male",
"height_inches": 70, "weight_lbs": 180, "position": "driver",
"seatbelt_worn": true,
"alleged_injuries": []string{"cervical_spine", "lumbar_spine", "shoulder"},
"injury_severity": "moderate",
},
},
})
caseID := caseResp["data"].(map[string]any)["case"].(map[string]any)["id"].(string)
Response — note the case ID is at data.case.id:
{
"success": true,
"data": {
"case": {
"id": "case_xyz789abc123",
"name": "Smith v. Johnson",
"plaintiff_name": "John Smith"
}
}
}
Step 3: Upload Vehicle Damage Photos
Upload each damage photo as multipart form-data. The API supports JPEG, PNG, GIF, and WebP images up to 50MB. Set file_category to vehicle_photo and vehicle_role to plaintiff or defendant. Files are automatically linked to the correct vehicle.
- curl
- Python
- TypeScript
- Go
# Upload front damage photo
curl -X POST "https://api.silentwitness.ai/api/files/upload" \
-H "X-API-Key: $API_KEY" \
-F "file=@front_damage.jpg" \
-F "case_id=case_xyz789abc123" \
-F "file_category=vehicle_photo" \
-F "vehicle_role=plaintiff"
# Upload rear damage photo
curl -X POST "https://api.silentwitness.ai/api/files/upload" \
-H "X-API-Key: $API_KEY" \
-F "file=@rear_damage.jpg" \
-F "case_id=case_xyz789abc123" \
-F "file_category=vehicle_photo" \
-F "vehicle_role=plaintiff"
def upload_file(case_id, filepath, file_category, vehicle_role=None):
fields = {"case_id": case_id, "file_category": file_category}
if vehicle_role:
fields["vehicle_role"] = vehicle_role
with open(filepath, "rb") as f:
resp = requests.post(
f"{BASE_URL}/api/files/upload",
headers={"X-API-Key": API_KEY},
files={"file": (os.path.basename(filepath), f)},
data=fields,
)
resp.raise_for_status()
return resp.json()["data"]["file_id"]
file_ids = []
for photo in ["front_damage.jpg", "rear_damage.jpg"]:
file_id = upload_file(case_id, photo, "vehicle_photo", "plaintiff")
file_ids.append(file_id)
async function uploadFile(
caseId: string, filePath: string, fileCategory: string, vehicleRole?: string,
): Promise<string> {
const form = new FormData();
form.append("file", await Bun.file(filePath));
form.append("case_id", caseId);
form.append("file_category", fileCategory);
if (vehicleRole) form.append("vehicle_role", vehicleRole);
const resp = await fetch(`${BASE_URL}/api/files/upload`, {
method: "POST",
headers: { "X-API-Key": API_KEY },
body: form,
});
if (!resp.ok) throw new Error(`Upload failed: ${resp.status}`);
const data = await resp.json();
return data.data.file_id;
}
const fileIds: string[] = [];
for (const photo of ["front_damage.jpg", "rear_damage.jpg"]) {
fileIds.push(await uploadFile(caseId, photo, "vehicle_photo", "plaintiff"));
}
func uploadFile(caseID, filePath, fileCategory, vehicleRole string) (string, error) {
file, _ := os.Open(filePath)
defer file.Close()
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, _ := w.CreateFormFile("file", filepath.Base(filePath))
io.Copy(part, file)
w.WriteField("case_id", caseID)
w.WriteField("file_category", fileCategory)
if vehicleRole != "" {
w.WriteField("vehicle_role", vehicleRole)
}
w.Close()
req, _ := http.NewRequest("POST", baseURL+"/api/files/upload", &buf)
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
return result["data"].(map[string]any)["file_id"].(string), nil
}
// In main():
var fileIDs []string
for _, photo := range []string{"front_damage.jpg", "rear_damage.jpg"} {
fid, _ := uploadFile(caseID, photo, "vehicle_photo", "plaintiff")
fileIDs = append(fileIDs, fid)
}
Response — note the file ID is at data.file_id:
{
"success": true,
"data": {
"file_id": "file_img001",
"file_name": "front_damage.jpg",
"status": "ready"
}
}
Optional: Upload a Traffic Collision Report
If you have a police Traffic Collision Report, upload it with file_category=tcr_document. TCR is case-level — do not pass vehicle_role.
- curl
- Python
- TypeScript
- Go
curl -X POST "https://api.silentwitness.ai/api/files/upload" \
-H "X-API-Key: $API_KEY" \
-F "file=@traffic_collision_report.pdf" \
-F "case_id=case_xyz789abc123" \
-F "file_category=tcr_document"
tcr_id = upload_file(case_id, "traffic_collision_report.pdf", "tcr_document")
const tcrId = await uploadFile(caseId, "traffic_collision_report.pdf", "tcr_document");
tcrID, _ := uploadFile(caseID, "traffic_collision_report.pdf", "tcr_document", "")
When you later call POST /api/reports, the server automatically extracts accident details (date, time, location, description) from the TCR and populates the case before generating the report. No extra steps required.
Step 4: Create a Report
Report generation is asynchronous — this call returns immediately with status: "pending". You must poll for completion in the next step.
- curl
- Python
- TypeScript
- Go
curl -X POST "https://api.silentwitness.ai/api/reports" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"case_id": "case_xyz789abc123",
"type": "technical_report"
}'
report = api("POST", "/api/reports", json={
"case_id": case_id,
"type": "technical_report",
})
report_id = report["data"]["id"]
const report = await api("POST", "/api/reports", {
case_id: caseId,
type: "technical_report",
});
const reportId = report.data.id;
report, _ := api("POST", "/api/reports", map[string]any{
"case_id": caseID,
"type": "technical_report",
})
reportID := report["data"].(map[string]any)["id"].(string)
Add "options": {"use_demo_data": true} to use synthetic crash data. Reports complete in seconds instead of minutes.
Step 5: Poll for Completion and Download
Poll GET /api/reports/:id every 5 seconds until status is completed, then download the PDF and DOCX from the output URLs.
- curl
- Python
- TypeScript
- Go
# Poll until completed (repeat every 5 seconds)
curl "https://api.silentwitness.ai/api/reports/rpt_abc789xyz" \
-H "X-API-Key: $API_KEY"
# Once status is "completed", download the files:
curl -o report.pdf "$PDF_URL"
curl -o report.docx "$DOCX_URL"
import time
while True:
data = api("GET", f"/api/reports/{report_id}")["data"]
print(f" [{data['status']}] {data.get('progress', {}).get('message', '')}")
if data["status"] == "completed":
break
if data["status"] in ("failed", "cancelled"):
raise Exception(f"Report {data['status']}")
time.sleep(5)
# Download PDF and DOCX
for fmt in ("pdf", "docx"):
url = data["output"][f"{fmt}_url"]
resp = requests.get(url)
with open(f"report.{fmt}", "wb") as f:
f.write(resp.content)
print(f"Saved report.{fmt} ({len(resp.content):,} bytes)")
let completed: any;
while (true) {
const { data } = await api("GET", `/api/reports/${reportId}`);
console.log(` [${data.status}] ${data.progress?.message ?? ""}`);
if (data.status === "completed") { completed = data; break; }
if (data.status === "failed" || data.status === "cancelled") {
throw new Error(`Report ${data.status}`);
}
await new Promise((r) => setTimeout(r, 5000));
}
// Download PDF and DOCX
for (const fmt of ["pdf", "docx"] as const) {
const url = completed.output[`${fmt}_url`];
const resp = await fetch(url);
await Bun.write(`report.${fmt}`, resp);
console.log(`Saved report.${fmt}`);
}
for {
status, _ := api("GET", "/api/reports/"+reportID, nil)
data := status["data"].(map[string]any)
st := data["status"].(string)
fmt.Printf(" [%s]\n", st)
if st == "completed" {
output := data["output"].(map[string]any)
for _, ext := range []string{"pdf", "docx"} {
url := output[ext+"_url"].(string)
resp, _ := http.Get(url)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
os.WriteFile("report."+ext, body, 0644)
fmt.Printf("Saved report.%s (%d bytes)\n", ext, len(body))
}
break
}
if st == "failed" || st == "cancelled" {
fmt.Printf("Report %s\n", st)
break
}
time.Sleep(5 * time.Second)
}
}
Completed response:
{
"success": true,
"data": {
"id": "rpt_abc789xyz",
"status": "completed",
"progress": {
"steps_completed": ["delta_v_calculation", "biomechanics_analysis", "report_generation"],
"message": "Report generation complete"
},
"output": {
"pdf_url": "https://storage.silentwitness.ai/reports/rpt_abc789xyz.pdf",
"docx_url": "https://storage.silentwitness.ai/reports/rpt_abc789xyz.docx"
}
}
}
Download URLs expire after 1 hour. Call GET /api/reports/:id again for fresh URLs.
Complete Working Scripts
Each language has a complete, self-contained script you can copy and run:
| Language | Location | Run command |
|---|---|---|
| Python | examples/rest-api/python/main.py | API_KEY=sk-... python main.py |
| Go | examples/rest-api/technical-report/main.go | API_KEY=sk-... go run main.go |
Next Steps
- API Reference — Detailed endpoint documentation
- Error Handling — Error codes and resolution
- Analysis Types — Choose between accident-only and injury analysis