Quickstart
From an API key to a working task: provision a user, give them an assistant, run a task, and read what it did.
This guide takes you from an API key to a task your user's assistant is working on. Harborview, a property-management company, provisions its leasing manager Jane Rivera, gives her an assistant named Ava, and asks Ava to renew the Brightline Freight service contract. Every step is shown with curl, and the whole flow is repeated as one TypeScript script at the end.
How it works
1. API key ----> 2. provision user ----> 3. finish user setup
|
6. poll status, read activity <---- 5. run <---- 4. assistant + task
Account calls (provision a user, manage assistants) send only Authorization. User calls (anything the user owns, such as her profile and tasks) also send X-User-Email. See Authentication.
1. Get an API key
In the console, open Settings > API Keys and generate a key. The key is shown once.
export AGNT_API_KEY=ak_live_...
export [email protected]
curl https://api.agnt.ai/me \
-H "Authorization: Bearer $AGNT_API_KEY"
{ "ok": true, "accountId": "66f1a2b3c4d5e6f708192a3b", "email": null, "orgId": null, "userId": null, "scopes": [] }
userId: null confirms an account-wide key, which this guide uses. A 401 means the key is wrong or revoked.
2. Provision a user
POST /users/provision finds or creates a user by email. It is idempotent, so you can call it on every sign-in.
curl https://api.agnt.ai/users/provision \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "firstName": "Jane", "lastName": "Rivera" }'
The response is 201 when the user was created and 200 when it already existed:
{
"ok": true,
"user": {
"id": "6702b1e8f9a0b1c2d3e4f5a6",
"account": "66f1a2b3c4d5e6f708192a3b",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Rivera",
"assistant": null,
"timezone": "UTC",
"status": "active"
},
"teamId": "6702ab52c3d4e5f6a7b8c9d0",
"created": true,
"addedToTeam": true,
"alreadyMember": false,
"joinSkipped": false
}
| Name | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The user's identity in your account. Lower-cased and trimmed. |
firstName, lastName | string | No | Used in the assistant's introductions and signatures |
teamId | string | No | Place the user on an existing team. Without it, the user gets a team of their own. |
export AGNT_USER_ID=6702b1e8f9a0b1c2d3e4f5a6
3. Finish the user's setup
Before an API key can act as a user, the user needs a timezone and working hours, confirmed once with POST /onboarding/mark-onboarded. Until then every user call except these three returns 428 with error_code: "onboarding_required". If your own UI already runs these calls when the user signs up, skip this step.
curl https://api.agnt.ai/profile \
-X PATCH \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{ "timezone": "America/Los_Angeles" }'
Set working hours per day (MO to SU) in 24-hour local time:
curl https://api.agnt.ai/users/$AGNT_USER_ID/preferences/scheduling \
-X PUT \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{
"schedulingPreferences": {
"availability": {
"workingHours": {
"MO": [{ "start": "08:30", "end": "17:30" }],
"TU": [{ "start": "08:30", "end": "17:30" }],
"WE": [{ "start": "08:30", "end": "17:30" }],
"TH": [{ "start": "08:30", "end": "17:30" }],
"FR": [{ "start": "08:30", "end": "17:30" }]
}
}
}
}'
PUT replaces the scheduling preferences as a whole: any field you leave out returns to its default. Working hours are never blank: leave workingHours out and the saved hours are kept, and a workingHours with no slots on any day returns 400. To change one field later, GET the same path and send the preferences back with your change.
Confirm setup:
curl https://api.agnt.ai/onboarding/mark-onboarded \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
{ "ok": true }
4. Create an assistant
A user has a roster of assistants and one primary. Read the roster:
curl https://api.agnt.ai/users/$AGNT_USER_ID/assistants \
-H "Authorization: Bearer $AGNT_API_KEY"
{ "ok": true, "assistants": [], "primaryAssistantId": null }
If it is empty, create one. POST /users/:userId/assistants creates an assistant owned by this user and adds it to their roster. The first one becomes primary. The call is idempotent on name.
curl https://api.agnt.ai/users/$AGNT_USER_ID/assistants \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Ava" }'
{
"ok": true,
"assistant": {
"id": "6702b4c1d2e3f4a5b6c7d8e9",
"name": "Ava",
"email": "[email protected]",
"isPrimary": true
}
}
The assistant gets its own email address on creation. To set its address, personality, voice or phone number, see Assistants and messaging.
export AGNT_ASSISTANT_ID=6702b4c1d2e3f4a5b6c7d8e9
5. Create and run a task
Tasks are user resources, so send X-User-Email. title and assistant (an assistant id on the user's roster) are required.
curl https://api.agnt.ai/tasks \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{
"title": "Renew the Brightline Freight service contract",
"description": "The current contract ends 2027-01-01. Ask Brightline ([email protected]) for 2027 terms and keep the current rates if possible.",
"assistant": "6702b4c1d2e3f4a5b6c7d8e9",
"modelTier": "medium"
}'
{
"ok": true,
"task": {
"id": "6702c3a9b8c7d6e5f4a3b2c1",
"title": "Renew the Brightline Freight service contract",
"status": "pending"
}
}
| Name | Type | Required | Description |
|---|---|---|---|
title | string | Yes | What the task should achieve |
assistant | string | Yes | Id of an assistant on the user's roster. Any other id returns 403. |
description | string | No | The brief, in the user's words |
modelTier | low, medium, high or ultra | No | Overrides the default tier for this task. See Prime. |
onBehalfOf | string | No | Email of the person the task acts for: the user, or someone who has delegated to them. Defaults to the user. |
followers | string[] | No | Emails of people following the task |
skills | string[] | No | Skills to make available to this task |
Creating a task does not start it. Start it with POST /tasks/:taskId/process. The optional message is added to the task as the user's words before the run.
export AGNT_TASK_ID=6702c3a9b8c7d6e5f4a3b2c1
curl https://api.agnt.ai/tasks/$AGNT_TASK_ID/process \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{ "message": "Attach Brightline-MSA-2026.pdf for reference." }'
{
"ok": true,
"process": {
"success": true,
"taskId": "6702c3a9b8c7d6e5f4a3b2c1",
"executionId": "6702c3b4a1b2c3d4e5f6a7b8",
"message": "Task queued for processing"
}
}
The call returns as soon as the run is queued. The work happens asynchronously and can take seconds or, for work that waits on other people, days.
6. Read status and activity
curl https://api.agnt.ai/tasks/$AGNT_TASK_ID \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
The response wraps the task in task. These are the fields you read most:
| Name | Description |
|---|---|
status | Where the task is in its lifecycle. See Task statuses. |
summary | What the task has done, once there is something to report |
alarm | When the task will wake itself to follow up (wakeAt), with a headline to show the user |
pendingCart | Actions staged and waiting for approval, or null |
readyForReview | true while the status is awaiting_user_input and the work is finished, waiting only for the user to look |
scheduledFor | When a scheduled task will run |
completedAt | When it finished |
Read the timeline, including the assistant's messages:
curl "https://api.agnt.ai/tasks/$AGNT_TASK_ID/activities?limit=50" \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]"
{
"ok": true,
"activities": [
{
"id": "6702c3d2e3f4a5b6c7d8e9f0",
"type": "message",
"createdAt": "2026-11-02T16:24:11.000Z",
"message": {
"role": "assistant",
"content": "I drafted an email to [email protected] asking for 2027 terms at current rates, with Brightline-MSA-2026.pdf attached. It is waiting for your approval."
}
}
],
"hasMore": false,
"cursor": null
}
limit defaults to 50 and is capped at 100. When hasMore is true, pass cursor as before to get the next, older page. message.role is user, assistant or peer (another assistant).
An email to Brightline is a consequential action, so depending on Jane's autonomy settings Ava stages it for approval and pendingCart is set. See Tasks and approvals to approve it.
Reply to a waiting task
When the status is awaiting_user_input, send the user's answer the same way you started the task:
curl https://api.agnt.ai/tasks/$AGNT_TASK_ID/process \
-X POST \
-H "Authorization: Bearer $AGNT_API_KEY" \
-H "X-User-Email: [email protected]" \
-H "Content-Type: application/json" \
-d '{ "message": "Ask for a two-year term as well." }'
If the task is mid-run, the message goes to the running task instead of starting a second run. On a scheduled task that has not run yet, message is required; it is attached and the task still runs at its scheduled time.
The same flow in TypeScript
A complete script for Node.js 18 or later, using the built-in fetch. Save it as quickstart.ts and run it with npx tsx quickstart.ts.
const API = 'https://api.agnt.ai';
const KEY = process.env.AGNT_API_KEY!;
const EMAIL = '[email protected]';
type Json = Record<string, any>;
async function call(method: string, path: string, opts: { body?: Json; asUser?: boolean } = {}): Promise<Json> {
const res = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
...(opts.asUser ? { 'X-User-Email': EMAIL } : {}),
},
body: opts.body ? JSON.stringify(opts.body) : undefined,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${data.error_code ?? ''} ${data.error ?? ''}`);
return data;
}
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// Statuses in which a run is still in progress.
const RUNNING = new Set(['pending', 'executing', 'subtasks_running', 'paused']);
async function main() {
// 2. Provision the user (idempotent)
const { user } = await call('POST', '/users/provision', {
body: { email: EMAIL, firstName: 'Jane', lastName: 'Rivera' },
});
// 3. Finish setup (safe to repeat)
await call('PATCH', '/profile', { asUser: true, body: { timezone: 'America/Los_Angeles' } });
const day = [{ start: '08:30', end: '17:30' }];
await call('PUT', `/users/${user.id}/preferences/scheduling`, {
asUser: true,
body: { schedulingPreferences: { availability: { workingHours: { MO: day, TU: day, WE: day, TH: day, FR: day } } } },
});
await call('POST', '/onboarding/mark-onboarded', { asUser: true });
// 4. Use the primary assistant, or create one
const roster = await call('GET', `/users/${user.id}/assistants`);
const assistantId: string =
roster.primaryAssistantId ??
(await call('POST', `/users/${user.id}/assistants`, { body: { name: 'Ava' } })).assistant.id;
// 5. Create and run a task
const { task } = await call('POST', '/tasks', {
asUser: true,
body: {
title: 'Renew the Brightline Freight service contract',
description: 'The current contract ends 2027-01-01. Ask Brightline ([email protected]) for 2027 terms and keep the current rates if possible.',
assistant: assistantId,
modelTier: 'medium',
},
});
await call('POST', `/tasks/${task.id}/process`, {
asUser: true,
body: { message: 'Attach Brightline-MSA-2026.pdf for reference.' },
});
// 6. Poll until the run settles (up to 5 minutes)
let current: Json = task;
for (let i = 0; i < 60 && RUNNING.has(current.status); i++) {
await sleep(5000);
current = (await call('GET', `/tasks/${task.id}`, { asUser: true })).task;
console.log('status:', current.status, current.alarm?.headline ?? '');
}
// Print what the assistant said
const { activities } = await call('GET', `/tasks/${task.id}/activities?limit=50`, { asUser: true });
for (const a of activities) {
if (a.message?.role === 'assistant' && a.message.content) console.log('\nAva:', a.message.content);
}
}
main().catch((err) => { console.error(err); process.exit(1); });
Poll every few seconds while a run is in progress, and back off for tasks in a waiting status. Long waits are normal for tasks that depend on other people. To use @agnt-sdk/client instead of an API key, see Certificate JWTs for the SDK.
Endpoints
| Method | Path | User call | Purpose |
|---|---|---|---|
GET | /me | No | Inspect the key: account, organization, user, scopes |
POST | /users/provision | No | Find or create a user by email |
PATCH | /profile | Yes | Set the user's timezone, names and locale |
PUT | /users/:userId/preferences/scheduling | Yes | Replace the user's scheduling preferences, including working hours |
POST | /onboarding/mark-onboarded | Yes | Confirm setup so the key can act as the user |
GET | /users/:userId/assistants | No | The user's roster and primaryAssistantId |
POST | /users/:userId/assistants | No | Create an assistant for the user and add it to their roster |
POST | /tasks | Yes | Create a task |
POST | /tasks/:taskId/process | Yes | Start a task, or send the user's message to it |
GET | /tasks/:taskId | Yes | Read a task |
GET | /tasks/:taskId/activities | Yes | Read its timeline (limit, before) |
See also
- Authentication: key levels, acting as a user, errors
- Tasks and approvals: task statuses, autonomy and approvals
- Prime: the harness: what runs your tasks and chats