Back to Documentation

Sutram REST API v1


Overview

The Sutram REST API provides JSON endpoints for external consumption of project content (folders, files, links). It uses the same dual-key authentication as the MCP Server (x-project-key + x-user-key), so any project with Remote Access enabled becomes automatically accessible through the REST API.

Use cases

  • Institutional site (dogfooding) — Serving the blog posts and documentation pages of the sutram.io site itself, with Sutram as the single source of truth (SSOT)
  • Custom dashboards — Building content listing pages outside of Sutram
  • Mobile apps — Fetching files and links with pre-signed download URLs
  • Automation — Programmatic content discovery and retrieval via scripts or CI/CD

Base URL

https://sutram.io/api/v1

How it works

External Client (app, script, dashboard)
        |
        | HTTPS + JSON
        v
  Sutram REST API
    /api/v1/*
        |
        | Dual Key Authentication
        v
  Project Content (folders, files, links, tags)

Authentication

Every request must include two headers:

Header Value Description
x-project-key sk_proj_... Identifies the project
x-user-key sk_user_... Identifies the user

Both keys are created through the Sutram web interface.

Example

curl -H "x-project-key: sk_proj_ej8NWMisd2rJgMwAJ22T..." \
     -H "x-user-key: sk_user_zQD0BjH56nQhOgX3uxni..." \
     https://sutram.io/api/v1/project

Error responses

When authentication fails, the API returns HTTP 401:

{ "error": "Missing x-project-key header" }
{ "error": "Missing x-user-key header" }
{ "error": "Invalid project key" }
{ "error": "Invalid user key" }
{ "error": "Not a member of this project" }

Permissions

Role Access
Owner Full read access
Admin Full read access
Member Full read access
Viewer Read access (the v1 API is read-only for all roles)

The v1 REST API is read-only for all roles. Since the entire API is read-only, any active member — including viewers — can consume it with valid keys.


Endpoints

GET /api/v1/project

Returns information about the authenticated project.

Example request:

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     https://sutram.io/api/v1/project

Response:

{
  "project": {
    "id": "a1b2c3d4-...",
    "name": "Construction Site Alpha",
    "description": "Main project documentation",
    "your_role": "owner",
    "created_at": "2026-01-15T10:00:00Z",
    "updated_at": "2026-02-19T14:30:00Z"
  }
}

GET /api/v1/folders

Lists the project's root-level folders.

Example request:

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     https://sutram.io/api/v1/folders

Response:

{
  "folders": [
    {
      "id": "f1a2b3c4-...",
      "name": "Reports",
      "parent_id": null,
      "tags": {"category": "monthly"},
      "created_at": "2026-01-20T10:00:00Z"
    },
    {
      "id": "f5e6d7c8-...",
      "name": "Photos",
      "parent_id": null,
      "tags": {},
      "created_at": "2026-01-22T08:00:00Z"
    }
  ],
  "meta": {
    "total_count": 2
  }
}

GET /api/v1/folders/:id

Returns the details of a folder, its subfolders, and its content items.

Path parameters:

Parameter Type Description
id UUID Folder ID

Example request:

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     https://sutram.io/api/v1/folders/f1a2b3c4-...

Response:

{
  "folder": {
    "id": "f1a2b3c4-...",
    "name": "Reports",
    "parent_id": null,
    "path": "/Reports",
    "tags": {"category": "monthly"},
    "created_at": "2026-01-20T10:00:00Z"
  },
  "subfolders": [
    {
      "id": "a1b2c3d4-...",
      "name": "2024",
      "parent_id": "f1a2b3c4-...",
      "tags": {"year": "2024"},
      "created_at": "2026-01-25T12:00:00Z"
    }
  ],
  "items": [
    {
      "id": "d5e6f7a8-...",
      "type": "file",
      "name": "summary",
      "description": null,
      "tags": {"status": "final"},
      "folder_id": "f1a2b3c4-...",
      "created_at": "2026-02-01T10:00:00Z",
      "file": {
        "filename": "summary.pdf",
        "content_type": "application/pdf",
        "file_size": 245000
      }
    }
  ],
  "meta": {
    "subfolder_count": 1,
    "item_count": 1
  }
}

Error responses:

Status Body When
404 {"error": "Folder not found"} The folder does not exist or belongs to another project

GET /api/v1/content

Lists content items with filtering, tag search, text search, and pagination.

Query parameters:

Parameter Type Default Description
folder_id UUID Filter by folder. Without this parameter, returns only root-level items.
all_folders boolean false If true, searches across all folders (not just the root). Required for project-wide tag/search queries.
type string Filter by content type: "file", "web_link", "video_link", "audio_link"
tag string Single tag key to filter by (e.g., tag=category)
tag_value string Tag value to match (used with tag). Substring match, case-insensitive.
tags JSON Multiple AND conditions as a JSON array (e.g., tags=[{"key":"topic","value":"neuro"}])
search string Search by item name (substring match, case-insensitive)
page integer 1 Page number
per_page integer 50 Items per page (maximum: 100)

Tag search behavior:

  • Key match: Exact match, case-insensitive ("Category" matches "category")
  • Value match: Substring match, case-insensitive ("neuro" matches "Neurologia Avançada")
  • Multiple conditions (tags parameter): All conditions must be satisfied (AND logic)
  • No value: When tag_value is omitted, matches any item that has the tag key, regardless of value

Example — list root items

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content"

Example — list items in a folder

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?folder_id=f1a2b3c4-..."

Example — filter by type

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?type=video_link&all_folders=true"

Example — single-tag search

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?tag=category&tag_value=cientifico&all_folders=true"

Example — AND search with multiple tags

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?tags=%5B%7B%22key%22%3A%22patient%22%2C%22value%22%3A%22jo%C3%A3o%22%7D%2C%7B%22key%22%3A%22year%22%2C%22value%22%3A%222024%22%7D%5D&all_folders=true"

The tags parameter above is the URL-encoded version of:

[{"key": "patient", "value": "joão"}, {"key": "year", "value": "2024"}]

Example — search by name

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?search=report&all_folders=true"

Example — paginated listing

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?page=2&per_page=20"

Response:

{
  "items": [
    {
      "id": "d5e6f7a8-...",
      "type": "file",
      "name": "site-plan",
      "description": null,
      "tags": {"category": "planta", "year": "2024"},
      "folder_id": "f1a2b3c4-...",
      "created_at": "2026-02-19T10:00:00Z",
      "file": {
        "filename": "site-plan.pdf",
        "content_type": "application/pdf",
        "file_size": 2450000
      }
    },
    {
      "id": "e5f6a7b8-...",
      "type": "video_link",
      "name": "Site walkthrough",
      "description": null,
      "tags": {"topic": "neurologia"},
      "folder_id": null,
      "created_at": "2026-02-18T14:00:00Z",
      "video_link": {
        "url": "https://youtube.com/watch?v=abc",
        "platform": "youtube",
        "video_id": "abc",
        "thumbnail_url": "https://img.youtube.com/vi/abc/hqdefault.jpg"
      }
    },
    {
      "id": "a1b2c3d4-...",
      "type": "web_link",
      "name": "Documentation",
      "description": "Project reference docs",
      "tags": {},
      "folder_id": null,
      "created_at": "2026-02-17T09:00:00Z",
      "web_link": {
        "url": "https://docs.example.com",
        "fetched_title": "Documentation Hub",
        "favicon_url": "https://docs.example.com/favicon.ico"
      }
    },
    {
      "id": "c9d0e1f2-...",
      "type": "audio_link",
      "name": "Weekly recap",
      "description": null,
      "tags": {"series": "weekly"},
      "folder_id": null,
      "created_at": "2026-02-16T16:00:00Z",
      "audio_link": {
        "url": "https://open.spotify.com/episode/xyz",
        "platform": "spotify",
        "artist_or_author": "Team Alpha",
        "thumbnail_url": "https://i.scdn.co/image/abc"
      }
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 50,
    "total_count": 4,
    "total_pages": 1
  }
}

Contentable fields by type:

The contentable is serialized under a key matching the item's type:

Type Key Fields
file file filename, content_type, file_size
web_link web_link url, fetched_title, favicon_url
video_link video_link url, platform, video_id, thumbnail_url
audio_link audio_link url, platform, artist_or_author, thumbnail_url

GET /api/v1/content/:id

Returns the full details of a single content item, including pre-signed download URLs for files.

Path parameters:

Parameter Type Description
id UUID Content item ID

Example request:

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     https://sutram.io/api/v1/content/d5e6f7a8-...

Response (file):

{
  "item": {
    "id": "d5e6f7a8-...",
    "type": "file",
    "name": "site-plan",
    "description": null,
    "tags": {"category": "planta", "year": "2024"},
    "folder_id": "f1a2b3c4-...",
    "folder_path": "/Reports/2024",
    "created_at": "2026-02-19T10:00:00Z",
    "file": {
      "filename": "site-plan.pdf",
      "content_type": "application/pdf",
      "file_size": 2450000,
      "version": 1,
      "download_url": "https://storage.example.com/projects/.../site-plan.pdf?X-Amz-..."
    }
  }
}

Response (video link):

{
  "item": {
    "id": "e5f6a7b8-...",
    "type": "video_link",
    "name": "Site walkthrough",
    "description": null,
    "tags": {"topic": "neurologia"},
    "folder_id": null,
    "folder_path": "/",
    "created_at": "2026-02-18T14:00:00Z",
    "video_link": {
      "url": "https://youtube.com/watch?v=abc",
      "platform": "youtube",
      "video_id": "abc",
      "thumbnail_url": "https://img.youtube.com/vi/abc/hqdefault.jpg",
      "duration_seconds": null,
      "fetched_title": "Construction Site Alpha - Full Tour"
    }
  }
}

Response (audio link):

{
  "item": {
    "id": "c9d0e1f2-...",
    "type": "audio_link",
    "name": "Weekly recap",
    "description": null,
    "tags": {"series": "weekly"},
    "folder_id": null,
    "folder_path": "/",
    "created_at": "2026-02-16T16:00:00Z",
    "audio_link": {
      "url": "https://open.spotify.com/episode/xyz",
      "platform": "spotify",
      "artist_or_author": "Team Alpha",
      "thumbnail_url": "https://i.scdn.co/image/abc",
      "duration_seconds": 1800,
      "fetched_title": "Weekly Standup Recap - Feb 16"
    }
  }
}

Response (web link):

{
  "item": {
    "id": "a1b2c3d4-...",
    "type": "web_link",
    "name": "Documentation",
    "description": "Project reference docs",
    "tags": {},
    "folder_id": null,
    "folder_path": "/",
    "created_at": "2026-02-17T09:00:00Z",
    "web_link": {
      "url": "https://docs.example.com",
      "fetched_title": "Documentation Hub",
      "fetched_description": "Complete project documentation",
      "favicon_url": "https://docs.example.com/favicon.ico"
    }
  }
}

Differences between the detail and listing responses:

The /content/:id endpoint returns additional fields compared to the listing endpoint:

Field Listing (/content) Detail (/content/:id)
folder_path No Yes
file.version No Yes
file.download_url No Yes (pre-signed, temporary)
web_link.fetched_description No Yes
video_link.duration_seconds No Yes
video_link.fetched_title No Yes
audio_link.duration_seconds No Yes
audio_link.fetched_title No Yes

Error responses:

Status Body When
404 {"error": "Content item not found"} The item does not exist or belongs to another project

GET /api/v1/document-classes/:id/documents

Lists the governed documents of a Document Class (a governance feature, Max plan). By default it returns one row per family — the latest version you can read — and honors need-to-know visibility: only documents in states you have permission to read are returned.

Path parameters:

Parameter Type Description
id UUID or string The Document Class UID or the class's category_id (e.g., docs)

Query parameters:

Parameter Syntax Effect
tag[key] tag[category]=getting-started&tag[lang]=PT Generic filter by any document metadata (e.g., category, lang). Multiple keys combine with AND.
state / states state[]=draft&state[]=approved or state=draft,approved Filters by lifecycle state; multiple states = union (OR). Always intersected with the states you can read (need-to-know) — it never widens visibility.
all_versions all_versions=true false (default) = one row per family (the latest readable version); true = one row per version.
page / per_page page=2&per_page=100 Pagination. per_page defaults to 50, maximum 200.

Readable states and default: without state, the endpoint returns the latest version you can read of each document — which may be an unpublished draft. To consume only published content (e.g., a public site), filter with state=approved.

Example request:

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/document-classes/docs/documents?state=approved"

Response:

{
  "category_id": "docs",
  "category_name": "Documentation",
  "documents": [
    {
      "document_id": "a6a01eb3-...",
      "version_id": "117665e1-...",
      "slug": "guia-de-inicio-rapido",
      "name": "guia-de-inicio-rapido",
      "lifecycle_state": "approved",
      "state_label": "Approved",
      "version_key": "1.38.a",
      "metadata": {
        "title": "Guia de Início Rápido",
        "category": "getting-started",
        "excerpt": "Comece a usar o Sutram em poucos minutos...",
        "lang": "PT",
        "published_at": "2026-03-16",
        "sort_order": "10",
        "translation_key": "quick-start-guide"
      }
    }
  ],
  "page": 1,
  "per_page": 50,
  "total_count": 27,
  "total_pages": 1
}

Fields per document:

Field Description
document_id The document family ID (stable across versions)
version_id ID of the returned version. It is also a folder ID — use GET /api/v1/folders/{version_id} to obtain the body file (e.g., the .md)
slug / name Computed identity of the document
lifecycle_state / state_label Lifecycle state (id and human-readable label)
version_key Version label (e.g., 1.38.a)
metadata Typed class metadata (title, category, excerpt, lang, sort_order, etc.)

Getting a document's body:

A document's body (e.g., the .md) lives in the version folder. Combine this endpoint with the folder and content endpoints:

# 1. List the governed documents
curl ... "https://sutram.io/api/v1/document-classes/docs/documents?state=approved"

# 2. For each document, open the version folder by version_id
curl ... "https://sutram.io/api/v1/folders/117665e1-..."

# 3. Get the pre-signed download URL for the body file
curl ... "https://sutram.io/api/v1/content/{file_id}"

Error responses:

Status Body When
404 {"error": "Document class not found"} The class does not exist or belongs to another project

Content item tags

Tags are free-text key-value pairs attached to content items and folders. They enable structured metadata and advanced search features.

Tag constraints

Constraint Limit
Maximum tags per item 50
Maximum key length 100 characters
Maximum value length 500 characters
Key and value types Strings only

Managing tags

Content item tags are managed through the MCP Server tools:

MCP tool Description
sutram_set_item_tags Set/replace all tags on a content item
sutram_search_items Search items by tag (single condition or AND with multiple conditions)
sutram_get_item_tag_keys List all distinct tag keys across items

Querying tags via the REST API

Use the tag, tag_value, and tags query parameters on GET /api/v1/content:

# Find all items tagged with "category"
curl ... "https://sutram.io/api/v1/content?tag=category&all_folders=true"

# Find items where category contains "report"
curl ... "https://sutram.io/api/v1/content?tag=category&tag_value=report&all_folders=true"

# Find items matching multiple conditions (AND)
curl ... "https://sutram.io/api/v1/content?tags=[{\"key\":\"patient\",\"value\":\"joão\"},{\"key\":\"year\",\"value\":\"2024\"}]&all_folders=true"

Comparison: REST API vs. MCP Server

Feature REST API (/api/v1) MCP Server (/mcp)
Protocol HTTP/JSON JSON-RPC 2.0 (MCP)
Read content Yes Yes
Search by tags Yes Yes
List governed documents Yes Yes
Download URLs Yes (pre-signed) Yes (pre-signed)
Upload files No Yes
Create links No Yes
Create folders No Yes
Set tags No Yes
Move/rename/delete No Yes
Authentication Same dual key Same dual key
Best for External apps, dashboards, scripts AI assistants, automation

Rate limits

The REST API enforces per-project and per-IP rate limits:

  • Per project (per hour) — Determined by the project owner's plan (api_rate_limit_per_hour). The default is 1,000 requests/hour, reaching 10,000/hour on higher plans. When exceeded, the API responds with HTTP 429 and the body {"error": "Rate limit exceeded", "retry_after": <seconds>}.
  • Limit headers — Successful responses include x-ratelimit-limit and x-ratelimit-remaining; the 429 response includes retry-after (in seconds).
  • Per IP — An additional per-IP-address limit protects against abuse and brute force.

When you receive a 429, respect the retry-after header and apply backoff before retrying the request.


Versioning

The API is versioned by URL path (/api/v1). Breaking changes will be introduced in new versions (/api/v2). Non-breaking additions (new fields, new optional parameters) may be added to the current version without prior notice.


Examples

Fetch all video links of a project

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?type=video_link&all_folders=true"

Fetch content tagged with a specific topic

curl -H "x-project-key: $PROJECT_KEY" \
     -H "x-user-key: $USER_KEY" \
     "https://sutram.io/api/v1/content?tag=topic&tag_value=neurologia&all_folders=true"

Navigate the folder hierarchy

# 1. List root folders
curl ... "https://sutram.io/api/v1/folders"

# 2. Open a specific folder
curl ... "https://sutram.io/api/v1/folders/f1a2b3c4-..."

# 3. Get download URL for a file
curl ... "https://sutram.io/api/v1/content/d5e6f7a8-..."

Paginate through all content

# Page 1
curl ... "https://sutram.io/api/v1/content?page=1&per_page=20&all_folders=true"

# Page 2
curl ... "https://sutram.io/api/v1/content?page=2&per_page=20&all_folders=true"

# Continue until page >= total_pages from meta

JavaScript integration (fetch)

const PROJECT_KEY = "sk_proj_...";
const USER_KEY = "sk_user_...";
const BASE_URL = "https://sutram.io/api/v1";

async function fetchContent(params = {}) {
  const query = new URLSearchParams(params).toString();
  const url = `${BASE_URL}/content${query ? "?" + query : ""}`;

  const response = await fetch(url, {
    headers: {
      "x-project-key": PROJECT_KEY,
      "x-user-key": USER_KEY,
    },
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error);
  }

  return response.json();
}

// List all video links
const videos = await fetchContent({ type: "video_link", all_folders: "true" });
console.log(`Found ${videos.meta.total_count} videos`);

// Search by tag
const tagged = await fetchContent({
  tag: "topic",
  tag_value: "neurologia",
  all_folders: "true",
});

Python integration (requests)

import requests

PROJECT_KEY = "sk_proj_..."
USER_KEY = "sk_user_..."
BASE_URL = "https://sutram.io/api/v1"

headers = {
    "x-project-key": PROJECT_KEY,
    "x-user-key": USER_KEY,
}

# List root folders
folders = requests.get(f"{BASE_URL}/folders", headers=headers).json()

# Get content in a folder
folder_id = folders["folders"][0]["id"]
content = requests.get(
    f"{BASE_URL}/content",
    headers=headers,
    params={"folder_id": folder_id},
).json()

# Search by multiple tags
import json
tags = json.dumps([
    {"key": "patient", "value": "joão"},
    {"key": "year", "value": "2024"},
])
results = requests.get(
    f"{BASE_URL}/content",
    headers=headers,
    params={"tags": tags, "all_folders": "true"},
).json()

print(f"Found {results['meta']['total_count']} items")

Troubleshooting

401 Unauthorized

  • Check that both the x-project-key and x-user-key headers are present
  • Confirm that the keys have not been revoked
  • Make sure you are an active member of the project (viewers have read access; only non-members receive 401)

404 Not Found

  • Check that the folder/content ID belongs to the authenticated project
  • Confirm that the item has not been deleted

429 Too Many Requests

  • You have exceeded the project's per-hour rate limit (see Rate limits)
  • Read the retry-after header from the response and wait the indicated number of seconds before retrying
  • Track x-ratelimit-remaining in responses to avoid hitting the limit
  • If the volume is legitimate, consider a plan with a larger quota

Empty results in tag search

  • Add all_folders=true to search across all folders (without it, only root items are returned)
  • Tag search is case-insensitive — check for typos in key names
  • Use GET /api/v1/content?tag=YOUR_KEY&all_folders=true without a value first to verify that the tag key exists

tags parameter returns an error

  • The tags parameter must be a valid JSON array: [{"key": "...", "value": "..."}]
  • URL-encode the JSON string when passing it as a query parameter
  • Each condition must have a key field; value is optional