Personal assistant workers
A personal-assistant platform runs one worker per end user inside one follower organization. Every call below acts as that worker. Replies are snake_case. Errors use the standard envelope { "error": { "code", "message", "details"? } }: 401 for a bad key, 404 for an unknown worker, 409 for idempotency or configuration conflicts, and 429 when rate limited.
All routes live under /api/partner/v1/organizations/{orgId} and take Authorization: Bearer ykp_…. Worker routes take the worker in the path. If you also send X-Yekar-Subject, it must name the same worker; otherwise the request gets the same 404 as an unknown worker. Every mutating call accepts Idempotency-Key. A replayed success carries Idempotency-Replayed: true.
yekar runs the assistant: the conversation, the tools it acts with, and the approvals it waits on. Your platform owns everything around it. You receive the user's WhatsApp, SMS or web messages and post them here, decide when a turn is due and start it, deliver the assistant's replies, and keep tasks, reminders, memory and per-user limits in your own product.
Configure the organization
PUT /assistant-config names the agent that answers and where events go:
{
"assistant_automation_id": "00000000-0000-4000-8000-000000000040",
"webhook": { "url": "https://example.com/yekar/webhook", "secret": "at-least-sixteen-characters" }
}
A key you leave out keeps its current value, and null clears it. The webhook secret is write-only: GET /assistant-config returns only a hint.
Keep your own limits (per user, per plan, per day) in your product and apply them before you post a message or start a turn, using usage.recorded to count what each turn cost. Your yekar plan has its own limits too. When one of them stops a turn, you receive message.created with outcome: "failed" and an error_code, and the message is yekar's notice rather than an answer. Show your user something in your own words instead.
Workers
| Call | Result |
|---|---|
POST /workers {external_ref, email} | 201 {worker_ref}. The same external_ref always returns the same worker. |
DELETE /workers/{worker_ref} | 204. Erases the worker's conversations, memory, connections and files, then retires the worker. |
PUT /workers/{w}/policy | 204. See Policy. |
The conversation
Each worker has one ongoing conversation.
POST /workers/{w}/messages {body}posts a message as the user and returns the created message{id, role, body, created_at}. Post every message you receive for that user here, whichever channel it came in on. The reply is appended asynchronously, and you receivemessage.createdwhen it is ready.GET /workers/{w}/messages?before={cursor}&limit=returns{items: [{id, role, body, created_at}], next_cursor}. Items are oldest first within a page, andbeforepages back. Read the reply's text here aftermessage.created.POST /workers/{w}/turns {instruction}returns202 {turn_id, status: "queued"}and runs a turn now in the worker's conversation: a follow-up that has come due, a morning brief, or undoing something. The instruction is never shown as a user message, and the reply arrives asmessage.createdlike any other. Scheduling is yours: call this when the turn is due.
The assistant does not keep its own memory for workers. What it should know about a user is yours to keep, and to give it through your own integration if you want it to.
Connecting Google
POST /workers/{w}/connections/{provider}/start {return_to}, where provider is gmail or google_calendar, returns 201 {authorize_url, expires_at}. Open authorize_url in the user's browser. It works once, lasts 10 minutes, and takes the user to Google's consent screen. Afterwards the browser returns to return_to with status=connected and connectionId, or with status=error and an error code. yekar stores and refreshes the user's tokens.
To sign users in with your own Google app, so the consent screen shows your name:
GET /oauth-appsreturns{apps, redirectUri}. RegisterredirectUrias an authorized redirect URI in your Google Cloud project.- Set your Google client with
PUT /oauth-apps {appId, values}, using the Google app'sappIdand field keys fromapps. - Select it with
PUT /oauth-apps/google/state {state: "byok"}.
These three calls need a worker with the organization admin role.
return_tomust sit under one of your registered return URLs (PUT /api/partner/v1/platform/oauth-return-urls). Otherwise the call returns400.- If the organization has neither your app nor yekar's Google app, the call returns
400 MANAGED_OAUTH_UNAVAILABLE. DELETE /workers/{w}/connections/{provider}returns204and removes the worker's sign-in. It also returns204if there was nothing to remove.GET /workers/{w}/connectionsreturns{items: [{provider, status, scopes, connected_at, last_error}]}.
Policy
{
"rules": [
{ "action": "email.send", "mode": "ask" },
{ "action": "*", "mode": "allow" }
],
"approval_ttl": "PT8H"
}
neverrefuses the action in code before it runs.askpauses for approval. The approval expires afterapproval_ttl, an ISO 8601 duration between 1 minute and 30 days.- A rule for an action's own class outranks
*. Among rules of the same rank, the strictest wins. - Actions are
email.send,calendar.write,calendar.accept,inbox.tidy,message.send,purchaseand*. - Quiet hours, recipient rules and spending limits are yours to apply before you post a message or start a turn.
Approvals
When a policy rule says ask, the assistant pauses and you receive approval.requested. Ask the user however suits your product, then send their decision here. Previews are built from the action's own details, for example Send an email to [email protected]: "Friday".
GET /workers/{w}/approvals?state=pending|decided|expiredreturns{items: [{id, action_id, action, preview, args, state, decision, number, requested_at, expires_at, decided_at}]}. Pending items come first, oldest first, numbered from 1.action_idmatches theaction.recordedevents.POST /workers/{w}/approvals/{id}decides one approval.{decision: "approve", edited_args?}runs it, with your edited arguments when given.{decision: "reject"}cancels it.{instruction: "make it Friday"}sends it back to the assistant to revise, and the revised action comes back as a new approval. A second decision on the same approval returns409.
Events
Each event is sent to your webhook as {id, type, occurred_at, worker_ref, data}. Delivery is at least once, and id is stable across retries. The request carries these headers:
X-Yekar-Timestamp: Unix seconds.X-Yekar-Signature:sha256=<hex HMAC-SHA256 of "${timestamp}.${rawBody}">.
| Type | Data |
|---|---|
message.created | {message_id, session_id, created_at, outcome, error_code?}, when the assistant replies. Read the text with GET /workers/{w}/messages. outcome is ok, failed (yekar could not answer; error_code says why, for example LLM_BUDGET_EXHAUSTED) or cancelled. |
approval.requested | The approval, as GET /workers/{w}/approvals returns it. Decide it before expires_at. |
action.recorded | {action_id, at, action, summary, trigger, approval, result}. The same action_id is sent again as pending becomes done, failed or dropped. |
usage.recorded | {usage_id, at, cost_usd, tokens}, sent once per turn |
To let a user undo something the assistant did, start a turn with an instruction naming the action from action.recorded, for example "Delete the calendar event you created for Friday's dentist appointment."
Replaying events
GET /events?since={event_id}&worker_ref=&limit= returns {items, next_cursor}: the events recorded after since, oldest first, with the same ids they were delivered with. Leave out since to start from the oldest. Pass next_cursor as since to get the next page; it is null on the last page. Use this to catch up after an outage and dedupe on id. Only events recorded while a webhook URL was configured can be replayed.