Agent sessions: the session resource and errors
Part of the Agent sessions reference, which lists every call.
Resource shape
{
"id": "agt_<uuid>",
"account_id": "<uuid>",
"driftstack_session_id": "ses_<uuid> | null",
"status": "provisioning | active | paused | closed",
"closed_reason": "<string> | null",
"closed_reason_detail": "<string> | null",
"customer_close_reason": "<string> | null",
"close_conditions": ["only_if_stale"],
"provisioning_detail": null,
"ready": false,
"ready_at": "<ISO-8601> | null",
"timings": {
"requested_at": "<ISO-8601>",
"allocated_at": "<ISO-8601> | null",
"device_ready_at": "<ISO-8601> | null",
"allocation_ms": "1840 | null",
"ready_ms": "9120 | null"
},
"closed_at": "<ISO-8601> | null",
"token_budget_total": 100000,
"token_budget_remaining": 99500,
"transcript_length": 12,
"history": {
"entries_used": 12,
"entries_max": 256,
"entries_left": 244,
"nearly_full": false,
"entries_dropped": 0
},
"created_by_user_id": "<user-uuid> | null",
"mode": "ai | manual | pair",
"model": "claude-sonnet-5-5 | claude-haiku-5-5 | claude-fable-5-1 | claude-opus-5-5 | claude-opus-5 | claude-sonnet-5 | claude-opus-4-8 | claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5",
"stop_on_exit_ip_change": false,
"proxy_id": "<uuid> | null",
"profile_id": "prof_<uuid> | null",
"archetype": "iphone15_ios18_7_safari26_5 | null",
"archetype_source": "explicit | profile | random | null",
"resumed_from": "agt_<uuid> | null",
"created_at": "<ISO-8601>",
"updated_at": "<ISO-8601>",
"livekit": {
"ws_url": "wss://<livekit-host>",
"room": "agt_<uuid>",
"token": "<HS256 JWT>",
"participant_identity": "customer-<account-uuid>",
"expires_at": "<ISO-8601>"
},
"error_event": {
"timestamp": "<ISO-8601>",
"code": "launch_timeout",
"severity": "info | warn | error | fatal",
"summary": "The browser did not become ready in time.",
"detail": "<string> | null",
"customer_actionable": false,
"retryable": true
}
}
The status field reads active from the moment the session exists, before
its browser has finished starting, so status alone does not tell you the
browser is ready; ready below does. provisioning is kept in the list for a
session whose setup step is reported in provisioning_detail; today a session
reads active while it starts, VPN sessions included. If you do see
provisioning, treat it as running but not ready: do not start work against
the session, and do not treat it as finished. It is already consuming
one of your concurrent-session slots, so a provisioning session counts against
your cap exactly as an active one does.
ready turns true once the session’s browser has finished starting, and
ready_at says when; status reads active before that. Wait for
ready: true before sending the first message — a session on a VPN can take
longer to become ready — and read a session that closes while ready is still
false as one that failed to start, with closed_reason saying why. An older
deployment does not send ready; read a missing ready as ready.
ready comes from the session’s own report, and some sessions cannot send that
report again once it is lost (for example across a reconnect), so a running
session can keep reading ready: false. A message is held for a session that
is not ready only within 125 seconds of its creation, long enough for a VPN
session to finish starting: if ready is still false after that while
status stays active, send the message anyway — it is no longer held.
timings says where a session’s start time went, so you can tell the time on
your side (your request and network) from the time on ours. Every value comes
from times the server records on its own clock:
requested_at— the server accepted the create request (equalscreated_at).allocated_at— the device the session was placed on picked it up and began starting the browser. Time spent waiting for a device counts before this.nulluntil then, and on a session no device picked up.device_ready_at— the browser was ready (the same instant asready_at).nulluntil then, and for ever on a session that failed to start.allocation_msandready_ms— the milliseconds fromrequested_attoallocated_atand todevice_ready_at;nullwhile that time isnull.ready_ms - allocation_msis the time the device spent starting the browser.
Poll GET /v1/agent-sessions/{id} until ready is true to see all of them.
An older deployment does not send timings.
provisioning_detail names the step it is on (a snake_case token such as
vpn_egress_bringing_up or vpn_egress_active), and is null once the session
is active or closed. It may be absent entirely on older deployments, so
read it defensively rather than assuming the key exists.
history says how much room the session’s live history has. The
live history is what the agent works from: it holds at most 256 entries (about
256 steps calls, or roughly 85 to 127 AI messages) and 1 MiB. A steps call
needs 1 free entry, a note in manual mode 1, and an AI message 3 (your message,
the plan with its results, and the answer). When a call does not fit, the
session does not close: the oldest entries are dropped from the live history
and the session goes on. The drop keeps the newest entries that fit in half of
each limit (128 entries and 512 KiB), from the start of a turn, and always
keeps your first task and the newest two entries, so it happens about once
every 125 steps calls, not on every call. Dropped entries are not deleted: the
transcript stream still returns them under their
own numbers, until the deployment’s retention empties a closed session’s
transcript, dropped entries included. The agent no longer works from them, and
a recipe built from the session and the profile’s
activity leave them out too.
transcript_length counts every entry the session has written, dropped ones
included, and never goes down; the newest entry is number
transcript_length - 1. entries_used counts the entries in the live history,
so it is the same number as transcript_length only until the first drop.
entries_max is 256, and entries_left is the room before the next drop.
entries_dropped says how many entries have been dropped so far (0 until the
first drop); it is absent on older deployments, which close a full session
instead. nearly_full turns true when fewer than 32 entries are left, or when
the history is close to the 1 MiB limit, which a session with long entries can
reach first with many entries still left. It means a drop is near; you need do
nothing.
A session is still closed with closed_reason: "transcript-limit" in two
cases: one call alone needs more room than the history has after a drop
(a single call that adds several hundred KiB of text), or the session
has already dropped the most it can (4,096 entries or 16 MiB). Then start a new
session, with continue_from_agent_session_id to carry the recent conversation
over. On the session of a message or
steps answer the numbers already count that call’s own
entries, and the same answer carries a warning while nearly_full is true.
history is null on a closed session, in each row of List (read one
session with GET /v1/agent-sessions/{id} to get it), and when the server
cannot count the history; it is absent on older deployments. It never carries a
size or any of the history itself.
closed_reason says why a closed session ended. It is an open string, not a
fixed list: customer-closed (you closed it), budget-exhausted (its token
budget ran out), transcript-limit (its history could not take a call; see history
above), exit_ip_changed
(see stop_on_exit_ip_change under Create), trial_session_limit
(a free-trial session reached its 20 minutes), trial_time_used (the free
trial’s 2 hours of session time ran out during the session), payment_failed
(the account’s renewal payment didn’t go through, so its paid plan stopped),
plan_ended (the account’s paid plan ended),
browser_crashed (the session’s browser crashed — including when it reported
that its network process crashed, after which Driftstack ends the session for
you; see “When the browser’s network process crashes” under
Message), and values such as
idle_timeout or max_duration when the browser behind the session ended it.
Read any other value as “the session could not start, or Driftstack ended it”.
worker-disconnected means the Mac running the session went offline, and
worker-restarted that it restarted (for example, to install an update); the
session ended with it. Nothing in your requests caused either; start a new
session.
node_resource_pressure means the Mac running the session was briefly short
of system resources on our side, so the session was stopped. Its
error_event, when it has one, reports retryable: true. Nothing in your
requests caused it; start a new session.
session-errored means the session stopped on the Mac running it without a
more specific reason: the browser reported an error, or the Mac stopped
listing the session (it lost it) without saying how it ended, so Driftstack
closed it rather than let it hold one of your concurrent-session slots.
Nothing in your request caused it; start a new session.
provisioning_interrupted means the Mac running the session picked it up and
then stopped reporting it before it was ready, so Driftstack closed it rather
than let it hold one of your concurrent-session slots. It closes with ready
still false, and its error_event carries the same code, the summary “The
session could not start on our side. Start it again.” and retryable: true.
Nothing in your request caused it; start a new session.
dispatch_no_live_node means the device the session was sent to went out of
service just as it started, so the session closed moments after the create
returned (rarely, it is already closed in the create response). Nothing in the
request caused it; create the session again shortly with a new
Idempotency-Key (the same key replays the closed session). When no device is
free at the moment of the create, the create itself is refused with 503 and
nothing is created — see No device free.
archetype_config_failed means the device’s own configuration, on the Mac
running the session, could not be loaded, either as the session started or
later while it ran; launch_config_failed means that Mac could not set up the
environment the device needs to start. Both are on our side, not in your
request: the error_event carries the same code, customer_actionable: false, detail: null and the summary “This device’s configuration couldn’t
be loaded on our side, so the session didn’t start. Choose a different device,
or contact support if it keeps happening.” (or “…so this session was stopped.
…” when it had been ready). retryable is false: until that Mac is fixed, a
new session with the same device is likely to be sent to it and fail the same
way, so choose a different device, or contact support if it keeps happening.
upstream_proxy_not_public means the proxy saved for the session points at an
address that is not on the public internet (a private or local one), so the
Mac that would have run the session did not start it. Nothing ran. The
error_event carries the same code, customer_actionable: true, detail: null and the summary “This session’s proxy address is not a public address,
so the session was not started. Change the proxy and start again.” retryable
is false: the same proxy is refused the same way each time, so change the
proxy’s address first.
closed_reason_detail says why a closed session ended in plain words you can
show to a person, and is null while the session has not ended. When the
session’s error_event names the cause — an error or fatal event, or any
event on a session that closed session-errored (for example because your
proxy refused its sign-in) — it explains that
cause rather than the close reason; a session you or Driftstack support
stopped, or that reached a trial limit, says so whatever its error_event.
Where there is something to do next, it ends with “Reopen the session to
continue.” while the session can still be
reopened (within 7 days of closed_at, for a
session that ran through one of your proxies and whose profile, if its device
came from one, still exists), and with “Start a new session to
continue.” after that. A reason not in the table below gets “This session has
ended. Contact support if you did not expect it and it keeps happening.” and
the next step, never the raw value. The wording may change: branch on
closed_reason and error_event.code, show closed_reason_detail. It is
absent on older deployments.
The words for each reason (a closed_reason, or the error_event.code that
explains it); “Next step: yes” means one of the two sentences above follows.
transcript-limit and budget-exhausted are the exceptions: they end “To
keep the recent conversation, start a new session that continues from this
one.” (create it with continue_from_agent_session_id), and while the session
can still be reopened they add “Reopening it starts a new session with the same
setup and an empty history.” (budget-exhausted: “…with the same budget…”),
because a reopen carries no conversation and keeps the budget:
| Reason | Words | Next step |
|---|---|---|
customer-closed |
You closed this session. | no |
session_ended |
This session ended normally. | no |
staff-stopped |
Driftstack support stopped this session. Contact support if you need to know why. | no |
idle_timeout |
This session was closed because nothing happened in it for longer than its idle limit. | yes |
max_duration |
This session reached the maximum running time set for it, so it was closed. | yes |
challenge_pause_timeout |
This session was paused for a verification check on the page, and it was not resumed within 5 minutes after the last activity, so it was closed. | yes |
orphaned-lifetime |
This session reached the longest time a session may stay open, counted from when it started, so it was closed automatically. | yes |
trial_session_limit |
This trial session reached its time limit. You can start another while your trial has session time left, or choose a plan for longer sessions. | no |
trial_time_used |
Your trial’s session time ran out, so this session was closed. Choose a plan to keep running sessions. Your profiles, proxies and settings stay saved for at least 30 days after your trial ends. | no |
payment_failed |
This workspace’s last payment didn’t go through, so this session was stopped. The account owner can pay the open invoice or update the card; the plan comes back as soon as the payment goes through. | no |
plan_ended |
This session was closed because this workspace’s plan has ended. The account owner can choose a plan (or pay for a new crypto term) to start sessions again; profiles and settings are kept. | no |
budget-exhausted |
This session used up its token budget, so it was closed. Give the next session a larger budget if you need one. | yes |
transcript-limit |
This session’s history reached its maximum length, so it was closed. | yes |
exit_ip_changed |
Your proxy’s exit IP address changed while the session ran, and this session was set to stop when that happens. Check the proxy’s settings if its address should stay the same. | yes |
intent_deadline_exceeded |
A step ran past the time allowed for it, so the session was stopped. | yes |
session_resource_overuse |
The page used more memory or processing power than a session allows, so the session was stopped. | yes |
browser_crashed |
The browser running this session stopped unexpectedly. | yes |
browser_closed |
The browser running this session was closed. | yes |
browser_exited |
The browser running this session shut down. | yes |
renderer_crashed |
The page in this session stopped running and could not be recovered. | yes |
launch_timeout |
This session did not start in time. Nothing in your requests caused it. | yes |
render_failed |
The browser for this session did not start. Nothing in your requests caused it. | yes |
webkit_spawn_failed |
The browser for this session did not start. Nothing in your requests caused it. | yes |
webdriver_connect_failed |
The browser for this session did not start. Nothing in your requests caused it. | yes |
worker-restarted |
The Mac running this session restarted. | yes |
worker-disconnected |
The Mac running this session went offline. Nothing in your requests caused it. | yes |
node_drain |
The Mac running this session was taken out of service for maintenance. Nothing in your requests caused it. | yes |
node_shutdown |
The Mac running this session was taken out of service for maintenance. Nothing in your requests caused it. | yes |
node_shutting_down |
The Mac running this session was taken out of service for maintenance. Nothing in your requests caused it. | yes |
control_plane_unreachable |
We lost contact with the Mac running this session, so it was stopped. Nothing in your requests caused it. | yes |
session_not_found |
This session had already stopped on the Mac running it. | yes |
node_resource_pressure |
The Mac running this session was briefly overloaded on our side, so the session was stopped. Nothing in your requests caused it. | yes |
dispatch_no_live_node |
No Mac was free to start this session just then. Nothing in your requests caused it. Wait a minute or two first. | yes |
node_shedding |
No Mac was free to start this session just then. Nothing in your requests caused it. Wait a minute or two first. | yes |
session_refused_at_capacity |
No Mac was free to start this session just then. Nothing in your requests caused it. Wait a minute or two first. | yes |
dispatch_failed |
This session could not be started, so nothing ran in it. Nothing in your requests caused it. | yes |
create_failed |
This session could not be started, so nothing ran in it. Nothing in your requests caused it. | yes |
dispatch_owner_claim_failed |
This session could not be started, so nothing ran in it. Nothing in your requests caused it. | yes |
provisioning_interrupted |
This session stopped before it finished starting. Nothing in your requests caused it. | yes |
reaped_during_provisioning |
This session stopped before it finished starting. Nothing in your requests caused it. | yes |
screen_capture_denied |
Live view was not available for this session, so it was stopped before it started. Wait a few minutes first. | yes |
live_view_capture_unavailable |
The live view of this session could not start, so the session was stopped. Nothing in your requests caused it. | yes |
archetype_lookup_failed |
The device this session’s profile uses is on hold or not available right now. Pick another device for the profile. | yes |
archetype_not_supported |
The device profile this session asked for is not available right now. Pick another device for the profile. | yes |
session_config_invalid |
This session could not start with its current settings. Check the profile and proxy it uses. | yes |
archetype_config_failed |
This device’s configuration couldn’t be loaded on our side, so the session didn’t start. Choose a different device, or contact support if it keeps happening. | no |
launch_config_failed |
This device’s configuration couldn’t be loaded on our side, so the session didn’t start. Choose a different device, or contact support if it keeps happening. | no |
session-errored |
This session stopped unexpectedly because of a problem on our side. | yes |
unknown_error |
This session stopped unexpectedly because of a problem on our side. | yes |
proxy_required |
This session needs one of your saved proxies and none was given, so it was not started. Choose a proxy for it. | yes |
no_proxy_configured |
This session needs one of your saved proxies and none was given, so it was not started. Choose a proxy for it. | yes |
upstream_proxy_not_public |
This session’s proxy address is not a public address, so the session was not started. Change the proxy and start again. | no |
exit_timezone_unresolved |
This session was not started because we could not confirm where your proxy’s connection comes out. If it keeps happening, test the proxy. | yes |
proxy_connection_failed |
This session could not connect through your proxy. Check the proxy’s settings, or test it. | yes |
proxy_udp_unsupported |
Your proxy cannot carry the UDP traffic this session needs for HTTP/3, so it was not started. Use a proxy that supports UDP. | yes |
egress_unreachable |
We could not confirm where your proxy’s connection comes out, so the session was stopped. Check the proxy’s settings, or test it. | yes |
egress_invariant_violation |
Some of this session’s traffic did not go through your proxy, so the session was stopped to keep it on the proxy. Check the proxy’s settings, or test it. | yes |
egress_bind_failed |
The VPN for this session could not be started. Check the proxy’s VPN settings and that its server is up. | yes |
egress_lost |
The part of the connection we run for this session stopped while it was running. Nothing is wrong at your end. | yes |
proxy_boot_failed |
The part of the connection we run for this session did not start. Nothing is wrong at your end. | yes |
network_shim_boot_failed |
The part of the connection we run for this session did not start. Nothing is wrong at your end. | yes |
default_egress_unavailable |
The connection this older session ran on, which we provided, stopped working. That was ours to fix, not yours. | yes |
proxy_auth_failed |
Your proxy refused the username or password saved for it. Check them in the proxy’s settings. | yes |
egress_verification_unavailable |
We could not confirm that this session’s traffic was going through your proxy, so we stopped it rather than risk it going out unprotected. This one is ours, not your proxy. | yes |
vpn_bringup_failed |
The VPN for this session could not be brought up, and there was not enough detail to say why. Check the proxy’s VPN settings and that its server is up. | yes |
vpn_bringup_no_active_state |
The VPN for this session could not be brought up, and there was not enough detail to say why. Check the proxy’s VPN settings and that its server is up. | yes |
remote_unresolved |
The address your proxy points at could not be found, so nothing could connect. Check the address in the proxy’s settings, or ask whoever supplies it. | yes |
remote_refused |
Your proxy’s server refused the connection. Check the address and port in the proxy’s settings, or ask whoever supplies it. | yes |
remote_unreachable |
Nothing answered at your proxy’s address. Check the address and port in the proxy’s settings, or ask whoever supplies it. | yes |
remote_closed_during_setup |
Your proxy’s server closed the connection before the VPN was up. Check the proxy’s VPN settings, or ask whoever supplies it. | yes |
tls_handshake_failed |
The secure handshake with your proxy’s server did not complete. A certificate or key in the proxy’s settings may no longer match the server. | yes |
config_rejected |
The VPN configuration for this proxy was refused. Open the proxy and paste its configuration again. | yes |
auth_failed |
Your proxy refused the username or password saved for it. Check them in the proxy’s settings. | yes |
tunnel_setup_timeout |
The VPN for this session did not finish connecting in time. Check the proxy’s VPN settings and that its server is up. | yes |
no_output |
The VPN for this session could not be started on our side. This one is ours, not yours. | yes |
egress_bypasses_tunnel |
The VPN came up but did not carry all of this session’s traffic, so the session was stopped. Set the proxy to send all traffic through the VPN. | yes |
tunnel_no_egress |
The VPN came up but no traffic came back through it, so the session could not browse. Check that the VPN server forwards traffic. | yes |
vpn_no_dns |
Your VPN configuration sets no DNS server, so this session was not started. Add a DNS = line naming a resolver inside your VPN (WireGuard), or have the server push dhcp-option DNS (OpenVPN). We don’t use a public resolver for you. | yes |
exit_ip_check_unavailable |
We couldn’t check this session’s exit address, so it was stopped. The connection itself may be fine. | yes |
egress_ip_lookup_failed |
We couldn’t check the connection on our side, so this session was stopped. Start the session again. | yes |
stop_on_exit_ip_change is always present and echoes the create-time setting.
network_detail is on GET /v1/agent-sessions/{id} and on the create
answer (never on a list row or another call’s answer): requested is true
when the create sent network_detail: true, served says whether request
detail is being recorded for the session, and reason is null when it is,
otherwise not_requested, mode_off (the deployment is not recording request
detail for this session) or not_recorded (asked, but the browser has sent no
request detail yet).
proxy_id is the proxy the session’s traffic goes out through, one of your
account’s proxies: the proxy_id you passed at
create. It is set from the create’s response onwards, and routing
new sessions somewhere else does not change it. Every agent session created
since proxy_id became required has one; null appears only on an older
session that was created without a proxy of yours. It is absent on older
deployments, so read a missing key as “not reported”, not as null.
archetype is the device profile the session runs (an id from
GET /v1/archetypes), fixed for the session’s lifetime,
and archetype_source says where it came from: explicit (the create’s
archetype), profile (the device of the profile it runs) or random (the
create named neither, so the session runs as a fresh visitor on a random
current iPhone your plan includes — see Create). Both are null on
a session created before the server recorded them, and absent on older
deployments.
resumed_from is the id of the ended session this one was
reopened from, and null on a session that was
created. It is only a link: the reopened session has the ended one’s
configuration and none of its history. Absent on older deployments.
Two more fields are optional and read-only: liveness ({ state, fresh } —
the browser’s latest reported state, active, provisioning, idle or
terminating, and whether that report is recent) and capability_report (the
browser’s latest report of what the session can do — for example its exit IP,
country and timezone, and whether live video is streaming). Both are absent
until the browser has reported; treat an absent field as “not known yet”, never
as “not running”.
capability_report.egress_state says whether the session’s traffic is getting
out: live; dead_proxy — your proxy stopped carrying traffic while the
session ran, so check it before starting another; or default_connection_down
— only on a session created before proxy_id became required, without a
proxy of its own: the connection Driftstack provided for it then stopped
carrying traffic. That one was on our side. No agent session created since
reports it, because every one runs through one of your proxies.
null means it has not been reported. Read a value you do not recognise as
null.
capability_report.os_fingerprint is the last OS reading Driftstack took of the
session’s proxy — the same reading as the proxy’s own os_fingerprint (see
Proxies): os, confidence, at (when it was taken; it can be
any age), and how it was taken — observed_via, single_host_vantage and
web_port_vantage, with direct_reading and website_like_reading as the
customer names of the last two. Read a missing or false value on either pair as
“this reading does not describe the path a website sees”. null means not
measured, never “no OS”.
capability_report.exit_tcp_stack says whether the network stack of the
session’s proxy looks like an iPhone’s. Your proxy opens its own connection to
every site, so the first settings of that connection come from the proxy’s
own operating system, not from the Mac running the session, and a site that checks them can notice when they
differ. It is measured once, when the session starts: verdict is match,
mismatch or unknown (it could not be decided), measured_at is when
Driftstack took the reading, and proxy_id is the proxy it describes. A
mismatch is a warning: nothing is blocked and the session keeps running.
null means it was not measured: the check is off, the session has no proxy of
its own, the reading could not be timed, the Mac running the session was too
busy to run the check, or the session has moved to another proxy since the
reading. Read a verdict you do not recognise as unknown.
upload_max_file_bytes is the largest file, in bytes, that one
upload to this session can carry right now. Each session takes
a file up to its own size, so this is often smaller than the 64 MiB per-file
maximum, and it can change while the session runs. It is returned by
GET /v1/agent-sessions/{id} while the session is running and has reported
it, and absent otherwise — absent means “not known”, not “no limit”. Check
a file against it before reading or encoding the file.
viewport is the phone’s page viewport for the session, in the phone’s CSS
pixels (for example { "width": 402, "height": 714 }): window.innerWidth x
window.innerHeight of a page in the session. It is the coordinate space taps
and clicks on the live screen are given in, and it is fixed for the session’s
lifetime by its device. Each side is a whole number from 1 to 4096, the
largest the server accepts. It is
returned only by GET /v1/agent-sessions/{id}; it is null until the device
has reported it (normally as the session starts) and after the session has
ended, and absent from the list and create answers and from older servers.
The error_event field is optional and nullable — it carries the most
recent launch or runtime failure recorded for the session, and is
absent or null when none has been reported. Branch on its two booleans
rather than on the prose: customer_actionable says whether a human can do
anything about the failure, and retryable says whether repeating the same
call is worth trying. detail is null when the server has nothing to add
beyond summary, and severity is one of info, warn, error, fatal.
An error_event does not by itself close the session — read status for
that.
Two codes concern a session started without a proxy of your own.
default_egress_unavailable appears only on a session created before
proxy_id became required, without one: the connection Driftstack provided for it then failed;
customer_actionable is false, because it was ours to fix. The same value
can appear as closed_reason. proxy_required means the machine running the
session refused a session that reached it with no proxy; sessions run only through a proxy you
have added, so start it again with one. (A create without proxy_id never gets
that far — it is refused with 422 proxy-required; see Create.)
session_not_found means a request reached the Mac running the session after
the session had already stopped there. Its summary is “This session had
already stopped on the Mac running it, so the last request sent to it could not
be carried out.” and detail is null. It is not recorded on a session that
has already closed for a reason of its own, so it does not hide why that
session stopped.
archetype_lookup_failed means the session could not start on its profile’s
device: that device is on hold or still in development (the create refuses
these up front with 409 device-unavailable, so this appears only when the
catalog changed in between), or it cannot run right now for another reason. Its
summary says so in words you can show — for a device on hold, the hold
sentence with its reason in plain words, such as “This device profile is on
hold while we confirm it matches a real iPhone. Pick another device.”;
otherwise “This
profile’s device isn’t available right now.” — and detail is null. It is
fatal, customer_actionable and not retryable: the same profile fails the
same way. The session closes with it; start a new one on a profile with another
device.
The livekit field is optional — auto-populated on the
session-create response when live video is available on the
deployment, and absent otherwise. It is also absent when the session in the
response is already closed, since nothing will ever stream into it. Clients
that need a token for a running session in the absent case use the explicit
endpoint at Live video (LiveKit) below.
ID-format note. The agent-sessions resource emits
account_idas a bare UUID (noacc_prefix), unlikeGET /v1/whoami, which emitsacc_<uuid>, and theGET /v1/sessions/:idresource which emits prefixedses_/acc_/ key_IDs. Customer code comparingagentSession.account_idagainstwhoami.account_idmust strip theacc_prefix from the latter first. (idanddriftstack_session_idare prefixed —agt_<uuid>andses_<uuid>. Onlyaccount_idis bare here.)
Errors
| Status | Type | When |
|---|---|---|
| 400 | validation-failed | body fails schema (missing user_message, a user_message over 8,000 characters, etc.) |
| 400 | bad-request | create’s proxy_id names an HTTP proxy |
| 403 | forbidden | create with mode: ai on a plan without the AI agent (the free trial, Free, Personal) |
| 403 | forbidden | a model that runs only on your own key, at create or on a message (requires_own_key: true, with model naming it) |
| 403 | forbidden | a message on bundled billing when the account’s current plan no longer includes it (add your own key) |
| 404 | not-found | session id you cannot access (not your own, and not a team you hold admin on) |
| 409 | conflict | mode mismatch, or ai_control_unavailable: true when control of the session changes while a message is running; the latter includes phase and can include consumed tokens_consumed, usage, and redacted partial_results that must not be replayed automatically |
| 409 | conflict | turn_in_progress: true — another message is still running on this session; wait for it or stop it |
| 409 | conflict | session_status — the session was not active ("closed", with closed_reason beside it when the session records one and closed_reason_detail, the same reason as a sentence, or "paused"); start a new session, optionally with continue_from_agent_session_id, or resume the paused one |
| 409 | conflict | idempotency_status: "in_progress" — the first request with this Idempotency-Key is still running (retry later with the same key); "mismatch" — the key was used for a different request (use a new key) |
| 409 | session-not-ready | a message to a session whose browser is still starting (ready is still false), after waiting up to 45 seconds (20 seconds on a JSON response) for it; nothing ran — send the same message again after retry_after_seconds (carries code: "session_not_ready", retryable: true) |
| 409 | profile-in-use | create’s profile_id already has a live session (carries active_session_id) |
| 409 | device-unavailable | create’s profile_id is on a device that is not offered right now (carries archetype, and held_reason when there is one) |
| 409 | storage-quota-exceeded | a profile-backed create when your plan’s profile storage is full |
| 409 | conflict | reopen of a session still running (code: "session_not_ended"), or of one whose configuration cannot start a session today (code: "reopen_configuration_unavailable", with fields) |
| 409 | conflict | top-up of a session that has ended (code: "session_not_running"), or past 10,000,000 tokens (code: "token_budget_cap_exceeded", with max_add_tokens) |
| 410 | session-destroyed | reopen of a session that ended more than 7 days ago (code: "reopen_window_passed", reopen_window_days: 7); create a new session instead |
| 402 | bundled-llm-budget-exhausted | bundled-LLM monthly cap reached |
| 402 | bundled-llm-consent-required | no key of your own and no opt-in to bundled billing; on a plan without bundled billing opting in is refused, so add your own key instead |
| 422 | proxy-validation-failed | create’s live proxy test failed (carries reason) |
| 422 | proxy-required | create named no proxy_id; nothing was created (carries code: "proxy_required") |
| 429 | rate-limited | the agent_sessions:message request rate for your plan (agent_sessions:steps for a list of steps), or your account already has as many AI turns running at once as it may — across your sessions, or on bundled billing (retry_after_seconds) |
| 429 | concurrency-limit | at create, your account already has as many open agent sessions as your plan allows; on a message, with ai_tasks_in_flight: true, 3 AI tasks are already running (depending on how your account’s AI is billed, that limit answers this way or as rate-limited, above): nothing ran, wait and send the message again |
| 429 | tier-limit | at create only: the free trial’s 2 hours of session time are used up (code: "trial_session_time_used"), or the profile or proxy is outside the ones your plan can launch (code: "over_plan_limit") |
| 429 | rate-limited | at create and reopen: the account’s last sessions could not connect through their proxy (code: "proxy_failure_backoff"), or it is at its limit: at most as many sessions starting at once as your plan runs at once, and never more than 10 (this number can change; the 429 tells you how long to wait) (code: "provisioning_limit"); retryable: true, with Retry-After; nothing was created — see Create errors. The limit is per account for now |
| 500 | internal | something went wrong on our side, except a refusal from Anthropic we do not recognise yet (for example a spend limit set in the Anthropic Console): if it repeats on your own key, check that account first; a refusal we recognise, of your own Anthropic key or of the account behind it, is the 502 below |
| 502 | byok-anthropic-required | no Anthropic key could be found for the turn: none sent, none stored, and bundled billing is not available — or, with key_rejected: true, Anthropic refused yours, or the account behind it (no credit left, or the organization disabled), on the first planning call (key_source, key_rejected_reason) |
| 503 | feature-unavailable | on a message, an Idempotency-Key was sent but this deployment cannot record one, so the turn did not run — the same key will fail the same way, and the same request without the header runs the turn (Idempotency); that answer has no code (with code: "ai_provider_unavailable", see the next row: keep the header and use a new key); on Stop, stop_unconfirmed: true means the stop could not be confirmed just now (try again) |
| 503 | feature-unavailable | on a message, code: "ai_provider_unavailable" (retry_after_seconds; Retry-After except when streamed): the AI service is temporarily unavailable, so nothing ran; steps sent to /steps still work. A turn on your own key never gets it |
| 503 | maintenance | on a create or a reopen, every machine that could run the session is in a booked maintenance window (code: "maintenance", retryable: true, until, retry_after_seconds and Retry-After); nothing was created — send the same request again after until, with the same Idempotency-Key if you sent one |