Develop

API essentials

The API is how your software talks to Mobius directly. Your product can put its own interface in front of an agent, an internal service can start a loop, or a setup process can create the same resources repeatedly.

Use an SDK when one is available for your language. Use the raw HTTP API when you need direct control or are integrating from a different stack. Both use the same resources, authentication, errors, and idempotency rules described here.

Choose the right entry point

Start from the job your software needs to do:

Send a message to an agent. Start with agent invocation. It handles conversation lookup, creation, and streaming through one operation.

React to something outside Mobius. When the other system can call a URL, an HTTP trigger can start a run without custom API code. Use the runs API when your software needs to choose the loop, input, budget, or idempotency key itself.

Create the same setup repeatedly. See blueprints and provisioning.

Operate from a terminal or pipeline. Prefer the mobius CLI when it already exposes the task. It handles authentication, formatting, and pagination for you.

What a request looks like

Here's the whole idea. Ask Mobius for the loops in one project, using curl, a program for making web requests that's already on your Mac:

curl https://api.mobiusops.ai/v1/projects/ridgeline/loops \
  -H "Authorization: Bearer $MOBIUS_API_KEY"
{
  "items": [
    {
      "id": "loop_9q2m7x5v3p8n4r6t",
      "name": "Triage tickets",
      "status": "active",
      "created_at": "2026-06-15T14:00:00Z",
      "updated_at": "2026-06-15T14:00:00Z"
    }
  ],
  "has_more": false
}

A web address, a key that proves who you are, and some structured text back. That's the shape of every request on this page. Everything else is detail.

Every address starts with https://api.mobiusops.ai/v1. There is no v0, and the v1 won't change under you.

Three ways in

Most of the API works like the request above: you ask, we answer, done. Two things don't, because they're still happening while you're listening.

HowWhat it's for
Ordinary requestsEverything list-shaped or form-shaped. The bulk of it.
A stream you hold openWatching a run unfold, or an agent's reply arriving word by word.
A two-way connectionThe worker, which stays connected so we can hand it work.

The streams use Server-Sent Events, which is a long-lived HTTP response the browser and most languages already know how to read. Nothing exotic.

Agent conversations mostly go through one address:

POST /v1/projects/{project}/agents/invoke

Send a message, get a reply or a stream of one. We find or create the conversation for you. Reach for the lower-level session endpoints only when your own software wants to own the conversation:

POST /v1/projects/{project}/sessions
POST /v1/projects/{project}/sessions/{session_id}/turns
GET  /v1/projects/{project}/sessions/{session_id}/stream

Start with invoke. See agent invocation for the request shape and sessions and turns for direct control.

Your key

Every request carries a key in a header called Authorization:

Authorization: Bearer mbx_...

"Bearer" is just the word the header expects before the key. It doesn't mean anything you need to care about.

Keys starting with mbx_ are API keys, which you create in the app under Settings > Access. Two kinds:

KindWhat it can reachUse it for
Project keyOne projectAlmost everything, including workers
Organization keyEvery projectSetup scripts and cross-client admin

Use a project key unless you genuinely need to work across clients. A key that can only touch Ridgeline Dental cannot accidentally touch Acme.

Neither kind carries permissions of its own. A project key signs in as an API client and inherits that client's role; an organization key gets the role you pick when you create it. Change the role, change what the key can do, without reissuing anything.

Keys starting with mbc_ are different: those belong to the mobius CLI and act as the person who logged in. You won't create those by hand.

Warning: Anyone holding the key can do everything the key can do. Put it in your secret store, not in a script, a config file you commit, or an email.

Which project

Nearly every address has the project in it:

/v1/projects/{project}/...

Use the project's handle, the short name you gave it. If you run one project per client, that handle is the client.

A few addresses sit above projects, because they're about the whole organization: /v1/projects itself, /v1/audit-logs, and /v1/api-keys. Those are the exceptions and they look like exceptions.

What comes back

IDs tell you what they are. Every ID starts with a short prefix, so a stray one in a log is identifiable at a glance: loop_ for loops, run_ for runs, job_ for worker jobs, key_ for API keys.

Long lists arrive in pages. A list response includes has_more and next_cursor. If has_more is true, send next_cursor back as cursor on your next request and you'll get the following page. Don't try to read next_cursor; it's deliberately meaningless.

Times are UTC, written like 2026-06-15T14:00:00Z.

Errors explain themselves. Anything that isn't a success comes back with a code you can branch on and a message you can show a human:

{
  "error": {
    "code": "bad_request",
    "message": "invalid request body",
    "details": {
      "fields": [
        {
          "field": "actions.selector_type",
          "message": "expected string, got number",
          "expected": "string",
          "actual": "number"
        }
      ]
    }
  }
}

When details.fields is present, each field is a path into what you sent. If you're building a form, show the message next to that field. Some operational failures use details too, for things like rate limits and duplicate-request conflicts.

Sending the same thing twice

Networks drop responses. Your request went through, you never heard back, and now you don't know whether to retry.

For anything that shouldn't happen twice, send an Idempotency-Key header with a value you make up. If we've already handled a request with that key, we return the original result instead of doing the work again. Starting a run and invoking an action both accept it.

Pick a key that describes the thing, not the attempt: ticket-4182-triage, not a random string you generate on each retry. A random one defeats the entire point.

Use the CLI or an SDK when you can

The mobius CLI and the Go, TypeScript, and Python libraries speak this same API and handle the tedious parts. Some things you should not build by hand:

  • Recovering a run. mobius runs resume and mobius runs retry assemble the right request and print what happened.
  • Running a worker. The libraries handle the connection, the leases, the heartbeats, and the reconnects. This one is genuinely hard to get right.
  • Checking a webhook is really from us. The libraries ship a verifyWebhook helper that does the signature check, the clock-drift check, and the replay check in one call.
  • Paging through a long list. The CLI follows the cursors for you; the libraries hand you something you can loop over.

If you catch yourself implementing any of those, look in the mobius repo first.

The rest of these pages

Putting an agent in your own software

  • Agent invocation: start a conversation, stream the reply, retry safely.
  • Sessions and turns: read a transcript, stream it, steer a reply mid-flight, keep a long conversation from growing forever.
  • Agent definitions: send the agent's behavior with the request, or have us fetch it from your backend.
  • Structured output: get an answer back in a shape your code can use.
  • Embedded OAuth return: let your users connect their own Google or Slack account from inside your product.

Setting up clients in bulk

Reaching in and out

  • Actions: run one specific thing directly, and the reply format your own HTTP actions have to return.
  • Interactions: the approvals and questions a run waits on.
  • Webhooks: have Mobius tell your system when something happens.
  • Event catalog: every event that can start a run, and what's inside it.

Data

When something went wrong

The full list

Every address, every field, every error code lives in the interactive reference. It's generated from the API itself, so it can't drift. If a page here disagrees with it, believe the reference and tell us.