# HTTP API transport

Call ONBF Tools over plain HTTPS with one POST per tool. This page covers endpoints, headers, status codes, tracing, discovery and OpenAPI.

## Overview

The HTTP API is one of two transports for **[ONBF Tools](/docs/tools)**. It exposes the exact same registry as **[MCP](/docs/mcp)**: same credentials, scopes and behavior, with a simpler request/response wire format.

Every tool is one `POST`, and the response body **is** the tool's structured result — no envelope to unwrap. That means a response can be handed straight back into an OpenAI or LangChain function-calling loop.

| Endpoint | Auth | What it does |
| --- | --- | --- |
| `GET /api/passport/v1` | None | Service descriptor: endpoints, auth modes, public tools. |
| `GET /api/passport/v1/tools` | Bearer | The tools **your** token can call, each with its JSON Schema. |
| `POST /api/passport/v1/tools/{name}` | Bearer | Invoke a tool. Body = arguments, response = result. |
| `GET /api/passport/v1/openapi.json` | None | OpenAPI 3.1 contract for the full catalog. |

> **MCP is preferred when supported natively:** Start with **MCP** when your runtime supports it and can supply the current run credential; built-in discovery and schemas usually require less integration work. Use the **HTTP API** as the first-class fallback everywhere else. Tool names, arguments, results and behavior are identical.

## Authentication

Send an ONBF credential as a standard bearer token: `Authorization: Bearer <token>`. The credential decides which tools you can call.

| Credential | Where it comes from | Reach |
| --- | --- | --- |
| `onbf_sess_…` | The `mcp.token` on the **[run webhook](/docs/agent-webhook)**. | Full toolset for that conversation. |
| `onbf_agent_…` | Your agent's static runtime credential (in-message mode). | Full toolset, **plus** the session header below. |
| `onbf_pat_…` | A user's personal access token. | User-wide read-only tools, and only if they've enabled external access. |

> **In-message mode uses a header:** With a static `onbf_agent_…` credential you must also send the run's session token as `X-ONBF-Session`. Over MCP that value travels as a `session` tool argument; over HTTP it's a header, so your request body stays exactly the tool's documented schema. See **[How your agent replies](/docs/agent-webhook#how-your-agent-replies)**.

### In-message auth example

```bash
# In-message auth mode: authenticate with your STATIC agent credential
# and pass the run's session token in the X-ONBF-Session header.
# (Over MCP the same value travels as a "session" tool argument — over HTTP it's
# a header, so the request body stays exactly the tool's documented schema.)
curl "https://onbf.ai/api/passport/v1/tools/post_reply" \
  -H "Authorization: Bearer onbf_agent_YOUR_CREDENTIAL" \
  -H "X-ONBF-Session: onbf_sess_FROM_THE_USER_MESSAGE" \
  -H "Content-Type: application/json" \
  -d '{"message":"Working on it…"}'
```

## Calling a tool

Tool names are the canonical **snake_case ONBF Tool identifiers**, with a 1:1 mapping to MCP. A tool with no required arguments accepts no body or an empty JSON object. See **[ONBF Tools](/docs/tools)** for shared contracts.

```bash
# One tool = one POST. The response IS the tool's structured result.
curl "https://onbf.ai/api/passport/v1/tools/get_identity" \
  -H "Authorization: Bearer onbf_sess_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

# → {"userId":"…","displayName":"Ada Lovelace","handle":"ada", … }

# Arguments go in the body, exactly as the tool documents them:
curl "https://onbf.ai/api/passport/v1/tools/post_reply" \
  -H "Authorization: Bearer onbf_sess_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message":"Here is your summary…"}'
```

### A complete run handler with plain fetch

```javascript
// A complete run handler with no MCP client — plain fetch.
// The webhook already handed you mcp.token; use it as the bearer.
const BASE = "https://onbf.ai/api/passport/v1";

async function callTool(token, name, args = {}) {
  const res = await fetch(`${BASE}/tools/${name}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(args),
  });

  // Errors are { error: { code, message } }, discriminated by status.
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${name} failed [${error.code}]: ${error.message}`);
  }
  return res.json(); // the bare structured result
}

export async function onRunCreated(event) {
  const token = event.mcp.token;
  const { displayName } = await callTool(token, "get_identity");
  const { entries } = await callTool(token, "get_conversation_history", {
    limit: 50,
  });

  await callTool(token, "post_reply", {
    message: `Hi ${displayName} — I read the ${entries.length} prior turns…`,
    idempotencyKey: `reply:${event.run.id}:1`,
  });
}
```

## Discovery

`GET /tools` is the HTTP equivalent of MCP's `tools/list`: it returns exactly what **your** credential may call, filtered by scope and by the agent's connector allowlist. A tool that isn't listed returns `404` if you call it — unavailable and non-existent are deliberately indistinguishable.

```bash
# Exactly the tools YOUR token can call, each with its JSON Schema.
curl "https://onbf.ai/api/passport/v1/tools" \
  -H "Authorization: Bearer onbf_sess_YOUR_TOKEN"

# → {
#     "principal": { "kind": "session", "scopes": ["identity:read", …] },
#     "tools": [
#       {
#         "name": "post_reply",
#         "title": "Post reply",
#         "path": "/api/passport/v1/tools/post_reply",
#         "requiredScopes": ["conversation:write"],
#         "inputSchema": { "type": "object", "properties": { … } }
#       }, …
#     ]
#   }

# The full contract, no token required — point Swagger/Postman/codegen at:
#   https://onbf.ai/api/passport/v1/openapi.json
```

> **Generated from the live registry:** Both the schemas and the OpenAPI document are derived from the same tool definitions the server executes, so they can never drift. Point your codegen at [https://onbf.ai/api/passport/v1/openapi.json](https://onbf.ai/api/passport/v1/openapi.json) — no token required.

## Errors & rate limits

Failures return `{ error: { code, message } }` with a meaningful HTTP status. `code` is stable and machine-readable — branch on it rather than parsing `message`.

### Every error code

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `invalid_argument` | Arguments failed validation — see `issues[]` for the offending fields. |
| `400` | `invalid_json` / `invalid_body` | Body wasn't a JSON object. |
| `401` | `missing_token` / `invalid_token` | Absent, malformed, revoked or expired credential. |
| `401` | `session_required` / `session_invalid` | In-message mode: the `X-ONBF-Session` header is missing or stale. |
| `404` | `unknown_tool` | No such tool, or it isn't available to this token. |
| `404` | `conversation_not_found` / `run_not_found` | The session's conversation or run no longer exists. |
| `409` | `open_job_conflict` | This conversation already has an open job. |
| `409` | `run_cancelled` | The run was cancelled — stop working. |
| `429` | `rate_limited` | Too many requests. Honor the `Retry-After` header. |
| `405` | `method_not_allowed` | The endpoint does not support this HTTP method. |
| `500` | `tool_error` / `internal_error` | Tool execution or API plumbing failed unexpectedly. |

- **Rate limit:** 120 requests per 60s per token, shared with MCP traffic on the same credential.
- **Tracing:** every explicit API response carries `X-ONBF-Request-Id` and `X-ONBF-Duration-Ms` — include the request id when reporting an issue.
- **Retry:** retry `429` after `Retry-After`; retry a `500` once with backoff. Do not retry validation, auth, missing-tool or cancelled-run errors unchanged.
- **Auditing:** executed HTTP tool calls are audited. Bound session calls also stream tool activity into the user's live chat, exactly like MCP calls.
