Uutter

Uutter API Documentation

Base URL: https://uutter.com/api/v1

Authentication

All API requests require a Bearer token. Generate an API key at /settings/api. Include it in the Authorization header:

curl -H "Authorization: Bearer uut_your_key_here" https://uutter.com/api/v1/me

Rate Limits

Hourly limits scale with your account tier: 200 requests/hour (Sapling), 500 (Oak), 1,000 (Grove), or 2,000 (Forest) — sliding window, plus a burst cap of 5 requests per second. Rate limit headers are included on every response:

X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 2026-03-25T19:00:00.000Z

Discovery

GET/me

Returns your user info, API key metadata, rate limit status, and all collections you have access to with their permissions and field configurations.

{
  "data": {
    "user": { "id": "uuid", "username": "jonathan" },
    "apiKey": {
      "prefix": "uut_d0f1",
      "createdAt": "2026-03-23T...",
      "lastUsedAt": "2026-03-25T...",
      "requestCount": 42
    },
    "rateLimit": { "limit": 500, "remaining": 487, "resetAt": "..." },
    "collections": [
      {
        "id": "uuid",
        "name": "Terence McKenna",
        "url": "terence-mckenna",
        "role": "owner",
        "apiEnabled": true,
        "permissions": ["create_entry", "get_entry", "search"],
        "fields": {
          "title": "required",
          "introduction": "disabled",
          "description": "optional",
          "image": "optional",
          "audio": "optional",
          "contentCategory": "optional",
          "people": "optional"
        }
      }
    ]
  }
}

Collections

Create Collection

POST/collections

Creates a new collection. Private by default. API and MCP access are enabled by default with all endpoint permissions seeded, so the collection is immediately usable via both API and MCP.

name(string)required3-42 characters
url(string)required3-42 chars, lowercase alphanumeric + hyphens
isPrivate(boolean)Default: true
description(string)7-159 characters
theme(string)Hex color
collaborationType(string)open | approval | contributors | private
isOpenToRequests(boolean)Default: false
shouldAutoTranscribe(boolean)Default: true
shouldAutoProcessDocuments(boolean)Default: true
allowsCrossPosting(boolean)Default: false
apiAccessEnabled(boolean)Default: true. All endpoint permissions are seeded regardless of this flag.
mcpAccessEnabled(boolean)Default: true. Requires apiAccessEnabled to also be true.
hasTableView(boolean)Enable table view on the collection page
hasLibraryView(boolean)Enable library view on the collection page
defaultView(string)post | table | library
hideEntryAuthors(boolean)Hide the poster + date on entry cards. Default: false
isEntity(boolean)Mark the collection as representing a real-world person/entity (drives schema.org Person JSON-LD). Default: false
entityName(string)Display name of the entity (used with isEntity)
entitySameAs(array)schema.org sameAs URLs identifying the entity, e.g. its Wikipedia / Wikidata pages. Max 8
allowedContentTypes(object)Field name to "required" | "optional" | "disabled"
mediaLimits(object)image/video/audio/document limits (1-10)
links(array)Resource links (max 6). Array of { url, name } objects — name is shown in the UI (legacy title also accepted)
tags(array)Up to 8 tags. Each value is an existing tag id OR a tag name — unknown names are created automatically.
curl -X POST https://uutter.com/api/v1/collections \
  -H "Authorization: Bearer uut_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "My Archive", "url": "my-archive"}'

Get Collection

GET/collections/{collectionId}

Returns full collection settings including field configuration, media limits, and feature flags.

Update Collection

PUT/collections/{collectionId}

Partial update — only provided fields are changed. Owner or admin required.

name(string)
description(string | null)7-159 characters; null to clear
theme(string | null)
isPrivate(boolean)
collaborationType(string)
shouldAutoTranscribe(boolean)
enableContentAnalytics(boolean)
hasTableView(boolean)
hasLibraryView(boolean)
defaultView(string)post | table | library — must point at an enabled view
hideEntryAuthors(boolean)Hide the poster + date on entry cards
isEntity(boolean)Mark the collection as representing a real-world person/entity
entityName(string | null)Display name of the entity; null to clear
entitySameAs(array)schema.org sameAs URLs (Wikipedia / Wikidata, etc.). Max 8; empty array clears
avatarUrl(string | null)URL of the collection avatar image; null to clear. Prefer avatarFile to upload an image.
coverImageUrl(string | null)URL of the collection cover image; null to clear. Prefer coverFile to upload an image.
avatarFile(string | null)Avatar image file name (jpg/jpeg/png/gif/webp) to upload. The response returns an uploads array with a signed PUT URL — PUT the bytes to it with the returned Content-Type. null clears.
coverFile(string | null)Cover image file name to upload (same flow as avatarFile). null clears.
tags(array)Full replacement of tags. Max 8. Each value is an existing tag id OR a tag name — unknown names are created. Pass an empty array to clear.
links(array | null)Resource links (max 6, null to clear). Array of { url, name } objects (legacy title also accepted)
allowedContentTypes(object)Merges with existing (partial update)

Entries

List Entries

