API Reference

Corral exposes a REST API mounted in your app. All endpoints are under /api/auth and /api/billing by default (configurable).

Base URL
All paths below are relative to your app's base URL. Auth endpoints: /api/auth/* Billing endpoints: /api/billing/*

Endpoint Index (v0.4.1)

text
Auth: /session, /sign-up/email, /sign-in/email, /sign-in/social, /sign-out, /verify-email,
      /forgot-password, /reset-password, /magic-link, /otp/send, /otp/verify,
      /device/authorize, /device/token, /device/verify, /keys, /keys/:id
Billing: /checkout, /portal, /cancel, /reactivate, /webhook,
         /usage/track, /usage/:meter, /plans, /subscription
Admin: /admin/users, /admin/users/:id/promote, /admin/users/:id/plan

Authentication

Most endpoints require authentication. Pass credentials using:


Auth Endpoints

GET /api/auth/session Get current session

Returns the current user's session, plan, and feature list. No authentication required โ€” returns null user for anonymous requests.

Response

json
{
  "user": {
    "id": "usr_01HN2X...",
    "email": "alice@horizon.mil",
    "name": "Alice Chen",
    "image": "https://lh3.googleusercontent.com/...",
    "role": "user",
    "createdAt": "2024-01-15T09:23:00Z"
  },
  "plan": {
    "id": "pro",
    "status": "active",
    "currentPeriodEnd": "2024-02-15T09:23:00Z"
  },
  "features": ["basic_search", "export_csv", "ai_analysis", "real_time_feed"],
  "limits": {
    "api_calls": 10000,
    "export_rows": 100000
  }
}

Anonymous response

json
{ "user": null, "plan": { "id": "anonymous" }, "features": [], "limits": {} }
POST /api/auth/sign-up/email Register with email + password

Request

json
{
  "email": "alice@horizon.mil",
  "password": "hunter2",
  "name": "Alice Chen"
}

Response โ€” 200 OK

json
{ "user": { "id": "usr_01HN2X...", "email": "alice@horizon.mil" }, "token": "..." }
POST /api/auth/sign-in/email Sign in with email magic link or password

Magic link request

json
{ "email": "alice@horizon.mil", "callbackURL": "/dashboard" }

Response โ€” 200 OK (magic link sent)

json
{ "status": "magic_link_sent", "email": "alice@horizon.mil" }
POST /api/auth/sign-in/social Start OAuth flow

Request

json
{ "provider": "google", "callbackURL": "/dashboard" }

Response โ€” 200 OK

json
{ "url": "https://accounts.google.com/o/oauth2/auth?..." }

Redirect the user to url to complete OAuth.

POST /api/auth/sign-out Sign out current user

Clears the session cookie and invalidates the session in the database.

Response โ€” 200 OK

json
{ "status": "signed_out" }
POST /api/auth/verify-email Verify magic link token

Request

json
{ "token": "ml_01HN2X..." }

Response โ€” 200 OK (sets session cookie)

json
{ "status": "verified", "user": { ... }, "token": "..." }

Billing Endpoints

POST /api/billing/checkout Create Stripe Checkout session

Requires authentication.

Request

json
{
  "plan": "pro",
  "successUrl": "https://horizon.mil/dashboard?upgraded=1",
  "cancelUrl": "https://horizon.mil/pricing"
}

Response โ€” 200 OK

json
{ "url": "https://checkout.stripe.com/c/pay/cs_test_..." }
GET /api/billing/portal Redirect to Stripe Customer Portal

Requires authentication. Query params: ?return_url=/dashboard

Redirects (302) to the Stripe Customer Portal URL for the authenticated user.

POST /api/billing/cancel Cancel subscription at period end

Requires authentication. Cancels at end of current billing period (not immediately).

Response

json
{
  "status": "canceled",
  "active_until": "2024-02-15T09:23:00Z"
}
POST /api/billing/reactivate Reactivate a canceled subscription

Only works if the subscription hasn't ended yet (still in grace period).

Response

json
{ "status": "active", "plan": "pro" }
POST /api/billing/webhook Stripe webhook endpoint

