Skip to main content

Run an assessment via the API

Start an assessment for one of your members from your own backend and run the conversation in your own interface.

Use this API to run the conversation in your own interface (a native mobile app, a desktop tool, a backend job, or any surface where you render the conversation yourself and own every turn). Claire, the AI agent, still asks the questions and scores the evidence; you carry the messages.

Prefer to let Wiselook render the conversation on your own website instead? Then embed the widget: Wiselook handles the streaming and the session, and your backend only mints visitor sessions.

Consent is yours to obtain. You are the data controller for your members. Starting an assessment notifies nobody, so make sure the person has agreed before you begin.

Prerequisites​

  • An API client with the evaluations:write permission, plus evaluations:read to poll the result.
  • A bearer token minted from that client, as described in Authentication. The examples below assume it is in $TOKEN.
  • A member to assess. This flow assesses people who are already on your Members list, and never creates them. To add someone first, see Bulk-invite members.
  • One of your organisation's adopted methodologies.

1. Find the member​

Every assessment names the person it is for. Use the member's id, as returned by the Members API:

curl "https://$WISELOOK_HOST/v1/tenant-users?email=maria@example.com" \
-H "Authorization: Bearer $TOKEN"

Reading members needs the tenant_users:read permission. See Manage members via the API.

2. Start the assessment​

curl -X POST "https://$WISELOOK_HOST/v1/members/$TENANT_USER_ID/evaluations" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"methodology_id": "<ADOPTED_METHODOLOGY_UUID>"}'
{
"id": "7c1e9a04-…",
"tenant_user_id": "3f8b2d61-…",
"methodology_id": "009f91ce-…",
"status": "in_progress",
"locale": "en-US",
"channel": "text",
"created_at": "2026-09-18T09:14:22Z"
}

Keep the id. It is the evaluation_id every later call uses. The row echoes the member back as tenant_user_id: the same id you passed in the path, named for the field it fills on the assessment.

FieldRequiredNotes
methodology_idyesMust be one your organisation has adopted
localenoDefaults to the member's preferred language, then the methodology's default
channelnotext (default) or voice. Fixed once the assessment starts

Locale and channel cannot be changed afterwards.

Common refusals:

StatusMeaning
400The person is not an assessable member of your organisation. Removed members and unknown ids answer the same way
403The token lacks evaluations:write
409Your assessment balance is exhausted
422The methodology is not adopted by your organisation, or a field is invalid

3. Run the conversation​

Send each of the person's turns to the chat endpoint. The reply streams back as server-sent events:

curl -N -X POST \
"https://$WISELOOK_HOST/v1/members/$TENANT_USER_ID/evaluations/$EVALUATION_ID/chat/messages" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"content": "Hello"}'

What comes back​

The response is text/event-stream. Each event is an SSE event: line and a data: line of JSON. The JSON repeats the event name in a type field, so you can switch on either one:

event: message_start
data: {"type": "message_start", "message_id": "m_resp_7c1e9a04_1", "role": "assistant"}

event: message_delta
data: {"type": "message_delta", "message_id": "m_resp_7c1e9a04_1", "content_delta": "Got "}

event: message_delta
data: {"type": "message_delta", "message_id": "m_resp_7c1e9a04_1", "content_delta": "it. "}

event: message_delta
data: {"type": "message_delta", "message_id": "m_resp_7c1e9a04_1", "content_delta": "Tell me about a deadline you missed. "}

event: message_complete
data: {"type": "message_complete", "message_id": "m_resp_7c1e9a04_1", "content": "Got it. Tell me about a deadline you missed. "}

event: progress_update
data: {"type": "progress_update", "current_eco_id": "3f2a7b18-…", "progress_percent": 25}

event: stream_end
data: {"type": "stream_end", "reason": "complete"}

These are semantic events, never raw model tokens.

EventFieldsWhat to do with it
message_startmessage_id, roleA reply is beginning
message_deltamessage_id, content_deltaAppend the fragment to what you are showing
message_completemessage_id, contentThe whole reply, assembled. Use it to verify what you rendered
progress_updateprogress_percent, current_eco_idUpdate any progress indicator
assessment_completeevaluation_id, statusThe conversation is over. Stop sending turns
stream_endreasonThis turn's stream is closed

Every turn ends with stream_end. On the final turn, and only then, assessment_complete arrives just before it.

Handling the loop​

One request per turn. Read the stream to its end, show the reply, collect the person's answer, then send the next turn. Keep going until you see assessment_complete.

import json, requests

BASE = f"https://{WISELOOK_HOST}/v1/members/{tenant_user_id}/evaluations/{evaluation_id}"
HEADERS = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}


def send_turn(text: str) -> tuple[str, bool]:
"""Send one turn. Returns the assistant's reply and whether we are done."""
reply, done = "", False
with requests.post(
f"{BASE}/chat/messages", headers=HEADERS, json={"content": text}, stream=True
) as r:
r.raise_for_status()
for line in r.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue # the `event:` line repeats what `type` already tells us
event = json.loads(line[len("data: "):])

if event["type"] == "message_delta":
reply += event["content_delta"]
print(event["content_delta"], end="", flush=True)
elif event["type"] == "assessment_complete":
done = True
elif event["type"] == "error":
raise RuntimeError(event["message"])
return reply, done


done = False
answer = "Hello"
while not done:
_, done = send_turn(answer)
if not done:
answer = input("\n> ")

Three things this example is careful about, because they are easy to get wrong:

  • Read to the end of the stream before sending the next turn. A turn is finished when its stream closes, not when the text stops arriving.
  • An error arrives inside a healthy stream. The HTTP status is already 200 by the time events flow, so a failure mid-turn comes as an error event followed by stream_end with reason set to error. Handle the event; do not rely on the status code alone. The error payload carries code, message and retryable, so you can tell a transient failure from one worth surfacing.
  • Accumulate the deltas yourself. message_complete carries the full text, so you can use it as a check on what you assembled, or simply render it if you do not want to stream.

The conversation endpoint is nested under the evaluation on purpose. The assessment it belongs to is part of the address, so a turn can never be sent to somebody else's conversation by mistake.

4. Read the result​

After assessment_complete, scoring runs in the background. The evaluation moves to completed when the report is ready. Poll it:

curl "https://$WISELOOK_HOST/v1/members/$TENANT_USER_ID/evaluations/$EVALUATION_ID" \
-H "Authorization: Bearer $TOKEN"

Reading needs the evaluations:read permission, separate from the evaluations:write that starts an assessment. A client that only polls results should not also be able to start them. Grant both to one client if it does both.

Scores and report fields stay null until the status is completed.

What you can and cannot do​

  • You cannot assess a removed member. Invited members, who have not signed in yet, are allowed.
  • An evaluation belongs to the organisation that created it. A token from another organisation cannot read it or send turns to it.
  • Members here, visitors in the widget. A member is on your Members list with a role, a status and tags. A widget visitor is identified by whatever string your backend passes when it mints the session, and is a separate record even when that string names someone who is also a member. Pick the route that matches who you are assessing.

See also​