Agent API Reference

Relentless gives AI agents their own buildspace — the same product, a second room. Agents authenticate with a scoped API key, organize work using typed nodes, and share context with humans through a Fusion boundary.

bash
curl https://www.relentless.build/api/nodes?depth=skeleton \
  -H "Authorization: Bearer rlnt_YOUR_KEY_HERE"
BASE URLhttps://www.relentless.build

Authentication

All requests require a Bearer token in the Authorization header. Agent API keys start with rlnt_ and are scoped to a single buildspace.

bash
curl https://www.relentless.build/api/nodes \
  -H "Authorization: Bearer rlnt_4bc36fc7aa00c62d2ee0e0185b9658293bd769b4"

Key Properties

PropertyValue
Formatrlnt_{40_hex_chars}
ScopeSingle buildspace (cross-buildspace requests return 401)
Expiry90 days from creation
StorageSHA-256 hash (plaintext never stored)

Security Model: Allowlist

Agent keys are rejected by default on all endpoints. Only the CRUD routes documented here explicitly accept scoped keys. New endpoints are locked automatically — no developer action needed. Admin, key management, profile, and invite endpoints are permanently blocked for API key auth.


Nodes

Nodes are the core data primitive. Every piece of content is a node with a kind, title, and content (kind-specific JSON).

GET/api/nodes

List nodes in the agent's buildspace. Use depth=skeleton for a fast full-workspace scan without content.

Query Parameters

ParamTypeDescription
depth"skeleton"Omits content, includes _parentIds
parentIduuidChildren of a container. Powers Fusion queries.
includeSystem"true"Include system nodes (Home, Fusion)
systemKeystringFilter by systemKey (e.g. "home", "fusion")
kindNodeKindFilter by node kind
pinned"true"Only pinned nodes
archived"true"Only archived nodes
limit1-100Max results per page (default 50)
cursorstringPagination cursor from nextCursor
json
// Skeleton response
{
  "nodes": [
    {
      "id": "019c7389-d819-...",
      "kind": "nodebook",
      "title": "Home",
      "pinned": false,
      "archived": false,
      "systemKey": "home",
      "createdAt": "2026-02-19T01:35:39Z",
      "updatedAt": "2026-02-19T01:35:39Z",
      "_parentIds": []
    }
  ]
}
POST/api/nodes

Create a node. Returns the created node with a meta object showing remaining content capacity.

Request Body

FieldTypeDescription
kindNodeKindNode type (default "notebook")
titlestringMax 500 chars
contentobjectKind-specific JSON (max 50,000 chars serialized for API keys)
parentIduuidAuto-creates "contains" edge to parent
iconstringMax 50 chars
colorstringMax 50 chars
bash
curl -X POST https://www.relentless.build/api/nodes \
  -H "Authorization: Bearer rlnt_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "notebook",
    "title": "Research Notes",
    "content": { "body": "Initial findings..." },
    "parentId": "home-node-id"
  }'
json
// 201 Created
{
  "id": "019c738b-9b9f-...",
  "kind": "notebook",
  "title": "Research Notes",
  "content": { "body": "Initial findings..." },
  "shared": false,
  "meta": {
    "contentLength": 28,
    "maxContentLength": 50000,
    "remainingCapacity": 49972
  }
}
GET/api/nodes/:id

Get a single node by ID. Returns the full node including content.

PATCH/api/nodes/:id

Update a node. All fields optional — only provided fields are changed. Setting shared: true makes the node visible through the Fusion boundary.

FieldTypeDescription
titlestringMax 500 chars
contentobjectMerged with existing content
iconstring | nullSet or clear icon
colorstring | nullSet or clear color
pinnedbooleanPin/unpin in sidebar
archivedbooleanArchive/unarchive
sharedbooleanVisible through Fusion boundary
bash
curl -X PATCH https://www.relentless.build/api/nodes/NODE_ID \
  -H "Authorization: Bearer rlnt_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "shared": true, "title": "Updated Title" }'
DELETE/api/nodes/:id

Delete a node. Returns { "ok": true } on success.


Node Kinds

Each node kind has a specific content schema. Here are the available kinds and their content shapes.

notebookbookmarkprojecttaskeventcodenodebooksnippetlistcontactdecisiondecstasklistfusion
typescript
// notebook
{ body?: TipTapJSON | string }

// bookmark
{
  url: string;
  description?: string;
  favicon?: string;
}

// project
{
  status: "active" | "paused" | "done" | "archived";
  description?: string;
  tags?: string[];
}
typescript
// task (done is REQUIRED)
{
  done: boolean;
  dueAt?: string;       // ISO date
  priority?: 1 | 2 | 3; // 1=high, 2=medium, 3=low
  notes?: string;
}

// decision (all four REQUIRED)
{
  what: string;
  why: string;
  purpose: string;
  constraints: string;
}
typescript
// code
{
  language: string;
  source: string;
}

// snippet
{
  text: string;
  description?: string;
}

// event
{
  startsAt: string;
  endsAt?: string;
  location?: string;
}
typescript
// tasklist
{
  items: [{
    id: string;
    value: string;
    done?: boolean;
    priority?: 1 | 2 | 3;
    order: number;
    createdAt: string;
  }];
}

// contact
{
  type: "person" | "organization" | "ai";
  email?: string;
  phone?: string;
  company?: string;
}

Edges

Edges connect nodes. Four kinds of relationships are supported.

KindMeaning
containsParent-child hierarchy (space contains a notebook)
referencesSoft link (decision references a project)
blocksDependency (task A blocks task B)
followsSequence (step 1 follows step 2)
GET/api/edges

Query edges. At least one filter parameter is required.

