Canonical page: https://docs.singlebase.io/agents

# Agents

**Their own access. Your boundaries.**

Give every agent its own database access and permissions. Each agent works with the part of your data you choose, on the same foundation as your application.

## Overview

AI agents, such as shopping assistants, support bots, LLM tool-calling loops, MCP servers, and background workers, need context from your data without holding a full server key. An **agent key** gives one agent exactly the collections and operations its job requires.

**Example policy:**

| Collection | Shopping agent | Support agent |
|---|---|---|
| `product_catalog` | `read`, `search` | `read` |
| `orders` | No access | `read` |
| `support_articles` | No access | `read`, `search` |

The shopping agent can query products without seeing orders or support content.

**What agents can do with Singlebase:**
- **Read context** from DocumentDB with filters and full-text search
- **Query by meaning** through vector [docsets](/chat.md#docsets) built for agents
- **Write results** back as documents, or save reports and files to [Storage](/files.md)
- **Use models** for generation and embeddings with [LLM](/llm.md), and hold chats with [Conversation](/chat.md)
- **Manage users** for support and ops workflows with [Users](/users.md)

**Key features:**
- One named key per agent, with an `agk_` prefix
- Per-collection permissions: `read`, `search`, `write`, `delete`
- Per-feature permissions for LLM, files, and users
- Every operation is checked, including each item in a batch
- Enable, disable, rotate, or delete a key without touching other keys
- Usage tracked with `last_used_at`
- Optional expiry, extended through the existing key update operation

**Use cases:** Support agents that look up orders, content agents that draft and publish documents, ingestion agents that upload files, ops agents that manage users

**How it works:**
1. **Define the task** - Decide what the agent should help with.
2. **Choose its access** - Create an agent key in the console with the collections and permissions it needs.
3. **Connect the agent** - It calls the normal API with `X-API-KEY: agk_...`.
4. **Review and adjust** - Singlebase allows the call only if the key's permissions cover it. Otherwise it returns `403 AGENT_SCOPE_DENIED`. Update the scope as the agent's task changes.

A project can store up to 50 agent keys.

---

## Calling the API

Agents use the same endpoint and [request format](/api.md#request-format) as any client. Send the agent key in `X-API-KEY` and no `Authorization` header.

### Connect with MCP

Agent runtimes can also connect to `https://v1.api.singlebase.io/mcp/<ENDPOINT_KEY>`
using `Authorization: Bearer agk_…` or `X-API-KEY: agk_…`. Enable MCP for the
project first. The server lists only tools and collection choices allowed by
the key and applies the same gateway rules to every call. See [MCP](/mcp-guide) for
Claude Code, Cursor, Claude Desktop, and raw HTTP examples.

```http
POST https://v1.api.singlebase.io/api/<ENDPOINT_KEY>
Content-Type: application/json
X-API-KEY: agk_<AGENT_KEY>
```

```json
{
  "operation": "data.query",
  "payload": {
    "collection": "orders",
    "filter": { "customer_email": "jane@example.com" },
    "per_page": 10
  }
}
```

**Within its permissions, an agent key has admin-level access:**
- `data.query` and `data.count` read across all users. Scope a request to one user with payload-level `"_user_id": "<USER_ID>"`, alongside `filter`. Ownership fields inside `filter` are rejected.
- `data.insert` can set an owner with payload-level `_user_id`. A single update-by-ID can reassign an owner with `data._user_id`; the user must exist. Payload-level `_user_id` on update selects an owner rather than changing it.
- Conversations and `chat.ask` can use every [retrieval source](/chat.md#sources-by-key-type), including raw collections.
- `files.*` can act on any project file.

Grant only what the agent needs.

---

## Permissions

An agent key has two permission sets. It needs at least one entry in either.

### Collection permissions

`collections` maps collection names to allowed actions. They apply to `data.*` operations using `payload.collection`.

```json
{
  "orders": ["read", "search"],
  "support_notes": ["read", "write"],
  "drafts": ["read", "write", "delete"]
}
```

| Permission | Allows |
|---|---|
| `read` | `data.query`, `data.count`, `data.aggregate` |
| `search` | `data.query` and `data.count` with a non-empty `search`. Also grants `read` |
| `write` | `data.insert`, `data.update`, `data.upsert` |
| `delete` | `data.archive`, `data.delete` |

Collections that are not listed are denied. Internal system collections cannot be granted.

### Operation permissions

`permissions` lists features the agent may use.

| Permission | Allows |
|---|---|
| `ai.generate` | `llm.generate`, `llm.write`, `llm.translate`, `llm.summarize`, `llm.categorize`, `llm.extract`, `llm.analyze` |
| `ai.chat` | All `chat.*` operations and their deprecated `llm.*` aliases, including preparation |
| `ai.embed` | `llm.embed` |
| `files.read` | `files.get_signed_url` |
| `files.list` | `files.query` |
| `files.upload` | `files.initiate_upload`, `files.complete_upload`, `files.fail_upload`, `files.update`, `files.reprocess` |
| `files.delete` | `files.delete` |
| `users.list` | `users.query`, `users.get_details` |
| `users.create` | `users.create` |
| `users.update` | `users.update`, `users.update_status`, `users.update_password`, `users.link_provider`, `users.unlink_provider` |
| `users.delete` | `users.delete` |

For `files.reprocess`, agent keys always follow owner rules even with an admin
role: they cannot retry another owner's file or a completed pipeline, and are
limited to four starts per file. Recently active processing is refused for all
callers; processing at least 30 minutes old can be restarted.

### Always denied

Agent keys can never call:
- `auth.*` and `user.*` (agents don't sign in as users)
- `users.get` and the user token operations
- Console operations

**Batches:** Every operation in a [batch](/api.md#batch-requests) is checked before any runs. One denied item denies the whole request.

**Docsets:** In [retrieval](/chat.md#docsets), agent keys count as `agent`. They can use docsets whose `acl` is `agent`, but not `user` or `admin` docsets.

---

## Managing Agent Keys

Manage agent keys in the [Singlebase console](https://console.singlebase.io), in your project's settings. Project owners and admins can create, update, rotate, and delete them. The key secret is shown in the `key` field; store it right away.

**Key record:**
```json
{
  "_id": "agt_3f9a1c",
  "name": "support-bot",
  "description": "Answers order questions in the help widget",
  "enabled": true,
  "key": "agk_3f9a1c_<SECRET>",
  "collections": {
    "orders": ["read", "search"],
    "support_notes": ["read", "write"]
  },
  "permissions": ["ai.chat", "files.read"],
  "expires_at": "2027-01-10T12:00:00Z",
  "last_used_at": "2026-10-03T12:00:00+00:00",
  "_created_at": "2026-09-01T09:00:00+00:00",
  "_modified_at": "2026-09-15T14:30:00+00:00"
}
```

**Rules:**
- Names are unique per project, ignoring case.
- A project can have up to 50 agent keys.
- Updating `collections` or `permissions` replaces the whole list; it does not merge.
- Rotating a key issues a new secret. The old key stops working right away.
- Rotation preserves `expires_at`.
- Disabled, rotated, and deleted keys return `404 PROJECT_NOT_FOUND`.
- Expired keys return `401 AGENT_KEY_EXPIRED` before scope checks and usage tracking.

### Expiry and extension

Create and update accept `expires_at`, an absolute ISO 8601 time with `Z` or a
timezone offset. The stored value is returned in UTC with `Z`. `null` means never
expires, and is the default for new keys. Older keys without this field continue
to work without expiry.

The timestamp must be in the future, allowing up to one minute of clock skew at
save time. Naive or older timestamps return `400 INVALID_EXPIRY`; invalid syntax
returns `INVALID_PAYLOAD`. Authentication rejects at the exact expiry time.

Use `console.update_agent_key { id, expires_at }` to extend an expired key. The
same secret works immediately afterward. Omitting the field keeps its current
value; sending `null` removes the expiry. Expiration does not delete the key.

---

## Example: Tool for an LLM Agent

Expose Singlebase to a tool-calling model with one generic tool. The agent key's permissions remain the real security boundary.

**Tool definition:**
```json
{
  "name": "singlebase",
  "description": "Call the Singlebase backend API. Use operation names such as data.query, data.insert, files.query, llm.summarize. Put all parameters in payload.",
  "input_schema": {
    "type": "object",
    "properties": {
      "operation": {
        "type": "string",
        "description": "Operation name, for example data.query"
      },
      "payload": {
        "type": "object",
        "description": "Operation parameters, for example {\"collection\": \"orders\", \"filter\": {\"status\": \"open\"}}"
      }
    },
    "required": ["operation", "payload"]
  }
}
```

**Tool handler (JavaScript SDK, server-side):**
```javascript
import { SinglebaseClient, SinglebaseError } from "@singlebase/singlebase-sdk";

const sbc = SinglebaseClient({
  baseUrl: process.env.SINGLEBASE_API_URL,
  apiKey: process.env.SINGLEBASE_AGENT_KEY,
});

async function singlebase(operation, payload) {
  try {
    const data = await sbc.dispatch({ operation, payload }, { bearer: null });
    return { data };
  } catch (error) {
    if (error instanceof SinglebaseError) {
      return { error: error.code, status: error.status };
    }
    throw error;
  }
}
```

The JavaScript SDK returns `data` directly and throws API errors. Keep this agent handler on your server. See [JavaScript SDK](/sdk-javascript.md) for connection options.

**Example calls the agent might make:**
```json
{ "operation": "data.query", "payload": { "collection": "orders", "filter": { "status": "open" }, "per_page": 5 } }
```
```json
{ "operation": "data.insert", "payload": { "collection": "support_notes", "data": { "order_id": "ord_123", "note": "Customer asked for a refund." } } }
```
```json
{ "operation": "llm.summarize", "payload": { "content": "<ticket thread>", "input": "Three bullets." } }
```

---

## Quick Reference for AI Agents

A compact summary of the API for models that read these docs.

**Request:** `POST <API_URL>`, headers `Content-Type: application/json` and `X-API-KEY: <key>`, body `{"operation": "<namespace.action>", "payload": {...}}`.

**Success:** `{"data": ..., "meta": {...}}`. **Error:** `{"error": {"type", "status", "message"}}`; `message` is a stable code.

**Common operations:**

| Goal | Operation | Minimal payload |
|---|---|---|
| Find documents | `data.query` | `{"collection": "c", "filter": {...}}` |
| Get one document | `data.query` | `{"collection": "c", "filter": {"_id": "id"}, "limit": 1}` |
| Search by keyword | `data.query` | `{"collection": "c", "search": "words"}` |
| Count | `data.count` | `{"collection": "c", "filter": {...}}` |
| Totals | `data.aggregate` | `{"collection": "c", "compute": {"n": {"$count": "*"}}}` |
| Create | `data.insert` | `{"collection": "c", "data": {...}}` |
| Patch by ID | `data.update` | `{"collection": "c", "data": {"_id": "id", "field": "value"}}` |
| Create or update | `data.upsert` | `{"collection": "c", "filter": {...}, "insert_data": {...}, "update_data": {...}}` |
| Delete | `data.delete` | `{"collection": "c", "id": "id"}` |
| List files | `files.query` | `{"per_page": 20}` |
| File download URL | `files.get_signed_url` | `{"id": "file_id"}` |
| Summarize text | `llm.summarize` | `{"content": "..."}` |
| Extract JSON | `llm.extract` | `{"content": "...", "format": "json"}` |
| Search by meaning | `chat.ask` | `{"message": "...", "mode": "kb", "retrieval": [{"source": "docset", "ids": "slug"}]}` |
| Find a user | `users.query` | `{"search": "email or name"}` |

**Syntax reminders:**
- Filters: `{"field": value}`, `{"field:$gte": 10}`, `{"$or": [{...}, {...}]}`. See [Query Syntax](/queries.md).
- Update operators: `"count:$incr": 1`, `"tags:$push": "x"`, `"old:$unset": true`. See [Data Operators](/data.md#data-operators).
- Templates: `"{{ field }}"` (string), `"&:{{ a * b }}"` (native), `"{{ $now() }}"` (helper). See [Templates](/templates.md).

**Error handling:**

| Message | What the agent should do |
|---|---|
| `AGENT_SCOPE_DENIED` | Stop. The key lacks permission; don't retry |
| `INVALID_PAYLOAD` | Check `details`, fix the payload, retry once |
| `MISSING_COLLECTION_NAME` | Add `payload.collection` |
| HTTP `429` | Wait with exponential backoff, then retry |
| HTTP `5xx` | Retry later with backoff |

---

## Best Practices

- **Least privilege** - Start with `read` on one collection and add permissions as needed.
- **One key per agent** - Separate keys make usage, auditing, and revocation simple.
- **Keep keys server-side** - Store agent keys in environment variables or a secret manager, never in prompts, client code, or logs.
- **Use docsets for knowledge** - Give agents curated [docsets](/chat.md#docsets) with `acl: "agent"` instead of broad collection access.
- **Rotate regularly** - Rotate keys on a schedule and right away if one leaks.
- **Return errors to the model** - Error codes are stable, so agents can recover from `INVALID_PAYLOAD` on their own.

---

## Next Steps

- [Data](/data.md) - Operations available to collection permissions
- [LLM](/llm.md) - Generation, answers, and embeddings
- [Conversation](/chat.md) - Chats, docsets, and attachments
- [Files](/files.md) - Upload and serve files
- [Users](/users.md) - Admin user management
