Skip to content

Resolve the persisted plan for a subject

GET
/api/agents/plans/lookup/
curl --request GET \
--url 'http://localhost:8000/api/agents/plans/lookup/?agent=example&subject_kind=client' \
--header 'Authorization: Bearer <token>'

Resolve the persisted plan for one (agent, subject, lang). 404 when no plan has been generated yet — the frontend renders its empty state on that.

With a thread_id this returns every plan of that conversation, whichever persona wrote each one, and agent is then ignored. Without one it falls back to the subject’s plans, where agent is the only thing separating two personas’ plans — which is why it stays required here.

agent
required
string

Persona key from GET /api/agents/ (e.g. rehab).

lang
string

Reply language (en / ru / es). Defaults to en.

subject_id
string

Client id — required when subject_kind=client, ignored for team (a team subject always resolves to the caller’s active team).

subject_kind
required
string
Allowed values: client team

team or client.

Media typeapplication/json

Read shape of a plan — the ordered steps plus the framing the agent produced. is_edited tells the UI a regenerate would preserve coach work.

object
id
required
string format: uuid
agent
required
string
<= 32 characters
thread
required
string format: uuid
nullable
subject_kind
required
string
Allowed values: team client
subject_id
required
string format: uuid
lang
string
default: en <= 5 characters
title
required
string
<= 200 characters
summary
string
recheck_metric
string
<= 160 characters
mode
string
<= 8 characters
citations
required
Array<string>
is_edited
required
boolean
steps
required
Array<object>

One step. edited is read-only — the service sets it from a text change (ticking done deliberately does not count as authorship).

object
id
required
string format: uuid
order
required
integer
title
required
string
<= 200 characters
rationale
string
prescription
string
done
boolean
edited
required
boolean
generated_at
string format: date-time
updated_at
required
string format: date-time
Example
{
"subject_kind": "team",
"lang": "en"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}

The caller’s tier does not include the coaching agents.

Media typeapplication/json
object
detail
required

One of five canonical strings. The front-end paywall switches copy + CTA on this value (exact string equality — do not localise). quota_exhausted / seat_limit_reached / client_limit_reached / feature_not_in_plan lead to an upgrade CTA; subscription_suspended leads to a Billing Portal CTA so the buyer can update their payment method.

  • quota_exhausted - Session quota for the period reached 0
  • subscription_suspended - Stripe dunning in flight (past_due / unpaid)
  • seat_limit_reached - Adding another team member would exceed plan.max_seats
  • client_limit_reached - Adding another Client would exceed plan.max_client_profiles
  • feature_not_in_plan - The tier does not include this whole feature surface
string
Allowed values: quota_exhausted subscription_suspended seat_limit_reached client_limit_reached feature_not_in_plan
reset_at
required

ISO-8601 UTC timestamp the team’s current period ends. Null when the team has no live subscription (legacy un-migrated row). On Free + paid this is the renewal moment; quota refreshes to exactly plan.session_quota (NOT additive — leftover sessions don’t roll over).

string format: date-time
nullable
current_plan
required

The active plan’s role string (free, starter, professional, organization, strategic). Same value as current_role — kept as two separate fields for forward-compatibility with a future named-plan split where current_plan could carry an SKU and current_role the tier label.

string
current_role
required

The active plan’s role string. See current_plan.

string
feature

Which feature surface is locked. Present ONLY on feature_not_in_plan, so one modal can name what’s gated without a detail string per feature (#773). The only value today is agents (aikynetix/agents/services.py::AGENTS_FEATURE).

string
Examples
Example402—AgentsNotInPlan

Free team opens an agent conversation

{
"detail": "feature_not_in_plan",
"reset_at": null,
"current_plan": "free",
"current_role": "free",
"feature": "agents"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}