ReferenceReference by module

Bots API

Every Bots action (14) and data model (5): endpoints, scopes, input, preconditions, events and examples.

Named AI coworkers: each has a job, instructions, your own Claude Code or Codex, a computer, notes and routines, and asks you before anything risky.

Module code bots · 14 actions · 5 data models · 2 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: bots (everything), bots:read (read only) or bots:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
bot_approvalsbot_approval/api/v1/bot_approvals
bot_messagesbot_message/api/v1/bot_messages
bot_routine_runsbot_routine_run/api/v1/bot_routine_runs
bot_routinesbot_routine/api/v1/bot_routines
botsbot/api/v1/botsbots.createbots.updatebots.archive

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
bots.archivebots:writeArchive a bot
bots.claim_runsbots:writeTake my due routine runs
bots.createbots:writeMake a bot
bots.decide_approvalbots:writeAnswer a bot’s question
bots.delete_routinebots:writeDelete a routine
bots.mark_readbots:writeMark my chat with a bot read
bots.post_messagebots:writeAdd to my chat with a bot
bots.report_runbots:writeReport a routine run
bots.request_approvalbots:writeAsk before a bot’s step
bots.run_routinebots:writeRun a routine now
bots.save_routinebots:writeGive a bot a routine
bots.settle_approvalbots:writeClose a bot’s question
bots.sharebots:writeShare a bot
bots.updatebots:writeChange a bot

bots.archive

Archive a bot — Puts a bot away (its routines stop) or brings it back. Its chats and notes stay.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bots/{id}/actions/archive
API key or OAuth token · bots:write
  • REST alias: DELETE /api/v1/bots/{id}
  • Canonical address: POST /api/v1/actions/bots.archive

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Bot · id of bot
archivedbooleanArchived · false brings it back.

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
not_your_botOnly the member who made a bot archives it.
bot_workingA bot that is working right now is stopped first.
name_takenA bot brought back may not share a name with a live one.

What it writes: bot (update) — Archives or restores the bot; bot_routine (update) — An archived bot’s routines stop; bot_routine_run (update) — Its waiting runs are skipped

curl -X POST https://app.getera.io/api/v1/bots/BOT_ID/actions/archive \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"archived":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bots.archive("BOT_ID", {
  archived: true
})
era bots archive BOT_ID \
  --archived true
tools/call
{
  "name": "era__bots__archive",
  "arguments": {
    "id": "BOT_ID",
    "archived": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, not_your_bot, bot_working, name_taken · 429 slow down

bots.claim_runs

Take my due routine runs — Your own desktop takes your routine runs that are due, to work them on the bots’ computers with your subscription.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_routine_runs/actions/claim_runs
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.claim_runs

Input

FieldTypeRequiredNotes
deviceIduuidDesktop
limitnumberAt most · min 1 · max 5

What it writes: bot_routine_run (update) — Marks the runs taken by this desktop

curl -X POST https://app.getera.io/api/v1/bot_routine_runs/actions/claim_runs \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"deviceId":"DEVICE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botRoutineRuns.claimRuns({
  deviceId: "DEVICE_ID"
})
era bot_routine_runs claim_runs \
  --device-id "DEVICE_ID"
tools/call
{
  "name": "era__bots__claim_runs",
  "arguments": {
    "deviceId": "DEVICE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

bots.create

Make a bot — A named coworker with a job, instructions, your own engine and a computer. It acts only with your permissions.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bots/actions/create
API key or OAuth token · bots:write
  • REST alias: POST /api/v1/bots
  • Canonical address: POST /api/v1/actions/bots.create

Input

FieldTypeRequiredNotes
namestringyesName · max 80
rolestringJob · One line under its name: “Follows up quotes”. · max 160
avatarobject / arrayAvatar · { kind: initial | emoji | image, value? } — an image is a data: URL ≤ 200 KB.
colorstring (enum)Colour · Allowed values: blue, green, orange, red, teal, slate, amber, emerald, sky, rose
instructionsstringInstructions · max 20000
providerstring (enum)Engine · Allowed values: claude, codex
modelstringModel · max 120
computerstring (enum)Computer · Allowed values: this_pc, cloud
computerMachineIduuidCloud computer
allowedAppsobject / arrayOnly these apps · ["Chrome", "Excel"] — empty = any app (the denylist and the floor still apply).
eraDatabooleanMay use Era data (your permissions)
approvalModestring (enum)Asks before · Allowed values: ask, risky, auto
memoryEnabledbooleanKeeps notes
visibilitystring (enum)Who sees it · Allowed values: private, workspace

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
name_requiredA bot needs a name.
name_takenYour live bots have different names.
bad_engineThe engine is Claude Code or Codex.
bad_computerThe computer is this PC or your cloud computer.
machine_not_yoursA bot uses only your own cloud computer.
bad_approval_modeApproval mode is ask, risky or auto.
bad_colorThe colour is one of Era’s section colours (never purple).
bad_visibilityA bot is private or shared with the workspace.
bad_avatarAn avatar is an initial, an emoji or a small picture.
bad_appsAllowed apps is a short list of names.
too_many_botsA member keeps at most fifty live bots.

What it writes: bot (insert) — Adds the bot, owned by you

curl -X POST https://app.getera.io/api/v1/bots/actions/create \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bots.create({
  name: "Name"
})
era bots create \
  --name "Name"
