Guides

Agent intake

Let an agent complete a hosted intake session over the applicant API, the TypeScript SDK, or MCP

Overview

Hosted intake sessions are agent-operable. Any agent that holds a signed session link can read the session state, fill the form, answer follow-up questions, upload documents, and submit, using the same endpoints the hosted UI uses.

Use the canonical or discovered URL

Customer-plane integrations should copy their API base URL from Settings → Developer, or use https://www.klarefi.com. For applicant-plane discovery, /s/{sessionId}/agent.json carries the correct API base URL for that session; prefer the URL returned by discovery.

Two kinds of agents fit this model:

  • The applicant's own agent, completing the intake on the applicant's behalf.
  • Your agent, completing the parts your systems already know before handing the session to the applicant.

An agent can only complete the intake. It cannot approve, reject, or otherwise decide the case. Human review stays in Klarefi.

Two trust planes

Agent intake separates who integrates from who completes.

PlaneCredentialSurfaceUsed for
Customer planesk_live_ / sk_test_ API key/api/v1/* REST, @klarefi/nodeYour backend or agents: create sessions, prefill known values, read cases, get the decision package
Applicant planePer-session bearer token (the token query parameter of the signed session link)Session endpoints under /api/v1/sessions/{sessionId}/*Whoever completes the intake: the applicant's agent, or your agent before handoff

The applicant plane needs no API key. The token in the signed link is the credential, scoped to one session. Treat the link as scoped applicant access: anyone who holds it can act on that session until it expires or is revoked.

The agent loop

Applicant-plane agents run one loop: read state, act, wait, repeat until done.

Agent intake loop
  1. Read state. GET /api/v1/sessions/{sessionId} returns the current phase, the active task, and submit readiness.
  2. Act. Save draft form values, answer the active question, upload a document, or submit.
  3. Wait. After a reply the session enters processing while facts are resolved. Poll the events endpoint or re-read state until the next question is ready.
  4. Repeat until the phase is done.

Read session state

Authenticate every applicant-plane request with the session token as a bearer token:

GET /api/v1/sessions/550e8400-e29b-41d4-a716-446655440000
Authorization: Bearer 1751879000.9f2c4e...
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "state_pointer": {
    "phase": "question",
    "active_task_id": "task_7f3a",
    "submit_eligible": false,
    "submit_blocking_reasons": ["unresolved_follow_up"],
    "events_cursor": 14
  },
  "view": {
    "session": { "...": "..." },
    "applicant_projection": { "...": "..." },
    "session_progress": { "...": "..." }
  }
}

state_pointer is the small part an agent should branch on. view is the full hosted context: form blocks, draft values, open tasks with typed response fields, uploads, and progress.

The phase drives the loop:

PhaseMeaning
formStructured form fields are open. Save drafts, then submit.
processingKlarefi is resolving facts. Wait and poll.
questionA follow-up question is active. Answer it or upload a document.
doneNo applicant input remains. The case moves to review.
failedThe session cannot continue. Surface the state to a human.

Act on the session

All action endpoints live under /api/v1/sessions/{sessionId} and use the same bearer token.

Save draft form values

POST /api/v1/sessions/{sessionId}/draft
Authorization: Bearer <token>
Content-Type: application/json

{
  "client_mutation_id": "d3b1c9e0-5f4a-4b2e-9c1d-8a7f6e5d4c3b",
  "updated_fields": {
    "claimant_name": "Ada Lovelace",
    "claim_number": "CLM-123"
  }
}

Drafts do not trigger fact resolution. Use them to fill the form incrementally, then submit.

Answer the active question

POST /api/v1/sessions/{sessionId}/replies
Authorization: Bearer <token>
Content-Type: application/json

{
  "content": "The vehicle was parked at the time of the incident.",
  "active_task_id": "task_7f3a",
  "idempotency_key": "reply:task_7f3a:14"
}

To answer several open tasks in one request, send { "replies": [ ... ] } with the same fields per entry.

Upload a document

Uploads are three steps: initialize, transfer, complete.

POST /api/v1/sessions/{sessionId}/uploads
Authorization: Bearer <token>
Content-Type: application/json

{
  "upload_id": "b6f0a1d2-3c4e-4f5a-8b9c-0d1e2f3a4b5c",
  "filename": "policy-card.pdf",
  "mime_type": "application/pdf",
  "size_bytes": 482113,
  "active_task_id": "task_9c2b"
}

The response contains doc_id and a short-lived upload_url. Send the file bytes there with an HTTP PUT, then confirm:

POST /api/v1/sessions/{sessionId}/uploads/{docId}/complete
Authorization: Bearer <token>
Content-Type: application/json

{
  "storage_id": "st_a1b2c3",
  "active_task_id": "task_9c2b"
}

Uploads are limited to 25MB. Each upload task lists its accepted MIME types; the manifest carries the session-wide policy.

Submit

POST /api/v1/sessions/{sessionId}/submit
Authorization: Bearer <token>
Content-Type: application/json

{
  "submission_id": "e8c7d6b5-a4f3-4e2d-9c1b-0a9f8e7d6c5b"
}

If the session is not ready, the API responds 409 with the code submit_blocked and the list of blocking_reasons. Resolve them (answer the open question, provide the missing upload) and retry with the same submission_id.

Wait for the next step

After a reply or submit, the session enters processing while Klarefi resolves facts. Two equivalent ways to wait:

  • Poll GET /api/v1/sessions/{sessionId}/events?after_cursor=N. A gap.prompted event means a new question is ready.
  • Re-read GET /api/v1/sessions/{sessionId} and check state_pointer.phase.
GET /api/v1/sessions/{sessionId}/events?after_cursor=14
Authorization: Bearer <token>
{
  "events": [
    {
      "event_id": "evt_5c1d",
      "event_type": "gap.prompted",
      "cursor": 15,
      "created_at": "2026-07-07T09:14:03Z",
      "payload": {
        "active_task_id": "task_7f3a",
        "task_ids": ["task_7f3a"]
      }
    }
  ],
  "next_cursor": 15,
  "has_more": false,
  "phase": "question"
}

Pass next_cursor as the next after_cursor. Poll no faster than every 2 seconds, and respect Retry-After headers when the API sends them.

Discover capabilities

GET /api/v1/sessions/{sessionId}/manifest returns a machine-readable descriptor of what the session supports right now:

{
  "auth": { "scheme": "bearer", "scope": "session" },
  "actions": [
    { "name": "save_draft", "available": true },
    { "name": "reply", "available": true },
    { "name": "upload", "available": true },
    { "name": "submit", "available": false, "reason": "unresolved_follow_up" }
  ],
  "uploads": {
    "accepted_mime_types": ["application/pdf", "image/png", "image/jpeg"],
    "max_file_size_bytes": 26214400
  },
  "polling": { "min_interval_ms": 2000 }
}

Fetch the manifest once at the start of a run instead of hardcoding capabilities. Availability changes as the session progresses.

A web agent often starts from the signed session link itself, not from API documentation. Session pages expose a token-free pointer for that case:

GET /s/{sessionId}/agent.json
{
  "schema_version": "hosted_session_agent_pointer.v1",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "api_base": "https://your-deployment.convex.site/api/v1",
  "manifest_url": "https://your-deployment.convex.site/api/v1/sessions/550e8400-e29b-41d4-a716-446655440000/manifest",
  "auth": {
    "scheme": "bearer",
    "token_source": "the token query parameter of the signed session URL"
  },
  "docs_url": "https://www.klarefi.com/docs/guides/agent-intake"
}

The session page also carries a rel="alternate" link tag pointing at the same pointer, so an agent that parses the HTML finds it without knowing the URL shape.

The pointer carries no credentials. Its api_base and manifest_url are derived from the configured Convex deployment, so they remain correct even when the public vanity domain is unavailable. An agent landing on a session link should fetch the pointer, take the bearer token from the link's token query parameter, and then fetch the manifest with that token to learn what the session supports.

Idempotency

Every mutation is safe to retry:

  • Replies accept idempotency_key. The convention is reply:{task_id}:{source_event_sequence}, which makes a retry of the same answer to the same question a no-op.
  • Submit accepts submission_id and replays instead of double-submitting.
  • Drafts carry client_mutation_id and uploads carry upload_id for the same reason.

SDK quickstart

@klarefi/node ships an applicant-plane client that wraps the loop, idempotency keys, and upload steps:

npm install @klarefi/node
import { IntakeSession } from "@klarefi/node/intake";

const intake = IntakeSession.fromSignedUrl(
  "https://app.klarefi.com/s/550e8400...?token=1751879000.9f2c4e...",
);

let state = await intake.getState();

while (state.next_action.type !== "none") {
  switch (state.next_action.type) {
    case "fill_form":
      state = await intake.saveDraft({ claimant_name: "Ada Lovelace" });
      break;
    case "answer":
      state = await intake.answer("The claim number is CLM-123.");
      break;
    case "upload":
      state = await intake.uploadDocument({
        file: "./policy-card.pdf",
        mimeType: "application/pdf",
      });
      break;
    case "wait": {
      const result = await intake.nextAction({ timeoutMs: 60_000 });
      state = result.state;
      break;
    }
  }
}

You can also construct the client from parts:

const intake = new IntakeSession({
  sessionId: "550e8400-e29b-41d4-a716-446655440000",
  accessToken: process.env.INTAKE_SESSION_TOKEN!,
  baseUrl: process.env.KLAREFI_BASE_URL!,
  clientName: "claims-copilot",
});

The methods map onto the loop:

MethodWhat it does
getState()Distilled AgentIntakeState: phase, a typed next_action (fill_form, answer, upload, wait, none), progress counts, and submit blockers
answer(text, { taskId? })Reply to the active question with free text
answerFields({ factId: value })Reply with typed field values; validates against each task's response fields
saveDraft(fields)Save form field values without submitting
uploadDocument({ file, mimeType? })Runs initialize, transfer, and complete; accepts a path, Uint8Array, or Blob
submit()Returns { submitted, blockers, state }; a 409 submit_blocked becomes submitted: false with the blocking reasons
nextAction({ timeoutMs })Polls until the next action is ready or the timeout elapses
getView()The raw hosted view when an agent needs the full escape hatch

nextAction() polls every 2 seconds by default and respects the mutation it follows: it waits until the session has actually moved past the version your last write produced.

MCP server

@klarefi/mcp exposes the same loop as MCP tools, so an agent runtime can drive an intake without custom code:

{
  "mcpServers": {
    "klarefi": {
      "command": "npx",
      "args": ["-y", "@klarefi/mcp"],
      "env": {
        "KLAREFI_API_KEY": "sk_test_..."
      }
    }
  }
}

The applicant-plane tools need no API key; the signed session link passed to open_intake is the credential. Set KLAREFI_API_KEY only if you also want the customer-plane tools.

ToolPlaneWhat it does
open_intakeApplicantOpen a session from a signed link
get_intake_stateApplicantRead the distilled state and next action
answer_questionApplicantAnswer the active question with free text
answer_fieldsApplicantAnswer with typed field values
upload_documentApplicantUpload a document to the active upload task
save_intake_draftApplicantSave form field values
submit_intakeApplicantSubmit; returns blockers if the session is not ready
wait_for_next_actionApplicantPoll until the next action is ready
create_intake_sessionCustomerCreate a hosted session (requires KLAREFI_API_KEY)
get_caseCustomerRead a case and its resolved facts (requires KLAREFI_API_KEY)

Prefill and handoff

The customer plane lets your systems complete the parts they already know before the applicant sees the session.

POST /api/v1/sessions accepts prefill.field_values with form field values from your records. The applicant only completes what remains. Compliance and consent fields cannot be prefilled; the applicant must provide those.

POST /api/v1/sessions
Authorization: Bearer sk_live_...
Content-Type: application/json

{
  "case_type_id": "motor_claim",
  "idempotency_key": "session_claim_48392",
  "external_case_id": "claim_48392",
  "prefill": {
    "field_values": {
      "claimant_name": "Ada Lovelace",
      "policy_number": "POL-8842"
    }
  }
}

The response includes signed_url, an access_token for the applicant plane, and an agent block describing the session endpoints, so your own agent can act on the session directly.

When your side is done, mark the handoff:

POST /api/v1/sessions/{sessionId}/handoff
Authorization: Bearer sk_live_...
Content-Type: application/json

{
  "notify": { "email": "[email protected]" }
}

Handoff marks the customer-completed portion, returns a fresh signed link to send to the applicant, and emits a v1.intake.handoff_ready webhook so your delivery flow can pick it up. The webhook remains the default integration path.

notify is optional. When present, Klarefi emails the applicant a continue link directly, so a handoff works even without your own delivery flow. Notification is fail-soft: if the email cannot be sent, the handoff still succeeds. The response reports what happened in a notification block:

{
  "notification": { "requested": true, "sent": true }
}

requested reflects whether the request asked for a notification, sent whether Klarefi delivered one, and a reason field explains any sent: false.

Every agent-submitted answer is recorded with an actor and an optional client_name, and shows up in the case file. Reviewers see who provided each answer: your agent, the applicant's agent, or the applicant.

On this page