D Diagent docs

API reference

Widget API

The Widget API is the public HTTP surface the bundled JavaScript talks to. You normally don't call it yourself โ€” the widget loader does โ€” but the contract is documented here so you can build custom clients, audit traffic, or simulate the widget for testing.

All endpoints are under /api/v1/widget. Authentication is a signed JWT issued by /init. CORS is permissive on POST for cross-origin embeds.

POST POST /v1/widget/init

Boots the widget for a visitor. No auth โ€” but the request's Origin header must match the agent's allowed_origins (see Allowed origins).

Request

POST /api/v1/widget/init
Origin: https://your-site.com
Content-Type: application/json

{
    "agent_id": "01HXY...",
    "page_url": "https://your-site.com/pricing",
    "anon_id": "anon_abc123"     // optional; persists visitor across reloads
}

Response (200)

{
    "data": {
        "conversation_id": "01HXZ...",
        "visitor_id": "01HXY...",
        "anonymous_id": "anon_abc123",
        "jwt": "eyJhbGciOiJIUzI1NiI...",
        "expires_at": "2026-05-07T13:00:00Z",
        "agent": {
            "id": "01HXY...",
            "name": "Aria",
            "persona": { "name": "Aria", "tone": "friendly" },
            "theme": { "primary": "#111827", ... },
            "starter_prompts": [ "..." ],
            "language_default": "en"
        },
        "branding": { "show": true, "label": "...", "url": "...", "logo_url": "...", "display_mode": "logo_only" },
        "behavior_rules": [ ... ],
        "messages": [ ... ],          // last 30 messages of the resumed conversation
        "reverb": { "app_key": "...", "host": "...", "port": 8080, "scheme": "wss" }
    }
}

Error responses

StatusCodeCause
404agent_not_foundAgent doesn't exist or isn't published.
403origin_forbiddenOrigin not in allowed_origins.
429plan_limit_reachedWorkspace exceeded its monthly conversation quota.
429(throttled)Per-IP rate limit hit (60 rpm by default).

POST POST /v1/widget/messages/stream

The streaming endpoint. SSE response. Auth: Authorization: Bearer <jwt>. Use this for the visitor experience โ€” every other method is sync and slower.

Request

POST /api/v1/widget/messages/stream
Authorization: Bearer eyJhbGciOiJIUzI1NiI...
Content-Type: application/json

{
    "message": "What's your refund policy?",
    "page_url": "https://your-site.com/pricing",
    "page_context": { ... }       // optional; structured data extracted from the current page
}

Response (Server-Sent Events)

HTTP/1.1 200 OK
content-type: text/event-stream

data: {"event":"token","token":"Our "}

data: {"event":"token","token":"refund "}

data: {"event":"token","token":"policy is 30 days "}

data: {"event":"citations","citations":[{"id":1,"url":"https://your-site.com/refunds"}]}

data: {"event":"done","conversation_id":"01HXZ..."}

Token events come fastest in the first few hundred ms โ€” that's the 1-second-to-first-token target on the hot path. citations event arrives once after streaming completes; done closes the stream.

POST POST /v1/widget/messages

Sync version of /messages/stream. Returns the full response in one JSON payload. Slower (visitor waits for the full response) but easier to integrate with non-browser clients.

Response

{
    "data": {
        "message_id": "01HXZ...",
        "conversation_id": "01HXZ...",
        "content": "Our refund policy is 30 days...",
        "citations": [{"id": 1, "url": "..."}],
        "low_confidence": false
    }
}

POST POST /v1/widget/leads

Submit captured contact info. Auth: same JWT as messages.

POST /api/v1/widget/leads
Authorization: Bearer eyJhbGciOiJIUzI1NiI...

{
    "name": "Alex",
    "email": "[email protected]",
    "phone": "+1...",
    "fields": { "company": "Acme" }   // any agent-defined custom fields
}

Dedupes on (agent_id, email): repeat submissions update the existing lead instead of creating a new one. Rate-limited at 5 requests per JWT per window โ€” abuse-resistant.

POST POST /v1/widget/events

Lightweight client-side analytics. The widget calls this with telemetry events (launcher opened, CTA clicked, dismissed, scroll trigger fired). Auth: JWT. Rate-limited.

{
    "event": "cta.click",
    "rule_id": "01HXY...",
    "metadata": { ... }
}

JWT format

HS256, signed with WIDGET_JWT_SECRET. Claims:

{
    "iss": "pitchbar",
    "iat": 1714900000,
    "exp": 1714903600,           // 60 minutes
    "agent_id": "01HXY...",
    "visitor_id": "01HXY...",
    "conversation_id": "01HXZ..."
}

Tokens are scoped to a single conversation. Re-init to get a fresh token for a new conversation. Verifying happens in WidgetJwt::verify() โ€” invalid signatures, expired tokens, or tampered claims all return 401.

Rate limits

EndpointLimitKey
/init60 rpmper IP + agent_id (throttle:widget-init)
/messages, /messages/stream, /events, /conversation/*, DELETE /me30 rpmper JWT (throttle:widget-session)
/leads5 rpmper JWT (throttle:widget-leads)

All return 429 with a Retry-After header on limit.