API REFERENCE

History, sessions, feedback

Read a conversation back, clear it, or tell the agent how it did. Three small endpoints around a session.

Shared behaviour

All three endpoints authenticate with the project key as a Bearer token, and all three enforce the project origin allowlist when one is configured. Unlike POST /v1/chat, they return the standard error shape: a JSON body of {"error":"CODE"} with a matching status.

FieldTypeDescription
PROJECT_INVALID_KEY401The Authorization header is missing or the key matches no project.
ORIGIN_NOT_ALLOWED403The Origin header is not on the project allowlist.
IDENTITY_*401Signed mode only, and only when the named session exists: the identity did not verify. One of IDENTITY_NOT_CONFIGURED, IDENTITY_SIGNATURE_REQUIRED, IDENTITY_SIGNATURE_INVALID, IDENTITY_SIGNATURE_EXPIRED, IDENTITY_ID_REQUIRED.
SESSION_NOT_FOUND403The session exists but belongs to another project, or in signed mode to another user. A session id that does not exist at all is not an error on any of these routes — see below. The status is 403, not 404.
A session id that matches no row is treated as an empty, unowned session. In signed mode the identity check is skipped entirely for it, so GET returns an empty transcript and DELETE reports success. This is deliberate: the API never confirms whether a session id exists to a caller who does not own it.

Get history

GET/v1/history

Returns the recent messages for one session, oldest first. Useful when you render your own transcript instead of the widget.

FieldTypeDescription
session_idrequiredstringThe session to read. Query parameter.
limitnumberHow many messages to consider, counting back from the newest. Query parameter, defaults to 30. Not validated: limit=0 returns the entire transcript, and a negative value drops that many messages from the start.
In signed identity mode, pass the signed user_context URL-encoded in the X-Era-User header. There is no body on a GET, so the identity rides in the header.
bash
curl "https://agentifys.ai/v1/history?session_id=kJ2mQ8vX1pR4nT7bW0cLdA&limit=30" \
  -H "Authorization: Bearer era_your_project_key"

# Signed mode: add the URL-encoded signed identity of the session owner.
#   -H "X-Era-User: %7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1757721600%2C%22_sig%22%3A%22...%22%7D"

Response:

json
{
  "messages": [
    { "role": "user", "content": "Where is order 1043?" },
    { "role": "assistant", "content": "Order 1043 shipped, ETA Sep 2." }
  ],
  "session_id": "kJ2mQ8vX1pR4nT7bW0cLdA"
}

Each entry is only role and content. There are no ids, timestamps, token counts or trace ids here, so you cannot map a historical reply back to a trace_id for feedback. Capture the trace_id from the done event while the turn is live if you need it later.

There is no pagination. limit always counts back from the newest message, there is no offset or cursor, and no field tells you whether older messages exist. To page backwards, request a larger limit and slice client-side. limit has no upper bound, so a long conversation comes back in one response.

A turn is written to history only once it completes. A chat request whose connection dropped mid-stream leaves no trace here at all, neither the user message nor the partial reply.

Delete a session

DELETE/v1/sessions/{session_id}

Deletes the session row and cascades to its messages. Use it for a clear-conversation action or when a user asks to be forgotten. This also drops any write action the session was holding for approval.

In signed mode this route reads the identity from the X-Era-User header, the same way GET /v1/history does, and the caller must own the session.
bash
curl -X DELETE https://agentifys.ai/v1/sessions/kJ2mQ8vX1pR4nT7bW0cLdA \
  -H "Authorization: Bearer era_your_project_key"

# Signed mode: the caller must prove it owns the session.
#   -H "X-Era-User: %7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1757721600%2C%22_sig%22%3A%22...%22%7D"

Response:

json
{ "deleted": true, "session_id": "kJ2mQ8vX1pR4nT7bW0cLdA" }

deleted is always true on a 200. It reports that the delete ran, not that a row was found, so deleting an already-deleted or unknown session returns the same body. Treat the call as idempotent rather than as an existence check.

Submit feedback

POST/v1/feedback

Records a thumbs up or down on a reply. Pair it with the trace_id from that turn's done event so the rating lands on the right response.

FieldTypeDescription
ratingrequirednumberThe only required field. Normalised on write: any value of 0 or above is stored as +1, anything negative as -1. Send +1 or -1 and nothing else.
session_idstringThe session the reply belongs to. Optional, but without it the rating cannot be attributed to a user and the ownership check is skipped.
trace_idstringFrom the done event of the rated turn. Optional and not validated: an unknown trace_id is stored as sent.
commentstringOptional free-text note from the user. Truncated to 2000 characters.
user_contextobjectThe end-user identity. In signed mode it must be signed and must own session_id, otherwise the request is rejected. Ignored in open mode.
bash
curl -X POST https://agentifys.ai/v1/feedback \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "kJ2mQ8vX1pR4nT7bW0cLdA",
    "trace_id": "9f3a1c7d",
    "rating": 1,
    "comment": "Nailed it.",
    "user_context": { "id": "u_42", "_ts": 1757721600, "_sig": "..." }
  }'

Response:

json
{ "ok": true }

The user a rating is attributed to is read from the stored session, never from the user_context you send, so the body cannot name a different user. In signed mode the ownership check means only that user can rate the session. In open mode there is no such check: anyone holding the project key and a session id can rate that session, and it lands on the session owner.

This route has its own throttle, separate from the project rate limit: 20 requests per minute and 200 per day, counted per session_id. Over the limit it returns 429 with {"error":"RATE_LIMIT_EXCEEDED","retry_after":<seconds>}. Note the seconds are in the body as retry_after; this route sends no Retry-After header.

The full list of error codes is on the errors reference.