Gary Club
Resource

Agents

Every voice + SMS agent in your tenant has a stable identity. Address agents by human-readable slug or UUID; renames preserve old slugs as aliases for ~12 months so existing integrations don't break.

Multi-agent at a glance
A single client can have N agents — multiple inbound voice lines, outbound voice campaigns, SMS bots — each with its own slug, kind, purpose, labels, and state. Pass agent_id (slug or UUID) to any endpoint that accepts it; the API resolves through the agent_aliases table so old slugs keep working after renames.

Identity model#

iduuid
Internal identifier. Stable forever.
slugstring
Org-unique human-readable handle, e.g. agent_3kf9ab. Format: ^[a-z0-9][a-z0-9_-]{2,63}$. Auto-generated; renameable. Past slugs auto-redirect via agent_aliases for ~12 months.
kindenum
inbound_voice · outbound_voice · sms · hybrid_voice_sms. Drives provisioning + UI labels. Immutable after creation.
purposestring?
Free-text role label, e.g. "After-hours overflow", "Cold outreach", "Renewals". Disambiguates same-kind agents on the same client.
labelsstring[]
Free-form tags up to 16 entries × 32 chars each. Used for filter chips on the fleet view and the v1/agents?label= filter.
stateenum
active · paused · archived. Paused = config preserved, no calls/messages dispatched. Archived = soft-deleted; alias still resolves but the agent is not callable (returns 410).
client_iduuid
The client this agent belongs to.
org_iduuid
The agency that owns this agent.

Addressing agents#

Every endpoint that takes {idOrSlug} accepts both forms. We recommend slugs in code you write — they're readable, debuggable, and survive UUID lookup latency.

Both work
curl https://agency.gary.club/api/public/v1/agents/agent_3kf9ab \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"
No defaults — agent_id is required
For endpoints that filter by agent (calls, messages, conversations), pass agent_id explicitly. We do NOT pick a default agent for you, even when the client has only one — it's better to fail loud than silently route to the wrong agent if the client adds a second one later.

Exception: API keys minted with key_kind="agent" auto-narrow to their bound agent. The same call without agent_id works because the key already encodes scope.

List agents#

List agents in your tenant scope

GET/v1/agents
Paginated. Filter by client_id, kind, purpose, label, state, or free-text q (matches name, slug, purpose).

Query parameters

client_iduuid
Narrow to one client. Ignored when caller is already client-scoped.
kind"inbound_voice" | "outbound_voice" | "sms" | "hybrid_voice_sms"
purposestring
Exact match.
labelstring
Returns agents whose labels[] contains this value.
state"active" | "paused" | "archived"
Default: hides archived.
qstring
Free-text — name, slug, or purpose substring.
limitintegerdefault: 50
1–200.
page_tokenstring
Opaque cursor returned by the previous response.
Request
curl "https://agency.gary.club/api/public/v1/agents?kind=inbound_voice&state=active" \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Get agent detail#

Read full agent config

GET/v1/agents/{idOrSlug}
Returns the full client_agents row plus identity fields. Slugs that were renamed in the past resolve through aliases — when that happens, the response includes an X-Gary-Deprecation header pointing to the canonical slug.

Path parameters

idOrSlugstringrequired
Agent UUID or slug.
Request
curl https://agency.gary.club/api/public/v1/agents/agent_3kf9ab \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Update an agent#

Update purpose, labels, state, slug, name, greeting, voice

PATCH/v1/agents/{idOrSlug}
Slug renames are non-destructive — the old slug becomes an alias that auto-redirects for ~12 months. Kind is immutable; archive and re-create to change channel/direction.

Body parameters

namestring
≤200 chars.
slugstring
^[a-z0-9][a-z0-9_-]{2,63}$. Old slug becomes an alias.
purposestring | null
≤64 chars. null clears.
labelsstring[]
Up to 16 × 32 chars.
state"active" | "paused" | "archived"
Sets paused_at/archived_at automatically.
location_labelstring | null
≤80 chars.
greetingstring
≤4000 chars.
voice_idstring
ElevenLabs voice id.
Request
curl -X PATCH https://agency.gary.club/api/public/v1/agents/agent_3kf9ab \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "purpose": "After-hours overflow",
    "labels": ["english", "vip"],
    "state": "active"
  }'

Filter other endpoints by agent#

Pass agent_id (slug or UUID) as a query param to scope these endpoints to a specific agent:

GET /v1/calls?agent_id=
List calls handled by one agent.
GET /v1/sms/conversations?agent_id=
List SMS threads owned by one agent.
Example
curl "https://agency.gary.club/api/public/v1/calls?agent_id=agent_3kf9ab&limit=20" \
  -H "Authorization: Bearer gc_live_EXAMPLE_AbCdEfGhIj0123456789"

Per-agent webhooks#

Subscribe to events from one specific agent by passing agent_id when creating the subscription. Org-wide and client-wide subscriptions still receive that agent's events; agent-scoped subscriptions ONLY receive events from the bound agent.

Every event payload now includes an agent block:

Webhook payload (example: call.completed)
{
  "event": "call.completed",
  "agent": {
    "id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "slug": "agent_3kf9ab",
    "kind": "inbound_voice"
  },
  "data": {
    "call_id": "...",
    "duration_seconds": 124,
    "outcome": "booked"
  }
}

API key + agent narrowing#

There's no per-agent API key any more — the per-agent tab was retired. Use the client's cl_live_ key and pass ?agent_id=… on requests where you want to narrow to one agent. The key still scopes the tenancy; agent_id is just a filter inside it.

Scope tiers
  • Agency (gc_live_): all clients + all agents in the org.
  • Client (cl_live_): one client, all of its agents.
  • Per-agent narrowing: add ?agent_id=… on requests that support it (calls, sms, sms-broadcasts, contacts).

Slug renames + aliases#

When you rename an agent's slug, the previous slug is automatically inserted into agent_aliases and resolves to the same agent for the next 12 months. Requests that hit an alias receive an X-Gary-Deprecation response header pointing to the canonical slug — update your code at your leisure.