Receives Stripe events. Verified using STRIPE_WEBHOOK_SECRET. Do not call this endpoint directly โ€” it's for Stripe only.

Register this URL in Stripe Dashboard โ†’ Webhooks โ†’ Add Endpoint, or use:

bash
corral stripe push  # registers webhook automatically

Device Auth Endpoints

Implements RFC 8628 Device Authorization Grant.

POST /api/auth/device/authorize Start device auth flow

Request

json
{ "client_id": "horizon-cli", "scope": "openid profile" }

Response โ€” 200 OK

json
{
  "device_code": "dc_01HN2X...",
  "user_code": "XKCD-9821",
  "verification_uri": "https://horizon.mil/auth/activate",
  "verification_uri_complete": "https://horizon.mil/auth/activate?code=XKCD-9821",
  "expires_in": 300,
  "interval": 5
}
POST /api/auth/device/token Poll for device token

Poll at the interval returned by /device/authorize.

Request

json
{
  "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
  "device_code": "dc_01HN2X...",
  "client_id": "horizon-cli"
}

Response โ€” Pending (user hasn't approved yet)

json
{ "error": "authorization_pending" }   // keep polling

Response โ€” Approved

json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_01HN2X...",
  "scope": "openid profile"
}
POST /api/auth/device/verify User approves device code

Called from the browser approval page. Requires user to be authenticated.

Request

json
{ "user_code": "XKCD-9821" }

Response โ€” 200 OK

json
{ "status": "approved", "client_id": "horizon-cli" }

API Key Endpoints

POST /api/auth/keys Create API key

Requires authentication. The key is shown once โ€” store it securely.

Request

json
{
  "name": "horizon-cli production",
  "scopes": ["api:read", "api:write"],
  "expiresIn": "90d"
}

Response โ€” 201 Created

json
{
  "id": "key_01HN2X...",
  "key": "ck_live_01HN2X...",  // shown once
  "name": "horizon-cli production",
  "scopes": ["api:read", "api:write"],
  "expiresAt": "2024-04-15T09:23:00Z"
}
GET /api/auth/keys List API keys

Returns all API keys for the authenticated user. Key values are not returned (only metadata).

Response

json
{
  "keys": [
    { "id": "key_01HN2X...", "name": "horizon-cli production", "lastUsed": "2024-01-15" }
  ]
}
DELETE /api/auth/keys/:id Revoke API key

Response โ€” 200 OK

json
{ "status": "revoked" }

Usage Meter Endpoints

POST /api/billing/usage/track Track usage

Increment a usage meter for the authenticated user.

Request

json
{ "meter": "api_calls", "amount": 1 }

Response โ€” 200 OK

json
{
  "meter": "api_calls",
  "used": 1247,
  "limit": 10000,
  "remaining": 8753,
  "resetsAt": "2024-02-01T00:00:00Z"
}

Response โ€” 429 (limit exceeded)

json
{
  "error": "meter_exceeded",
  "meter": "api_calls",
  "limit": 500,
  "used": 500,
  "resetsAt": "2024-02-01T00:00:00Z",
  "upgrade_url": "/pricing"
}
GET /api/billing/usage/:meter Get meter usage

Response

json
{
  "meter": "api_calls",
  "used": 1247,
  "limit": 10000,
  "remaining": 8753,
  "percent": 12.47,
  "resetsAt": "2024-02-01T00:00:00Z"
}

Admin Endpoints

All admin endpoints require role: admin.

GET /api/admin/users List users

Query params

?page=1&limit=50&plan=pro&search=alice

Response

json
{
  "users": [ { "id": "...", "email": "...", "plan": "pro", "createdAt": "..." } ],
  "total": 1284,
  "page": 1,
  "pages": 26
}
POST /api/admin/users/:id/promote Change user role or plan

Request

json
{ "role": "admin" }
// or:
{ "plan": "enterprise", "skip_stripe": true }

Response

json
{ "status": "ok", "user": { "id": "...", "role": "admin", "plan": "pro" } }