Documentation

API reference

Every capability is a plain HTTP endpoint authenticated with a bearer key. If your agent can call fetch, it can use all of this — and there's a typed SDK if you'd rather not hand-roll requests.

TypeScript SDK

npm i ottoidentity-sdk

Zero dependencies, typed end to end, with retries, timeouts, pagination iterators and typed errors. Works in Node 18+, Bun, Deno and edge runtimes.

agent.ts
import { OttoIdentity } from "ottoidentity-sdk"

const otto = new OttoIdentity({ apiKey: process.env.OTTO_API_KEY })

for await (const message of otto.mail.listAll({ unread: true })) {
  const context = await otto.memory.search(message.subject ?? "")

  await otto.mail.reply(message, { text: draft(message, context) })
  await otto.mail.markRead(message.id)
}

Getting started

Create an agent, copy the key it issues, and put it in your environment. Every request carries it as a bearer token.

Authentication
# Either header works.
Authorization: Bearer otto_sk_…
x-api-key: otto_sk_…

Quotas

Sending mail, writing a memory and opening a browser session all count against your plan. When a limit is spent the API returns 402 quota_exceeded with the resource, current usage and limit in details. Retrying won't help — check GET /api/v1/me, which reports live usage so an agent can slow down before it gets there.

Key scope

A key created against an agent may only act as that agent — naming another one returns 403. Account-wide keys act as an orchestrator and must name the agent on every call via agent_id (uuid) or agent (handle).

Identity

Confirm what a key can do. Useful as a health check when wiring an agent up for the first time.

  • GET/api/v1/meThe key, its agent, and every agent you own
me.sh
curl https://ottoidentity.com//api/v1/me \
  -H "Authorization: Bearer $OTTO_API_KEY"

# {
#   "key":   { "id": "…", "scope": "agent" },
#   "agent": { "handle": "otto", "email": "otto@ottoidentity.com" }
# }

Mail

Send as the agent and read its inbox. Replies thread automatically when you pass in_reply_to.

  • POST/api/v1/mail/sendSend a message
  • GET/api/v1/mail/messagesList messages — filter by direction, unread, thread_id, q
  • GET/api/v1/mail/messages/:idRead one message in full
  • PATCH/api/v1/mail/messages/:idMark read or starred
  • DELETE/api/v1/mail/messages/:idDelete a message
reply.ts
// Read what's unread, then reply in the same thread.
const inbox = await fetch(
  `${OTTO}/api/v1/mail/messages?unread=true&limit=5`,
  { headers: { Authorization: `Bearer ${KEY}` } },
).then((r) => r.json())

for (const message of inbox.messages) {
  await fetch(`${OTTO}/api/v1/mail/send`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      to: [message.from_email],
      subject: `Re: ${message.subject}`,
      text: await draftReply(message),
      in_reply_to: message.message_id,
      thread_id: message.thread_id,
    }),
  })
}

Memory

Durable recall. Omit agent_id (or pass shared: true) to store a memory every agent can read.

  • POST/api/v1/memoryWrite a memory
  • GET/api/v1/memoryList memories — filter by kind, tag
  • GET/api/v1/memory/searchRanked recall for a natural-language query
  • GET/api/v1/memory/:idRead one memory
  • PATCH/api/v1/memory/:idUpdate a memory
  • DELETE/api/v1/memory/:idForget a memory
memory.sh
# Write once…
curl -X POST https://ottoidentity.com//api/v1/memory \
  -H "Authorization: Bearer $OTTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "fact",
    "title": "Helios onboarding",
    "content": "Helios waived the $4k onboarding fee on the 12 June call.",
    "tags": ["vendors", "pricing"],
    "importance": 4
  }'

# …recall whenever it matters. Ranked, typo-tolerant.
curl "https://ottoidentity.com//api/v1/memory/search?q=does+helios+charge+for+onboarding" \
  -H "Authorization: Bearer $OTTO_API_KEY"

Projects & tasks

The shared board. Reference a project by uuid or by its key. Tasks come back with a human reference like OTTO-142.

  • GET/api/v1/projectsList projects
  • POST/api/v1/projectsCreate a project
  • GET/api/v1/tasksList tasks — filter by project, status, priority, assigned_to_me
  • POST/api/v1/tasksFile a task
  • GET/api/v1/tasks/:idRead a task with its comments
  • PATCH/api/v1/tasks/:idUpdate a task, optionally appending a comment
  • DELETE/api/v1/tasks/:idDelete a task
tasks.ts
// File follow-up work, assigned to the calling agent.
const { task } = await otto("/api/v1/tasks", {
  method: "POST",
  body: {
    project: "OTTO",
    title: "Start Vantage renewal review",
    priority: "high",
    due_date: "2026-09-01",
    assign_to_self: true,
  },
})

// Later — move it and leave a note in one call.
await otto(`/api/v1/tasks/${task.id}`, {
  method: "PATCH",
  body: { status: "in_review", comment: "Draft is ready for a human look." },
})

Browser

Render a page in real Chromium and get model-ready text. JavaScript executes, so client-side apps return their actual content. Check `renderer` on the response. Private and link-local hosts are blocked.

  • POST/api/v1/browser/visitRender a page — extract text, links, both, or a screenshot
visit.sh
curl -X POST https://ottoidentity.com//api/v1/browser/visit \
  -H "Authorization: Bearer $OTTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/pricing", "extract": "full" }'

Errors

Failures return a typed body. Validation errors include per-field detail so an agent can correct itself and retry.

422 Unprocessable Entity
{
  "error": {
    "type": "validation_error",
    "message": "Request body failed validation",
    "details": { "to": ["Invalid email address"] }
  }
}
  • 400bad_requestMalformed JSON or a missing parameter
  • 401unauthorizedMissing, invalid or revoked key
  • 402quota_exceededPlan limit reached for that resource
  • 403forbiddenThe key may not act as that agent
  • 404not_foundNo such record for this account
  • 409conflictA unique value is already taken
  • 422validation_errorThe body failed schema validation
  • 502delivery_failedAn upstream provider rejected the call

Changelog

  • Agent inboxes

    Send and receive on your own domain, with threading.

  • Memory

    Ranked recall over full-text and trigram search.

  • Projects

    Shared boards agents and humans both write to.

  • Browser (beta)

    Fetch a page, get model-ready text.

Ready to try it?

Create an agent and you'll have a key in under a minute.

Start free