API Documentation

REST endpoints that power AUTOWAY Control Tower

LiveLast sync run_20260525_0830

Overview

All endpoints live on the same Cloudflare Pages deployment as the dashboard. The Apps ScriptAlertBrain pushes operational batches via POST /api/ingest/batch. Operators update lifecycle from the dashboard (or any client) via the /api/issues/* endpoints.

Base URL (production)

https://your-app.pages.dev

Base URL (preview)

https://<preview>.pages.dev

Authentication

All write endpoints require the header x-autoway-api-key matching the server-side environment variable AUTOWAY_API_KEY. Set this in Cloudflare Pages → Settings → Environment Variables (Production + Preview). If the env var is unset the server accepts all callers (dev fallback) and logs a warning.

# Required header on every write request
x-autoway-api-key: <your-secret>
Content-Type: application/json

CORS

All endpoints respond to OPTIONS preflight with Access-Control-Allow-Origin: *. Allowed methods:GET, POST, PATCH, OPTIONS. Allowed headers:Content-Type, Authorization, x-autoway-api-key.

Endpoints

POST/api/ingest/batch

Ingest a full operational batch

Receives the complete snapshot produced by the Apps Script Brain Cron: reservations, vehicles, EmailBrain events, GPS events, system health, alert issues, and matrices.

Request body

runId*
string
Unique identifier for this Brain Cron run.
importToken*
string
Token tying together all files imported in this run.
source
string
Origin tag, e.g. 'apps_script_brain_cron'.
generatedAt
ISO timestamp
When the Apps Script produced this batch.
actualImportedFiles
number
Number of source files successfully imported.
batchReady
boolean
True if all required sources arrived.
summary
object
Free-form per-run summary (counts, durations).
reservations
ReservationSnapshot[]
Reservation rows from MasterReservations.
vehicles
VehicleSnapshot[]
Vehicle rows with GPS station/freshness.
emailBrainEvents
EmailBrainEvent[]
Send/failure events from EmailBrain logs.
gpsEvents
object[]
GPS warehouse rows for non-revenue/mismatch detection.
systemHealth
SystemHealthEvent[]
Brain Cron, scheduler, and integration heartbeats.
issues
AlertIssue[]
Pre-computed alert issues from AlertBrain.
matrices
OperationalMatrix[]
Decision matrices (mode, keybox, station, payment).

curl

curl -X POST 'https://your-app.pages.dev/api/ingest/batch' \
  -H 'Content-Type: application/json' \
  -H 'x-autoway-api-key: $AUTOWAY_API_KEY' \
  -d '{
    "runId": "run_2026_05_25_0700",
    "importToken": "tok_abc123",
    "source": "apps_script_brain_cron",
    "generatedAt": "2026-05-25T07:00:00Z",
    "actualImportedFiles": 5,
    "batchReady": true,
    "summary": { "reservations": 142, "alerts": 9 },
    "reservations": [],
    "vehicles": [],
    "emailBrainEvents": [],
    "gpsEvents": [],
    "systemHealth": [],
    "issues": [],
    "matrices": []
  }'

Response 200

{
  "ok": true,
  "runId": "run_2026_05_25_0700",
  "importToken": "tok_abc123",
  "receivedAt": "2026-05-25T07:00:01.234Z",
  "batchReady": true,
  "counts": {
    "reservations": 142,
    "vehicles": 86,
    "emailBrainEvents": 23,
    "gpsEvents": 17,
    "systemHealth": 4,
    "issues": 9,
    "matrices": 6
  }
}

Apps Script

function pushBatchToControlTower(payload) {
  const url = 'https://your-app.pages.dev/api/ingest/batch';
  const res = UrlFetchApp.fetch(url, {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-autoway-api-key': PropertiesService
        .getScriptProperties()
        .getProperty('AUTOWAY_API_KEY'),
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  });
  Logger.log(res.getResponseCode() + ' ' + res.getContentText());
}

Errors

  • 400 — Invalid JSON or missing runId/importToken.
  • 401 — Missing or invalid x-autoway-api-key.
POST/api/ingest/issues

Ingest alert issues only

Lightweight endpoint when you only need to push new/updated alert issues without a full batch payload.

Request body

runId*
string
Run identifier.
importToken*
string
Import token.
issues
AlertIssue[]
Array of alert issues — see schema at bottom.

curl

curl -X POST 'https://your-app.pages.dev/api/ingest/issues' \
  -H 'Content-Type: application/json' \
  -H 'x-autoway-api-key: $AUTOWAY_API_KEY' \
  -d '{"runId":"run_1","importToken":"tok_1","issues":[]}'

Response 200

{
  "ok": true,
  "runId": "run_1",
  "issuesReceived": 0,
  "receivedAt": "2026-05-25T07:00:02.000Z"
}
PATCH/api/issues/:id/status

Update issue lifecycle status

Move an alert issue through its lifecycle. Used by the Solve Board and any external automation.

Path params

id*
string
Alert issue ID.

Request body

status*
enum
One of: open, in_progress, waiting, snoozed, resolved, ignored.
actor
string
Operator name for the audit log.
note
string
Optional note attached to the status change.

curl

curl -X PATCH 'https://your-app.pages.dev/api/issues/i_123/status' \
  -H 'Content-Type: application/json' \
  -H 'x-autoway-api-key: $AUTOWAY_API_KEY' \
  -d '{"status":"resolved","actor":"Maria"}'

Response 200

{
  "ok": true,
  "id": "i_123",
  "status": "resolved",
  "updatedAt": "2026-05-25T07:05:00.000Z"
}
POST/api/issues/:id/comment

Add a comment to an issue

Append a note to the issue's action log without changing status.

Request body

actor*
string
Operator name.
note*
string
Comment body.

curl

curl -X POST 'https://your-app.pages.dev/api/issues/i_123/comment' \
  -H 'Content-Type: application/json' \
  -H 'x-autoway-api-key: $AUTOWAY_API_KEY' \
  -d '{"actor":"Maria","note":"Called driver, will pay on arrival."}'

Response 200

{ "ok": true, "id": "i_123", "createdAt": "2026-05-25T07:06:00.000Z" }
POST/api/issues/:id/snooze

Snooze an issue until a future time

Hides the issue from active queues until the given timestamp.

Request body

until*
ISO timestamp
When the snooze expires.
actor
string
Operator name.

curl

curl -X POST 'https://your-app.pages.dev/api/issues/i_123/snooze' \
  -H 'Content-Type: application/json' \
  -H 'x-autoway-api-key: $AUTOWAY_API_KEY' \
  -d '{"until":"2026-05-26T08:00:00Z","actor":"Maria"}'

Response 200

{ "ok": true, "id": "i_123", "snoozedUntil": "2026-05-26T08:00:00Z" }

Core schemas

Full TypeScript definitions live in src/types/ops.ts. The most important shape for Apps Script is AlertIssue:

type AlertIssue = {
  id: string;
  batchId: string;
  source: "reservation" | "logistics" | "payment" | "emailbrain" | "gps" | "system" | "manual";
  alertCode: string;          // e.g. "ALERT_SELFP_UNPAID"
  title: string;
  summary: string;
  details?: string;
  severity: "critical" | "warning" | "info";
  urgency: "now" | "today" | "tomorrow" | "day2" | "future" | "unknown";
  category: string;
  station?: "HER" | "CHN" | "RTH" | "OTHER";
  resNo?: string;
  irn?: string;
  driverName?: string;
  driverEmail?: string;
  assignedCar?: string;
  vehiclePlate?: string;
  eventDate?: string;          // ISO
  status: "open" | "in_progress" | "waiting" | "snoozed" | "resolved" | "ignored";
  owner?: string;
  recommendedActionCode?: string;
  recommendedActionTitle?: string;
  recommendationWhy?: string;
  confidence: number;          // 0..1
  tags?: string[];
  matrixKey?: string;
  dedupeKey: string;           // stable across runs for upsert
  firstSeenAt: string;         // ISO
  lastSeenAt: string;          // ISO
  blocksSelfP?: boolean;
  paymentBlocker?: boolean;
  pickupInstructionsFailed?: boolean;
  vehicleStationMismatch?: boolean;
  keyboxConflict?: boolean;
  systemHealthCritical?: boolean;
  rawJson?: Record<string, unknown>;
};