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 callbacksAuth 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 credentials403 FORBIDDEN— user does not have access to the requested app403 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 expired401 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_ERROR—newPasswordshorter than 8 characters401 UNAUTHORIZED—currentPasswordis 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 registered400 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 matchrunId409 CONFLICT— this run has already received a callback (idempotency guard)404 NOT_FOUND—runIddoes 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';
};
}