API Documentation
REST endpoints that power AUTOWAY Control Tower
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.devBase URL (preview)
https://<preview>.pages.devAuthentication
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/jsonCORS
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
/api/ingest/batchIngest 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*importToken*sourcegeneratedAtactualImportedFilesbatchReadysummaryreservationsvehiclesemailBrainEventsgpsEventssystemHealthissuesmatricescurl
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 missingrunId/importToken.401— Missing or invalidx-autoway-api-key.
/api/ingest/issuesIngest alert issues only
Lightweight endpoint when you only need to push new/updated alert issues without a full batch payload.
Request body
runId*importToken*issuescurl
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"
}/api/issues/:id/statusUpdate issue lifecycle status
Move an alert issue through its lifecycle. Used by the Solve Board and any external automation.
Path params
id*Request body
status*actornotecurl
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"
}/api/issues/:id/commentAdd a comment to an issue
Append a note to the issue's action log without changing status.
Request body
actor*note*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" }/api/issues/:id/snoozeSnooze an issue until a future time
Hides the issue from active queues until the given timestamp.
Request body
until*actorcurl
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>;
};