All endpoints are served by this app at https://<your-app>/api/blue/* and return JSON. They proxy Blue's GraphQL API using the org token stored server-side — so callers (GHL, scripts, n8n) send no Blue credentials; just call the URL with a JSON body.
Every response is shaped { success: boolean, ... }. On failure you get { success: false, message, error } with an HTTP 4xx/5xx. Writes accept an optional projectId (the SID id) to scope the request.
Server env (set in Vercel): BLUE_TOKEN_ID, BLUE_TOKEN_SECRET, BLUE_COMPANY_ID, BLUE_TEMPLATE_PROJECT_ID. Generated in Blue → Organisation Settings → API.
In a GHL Workflow, add a Webhook action (or Custom Webhook):
https://<your-app>/api/blue/...Content-Type: application/json (no auth header needed — the server holds the Blue token).{{ contact.study_id }}.Example: when a study is approved, create a kickoff card and assign it
POST https://<your-app>/api/blue/todos
Content-Type: application/json
{
"todoListId": "{{ custom_values.sid_up_next_list_id }}",
"title": "Kickoff — {{ contact.study_name }}",
"text": "Auto-created from GHL when the study was approved.",
"projectId": "{{ custom_values.sid_project_id }}"
}
# → returns { "success": true, "card": { "id": "...", "title": "..." } }
# Feed card.id into a follow-up Webhook step to assign:
POST https://<your-app>/api/blue/todos/{{ card.id }}/assign
{ "user": "Josh Nichols", "projectId": "{{ custom_values.sid_project_id }}" }Tip: a one-time GET /api/blue/lists?projectId=… gives you the column ids to store as GHL custom values.
/api/blue/projectsGet every study workspace in the PVIP org (id + name + timestamps).
Full URL
GET https://<your-app>/api/blue/projectsExample response
{
"success": true,
"count": 156,
"projects": [
{
"id": "cm6awe5ja00kk38pr0zb04up8",
"uid": "b872eb79349344f88213e05ff6fae2db",
"name": "SID313 Foot Ulcer Cari Clinical Trials",
"description": "",
"createdAt": "2025-01-06T16:26:57.652Z",
"updatedAt": "2026-02-10T07:04:12.038Z"
}
]
}/api/blue/projectsStart a new study. Clones the 'new template' workspace (lists + cards) and renames it. Pass fromTemplate:false for an empty workspace.
Full URL
POST https://<your-app>/api/blue/projectsRequest body
{
"name": "SID365 ERG Psoriasis",
"description": "optional",
"fromTemplate": true
}Example response
{
"success": true,
"project": {
"id": "viypjx3a2c3da5ms85d9aafi",
"uid": "9ea18ceb27944302a92badcb44fdec7a",
"name": "SID365 ERG Psoriasis",
"description": "",
"createdAt": "2025-01-06T16:26:57.652Z",
"updatedAt": "2025-06-10T08:54:10.985Z"
}
}/api/blue/projects/{id}Read a study's full board — every column with its nested cards and assignees.
Full URL
GET https://<your-app>/api/blue/projects/{id}Example response
{
"success": true,
"project": {
"id": "viypjx3a2c3da5ms85d9aafi",
"name": "SID365 ERG Psoriasis",
"todoLists": [
{
"id": "h3f5w66fevrjuc4nxn9ppmze",
"title": "Up Next",
"position": 49151.25,
"todosCount": 5,
"todos": [
{
"id": "ehz6jonp9i1yi2e2ab7cwrp7",
"title": "🎨 Creatives Development",
"text": "These are for all of the images...",
"done": false,
"duedAt": null,
"users": []
}
]
}
]
}
}/api/blue/lists?projectId={id}Get just the columns (Up Next / Pending / In Progress / Completed) and their card counts.
Full URL
GET https://<your-app>/api/blue/lists?projectId={id}Example response
{
"success": true,
"count": 4,
"lists": [
{ "id": "h3f5w66fevrjuc4nxn9ppmze", "title": "Up Next", "position": 49151.25, "todosCount": 5 },
{ "id": "vo9720gzurx9skax4lxwpr0i", "title": "Pending", "position": 57343.125, "todosCount": 11 },
{ "id": "il1tct7kv9u2zbicwpsjtt1s", "title": "In Progress", "position": 65535, "todosCount": 0 },
{ "id": "grjg3rdz5v7oioy5kbon8u0e", "title": "Completed", "position": 131070, "todosCount": 0 }
]
}/api/blue/listsAdd a new column to a SID. Omit position to append after the last one.
Full URL
POST https://<your-app>/api/blue/listsRequest body
{
"projectId": "viypjx3a2c3da5ms85d9aafi",
"title": "QA"
}Example response
{
"success": true,
"list": { "id": "cmqrqwxv205msnu01vwb76ep4", "title": "QA", "position": 196605 }
}/api/blue/todos?projectId={id}&todoListId={listId}List cards in a SID (todoListId optional to filter to one column). Each card is tagged with its column.
Full URL
GET https://<your-app>/api/blue/todos?projectId={id}&todoListId={listId}Example response
{
"success": true,
"count": 17,
"cards": [
{
"id": "0073bea3371740bf92a9cf08d348e6f3",
"title": "Build landing page",
"text": "See patients.vip [https://patients.vip] for brand.",
"done": false,
"duedAt": null,
"users": [],
"todoList": { "id": "h3f5w66fevrjuc4nxn9ppmze", "title": "Up Next" }
}
]
}/api/blue/todosAdd a task to a column. dueDate is ISO 8601. Pass projectId to scope the request.
Full URL
POST https://<your-app>/api/blue/todosRequest body
{
"todoListId": "h3f5w66fevrjuc4nxn9ppmze",
"title": "Build landing page",
"text": "See https://patients.vip for brand.",
"dueDate": "2026-07-01T00:00:00Z",
"projectId": "viypjx3a2c3da5ms85d9aafi"
}Example response
{
"success": true,
"card": {
"id": "0073bea3371740bf92a9cf08d348e6f3",
"uid": "a108d3b350d14e5ca7f02c097c284f0d",
"title": "Build landing page"
}
}/api/blue/todos/{id}?projectId={id}Read a card's content, extracted links, checklists, files and assignees.
Full URL
GET https://<your-app>/api/blue/todos/{id}?projectId={id}Example response
{
"success": true,
"card": {
"id": "0073bea3371740bf92a9cf08d348e6f3",
"title": "Build landing page (v2)",
"text": "See patients.vip [https://patients.vip] for brand.",
"html": "See <a href=\"https://patients.vip\">patients.vip</a> for brand.",
"done": false,
"commentCount": 1,
"todoList": { "id": "h3f5w66fevrjuc4nxn9ppmze", "title": "Up Next" },
"users": [{ "id": "cm3exicja2kyrpo5om57ux2e9", "fullName": "Josh Nichols" }],
"checklists": [],
"links": [{ "url": "https://patients.vip", "label": "patients.vip" }]
}
}/api/blue/todos/{id}Change title/text/dueDate, mark done, or move to another column (set todoListId).
Full URL
PATCH https://<your-app>/api/blue/todos/{id}Request body
{
"title": "Build landing page (v2)",
"done": true,
"todoListId": "grjg3rdz5v7oioy5kbon8u0e",
"projectId": "viypjx3a2c3da5ms85d9aafi"
}Example response
{
"success": true,
"card": { "id": "0073bea3371740bf92a9cf08d348e6f3", "title": "Build landing page (v2)" }
}/api/blue/todos/{id}/assignProvide a Blue userId, or `user` as a name/email to resolve. action defaults to 'assign'.
Full URL
POST https://<your-app>/api/blue/todos/{id}/assignRequest body
{
"user": "Josh Nichols",
"action": "assign",
"projectId": "viypjx3a2c3da5ms85d9aafi"
}Example response
{
"success": true,
"action": "assign",
"userId": "cm3exicja2kyrpo5om57ux2e9",
"result": { "success": true }
}/api/blue/todos/{id}/comments?projectId={id}Read the comment thread on a card.
Full URL
GET https://<your-app>/api/blue/todos/{id}/comments?projectId={id}Example response
{
"success": true,
"totalCount": 1,
"comments": [
{
"id": "cmqrqx13t05nmnu01tpgpfsnr",
"text": "Kicking this off — see patients.vip [https://patients.vip]",
"createdAt": "2026-06-24T07:22:28.698Z",
"user": { "id": "cm3exicja2kyrpo5om57ux2e9", "fullName": "Josh Nichols" }
}
]
}/api/blue/todos/{id}/commentsPost a comment to a card.
Full URL
POST https://<your-app>/api/blue/todos/{id}/commentsRequest body
{
"text": "Kicking this off",
"projectId": "viypjx3a2c3da5ms85d9aafi"
}Example response
{
"success": true,
"comment": {
"id": "cmqrqx13t05nmnu01tpgpfsnr",
"text": "Kicking this off",
"createdAt": "2026-06-24T07:22:28.698Z"
}
}/api/blue/users?q={search}Find a userId by name/email before assigning a card. q is an optional filter.
Full URL
GET https://<your-app>/api/blue/users?q={search}Example response
{
"success": true,
"count": 1,
"users": [
{ "id": "cm3exicja2kyrpo5om57ux2e9", "fullName": "Josh Nichols", "email": "" }
]
}