Base URL & environments

https://api.<environment>.traffic-lights.io
EnvironmentBase URLUse
qahttps://api.qa.traffic-lights.ioIntegration testing, pilot validation
prodhttps://api.traffic-lights.ioProduction

All requests use HTTPS. HTTP is not accepted.

Authentication

Every route is authenticated. Two methods are supported.

MethodHeaderUse
Bearer tokenAuthorization: Bearer <token>Interactive or session-based callers.
API keyX-API-Key: <keyId>.<secret>Machine callers. Secret returned once at mint time.

Bearer wins. A route consults the API key only when no Authorization header is present. An empty Authorization header silently disables the key. Machine callers must send no Authorization header at all.

Example — API key

# Correct — no Authorization header
curl https://api.qa.traffic-lights.io/api/v1/processes \
  -H "X-API-Key: kid_abc123.secret_xyz"

# Wrong — Authorization header disables the key
curl https://api.qa.traffic-lights.io/api/v1/processes \
  -H "Authorization: " \
  -H "X-API-Key: kid_abc123.secret_xyz"

API key scopes

A key carries one or more scopes. Scopes are exact strings — not hierarchical.

ScopeGrants
readAll read routes (GET).
audit:writeThe approval-history write route only. No read access.
principal:resolvePOST /api/v1/principals/resolve only. No listing or search.
ingestReserved — no endpoint accepts it yet.

A key holding audit:write is refused by every read route. A key with an unrecognised scope returns 401 — indistinguishable from an invalid key.

Errors

StatusMeaning
401Missing, invalid, or insufficient credential.
403Credential valid but scope insufficient for this route.
404Resource not found or not visible to your tenant.
422Validation failure. Returns a structured detail array — one entry per violated field with path, rejected value, and machine-readable error type. All violations reported, not just the first.
5xxPlatform error. A first request to an idle service may take several seconds — retry once before treating as an error.

422 example

{
  "detail": [
    {
      "field":   "tasks[0].execType",
      "value":   "INVALID",
      "error":   "invalid_enum_value",
      "allowed": ["MACHINE", "HUMAN", "AGENT", "CORROBORATION", "NOOP"]
    }
  ]
}

Health

GET /health No auth required

Returns platform health. Use as a liveness check before integration tests.

# Response 200
{ "status": "ok" }

Processes

GET /api/v1/processes read

List process definitions for your tenant.

ParamInTypeDescription
limitqueryintMax results (default 25).
cursorquerystringPagination cursor from prior response.
POST /api/v1/processes read

Create a process definition. Versioned on write — each write creates a new version effective from the write timestamp.

{
  "name":        "Invoice Reconciliation",
  "description": "Nightly agent reconciliation with CFO sign-off gate",
  "tasks": [
    {
      "taskId":   "recon-agent",
      "execType": "AGENT",
      "label":    "Agent reconciliation"
    },
    {
      "taskId":        "cfo-signoff",
      "execType":      "HUMAN",
      "label":         "CFO sign-off",
      "deadlineSeconds": 86400
    }
  ],
  "flows": [
    {
      "from":      "recon-agent",
      "to":        "cfo-signoff",
      "condition": "detail.status == FAILED"
    }
  ]
}
GET /api/v1/processes/{id}/runs read

Run history for a process, newest first.

GET /api/v1/processes/{id}/status read

Current derived health (Red / Yellow / Green) and per-task state for the most recent run.

{
  "processId": "proc-001",
  "runId":     "MEC-20260916T044343Z",
  "health":   "GREEN",
  "tasks": [
    { "taskId": "recon-agent", "status": "SUCCESS" },
    { "taskId": "cfo-signoff",  "status": "PENDING" }
  ]
}
GET /api/v1/processes/{id}/policies read

Policies currently attached to a process, current version only.

Policies

Two policy types do nothing. delegate and branch policies are accepted, stored and returned, but nothing acts on them. Use retry, escalate, notify, and halt.

GET /api/v1/policies read

List all policy types available to your tenant.

POST /api/v1/policies read

Create a policy. Effective-dated on write. Changing a policy does not require a deployment.

{
  "processId":  "proc-001",
  "taskId":     "cfo-signoff",
  "policyType": "escalate",
  "config": {
    "escalateTo": "coo@yourco.com",
    "channel":    "email",
    "afterSeconds": 3600
  }
}
GET /api/v1/policies/{id} read

Read a specific policy version.

Approvals

GET /api/v1/approvals read

List pending approvals addressed to the authenticated caller. Returns approvals where the caller is the named assignee.

Role-holders who are not the named assignee may settle an approval that does not appear in their list. See Known limitations.

GET /api/v1/approvals/{id} read

Read a specific approval with full context: what the agent or system proposed, downstream consequence, policy version in force, and current decision chain.

{
  "approvalId":   "apr-20260916-04721",
  "runId":        "MEC-20260916T044343Z",
  "taskId":       "cfo-signoff",
  "assignee":     "cfo@yourco.com",
  "status":       "PENDING",
  "deadline":     "2026-09-17T04:43:43Z",
  "context": {
    "agentOutput": "Variance of $4,210 on invoice #48291",
    "confidence":  0.91,
    "consequence": "Approval posts the reconciled entry"
  },
  "policyVersion": "INVOICE_AUTO v3",
  "chain": []
}
POST /api/v1/approvals/{id}/approve read

Settle as approved. Run resumes and the next task dispatches. Decision is recorded with actor, timestamp, and policy version in force.

# Body optional
{ "note": "Reviewed source rows — variance within tolerance" }
POST /api/v1/approvals/{id}/reject read

Settle as rejected. Run does not continue past the rejected task. Decision and reason are recorded.