tools/call
{
  "name": "era__bots__create",
  "arguments": {
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 name_required, name_taken, bad_engine, bad_computer, machine_not_yours, bad_approval_mode, bad_color, bad_visibility, bad_avatar, bad_apps, too_many_bots · 429 slow down

bots.decide_approval

Answer a bot’s question — Allow or deny the step a bot is waiting on — from the desktop, the web, the phone or the API. Denied, the step never runs.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_approvals/{id}/actions/decide_approval
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.decide_approval

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Question · id of bot_approval
decisionstring (enum)yesAnswer · Allowed values: allow, deny
viastring (enum)From · Allowed values: desktop, web, phone, api

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bad_decisionThe answer is allow or deny.
approval_not_foundOnly the member whose bot asked answers it.
approval_closedA question is answered once, before it expires.

Events it publishes: bots.approval_decided

What it writes: bot_approval (update) — Records your answer; bot_routine_run (update) — A waiting routine run goes on

curl -X POST https://app.getera.io/api/v1/bot_approvals/BOT_APPROVAL_ID/actions/decide_approval \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"decision":"allow"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "bots.approval_decided"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botApprovals.decideApproval("BOT_APPROVAL_ID", {
  decision: "allow"
})
era bot_approvals decide_approval BOT_APPROVAL_ID \
  --decision "allow"
