Complete documentation for the RoutelyOS field service management platform API. 40 endpoints across 10 categories. All request bodies are validated with Zod schemas.
https://routelyos.comOpenAPI 3.1 SpecPublic endpoints require no authentication but are IP rate-limited.
HQ endpoints (requireHQAuth) require a valid Supabase session cookie from the admin dashboard.
Cron endpoints require Authorization: Bearer $CRON_SECRET header.
Stripe webhook verifies the stripe-signature header against STRIPE_WEBHOOK_SECRET.
All errors follow a consistent JSON structure:
{
"ok": false,
"error": "Human-readable error message",
"details": ["optional", "validation", "errors"]
}HTTP status codes: 400 (validation), 401 (unauthorized), 404 (not found), 429 (rate limited), 500 (server error), 502/504 (upstream).
No authentication required. Rate-limited by IP.
/api/geocodeForward geocode an address query via Nominatim. Returns lat/lng results.
Query params: q: string (1-500 chars, required)
{
"ok": true,
"data": {
"results": [
{ "lat": 38.88, "lng": -77.10, "label": "..." }
]
}
}curl "https://routelyos.com/api/geocode?q=Alexandria+VA"
/api/coverageGenerate drive-time isochrone polygons via Valhalla. Returns real GeoJSON coverage areas.
{
"lat": number // -90 to 90 (required)
"lng": number // -180 to 180 (required)
"maxDrive": number // 1-180 minutes (default: 60)
"contours": number[] // optional custom contour values
}{
"ok": true,
"data": {
"polygons": [{ "minutes": 60, "coords": [[lat,lng],...] }],
"source": "valhalla",
"generatedAt": "ISO string",
"requestedMinutes": 60,
"actualMinutes": 60
}
}curl -X POST https://routelyos.com/api/coverage \
-H "Content-Type: application/json" \
-d '{"lat":38.88,"lng":-77.10,"maxDrive":45}'/api/isochroneRaw Valhalla isochrone proxy. Returns GeoJSON feature collection for drive-time contours.
{
"lat": number // -90 to 90 (required)
"lng": number // -180 to 180 (required)
"time": number // 1-180 minutes (default: 60)
"contours": [{ // optional, 1-10 items
"time": number,
"color": string
}]
}GeoJSON FeatureCollection from Valhalla
curl -X POST https://routelyos.com/api/isochrone \
-H "Content-Type: application/json" \
-d '{"lat":38.88,"lng":-77.10,"time":30}'/api/vitalsStore web vitals metrics (LCP, FID, CLS, etc.) to Supabase.
{
"metric": string // max 100 chars (default: "unknown")
"value": number // (default: 0)
"page": string // max 500 chars (default: "/")
"id": string? // max 200 chars, nullable
}{ "ok": true, "data": { "received": true } }/api/vitalsHealth check. Returns status and version.
{
"ok": true,
"data": {
"status": "ok",
"timestamp": "ISO string",
"version": "1.0.0"
}
}curl https://routelyos.com/api/vitals
/api/reportsGenerate business reports (P&L, jobs, workers, revenue, coverage).
Query params: type: "pnl" | "jobs" | "workers" | "revenue" | "coverage" (default: "pnl") franchise: string? (max 200 chars)
{
"ok": true,
"data": {
"type": "pnl",
"franchise": "all",
"generatedAt": "ISO string",
"data": { ... }
}
}Customer-facing endpoints for email verification and booking cancellation.
/api/customer/verifyTwo-step email verification. First call (email only) sends a 6-digit code. Second call (email + code) verifies it.
Step 1 — Send code:
{
"email": string // valid email, max 254 chars
}
Step 2 — Verify code:
{
"email": string,
"code": string // exactly 6 digits
}// Send: { "ok": true, "data": { "sent": true } }
// Verify: { "ok": true, "data": { "verified": true } }# Send code
curl -X POST https://routelyos.com/api/customer/verify \
-H "Content-Type: application/json" \
-d '{"email":"customer@example.com"}'
# Verify code
curl -X POST https://routelyos.com/api/customer/verify \
-H "Content-Type: application/json" \
-d '{"email":"customer@example.com","code":"123456"}'/api/customer/cancelCancel a booking. Processes Stripe refund if payment was charged, cancels setup intent for card-on-file. Sends email/SMS notification.
{
"jobId": string // required, max 200 chars
"paymentId": string? // Stripe payment intent or setup intent ID
"paymentStatus": string? // "charged" | "card_on_file" | "pending"
"amount": number? // 0-99999
"email": string? // for cancellation notification
"phone": string? // for SMS notification
"type": string? // service type label
"dateISO": string? // scheduled date
}{
"ok": true,
"data": {
"refunded": boolean,
"refundId": string?,
"setupIntentCancelled": boolean,
"message": string
}
}curl -X POST https://routelyos.com/api/customer/cancel \
-H "Content-Type: application/json" \
-d '{"jobId":"abc-123","paymentId":"pi_xxx","paymentStatus":"charged"}'Job creation endpoint. Inserts into Supabase and triggers welcome email.
/api/jobs/createCreate a new service job. Inserts into Supabase, logs activity, fires welcome email to customer.
{
"customerName": string // required, max 200
"customerEmail": string? // valid email, max 200
"customerPhone": string? // max 50, digits/spaces/dashes
"address": string // required, max 500
"type": string // required, service type, max 200
"price": number // 0-99999, required
"preferredDate": string? // ISO date string, max 30
"preferredTime": string? // e.g. "9:00 AM - 12:00 PM", max 30
"addOns": string[] // optional array of add-on labels
"franchiseId": string? // max 100
"notes": string? // max 1000
"paymentId": string? // Stripe payment intent ID, max 200
"paymentStatus": string? // max 50
"gutterLength": number?
"stories": number?
}{
"ok": true,
"data": {
"job": { /* full Supabase row */ },
"message": "Job created successfully"
}
}curl -X POST https://routelyos.com/api/jobs/create \
-H "Content-Type: application/json" \
-d '{
"customerName": "John Doe",
"address": "123 Main St, Alexandria VA",
"type": "Gutter Cleaning",
"price": 249
}'Checkout sessions, setup intents, and webhook handling.
/api/stripe/checkoutCreate a Stripe Checkout Session for one-time payment. Returns the session URL for redirect.
{
"amount": number // 1-999999 (in cents), required
"customerName": string? // max 200
"customerEmail": string? // valid email, max 254
"jobDescription": string? // max 500
"returnUrl": string? // max 2000, must be same-origin
}{
"ok": true,
"data": {
"url": "https://checkout.stripe.com/...",
"sessionId": "cs_xxx"
}
}curl -X POST https://routelyos.com/api/stripe/checkout \
-H "Content-Type: application/json" \
-d '{"amount":24900,"customerEmail":"customer@example.com","jobDescription":"Gutter Cleaning"}'/api/stripe-webhookStripe webhook receiver. Verifies signature, processes events (payment_intent.succeeded, payment_intent.payment_failed, etc.), triggers franchise payouts, emails, and invoice generation.
Raw Stripe event JSON (max 64 KB). Requires stripe-signature header.
{ "received": true, "eventId": "evt_xxx" }Franchise onboarding, payout status, and transfer dashboard. All require HQ authentication.
/api/stripe/connect/onboardInitiate Stripe Connect onboarding for a franchise. Creates a Standard account and returns the onboarding URL.
{
"franchiseId": string // required, max 100
}{
"ok": true,
"data": { "url": "https://connect.stripe.com/setup/..." }
}/api/stripe/connect/statusCheck Stripe Connect onboarding status for a franchise. Syncs status back to Supabase.
Query params: franchiseId: string (required, max 100)
{
"ok": true,
"data": {
"connected": boolean,
"accountId": string | null,
"chargesEnabled": boolean,
"payoutsEnabled": boolean,
"detailsSubmitted": boolean,
"onboardingComplete": boolean
}
}/api/stripe/connect/dashboardFranchise transfer history and payout summary. Shows completed/pending transfers and platform fees.
Query params: franchiseId: string (required, max 100)
{
"ok": true,
"data": {
"transfers": [...],
"totalPaid": number,
"totalPending": number,
"totalPlatformFees": number,
"balance": number
}
}/api/stripe/connect/returnOAuth return handler after Stripe Connect onboarding. Updates franchise status in Supabase and redirects to admin dashboard.
Query params: account_id: string
302 Redirect to /admin?tab=franchise&connect={status}Invoice generation and delivery. Requires HQ authentication.
/api/invoices/generateGenerate an invoice from a job ID. Fetches job from Supabase, builds line items, stores invoice record.
{
"jobId": string // required, max 200
}{
"ok": true,
"data": {
"invoice": { /* Supabase row */ },
"invoiceData": { /* structured invoice */ },
"html": "<div>...",
"message": "Invoice generated"
}
}/api/invoices/sendSend an existing invoice via email or SMS. Updates invoice status to 'sent'.
{
"invoiceId": string // required, max 200
"method": "email" | "sms"
}{
"ok": true,
"data": {
"invoice": { /* updated row */ },
"message": "Invoice sent via email"
}
}Recurring billing schedule management. Requires HQ authentication.
/api/billing/recurringList recurring billing schedules. Filter by franchise and active status.
Query params: franchiseId: string? (max 200) active: "true" | "false"?
{
"ok": true,
"data": {
"schedules": [...],
"count": number
}
}/api/billing/recurringCreate a recurring billing schedule for a customer. Links to a recurring job template.
{
"recurringScheduleId": string // required, max 200
"franchiseId": string? // max 200
"customerId": string // required, max 200
"customerName": string // required, max 200
"customerEmail": string? // valid email, max 200
"serviceType": string // required, max 200
"intervalMonths": number // 1-24 integer
"price": number // 0-99999
"autoCharge": boolean // default: false
"stripeCustomerId": string? // max 200
"nextBillingDate": string? // ISO date, max 30
}{
"ok": true,
"data": {
"schedule": { /* Supabase row */ },
"message": "Recurring billing schedule created"
}
}Bulk messaging to customers. Requires HQ authentication.
/api/campaigns/sendSend bulk email/SMS campaign to customers from completed jobs. Supports audience targeting (all, new, returning).
{
"type": "email" | "sms" | "both"
"targetAudience": "all" | "new" | "returning" // default: "all"
"messageTemplate": string // required, max 5000 chars
// supports {name} and {email} placeholders
"subject": string? // email subject, max 200
"franchiseId": string? // filter by franchise, max 100
}{
"ok": true,
"data": {
"sent": number,
"failed": number,
"total": number,
"errors": string[]?
}
}Scheduled tasks triggered by Vercel Cron. Authenticated via CRON_SECRET Bearer token.
/api/cron/auto-dispatchAuto-dispatch unscheduled jobs to best-fit workers. Runs every 15 min, Mon-Sat 7 AM - 6 PM.
Authorization: Bearer {CRON_SECRET}{
"ok": true,
"data": {
"success": true,
"assigned": number,
"escalated": number,
"skipped": number,
"actions": [{ "type": "assigned", "jobId": "...", "worker": "..." }],
"timestamp": "ISO string"
}
}/api/cron/onboarding-emailsSend onboarding email sequences. Franchise welcome, setup guide, first-job guide, customer follow-up. Daily at 9 AM UTC.
{
"ok": true,
"data": {
"success": true,
"emailsSent": number,
"emailsFailed": number,
"details": [{ "type": "...", "to": "...", "status": "sent" }],
"timestamp": "ISO string"
}
}/api/cron/recurring-jobsGenerate upcoming jobs from recurring templates. Creates invoices for auto-billed schedules. Daily at 6 AM UTC.
Authorization: Bearer {CRON_SECRET}
Optional POST body:
{
"templates": any[] // recurring job templates
"existingJobs": any[] // current jobs for dedup
}{
"ok": true,
"data": {
"success": true,
"generated": number,
"jobs": [...],
"skipped": number,
"projectedRevenue": number,
"invoicesCreated": number,
"timestamp": "ISO string"
}
}/api/cron/webhook-retryRetry failed Stripe webhook events from the persistent queue. Every 5 minutes.
{
"ok": true,
"data": {
"success": true,
"processed": number,
"succeeded": number,
"failed": number,
"results": [{ "id": "...", "type": "...", "success": boolean }],
"timestamp": "ISO string"
}
}Internal developer tools. All require HQ authentication (Supabase session).
/api/dev/healthSystem health check. Tests Supabase, Stripe, email, Sentry, GitHub, Vercel connectivity with latency measurements. No auth required.
{
"ok": true,
"data": {
"status": "healthy" | "degraded" | "unhealthy",
"timestamp": "ISO string",
"version": "1.0.0",
"checks": {
"database": { "status": "up", "latencyMs": 45 },
"stripe": { "status": "configured" },
"email": { "status": "configured" },
"webhookQueue": { "depth": 0 },
"memory": { "heapUsedMB": 32 },
...
}
}
}curl https://routelyos.com/api/dev/health
/api/dev/metricsPlatform metrics dashboard. Job counts by status, worker availability, total revenue.
{
"ok": true,
"data": {
"jobs": { "total": 150, "pending": 12, "completed": 98, ... },
"workers": { "total": 8, "available": 5 },
"revenue": { "total": 24500, "completedJobs": 98 }
}
}/api/dev/insightsSystem improvement recommendations. Analyzes API speed, data quality, config, cancellation trends. Returns scored insights.
{
"ok": true,
"data": {
"insights": [
{
"severity": "high" | "medium" | "low",
"category": "performance" | "data" | "security" | "config",
"title": "...",
"detail": "...",
"action": "..."
}
],
"score": 85,
"analyzedAt": "ISO string"
}
}/api/dev/stripeStripe financial summary. Balance, revenue (today/week/all-time), recent events.
{
"ok": true,
"data": {
"balance": { "available": [...], "pending": [...] },
"revenue": { "today": 4900, "week": 24500, "allTime": 128000 },
"recentEvents": [...],
"chargeCount": 20
}
}/api/dev/githubGitHub repo summary. Recent commits, pull requests, repo stats.
{
"ok": true,
"data": {
"commits": [{ "sha": "abc1234", "message": "...", "author": "..." }],
"pulls": [{ "number": 42, "title": "...", "state": "open" }],
"repo": { "name": "...", "stars": 0, "openIssues": 3 }
}
}/api/dev/vercelRecent Vercel deployments. Includes production URL, commit messages, deployment state.
{
"ok": true,
"data": {
"deployments": [{ "id": "...", "url": "...", "state": "READY" }],
"productionUrl": "https://..."
}
}/api/dev/activityLog a developer activity event to Supabase.
{
"type": string // max 500
"actor": string // max 500
"summary": string // max 500
"metadata": {}? // optional key-value pairs
}{ "ok": true, "data": { "ok": true, "stored": true } }/api/dev/activityRetrieve last 50 developer activity events.
{ "ok": true, "data": { "events": [...], "stored": true } }/api/dev/errorsLog a client-side error to Supabase.
{
"message": string // max 500
"stack": string? // max 5000
"page": string? // max 500
"userAgent": string? // max 500
"metadata": {}?
}{ "ok": true, "data": { "ok": true, "stored": true } }/api/dev/errorsRetrieve recent errors with grouping by message.
{
"ok": true,
"data": {
"errors": [...],
"grouped": [{ "message": "...", "count": 5, "firstSeen": "...", "lastSeen": "..." }],
"total": 42
}
}/api/dev/email-testSend a test email to verify delivery pipeline. Detects provider (Resend/SendGrid).
{
"to": string // valid email, max 254
"template": "booking" | "completion" | "test" // default: "test"
}{
"ok": true,
"data": {
"provider": "resend",
"status": "sent",
"messageId": "...",
"to": "...",
"template": "test"
}
}/api/dev/email-testCheck which email provider is configured.
{
"ok": true,
"data": {
"provider": "resend" | "sendgrid" | "demo",
"configured": boolean,
"fromEmail": "noreply@routelyos.com"
}
}/api/dev/notifySend a styled notification (handoff, deploy, error, revenue) via email or SMS.
{
"type": "handoff" | "deploy" | "error" | "revenue"
"to": string // email or phone number, max 254
"data": {} // template-specific data
"channel": "email" | "sms" // default: "email"
}{
"ok": true,
"data": {
"success": boolean,
"messageId": string?,
"channel": "email",
"type": "deploy"
}
}/api/dev/invoiceGenerate an invoice preview from job data. Optionally emails it.
{
"job": { "id": string | number, ... } // job data (passthrough)
"franchiseName": string? // max 200
"sendEmail": boolean? // default: false
}{
"ok": true,
"data": {
"invoice": { /* structured invoice data */ },
"html": "<div>...",
"emailed": boolean
}
}/api/dev/locationUpdate a worker's real-time GPS location.
{
"workerId": string // max 100
"lat": number // -90 to 90
"lng": number // -180 to 180
"accuracy": number? // 0-100000
"heading": number? // 0-360
"speed": number? // 0-500
}{ "ok": true, "data": { "ok": true } }/api/dev/locationGet all worker GPS locations for the dispatch map.
{
"ok": true,
"data": { "locations": [{ "worker_id": "...", "lat": 38.88, "lng": -77.10, ... }] }
}