AGNTDocs

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
}
NameTypeRequiredDescription
emailstringYesThe user's identity in your account. Lower-cased and trimmed.
firstName, lastNamestringNoUsed in the assistant's introductions and signatures
teamIdstringNoPlace 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"
  }
}
NameTypeRequiredDescription
titlestringYesWhat the task should achieve
assistantstringYesId of an assistant on the user's roster. Any other id returns 403.
descriptionstringNoThe brief, in the user's words
modelTierlow, medium, high or ultraNoOverrides the default tier for this task. See Prime.
onBehalfOfstringNoEmail of the person the task acts for: the user, or someone who has delegated to them. Defaults to the user.
followersstring[]NoEmails of people following the task
skillsstring[]NoSkills 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:

NameDescription
statusWhere the task is in its lifecycle. See Task statuses.
summaryWhat the task has done, once there is something to report
alarmWhen the task will wake itself to follow up (wakeAt), with a headline to show the user
pendingCartActions staged and waiting for approval, or null
readyForReviewtrue while the status is awaiting_user_input and the work is finished, waiting only for the user to look
scheduledForWhen a scheduled task will run
completedAtWhen 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

MethodPathUser callPurpose
GET/meNoInspect the key: account, organization, user, scopes
POST/users/provisionNoFind or create a user by email
PATCH/profileYesSet the user's timezone, names and locale
PUT/users/:userId/preferences/schedulingYesReplace the user's scheduling preferences, including working hours
POST/onboarding/mark-onboardedYesConfirm setup so the key can act as the user
GET/users/:userId/assistantsNoThe user's roster and primaryAssistantId
POST/users/:userId/assistantsNoCreate an assistant for the user and add it to their roster
POST/tasksYesCreate a task
POST/tasks/:taskId/processYesStart a task, or send the user's message to it
GET/tasks/:taskIdYesRead a task
GET/tasks/:taskId/activitiesYesRead its timeline (limit, before)

See also