Skip to Content
APIShared API — Auth & Agent Callbacks

Shared API — Auth & Agent Callbacks

Endpoints shared across all client apps. Auth endpoints (/auth/v1) handle identity; agent callback endpoints (/agent/v1) handle LLM results phoning home.

Related: API Overview — JWT format | Agent Execution Engine — WebhookAdapter callback


Base Prefix

/auth/v1 — Authentication /agent/v1 — Agent phone-home callbacks

Auth Endpoints

POST /auth/v1/login

Authenticate with email and password. Returns access + refresh tokens.

Auth required: No

Request:

{ email: string; password: string; app: 'dashboard' | 'dm-portal' | 'manage'; // determines which role set to validate }

Response 200:

{ accessToken: string; // JWT, 15 min expiry refreshToken: string; // opaque token, 7 days expiry user: { id: string; // ULID email: string; name: string; role: 'admin' | 'member' | 'reviewer' | 'super_admin'; appAccess: string[]; tenantId?: string; // absent for super_admin tenantName?: string; }; }

Errors:

  • 401 UNAUTHORIZED — invalid credentials
  • 403 FORBIDDEN — user does not have access to the requested app
  • 403 FORBIDDEN — tenant is suspended

POST /auth/v1/refresh

Exchange a valid refresh token for a new access token.

Auth required: No

Request:

{ refreshToken: string; }

Response 200:

{ accessToken: string; }

Errors:

  • 401 UNAUTHORIZED — refresh token is invalid or expired
  • 401 UNAUTHORIZED — refresh token has been revoked (logout was called)

POST /auth/v1/logout

Revoke the caller’s refresh token.

Auth required: Bearer token

Request: Empty body

Response 204: No content


GET /auth/v1/me

Return the currently authenticated principal’s profile.

Auth required: Bearer token

Response 200:

{ id: string; email: string; name: string; role: string; appAccess: string[]; createdOn: string; // ISO 8601 // Optional profile fields (null if not set) phone?: string; country?: string; about?: string; whatsapp?: string; image?: string; // profile photo URL }

PATCH /auth/v1/me

Update own profile fields (name, phone, country, etc.). For password changes use PATCH /auth/v1/me/password.

Auth required: Bearer token

Request (all fields optional):

{ name?: string; // must be non-empty if provided phone?: string; country?: string; about?: string; whatsapp?: string; }

Response 200: Updated profile (same shape as GET /auth/v1/me)


PATCH /auth/v1/me/password

Change own password.

Auth required: Bearer token

Request:

{ currentPassword: string; // must match current password newPassword: string; // min 8 characters }

Response 200:

{ success: true }

Errors:

  • 400 VALIDATION_ERRORnewPassword shorter than 8 characters
  • 401 UNAUTHORIZEDcurrentPassword is wrong

GET /auth/v1/plans

List available subscription offerings and plans, optionally filtered by country.

Auth required: No

Query params:

country? string // ISO 3166-1 alpha-2 (e.g. IN, US). Unmapped codes default to US pricing.

Response 200:

Array<{ id: string; name: string; plans: Array<{ id: string; name: string; priceAmount: number; billingCycle: string; // 'monthly' | 'annual' trialDays: number; region: string; // region code (e.g. 'IN', 'US') }>; }>

POST /auth/v1/forgot-password

Send a password reset email.

Auth required: No

Rate limit: 5 requests / 15 minutes per IP

Request:

{ email: string; }

Response 204: Always succeeds (to avoid user enumeration). Duplicate requests for the same email are suppressed for 20 minutes.


POST /auth/v1/reset-password

Complete a password reset using the token from the email.

Auth required: No

Rate limit: 10 requests / 15 minutes per IP

Request:

{ token: string; newPassword: string; }

Response 204: No content

Errors:

  • 400 VALIDATION_ERROR — token is invalid or expired

Registration

Self-service tenant registration. A multi-step wizard (R1–R4) that creates a user, tenant, and subscription in one flow. Progress is tracked via a registration_sessions record so the wizard can resume if the browser is closed mid-way.

POST /auth/v1/register/start

Step R1 — Submit contact information and begin registration.

Auth required: No

Request:

{ firstName: string; lastName: string; email: string; phone: string; password: string; // min 8 chars, 1 uppercase, 1 number country: string; // ISO 3166-1 alpha-2 }

Response 201:

{ sessionToken: string; // Short-lived JWT (1 h) — passed in subsequent registration steps step: 1; }

Errors:

  • 409 CONFLICT — email already registered
  • 400 VALIDATION_ERROR — password too weak

POST /auth/v1/register/step/2

Step R2 — Submit company details.

Auth required: Authorization: Bearer <sessionToken> (from step 1)

Request:

{ companyName: string; website: string; industry: string; companySize: '1-10' | '11-50' | '51-200' | '201-500' | '500+'; businessType: 'agency' | 'in_house' | 'freelancer'; }

Response 200:

{ step: 2; }

POST /auth/v1/register/step/3

Step R3 — Submit location details (for Australia/India GST compliance).

Auth required: Authorization: Bearer <sessionToken>

Request:

{ state: string; // State/province code timezone: string; // IANA timezone: 'Australia/Sydney' gstNumber?: string; // Optional — for GST invoice purposes }

Response 200:

{ step: 3; }

POST /auth/v1/register/complete

Step R4 — Select plan and complete registration. Creates the tenant, user, and triggers the Razorpay subscription flow.

Auth required: Authorization: Bearer <sessionToken>

Request:

{ plan: 'free' | 'starter' | 'pro' | 'agency'; coupon?: string; }

Response 201:

{ tenantId: string; userId: string; plan: string; accessToken: string; refreshToken: string; // For paid plans: Razorpay checkout details razorpayOrderId?: string; razorpaySubscriptionId?: string; razorpayKeyId?: string; // Public key — safe to expose }

Note: For paid plans, the frontend uses the returned Razorpay details to open the Razorpay checkout modal. Payment completion is confirmed via the /billing/webhook endpoint. The tenant is activated on subscription.activated event.


GET /auth/v1/register/session

Resume a partially-completed registration. Returns the current step and pre-filled values.

Auth required: Authorization: Bearer <sessionToken>

Response 200:

{ currentStep: 1 | 2 | 3 | 4; completedSteps: number[]; prefilled: { // Fields collected in completed steps — safe to display in form firstName?: string; lastName?: string; email?: string; companyName?: string; // ... etc }; }

Agent Callback Endpoints

Called by LLM runtimes (external agents using the WebhookAdapter) to deliver their results. Not called by human client apps.

POST /agent/v1/callback/:runId

Phone-home callback: an external agent posts its completed result for a specific activity run.

Auth required: Authorization: Bearer <taskToken> — short-lived JWT scoped to this runId; generated by the control plane at dispatch time; expires at the activity’s configured timeout.

Request:

{ status: 'success' | 'error'; text?: string; // output text (on success) error?: string; // error message (on failure) usage?: { inputTokens: number; outputTokens: number; }; durationMs?: number; }

Response 200:

{ received: true; }

Errors:

  • 401 UNAUTHORIZED — task token is invalid, expired, or does not match runId
  • 409 CONFLICT — this run has already received a callback (idempotency guard)
  • 404 NOT_FOUNDrunId does not exist

GET /agent/v1/callback/:runId/stream

SSE: stream agent output text in real time for a running activity. Intended for agent runtimes that want to forward their output token-by-token rather than delivering in a single final POST.

Auth required: Authorization: Bearer <taskToken>

Protocol: text/event-stream

Events:

event: text_delta data: {"delta":"Hello ","sequenceNum":1} event: completed data: {"status":"success","usage":{"inputTokens":412,"outputTokens":891}} event: error data: {"error":"timeout"}

System Health

GET /health

Liveness probe. Returns immediately with no auth.

Response 200:

{ status: 'ok'; version: string; }

GET /health/ready

Readiness probe. Checks that PostgreSQL, MongoDB, and Redis are reachable.

Response 200:

{ status: 'ok' | 'degraded'; checks: { postgres: 'ok' | 'error'; mongo: 'ok' | 'error'; redis: 'ok' | 'error'; }; }

© 2026 Leadmetrics — Internal use only