tools/call
{
  "name": "era__bots__decide_approval",
  "arguments": {
    "id": "BOT_APPROVAL_ID",
    "decision": "allow"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_decision, approval_not_found, approval_closed · 429 slow down

bots.delete_routine

Delete a routine — Removes a bot’s routine and its run history. Its reports stay in your chat.

Needs bots:write · removes or cannot be undone

POSThttps://app.getera.io/api/v1/bot_routines/{id}/actions/delete_routine
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.delete_routine

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Routine · id of bot_routine

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
routine_not_foundThe routine must be one of yours.

What it writes: bot_routine (delete) — Removes the routine; bot_routine_run (delete) — Its runs go with it, by cascade

curl -X POST https://app.getera.io/api/v1/bot_routines/BOT_ROUTINE_ID/actions/delete_routine \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botRoutines.deleteRoutine("BOT_ROUTINE_ID")
era bot_routines delete_routine BOT_ROUTINE_ID
tools/call
{
  "name": "era__bots__delete_routine",
  "arguments": {
    "id": "BOT_ROUTINE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 routine_not_found · 429 slow down

bots.mark_read

Mark my chat with a bot read — Clears the unread count of your chat with a bot.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_messages/actions/mark_read
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.mark_read

Input

FieldTypeRequiredNotes
botIduuidyesBot

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot id is a bot id.

What it writes: bot_message (update) — Stamps your unread messages read

curl -X POST https://app.getera.io/api/v1/bot_messages/actions/mark_read \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"botId":"BOT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botMessages.markRead({
  botId: "BOT_ID"
})
era bot_messages mark_read \
  --bot-id "BOT_ID"
tools/call
{
  "name": "era__bots__mark_read",
  "arguments": {
    "botId": "BOT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found · 429 slow down

bots.post_message

Add to my chat with a bot — Your message to a bot, or its answer as your own desktop reports it. Your chat with a bot is yours alone.

Needs bots:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/bot_messages/actions/post_message
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.post_message

Input

FieldTypeRequiredNotes
botIduuidyesBot · id of bot
rolestring (enum)From · Allowed values: user, bot, system
kindstring (enum)Kind · Allowed values: text, report, approval, routine, error
textstringyesText · max 60000
runRefstringDesktop run · max 100
routineIduuidRoutine
approvalIduuidApproval
clientRefstringSame-message key · max 100

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
bot_archivedAn archived bot takes no messages.
bad_messageA message has words, a known sender and a known kind.

What it writes: bot_message (insert) — Adds the message to your chat with the bot

curl -X POST https://app.getera.io/api/v1/bot_messages/actions/post_message \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"botId":"BOT_ID","text":"Text"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botMessages.postMessage({
  botId: "BOT_ID",
  text: "Text"
})
era bot_messages post_message \
  --bot-id "BOT_ID" \
  --text "Text"
tools/call
{
  "name": "era__bots__post_message",
  "arguments": {
    "botId": "BOT_ID",
    "text": "Text"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, bot_archived, bad_message · 429 slow down

bots.report_run

Report a routine run — Your desktop says a run started, waits for you, finished or failed; a finished run’s result lands in your chat with the bot.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_routine_runs/{id}/actions/report_run
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.report_run

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Run · id of bot_routine_run
statestring (enum)yesState · Allowed values: running, waiting, completed, failed
runRefstringDesktop run · max 100
resultstringResult · max 20000
errorstringError · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bad_run_stateA run is running, waiting, completed or failed.
run_not_foundThe run must be one of yours, taken and not ended.

What it writes: bot_routine_run (update) — Moves the run on; bot_routine (update) — Stamps when it last ran; bot_message (insert) — Puts the result in your chat with the bot

curl -X POST https://app.getera.io/api/v1/bot_routine_runs/BOT_ROUTINE_RUN_ID/actions/report_run \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"state":"running"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botRoutineRuns.reportRun("BOT_ROUTINE_RUN_ID", {
  state: "running"
})
era bot_routine_runs report_run BOT_ROUTINE_RUN_ID \
  --state "running"
tools/call
{
  "name": "era__bots__report_run",
  "arguments": {
    "id": "BOT_ROUTINE_RUN_ID",
    "state": "running"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_run_state, run_not_found · 429 slow down

bots.request_approval

Ask before a bot’s step — A bot working for you stops before a risky step and asks; nothing runs until you allow it. In your quiet hours the question waits silently.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_approvals/actions/request_approval
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.request_approval

Input

FieldTypeRequiredNotes
botIduuidyesBot · id of bot
requestRefstringyesThe engine’s id for the question · max 200
summarystringyesWhat will happen · max 1000
toolstringTool · max 200
kindstringKind · screen · command · file_write · network · payment · send · delete · credentials · install · other · max 31
riskstring (enum)Risk · Allowed values: step, risky, floor
detailobject / arrayDetail
runRefstringDesktop run · max 100
runIduuidRoutine run
expiresInMinutesnumberWaits for (minutes) · min 1 · max 1440

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
bad_approvalA question has a summary, the engine’s id and a known risk.

Events it publishes: bots.approval_requested

What it writes: bot_approval (insert) — Records the question, for you only; bot_routine_run (update) — A routine run waits for you

curl -X POST https://app.getera.io/api/v1/bot_approvals/actions/request_approval \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"botId":"BOT_ID","requestRef":"The engine’s id for the question","summary":"What will happen"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "bots.approval_requested"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botApprovals.requestApproval({
  botId: "BOT_ID",
  requestRef: "The engine’s id for the question",
  summary: "What will happen"
})
era bot_approvals request_approval \
  --bot-id "BOT_ID" \
  --request-ref "The engine’s id for the question" \
  --summary "What will happen"
tools/call
{
  "name": "era__bots__request_approval",
  "arguments": {
    "botId": "BOT_ID",
    "requestRef": "The engine’s id for the question",
    "summary": "What will happen"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, bad_approval · 429 slow down

bots.run_routine

Run a routine now — Queues one run of the routine for your desktop to start now (quiet hours do not hold a run you asked for).

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_routine_runs/actions/run_routine
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.run_routine

Input

FieldTypeRequiredNotes
iduuidyesRoutine · id of bot_routine

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
routine_not_foundThe routine must be one of yours.
bot_archivedAn archived bot runs nothing.
routine_busyOne run of a routine at a time.

What it writes: bot_routine_run (insert) — Queues the run

curl -X POST https://app.getera.io/api/v1/bot_routine_runs/actions/run_routine \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"BOT_ROUTINE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botRoutineRuns.runRoutine({
  id: "BOT_ROUTINE_ID"
})
era bot_routine_runs run_routine \
  --id "BOT_ROUTINE_ID"
tools/call
{
  "name": "era__bots__run_routine",
  "arguments": {
    "id": "BOT_ROUTINE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 routine_not_found, bot_archived, routine_busy · 429 slow down

bots.save_routine

Give a bot a routine — A task your bot does on a schedule (daily, weekdays, weekly, every N minutes, once, cron) — on Era’s scheduler, reported into your chat with it.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_routines/actions/save_routine
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.save_routine

Input

FieldTypeRequiredNotes
iduuidRoutine · Absent = a new routine.
botIduuidyesBot · id of bot
namestringyesName · max 120
promptstringyesWhat to do each time · max 20000
scheduleobject / arrayyesWhen · { kind: daily|weekdays|weekly|interval|once|cron|manual, time?: "09:00", days?: [1,3], everyMinutes?: 60, at?: ISO, cron?: "0 9 * * 1-5", timezone }
enabledbooleanOn
respectQuietHoursbooleanWait out my quiet hours

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
not_your_botOnly the bot’s owner gives it routines.
bot_archivedAn archived bot takes no routines.
routine_incompleteA routine has a name and a task.
bad_scheduleA schedule is one of the kinds, with a real time, days, interval (≥ 15 min), date or cron, in a real time zone.
routine_not_foundThe routine must be one of yours, on this bot.
too_many_routinesA bot keeps at most twenty routines.

What it writes: bot_routine (upsert) — Writes the routine and its next run

curl -X POST https://app.getera.io/api/v1/bot_routines/actions/save_routine \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"botId":"BOT_ID","name":"Name","prompt":"What to do each time","schedule":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botRoutines.saveRoutine({
  botId: "BOT_ID",
  name: "Name",
  prompt: "What to do each time",
  schedule: {}
})
era bot_routines save_routine --data '{"botId":"BOT_ID","name":"Name","prompt":"What to do each time","schedule":{}}'
tools/call
{
  "name": "era__bots__save_routine",
  "arguments": {
    "botId": "BOT_ID",
    "name": "Name",
    "prompt": "What to do each time",
    "schedule": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, not_your_bot, bot_archived, routine_incomplete, bad_schedule, routine_not_found, too_many_routines · 429 slow down

bots.settle_approval

Close a bot’s question — The desktop says a question is no longer needed (the run ended or you answered on the card) or that its notification was shown.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bot_approvals/{id}/actions/settle_approval
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.settle_approval

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Question · id of bot_approval
requestRefstringThe engine’s id for it · max 200
statusstring (enum)Now · Allowed values: cancelled, expired
notifiedbooleanIts notification was shown

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bad_decisionA question that is no longer needed is cancelled or expired.
approval_not_foundThe question must be one of yours.

What it writes: bot_approval (update) — Closes the question or stamps its notification

curl -X POST https://app.getera.io/api/v1/bot_approvals/BOT_APPROVAL_ID/actions/settle_approval \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"requestRef":"The engine’s id for it"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.botApprovals.settleApproval("BOT_APPROVAL_ID", {
  requestRef: "The engine’s id for it"
})
era bot_approvals settle_approval BOT_APPROVAL_ID \
  --request-ref "The engine’s id for it"
tools/call
{
  "name": "era__bots__settle_approval",
  "arguments": {
    "id": "BOT_APPROVAL_ID",
    "requestRef": "The engine’s id for it"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bad_decision, approval_not_found · 429 slow down

bots.share

Share a bot — Shared, every member sees the bot and can talk to it — as themselves, on their own subscription and permissions. Only you change it or use its computer.

Needs bots:write · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/bots/{id}/actions/share
API key or OAuth token · bots:write
  • Canonical address: POST /api/v1/actions/bots.share

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Bot · id of bot
visibilitystring (enum)yesWho sees it · Allowed values: private, workspace

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
not_your_botOnly the member who made a bot shares it.
bad_visibilityA bot is private or shared with the workspace.

What it writes: bot (update) — Changes who sees the bot

curl -X POST https://app.getera.io/api/v1/bots/BOT_ID/actions/share \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"visibility":"private"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bots.share("BOT_ID", {
  visibility: "private"
})
era bots share BOT_ID \
  --visibility "private"
tools/call
{
  "name": "era__bots__share",
  "arguments": {
    "id": "BOT_ID",
    "visibility": "private"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, not_your_bot, bad_visibility · 429 slow down

bots.update

Change a bot — Its name, job, instructions, engine, computer, apps, Era data access or approval mode. A run already working keeps its old settings.

Needs bots:write · changes data

POSThttps://app.getera.io/api/v1/bots/{id}/actions/update
API key or OAuth token · bots:write
  • REST alias: PATCH /api/v1/bots/{id}
  • Canonical address: POST /api/v1/actions/bots.update

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Bot · id of bot
namestringName · max 80
rolestringJob · One line under its name: “Follows up quotes”. · max 160
avatarobject / arrayAvatar · { kind: initial | emoji | image, value? } — an image is a data: URL ≤ 200 KB.
colorstring (enum)Colour · Allowed values: blue, green, orange, red, teal, slate, amber, emerald, sky, rose
instructionsstringInstructions · max 20000
providerstring (enum)Engine · Allowed values: claude, codex
modelstringModel · max 120
computerstring (enum)Computer · Allowed values: this_pc, cloud
computerMachineIduuidCloud computer
allowedAppsobject / arrayOnly these apps · ["Chrome", "Excel"] — empty = any app (the denylist and the floor still apply).
eraDatabooleanMay use Era data (your permissions)
approvalModestring (enum)Asks before · Allowed values: ask, risky, auto
memoryEnabledbooleanKeeps notes

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
bot_not_foundThe bot must be one you can see.
not_your_botOnly the member who made a bot changes it, even when it is shared.
name_requiredA bot needs a name.
name_takenYour live bots have different names.
bad_engineThe engine is Claude Code or Codex.
bad_computerThe computer is this PC or your cloud computer.
machine_not_yoursA bot uses only your own cloud computer.
bad_approval_modeApproval mode is ask, risky or auto.
bad_colorThe colour is one of Era’s section colours.
bad_avatarAn avatar is an initial, an emoji or a small picture.
bad_appsAllowed apps is a short list of names.

What it writes: bot (update) — Rewrites the bot

curl -X POST https://app.getera.io/api/v1/bots/BOT_ID/actions/update \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": []
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bots.update("BOT_ID", {
  name: "Name"
})
era bots update BOT_ID \
  --name "Name"
tools/call
{
  "name": "era__bots__update",
  "arguments": {
    "id": "BOT_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold bots:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 bot_not_found, not_your_bot, name_required, name_taken, bad_engine, bad_computer, machine_not_yours, bad_approval_mode, bad_color, bad_avatar, bad_apps · 429 slow down

Data models

BotApproval

Table bot_approval · Resource bot_approvals

  • GET /api/v1/bot_approvals
  • GET /api/v1/bot_approvals/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
user_idstring (uuid) · uuidapp_user
bot_idstring (uuid) · uuidbot
run_idstring (uuid) · uuidyesbot_routine_run
run_refstring · textyes
request_refstring · text
toolstring · text
kindstring · text
summarystring · text
detailobject · jsonb
riskstring · textstep, risky, floor
statusstring · textpending, allowed, denied, expired, cancelled
decided_atstring (date-time) · timestamptzyes
decided_viastring · textyes
notify_afterstring (date-time) · timestamptzyes
notified_atstring (date-time) · timestamptzyes
expires_atstring (date-time) · timestamptz
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, user_id, request_ref

Checks:

  • CHECK (((decided_via IS NULL) OR (decided_via ~ '^[a-z]{2,12}$'::text)))
  • CHECK (((jsonb_typeof(detail) = 'object'::text) AND (length((detail)::text) <= 20000)))
  • CHECK ((kind ~ '^[a-z][a-z_]{0,30}$'::text))
  • CHECK (((length(request_ref) >= 1) AND (length(request_ref) <= 200)))
  • CHECK (((run_ref IS NULL) OR (length(run_ref) <= 100)))
  • CHECK (((length(btrim(summary)) >= 1) AND (length(btrim(summary)) <= 1000)))
  • CHECK (((length(tool) >= 1) AND (length(tool) <= 200)))

BotMessage

Table bot_message · Resource bot_messages

  • GET /api/v1/bot_messages
  • GET /api/v1/bot_messages/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
user_idstring (uuid) · uuidapp_user
bot_idstring (uuid) · uuidbot
rolestring · textuser, bot, system
kindstring · texttext, report, approval, routine, error
textstring · text
run_refstring · textyes
routine_idstring (uuid) · uuidyesbot_routine
approval_idstring (uuid) · uuidyes
client_refstring · textyes
read_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((client_ref IS NULL) OR (length(client_ref) <= 100)))
  • CHECK (((run_ref IS NULL) OR (length(run_ref) <= 100)))
  • CHECK ((length(text) <= 60000))

BotRoutineRun

Table bot_routine_run · Resource bot_routine_runs

  • GET /api/v1/bot_routine_runs
  • GET /api/v1/bot_routine_runs/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
user_idstring (uuid) · uuidapp_user
routine_idstring (uuid) · uuidbot_routine
bot_idstring (uuid) · uuidbot
triggerstring · textschedule, manual
statestring · textqueued, held, claimed, running, waiting, completed, failed, missed, skipped
scheduled_forstring (date-time) · timestamptzyes
not_beforestring (date-time) · timestamptzyes
promptstring · textyes
run_refstring · textyes
device_idstring (uuid) · uuidyes
resultstring · textyes
errorstring · textyes
claimed_atstring (date-time) · timestamptzyes
started_atstring (date-time) · timestamptzyes
finished_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((error IS NULL) OR (length(error) <= 4000)))
  • CHECK (((prompt IS NULL) OR (length(prompt) <= 20000)))
  • CHECK (((result IS NULL) OR (length(result) <= 20000)))
  • CHECK (((run_ref IS NULL) OR (length(run_ref) <= 100)))

BotRoutine

Table bot_routine · Resource bot_routines

  • GET /api/v1/bot_routines
  • GET /api/v1/bot_routines/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
user_idstring (uuid) · uuidapp_user
bot_idstring (uuid) · uuidbot
namestring · text
promptstring · text
schedule_kindstring · textdaily, weekdays, weekly, interval, once, cron, manual
schedule_specobject · jsonb
timezonestring · text
enabledboolean · bool
paused_reasonstring · textyes
respect_quiet_hoursboolean · bool
catch_up_minutesinteger · int2
next_run_atstring (date-time) · timestamptzyes
last_run_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((catch_up_minutes >= 0) AND (catch_up_minutes <= 1440)))
  • CHECK (((length(btrim(name)) >= 1) AND (length(btrim(name)) <= 120)))
  • CHECK (((paused_reason IS NULL) OR (length(paused_reason) <= 500)))
  • CHECK (((length(btrim(prompt)) >= 1) AND (length(btrim(prompt)) <= 20000)))
  • CHECK ((jsonb_typeof(schedule_spec) = 'object'::text))
  • CHECK (((length(timezone) >= 1) AND (length(timezone) <= 64)))

Bot

Table bot · Resource bots

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
owner_user_idstring (uuid) · uuidapp_user
namestring · text
rolestring · text
avatarobject · jsonb
colorstring · text
instructionsstring · text
providerstring · textclaude, codex
modelstring · textyes
computerstring · textthis_pc, cloud
computer_machine_idstring (uuid) · uuidyescomputer_machine
allowed_appsstring[] · text[]
era_databoolean · bool
approval_modestring · textask, risky, auto
memory_enabledboolean · bool
statusstring · textactive, archived
visibilitystring · textprivate, workspace
archived_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((cardinality(allowed_apps) <= 40))
  • CHECK (((jsonb_typeof(avatar) = 'object'::text) AND (length((avatar)::text) <= 280000)))
  • CHECK (((color ~ '^[a-z]{3,12}$'::text) AND (color <> ALL (ARRAY['purple'::text, 'violet'::text]))))
  • CHECK ((length(instructions) <= 20000))
  • CHECK (((model IS NULL) OR ((length(model) >= 1) AND (length(model) <= 120))))
  • CHECK (((length(btrim(name)) >= 1) AND (length(btrim(name)) <= 80)))
  • CHECK ((length(role) <= 160))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
bots.approval_decidedYou allowed or denied a bot’s step.id, botId, status
bots.approval_requestedA bot working for you waits for your OK before a step.id, botId, summary, risk, recipientUserId
Was this page helpful?