ParamTypeDescription
nodeIduuidMatches either sourceId or targetId
sourceIduuidExact source match
targetIduuidExact target match
kindEdgeKindFilter by edge kind
json
[{
  "edge": {
    "id": "...",
    "sourceId": "...",
    "targetId": "...",
    "kind": "contains"
  },
  "sourceNode": {
    "id": "...",
    "title": "Home",
    "kind": "nodebook"
  },
  "targetNode": {
    "id": "...",
    "title": "My Notes",
    "kind": "notebook"
  }
}]
POST/api/edges

Create an edge. Both nodes must exist in the same buildspace. Duplicate edges return 409 with the existing edge ID.

bash
curl -X POST https://www.relentless.build/api/edges \
  -H "Authorization: Bearer rlnt_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceId": "parent-node-id",
    "targetId": "child-node-id",
    "kind": "contains"
  }'
DELETE/api/edges

Delete an edge. All three query params required: sourceId, targetId, kind.


Fusion — The Collaboration Boundary

The Fusion Node bridges the agent buildspace and the human buildspace. Shared nodes appear here automatically — no events, no webhooks. The query is the sync.

How It Works

  1. Find the Fusion node: In your skeleton response, look for systemKey: "fusion"
  2. Query shared content: GET /api/nodes?parentId={fusionId}
  3. Share your work: PATCH /api/nodes/:id { "shared": true }
json
// Fusion query response
{
  "nodes": [
    {
      "id": "...",
      "title": "Agent's Research",
      "_source": "self"
    },
    {
      "id": "...",
      "title": "Human's Brief",
      "_source": "linked"
    }
  ]
}

Read-only boundary: Agents cannot modify linked nodes. To respond to shared content, create your own node and share it back. Setting shared: false immediately removes the node from the next Fusion query.


System Nodes

System nodes are auto-created nodes with special keys. Retrieve them by key name instead of ID.

GET/api/system-nodes/:key

Returns the full node object. Creates the system node if it doesn't exist yet. Known keys: home, fusion, video-history.


Buildspaces

GET/api/buildspaces

List buildspaces accessible to the authenticated key. Agent keys see their scoped buildspace and the linked human buildspace. Response includes buildspace limits.

json
{
  "buildspaces": [{
    "id": "...",
    "name": "My Agent Space",
    "type": "agent",
    "role": "owner",
    "memberCount": 1,
    "linkedBuildspaceId": "...",
    "linkedBuildspaceName": "Personal"
  }],
  "limits": {
    "personal": { "max": 1, "current": 1, "remaining": 0 },
    "team": { "max": 3, "current": 0, "remaining": 3 },
    "agent": { "max": 2, "current": 1, "remaining": 1 }
  }
}

Rate Limits

Agent API keys are rate-limited per key. Human sessions are exempt.

ActionLimitWindow
Node creates50per hour
Node updates100per hour
Node reads500per hour
Edge creates100per hour

Additional limits

ResourceLimit
Content size50,000 characters (JSON serialized, API keys only)
Node count500 nodes per agent buildspace
Title length500 characters
json
// 429 Too Many Requests
{
  "error": "rate_limit_exceeded",
  "message": "You've used 50 of your 50 node creates for this hour. The limit resets at 14:00 UTC (12 minutes from now).",
  "limit": 50,
  "remaining": 0,
  "resetAt": "2026-02-05T14:00:00Z",
  "retryAfterSeconds": 720
}

// 413 Content Too Large
{
  "error": "content_too_large",
  "message": "Content exceeds maximum size of 50000 characters (current: 50012, overage: 12)",
  "size": 50012,
  "maxSize": 50000
}

Errors

All errors follow a consistent shape. The message field is always human-readable.

typescript
{ error: string; message?: string; details?: unknown }
CodeMeaning
400Bad request (validation, missing params, invalid kind)
401Unauthorized (bad key, expired, revoked, wrong buildspace)
403Forbidden (viewer role, blocked endpoint)
404Not found
409Conflict (duplicate edge — returns existing edge ID)
413Content too large (over 50,000 chars, API keys only)
429Rate limited (includes Retry-After header)
500Server error

Security

Every layer of the Agent API is designed to prevent abuse and protect data isolation.

Allowlist Auth

Scoped keys are rejected by default on all endpoints. Routes explicitly opt in. New endpoints are locked automatically.

Buildspace Isolation

Each key is locked to one buildspace. Cross-buildspace requests return 401 — no exceptions. Fusion reads are server-mediated.

SHA-256 Key Storage

API keys are stored as SHA-256 hashes, never plaintext. A database breach doesn't expose keys.

Input Validation

Zod schemas validate every request body. UUIDs, kinds, titles, and content sizes are all enforced server-side.

SQL Injection Prevention

Drizzle ORM uses parameterized queries exclusively. User input never touches SQL strings.

Privilege Escalation Prevention

API keys cannot access admin endpoints, manage other keys, read user profiles, or send invites. Ever.

Per-Key Rate Limits

Each API key has independent rate counters via Redis sliding windows. One agent can't exhaust limits for another.

Suspension & Expiry

Every request checks user suspension status. Keys expire after 90 days. Revocation is immediate.


Recommended Agent Workflow

bash
# 1. Map the workspace
GET /api/nodes?depth=skeleton

# 2. Find the Fusion node
# Look for systemKey: "fusion" in the skeleton response

# 3. Check what the human shared
GET /api/nodes?parentId={fusionNodeId}

# 4. Do your work — create nodes, organize with edges
POST /api/nodes  { "kind": "notebook", "title": "Research" }
POST /api/edges  { "sourceId": "home-id", "targetId": "new-id", "kind": "contains" }

# 5. Share results back through Fusion
PATCH /api/nodes/{id}  { "shared": true }