API Reference

The XRAY Task Inbox API lets you create, read, update, and triage tasks programmatically. All endpoints return JSON.

Authentication

Obtaining an API Key

API keys are configured server-side via the API_KEY_MAP environment variable. Each key maps to an email address and display name:

API_KEY_MAP=your-secret-key-here:you@xray.tech:Your Name

To get an API key:

  1. Ask a system administrator to add an entry to the API_KEY_MAP environment variable on the server
  2. The format is key:email:name — multiple entries are comma-separated
  3. Your tasks will be scoped to the email address associated with your key

Using Your API Key

Include the X-API-Key header in every request:

curl -H "X-API-Key: your-secret-key-here" \
     -H "Content-Type: application/json" \
     https://tasks.xray.tech/api/tasks

Web Authentication

Web users authenticate via Google OAuth. Sign in at the app root (/) with your @xray.tech account. The session cookie is sent automatically with browser requests.

Auth Check

GET /auth/me

Returns the current user or 401 if not authenticated.

// 200 OK
{ "user": { "email": "mark@xray.tech", "name": "Mark Campos" } }

// 401 Unauthorized
{ "error": "Not authenticated" }

Endpoints

GET /api/tasks

List inbox tasks (Status = "Backlog", Triage Status = "New") scoped to the authenticated user.

Example

curl -H "X-API-Key: your-key" https://tasks.xray.tech/api/tasks

Response

{
  "tasks": [
    {
      "id": "recXXXXXXXXXXXXXX",
      "title": "Update onboarding docs",
      "description": "The onboarding guide needs updating for new hires",
      "status": "Backlog",
      "priority": "Normal",
      "triageStatus": "New",
      "origin": "Human",
      "originDetail": "Mark via Slack",
      "triageNotes": "",
      "assignedTo": { "id": "usr...", "email": "mark@xray.tech", "name": "Mark Campos" },
      "requestedBy": null,
      "project": [],
      "dueDate": null,
      "startDate": null,
      "hoursEstimated": null,
      "hoursUsed": null,
      "createdAt": "2026-03-10T14:30:00.000Z"
    }
  ]
}

GET /api/tasks/:id

Get a single task by its Airtable record ID.

Example

curl -H "X-API-Key: your-key" https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX

Response

{ "task": { "id": "recXXXXXXXXXXXXXX", "title": "...", ... } }

POST /api/tasks

Create a new inbox task. Sets Status = "Backlog" and Triage Status = "New" automatically.

Request Body

FieldTypeRequiredNotes
titlestringYesMax 120 characters
descriptionstringNoMax 500 characters
originstringNoDefault: "Human". Options: Human, AI Agent, Automation
originDetailstringNoE.g., "Claude Code", "Zapier webhook"
assignedTostringNoEmail address. Defaults to authenticated user
prioritystringNoDefault: "Normal". Options: Normal, High, Urgent
projectstringNoAirtable record ID of a project

Example — Minimal

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"title": "Review Q1 metrics"}' \
  https://tasks.xray.tech/api/tasks

Example — Full

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Update client proposal",
    "description": "Revise the SOW section with new pricing",
    "origin": "AI Agent",
    "originDetail": "Claude Code",
    "priority": "High",
    "assignedTo": "mark@xray.tech"
  }' \
  https://tasks.xray.tech/api/tasks

Response (201 Created)

{ "ok": true, "task": { "id": "recNEW...", "title": "Update client proposal", ... } }

Error Response (400)

{ "error": "Validation failed", "fields": { "title": "Required" } }

PATCH /api/tasks/:id

Partial update — only include fields you want to change.

Updatable Fields

FieldTypeNotes
titlestring
descriptionstring
prioritystringNormal, High, Urgent
statusstringBacklog, In Progress, Done, etc.
triageStatusstringNew, Accepted, Rejected, Needs More Info
triageNotesstring
dueDatestringFormat: YYYY-MM-DD
startDatestringFormat: YYYY-MM-DD
hoursEstimatednumber
assignedTostringEmail address (e.g., someone@xray.tech)
projectstringAirtable record ID
originDetailstring

Example — Enrich a task

curl -X PATCH \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{
    "dueDate": "2026-03-15",
    "hoursEstimated": 4,
    "priority": "High"
  }' \
  https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX

Example — Mark as done

curl -X PATCH \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"status": "Done", "triageStatus": "Accepted"}' \
  https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX

POST /api/tasks/:id/accept

Accept a task and link it to a project.

Request Body

FieldTypeRequired
projectIdstringYes

Example

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "recPROJECT123"}' \
  https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX/accept

POST /api/tasks/:id/reject

Reject a task with a reason.

Request Body

FieldTypeRequired
reasonstringYes

Example

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Duplicate of existing task"}' \
  https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX/reject

POST /api/tasks/:id/needs-info

Request more information before triaging.

Request Body

FieldTypeRequired
notestringYes

Example

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"note": "Which client is this for?"}' \
  https://tasks.xray.tech/api/tasks/recXXXXXXXXXXXXXX/needs-info

GET /api/projects?q=search

Search projects by name (min 2 characters). Returns up to 10 matches.

Example

curl -H "X-API-Key: your-key" "https://tasks.xray.tech/api/projects?q=onboard"

Response

{ "projects": [{ "id": "recPROJECT123", "name": "Client Onboarding v2" }] }

GET /health

Health check (no auth required).

{ "status": "ok", "timestamp": "2026-03-10T15:00:00.000Z" }

Error Responses

All errors return a JSON object with an error field:

StatusMeaningExample
400Bad request / validation{ "error": "Validation failed", "fields": { "title": "Required" } }
401Not authenticated{ "error": "Not authenticated" }
404Not found{ "error": "Task not found" }
502Airtable upstream error{ "error": "Failed to fetch tasks from Airtable" }