{ "reason": "Source invoice does not match. Routing to investigation." }
POST /api/v1/approvals/{id}/delegate read

Pass authority to a named delegate. Hop is recorded in the decision chain. Approval is repointed to the delegate.

{
  "delegateTo": "controller@yourco.com",
  "note":       "Delegating — out of office until Monday"
}

Delegation is unbounded. The permitted-hops value is displayed in the chain but not enforced. The platform does not refuse a delegation that exceeds it.

Approval history

POST /api/v1/approval-history audit:write

Write an approval history record. Append-only. Requires audit:write scope.

GET /api/v1/approval-history/{runId} read

Read the complete decision chain for a run — every decision, every hop, every actor, every timestamp.

{
  "runId": "MEC-20260916T044343Z",
  "chain": [
    {
      "actor":    "cfo@yourco.com",
      "kind":     "Person",
      "action":   "APPROVED",
      "at":       "2026-09-16T06:14:22Z",
      "policy":   "INVOICE_AUTO v3",
      "note":     "Variance within tolerance"
    }
  ]
}

Principal resolution

POST /api/v1/principals/resolve principal:resolve

Given identifiers you already hold, returns display name, identity kind and delivery address for each.

# Request
{ "identifiers": ["cfo@yourco.com", "svc-account-001"] }

# Response
{
  "resolved": [
    {
      "identifier": "cfo@yourco.com",
      "kind":       "Person",
      "displayName":"Sarah Chen",
      "email":      "cfo@yourco.com"
    },
    {
      "identifier": "svc-account-001",
      "kind":       "Service",
      "displayName":"Reconciliation service"
    }
  ]
}

Deliberately narrow. This route converts identifiers you already hold into delivery addresses. It cannot discover identifiers you do not have. Empty or wildcard requests are refused.

Event schema

Every participant communicates through the same event shape.

{
  "source":      "your-service-name",
  "detail-type": "TASK_COMPLETE",
  "detail": {
    "runID":    "MEC-20260916T044343Z",  // most important field
    "taskID":   "recon-agent",
    "status":   "SUCCESS",
    "severity": "INFO",
    "message":  "Reconciled 4,000 rows"
  }
}
FieldLevelRequiredDescription
sourceenvelopeyesWhere the event came from.
detail-typeenvelopeyesThe event category.
runIDdetailyesCorrelation identifier, end to end. Ties every event, decision and record to one run.
taskIDdetailyesWhich task this event concerns.
statusdetailyesSTARTED | SUCCESS | FAILED
severitydetailyesERROR | WARN | SUCCESS | INFO | DEBUG. Omitting defaults to INFO and may suppress notification delivery.
messagedetailnoHuman-readable description.
timestampdetailnoStamped automatically if omitted.

Severity is not decoration. Notification delivery filters on severity. Always set it explicitly.

Flow conditions

A flow may carry a single comparison that gates whether its downstream task dispatches.

detail.status == FAILED
detail.status == SUCCESS
detail.severity == ERROR
OperatorMeaning
==Equals
!=Not equals

Conditions fail open. An unparseable condition is treated as true — the branch runs regardless. Keep conditions to a single comparison.

SDK quickstart

The Python SDK wraps event emission. It is a convenience layer over the HTTP API — not required.

# Install
pip install traffic-lights-events

# Configure
export TL_API_BASE_URL="https://api.traffic-lights.io"
export TL_API_KEY="<your API key>"

# Emit an event
from traffic_lights_events import emit_event

emit_event('sai.tl.billing', 'FLOW_CALLBACK', {
    'runID':   'MEC-20260916T044343Z',
    'taskID':  '20006510',
    'status':  'SUCCESS',
})

The SDK is optional. Any system that can make an HTTPS POST can emit events directly. Node: npm install @traffic-lights/events. Full guides: SDKs & REST API.

# HTTP — works from any language or stack
curl -X POST https://api.traffic-lights.io/api/v1/events \
  -H "X-Api-Key: $TL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"source":"sai.tl.billing","detail-type":"FLOW_CALLBACK","detail":{"runID":"MEC-20260916T044343Z","taskID":"20006510","status":"SUCCESS"}}'

No SDK required. Any stack, any cloud. If it can make an HTTPS POST, it connects to Traffic Lights.

Execution types

TypeValueExecuted by
MachineMACHINEA system — DBT, Lambda, API call. Dispatched to an action; completion reported as an event.
Human sign-offHUMANA person — run pauses, approver decides.
Agent proposalAGENTAn AI agent — proposes an outcome, recorded as agent, subject to same policies as any outcome.
CorroborationCORROBORATIONA checker — compares a claimed value against an independent record. Verdict branches the process.
No-opNOOPA marker step — records that a stage was reached without dispatching work.

Roles

RolePermissions
AdministratorAuthor and modify process definitions, policies and users. Settle approvals.
Process ownerSettle approvals. Read everything in the tenant.
ApproverSettle approvals. Read everything in the tenant.
ViewerRead only.

Not yet available

CapabilityNotes
Event submission over RESTThe ingest scope is reserved. No endpoint accepts it yet.
One-tap email approvalApproval notifications link into the app. Approving directly from email without signing in is not yet available.
Segregation of dutiesThe platform does not record who requested a run, so it cannot prevent self-approval.
Structured approval inputApprovers may approve, reject, or delegate. Capturing structured input alongside a decision is not yet available.
Outcome attachmentA defect or incident discovered after a run cannot yet be attached to that run's record.
Fan-in on the process graphTwo branches cannot converge on a shared downstream task.

A claim you can check is a claim that must hold when you check it. Every item here is omitted from the API rather than documented as though it works.