Migrating from the Legacy Endpoints
What actually changed
Section titled “What actually changed”The field names are identical between the legacy flow and POST /api/v1/partner/cases/create/. What changed is the request shape: three separate calls (declare the case, upload each file, mark ready) collapse into one multipart request — a case JSON string plus repeated files and labels parts.
Before: the legacy 3-step flow
Section titled “Before: the legacy 3-step flow”Three calls, in order. This example is a Policy Existence case (POST /policy_existence_searches/); the Policy Limit endpoint (POST /policy_limit_searches/) works the same way.
1. Declare the case. Same fields as the v2 case JSON below, plus the legacy-only files_to_upload list — this is how you declared, up front, which file labels you intended to upload:
curl -X POST https://api.redfolderresearch.com/policy_existence_searches/ \ -H "Authorization: Token <your_token>" \ -H "Content-Type: application/json" \ -d '{ "is_test": true, "plaintiff_name": "Jane Smith", "date_of_loss": "2026-06-20", "base_product": ["personal_auto_policy_existence"], "search_reason": ["defendant_insurance_unknown"], "is_incident_report_provided": false, "is_user_agreeing_to_all_terms_and_conditions": true, "should_exclude_other_insurance_from_search": false, "cc_emails": "paralegal@lawfirm.com", "defendant_information": { "first_name": "John", "last_name": "Doe", "address": "123 Main St", "city": "Boise", "state_or_province": "ID", "postal_or_zip_code": "83702" }, "files_to_upload": ["incident_report"] }'import requests
case = { "is_test": True, "plaintiff_name": "Jane Smith", "date_of_loss": "2026-06-20", "base_product": ["personal_auto_policy_existence"], "search_reason": ["defendant_insurance_unknown"], "is_incident_report_provided": False, "is_user_agreeing_to_all_terms_and_conditions": True, "should_exclude_other_insurance_from_search": False, "cc_emails": "paralegal@lawfirm.com", "defendant_information": { "first_name": "John", "last_name": "Doe", "address": "123 Main St", "city": "Boise", "state_or_province": "ID", "postal_or_zip_code": "83702", }, "files_to_upload": ["incident_report"],}
response = requests.post( "https://api.redfolderresearch.com/policy_existence_searches/", headers={"Authorization": "Token <your_token>"}, json=case,)ticket_id = response.json()["id"]const casePayload = { is_test: true, plaintiff_name: "Jane Smith", date_of_loss: "2026-06-20", base_product: ["personal_auto_policy_existence"], search_reason: ["defendant_insurance_unknown"], is_incident_report_provided: false, is_user_agreeing_to_all_terms_and_conditions: true, should_exclude_other_insurance_from_search: false, cc_emails: "paralegal@lawfirm.com", defendant_information: { first_name: "John", last_name: "Doe", address: "123 Main St", city: "Boise", state_or_province: "ID", postal_or_zip_code: "83702", }, files_to_upload: ["incident_report"],};
const response = await fetch("https://api.redfolderresearch.com/policy_existence_searches/", { method: "POST", headers: { "Authorization": "Token <your_token>", "Content-Type": "application/json", }, body: JSON.stringify(casePayload),});const ticketId = (await response.json()).id;The response includes the new ticket’s id — use it as {ticket_id} below.
2. Upload each declared file. One call per file, file_label must match an entry from files_to_upload:
curl -X POST https://api.redfolderresearch.com/upload/{ticket_id}/incident_report \ -H "Authorization: Token <your_token>" \ -F "file=@police_report.pdf"with open("police_report.pdf", "rb") as file_handle: response = requests.post( f"https://api.redfolderresearch.com/upload/{ticket_id}/incident_report", headers={"Authorization": "Token <your_token>"}, files={"file": file_handle}, )// fileBlob: a File (from an <input>) or Blob holding police_report.pdfconst form = new FormData();form.append("file", fileBlob, "police_report.pdf");
const response = await fetch(`https://api.redfolderresearch.com/upload/${ticketId}/incident_report`, { method: "POST", headers: { "Authorization": "Token <your_token>" }, body: form,});3. Mark the case ready. Fails with 400 if any declared label from step 1 was never uploaded:
curl -X PUT https://api.redfolderresearch.com/tickets/{ticket_id}/ready \ -H "Authorization: Token <your_token>"response = requests.put( f"https://api.redfolderresearch.com/tickets/{ticket_id}/ready", headers={"Authorization": "Token <your_token>"},)const response = await fetch(`https://api.redfolderresearch.com/tickets/${ticketId}/ready`, { method: "PUT", headers: { "Authorization": "Token <your_token>" },});After: one-shot create
Section titled “After: one-shot create”The same case, in a single multipart request. The case field is a JSON-encoded string — the object below is shown decoded for readability; encode it with JSON.stringify() (JS) or json.dumps() (Python) before sending:
curl -X POST https://api.redfolderresearch.com/api/v1/partner/cases/create/ \ -H "Authorization: Token <your_token>" \ -F 'case={"case_type":"pe","is_test":true,"plaintiff_name":"Jane Smith","date_of_loss":"2026-06-20","base_product":["personal_auto_policy_existence"],"search_reason":["defendant_insurance_unknown"],"is_incident_report_provided":false,"is_user_agreeing_to_all_terms_and_conditions":true,"should_exclude_other_insurance_from_search":false,"cc_emails":"paralegal@lawfirm.com","defendant_information":{"first_name":"John","last_name":"Doe","address":"123 Main St","city":"Boise","state_or_province":"ID","postal_or_zip_code":"83702"}}' \ -F "files=@police_report.pdf" \ -F "labels=incident_report"import json
import requests
case = { "case_type": "pe", "is_test": True, "plaintiff_name": "Jane Smith", "date_of_loss": "2026-06-20", "base_product": ["personal_auto_policy_existence"], "search_reason": ["defendant_insurance_unknown"], "is_incident_report_provided": False, "is_user_agreeing_to_all_terms_and_conditions": True, "should_exclude_other_insurance_from_search": False, "cc_emails": "paralegal@lawfirm.com", "defendant_information": { "first_name": "John", "last_name": "Doe", "address": "123 Main St", "city": "Boise", "state_or_province": "ID", "postal_or_zip_code": "83702", },}
with open("police_report.pdf", "rb") as file_handle: response = requests.post( "https://api.redfolderresearch.com/api/v1/partner/cases/create/", headers={"Authorization": "Token <your_token>"}, data={"case": json.dumps(case), "labels": "incident_report"}, files=[("files", file_handle)], )const casePayload = { case_type: "pe", is_test: true, plaintiff_name: "Jane Smith", date_of_loss: "2026-06-20", base_product: ["personal_auto_policy_existence"], search_reason: ["defendant_insurance_unknown"], is_incident_report_provided: false, is_user_agreeing_to_all_terms_and_conditions: true, should_exclude_other_insurance_from_search: false, cc_emails: "paralegal@lawfirm.com", defendant_information: { first_name: "John", last_name: "Doe", address: "123 Main St", city: "Boise", state_or_province: "ID", postal_or_zip_code: "83702", },};
// fileBlob: a File (from an <input>) or Blob holding police_report.pdfconst form = new FormData();form.append("case", JSON.stringify(casePayload));form.append("files", fileBlob, "police_report.pdf");form.append("labels", "incident_report");
const response = await fetch("https://api.redfolderresearch.com/api/v1/partner/cases/create/", { method: "POST", headers: { "Authorization": "Token <your_token>" }, body: form,});One request, one 201 response — the case is created and the file is attached in the same round trip. No separate declare, upload, or ready step.
Field-by-field mapping
Section titled “Field-by-field mapping”| Legacy | v2 (POST /api/v1/partner/cases/create/) |
|---|---|
files_to_upload (declared on the create body) |
Removed. Attach files directly as files parts; give each a label via a matching labels entry (same order). No pre-declaration step. |
| Everything else | Same field name and semantics, now nested inside the case JSON string instead of being the top-level request body — see the Data Dictionary. |
Gotchas
Section titled “Gotchas”| Issue | Cause | Solution |
|---|---|---|
You send files_to_upload inside the case JSON out of habit and it doesn’t declare, block, or validate anything. |
v2 pops files_to_upload off the case payload before validation and ignores it entirely — the file-declaration mechanism doesn’t exist on v2. The conditional rule that required it on the legacy endpoints (when is_incident_report_provided is true) is also removed for v2, since files are decoupled and best-effort. |
Drop files_to_upload from the case JSON. Attach files directly as files parts with matching labels entries in the same request. |
You’re waiting for a 400 like the legacy ready step’s “Missing file upload for declared file” and it never comes. |
v2 has no ready step — there’s one call, so there’s one point where case-level 400s can happen: validation of the case JSON at create time. File problems never fail the request; each file’s outcome (accepted or rejected) is reported per-file in the 201 response’s attachments array. |
Validate required/conditional fields client-side against Conditional Requirements before submitting, and check attachments[].status in the response for file-level outcomes. |
| You assume you need new credentials or a different auth scheme for v2. | Nothing changed — v2 sits behind the same authentication as the legacy endpoints. | Keep sending the same Authorization: Token <your_token> header you already use. |