GET/collections/{collectionId}/entriesPermission: get_entry

Paginated list of entries with metadata, people, media, and engagement.

page(number)Default: 1
limit(number)Default: 20, max: 100

The id in each entry is the collection_entries ID (matches the web URL). Use this for GET/PUT single entry.

Get Single Entry

GET/collections/{collectionId}/entries/{entryId}Permission: get_entry

Full entry including editorText, media with transcripts, people, and engagement. The entryId is the collection_entries ID from the list.

Create Entry (Text Only)

POST/collections/{collectionId}/entriesPermission: create_entry

Send a JSON body with text fields. Creates and publishes immediately.

introduction(string)
description(string)
title(string)
editorText(string)
date(object){ day, month, year }
location(string)
contentCategory(string)interview, lecture, speech, etc.
medium(string)radio, television, podcast, etc.
sourceName(string)
sourceUrl(string)
contextLabel(string)
people(array)[{ name, role }] — speaker, host, guest, etc.
curl -X POST https://uutter.com/api/v1/collections/{id}/entries \
  -H "Authorization: Bearer uut_..." \
  -H "Content-Type: application/json" \
  -d '{
    "introduction": "Alan Watts on consciousness",
    "contentCategory": "lecture",
    "medium": "radio",
    "people": [{"name": "Alan Watts", "role": "speaker"}]
  }'

Create Entry (With Media)

Two-step flow: create draft with file names, upload files, then publish.

Step 1: Include a files array with file names. Returns upload URLs.

// Request
{"introduction": "...", "files": ["photo.jpg", "audio.mp3"]}

// Response
{
  "data": {
    "id": "entry-id",
    "status": "draft",
    "uploads": [
      {
        "mediaId": "uuid",
        "fileName": "photo.jpg",
        "mediaType": "IMAGE",
        "uploadUrl": "https://...signed-url...",
        "method": "PUT",
        "headers": { "Content-Type": "image/jpeg" }
      }
    ]
  }
}

Step 2: Upload each file to its uploadUrl using the specified method and headers.

Step 3: Publish the draft:

{"entryId": "entry-id-from-step-1"}

Supported file types: jpg, png, gif, webp, mp3, wav, ogg, m4a, flac, mp4, mov, webm, pdf, epub. Videos return a TUS upload URL.

Update Entry

PUT/collections/{collectionId}/entries/{entryId}Permission: update_entry

Partial update. Set a field to null to clear it. People array replaces all existing people.

POST/collections/{collectionId}/searchPermission: search

Hybrid semantic + full-text search across transcripts and documents. Uses OpenAI embeddings with PostgreSQL pgvector. 60 searches/hour limit.

query(string)requiredSearch text
type(string)all | transcripts | documents (default: all)
page(number)Default: 1
curl -X POST https://uutter.com/api/v1/collections/{id}/search \
  -H "Authorization: Bearer uut_..." \
  -H "Content-Type: application/json" \
  -d '{"query": "psychedelics and consciousness"}'

Returns ranked results with text snippets, entry context, and score. Cached queries are faster on repeat searches.

Media

GET/media/{mediaId}Permission: get_entry

Check processing status of a media file. Useful for polling video transcoding progress.

{
  "data": {
    "mediaId": "uuid",
    "status": "linked | transcoding | uploading | error",
    "mediaType": "VIDEO",
    "fileName": "lecture.mp4",
    "fileSize": 12345678
  }
}

Analytics

Entry Analytics

GET/collections/{collectionId}/entries/{entryId}/analyticsPermission: get_entry_analytics

Detailed NLP analytics per entry: readability scores, sentiment, topics, entities, vocabulary, filler words. Requires content analytics enabled on the collection.

Collection Analytics

GET/collections/{collectionId}/analyticsPermission: get_collection_analytics

Aggregated analytics across all entries: average readability, sentiment distribution, top topics, engagement stats.

MCP (Model Context Protocol)

Uutter supports MCP for LLM agents like Claude Desktop. The MCP server exposes all API endpoints as JSON-RPC tools. Configure it in API Settings.

MCP endpoint: https://uutter.com/api/mcp/v1

Requires OAuth 2.1 + PKCE authentication. Collections must have both API access and MCP access enabled.

Permissions

API access is controlled at three levels: collection toggle, collection allowlist, and per-member grants.

create_entry — Create posts
get_entry — Read posts
update_entry — Modify posts
delete_entry — Remove posts
get_comments — Read comments
create_comment — Add comments
get_transcript — Read transcripts
search — Search transcripts and documents
get_entry_analytics — View entry analytics
get_collection_analytics — View collection analytics

Collection owners inherit all collection-level permissions. Members need explicit grants from the owner/admin via the collaborators page.

Error Handling

Errors return a JSON object with an error field:

{ "error": "Description of what went wrong" }
400 — Bad request (validation error, missing fields)
401 — Unauthorized (invalid or missing API key)
403 — Forbidden (insufficient permissions, API not enabled)
404 — Not found (collection, entry, or media not found)
429 — Rate limit exceeded (includes Retry-After header)
500 — Internal server error