API

Read your data over the HTTP API

One endpoint reads every part of a project's data. Point an agent or a script at it and pull briefs, pains, and source posts as JSON.

Last updated July 3, 2026

On this page

The dist0 HTTP API lets an agent or a script read a project's data as JSON — the same briefs, pains, and source posts you see in your daily email and Slack. There is one endpoint to call and one endpoint to list what you can ask for.

Base URL

https://dist0.com

Authenticate

Every request carries your API key as a bearer token in the Authorization header:

Authorization: Bearer <your-key>

You make a key in the dashboard — see Authentication. A request with no key, or a key the server doesn't recognize, comes back as 403 forbidden.

The one endpoint: POST /api/v1/invoke

You name a capability (e.g. pains.list), pass its input, and get that capability's data back. Adding capabilities never changes how you call — the shape of the request is always the same.

Request body

FieldTypeRequiredWhat it does
capabilitystringyesThe capability name, e.g. briefs.list.
inputobjectnoThe capability's input. Defaults to {}.
projectstringnoA substring of the project's URL to select it (dist0.com matches https://dist0.com). Defaults to your most recent project.
renderstringnoSet to "text" to also get a plain-text rendering of the data (only for capabilities that have one).

Response

{ "data": { /* the capability's data */ } }

With "render": "text", the response also carries a text field:

{ "data": { /* ... */ }, "text": "• Slow onboarding — 12 posts\n• ..." }

One example call

curl -s https://dist0.com/api/v1/invoke \
  -H "Authorization: Bearer $DIST0_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "capability": "pains.list",
    "input": { "type": "pain", "sort": "posts", "pageSize": 5 }
  }'

Projects: which one you're reading

Most capabilities are project-scoped — they read one project. Pick the project with the project selector; leave it out and the API uses your most recent project.

If your key was tied to one project when you made it, it always reads that project — the project selector is ignored. Use a key with no project set to switch between projects with the selector.

{ "capability": "briefs.list", "project": "acme.com", "input": { "limit": 1 } }

A few capabilities are user-scoped and span your whole account (project.list, runs.get); for those the project field is ignored.

Errors

Errors come back as a typed envelope with a matching HTTP status:

{ "error": { "code": "not_found", "message": "pain theme not found" } }
codeStatusWhen
invalid400Missing capability, malformed body, or input fails its schema.
forbidden403Bad/absent token, or the project isn't yours.
not_found404Unknown capability, or the thing you asked for doesn't exist.
limit409A usage or spend limit was hit.

Discover capabilities: GET /api/v1/capabilities

Lists every capability — reads and writes — with its kind and JSON Schema, enough for an agent or a CLI to build its own tool list without hard-coding anything.

curl -s https://dist0.com/api/v1/capabilities \
  -H "Authorization: Bearer $DIST0_TOKEN"
{
  "capabilities": [
    {
      "name": "pains.list",
      "kind": "read",
      "scope": "project",
      "agentInvokable": true,
      "description": "The project's buyer pain themes or competitors, chosen by `type` ('pain' or 'competitor'), ranked by recent activity or post count.",
      "inputSchema": {
        "type": "object",
        "required": ["type"],
        "properties": {
          "type": { "type": "string", "enum": ["pain", "competitor"] },
          "page": { "type": "integer", "minimum": 1 },
          "pageSize": { "type": "integer", "minimum": 1, "maximum": 100 },
          "sort": { "type": "string", "enum": ["recent", "posts"] }
        }
      }
    }
  ]
}

Capability reference

Every capability below is a value for capability on POST /api/v1/invoke. The "Input" is what goes in the input field.

project.memory

What dist0 knows about the project: product, ideal customer, watched subreddits, competitors, offerings, and filter rules.

  • Scope: project · supports "render": "text"
  • Input: { "field"?: "product" | "icp" | "subreddits" | "competitors" | "offerings" | "filter_rules" } — omit field for everything; pass one to narrow it.
{ "capability": "project.memory", "input": {} }
{
  "data": {
    "memory": {
      "id": "prj_1a2b",
      "name": "Acme",
      "url": "https://acme.com",
      "category": "project management",
      "description": "Acme is a lightweight task tracker for small teams.",
      "idealCustomer": "Founders and ops leads at 2–20 person startups.",
      "competitors": [
        { "name": "Linear", "url": "https://linear.app", "description": "Issue tracking for software teams." }
      ],
      "offerings": [
        { "name": "Free plan", "description": "Up to 3 projects." }
      ],
      "subreddits": [
        { "name": "r/startups", "description": "Startup founders and operators." }
      ],
      "filterRules": ["Skip posts about enterprise sales"],
      "memoryStatus": "ready",
      "memoryRefreshRequestedAt": null
    },
    "field": null
  }
}

project.list

Every project on your account.

  • Scope: user · supports "render": "text"
  • Input: {}
{ "capability": "project.list", "input": {} }
{
  "data": {
    "projects": [
      { "id": "prj_1a2b", "name": "Acme", "url": "https://acme.com", "memoryStatus": "ready" }
    ]
  }
}

briefs.list

The project's most recent market briefs, newest first. Each brief is a set of sections (content, offers) holding numbered signal items.

  • Scope: project
  • Input: { "limit"?: number } — 1–5, default 3.
{ "capability": "briefs.list", "input": { "limit": 1 } }
{
  "data": {
    "briefs": [
      {
        "window": "Jul 1–2",
        "brief": {
          "sections": [
            {
              "kind": "content",
              "title": "Content",
              "items": [
                {
                  "origin": "pain",
                  "surfaced": {
                    "summary": "Teams keep losing track of tasks across tools.",
                    "quote": { "text": "I have tasks in three apps and forget half of them.", "who": "u/founder_kate", "sub": "r/startups" }
                  },
                  "move": { "title": "Write: one place for every task", "body": "A guide on consolidating task tracking." },
                  "sources": [
                    { "title": "How do you keep track of everything?", "url": "https://www.reddit.com/r/startups/comments/abc123/...", "sub": "r/startups" }
                  ]
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

briefs.get

One signal from the recent briefs in full, by its sequence number.

  • Scope: project · supports "render": "text"
  • Input: { "n": number } — the signal's 1-based number.
{ "capability": "briefs.get", "input": { "n": 3 } }
{
  "data": {
    "n": 3,
    "when": "Jul 1–2",
    "kind": "content",
    "item": {
      "origin": "pain",
      "surfaced": { "summary": "Teams keep losing track of tasks across tools." },
      "move": { "title": "Write: one place for every task", "body": "A guide on consolidating task tracking." },
      "sources": [
        { "title": "How do you keep track of everything?", "url": "https://www.reddit.com/r/startups/comments/abc123/...", "sub": "r/startups" }
      ]
    }
  }
}

pains.list

The project's buyer pain themes or competitors — chosen by type — ranked by recent activity or by post count.

  • Scope: project · supports "render": "text"
  • Input: { "type": "pain" | "competitor", "page"?: number, "pageSize"?: number, "sort"?: "recent" | "posts" }type is required; pageSize 1–100 (default 25), sort default recent.
{ "capability": "pains.list", "input": { "type": "pain", "sort": "posts", "pageSize": 5 } }
{
  "data": {
    "rows": [
      { "id": "sov_77", "title": "Slow onboarding", "summary": "New users churn before finishing setup.", "postCount": 12, "lastUpdatedAt": "2026-07-01T09:14:00.000Z" }
    ],
    "totalCount": 8,
    "totalPostCount": 41,
    "page": 1,
    "pageCount": 2,
    "rangeStart": 1,
    "rangeEnd": 5,
    "pageSize": 5,
    "sort": "posts"
  }
}

pains.get

One pain theme in full: its quotes, authors, and source posts, newest first.

  • Scope: project
  • Input: { "sovId": string } — a pain theme id from pains.list.
{ "capability": "pains.get", "input": { "sovId": "sov_77" } }
{
  "data": {
    "id": "sov_77",
    "title": "Slow onboarding",
    "summary": "New users churn before finishing setup.",
    "postCount": 12,
    "firstSeenAt": "2026-06-14T00:00:00.000Z",
    "lastSeenAt": "2026-07-01T09:14:00.000Z",
    "pains": [
      {
        "signalId": "sig_501",
        "quote": "It took me 40 minutes just to connect my first tool.",
        "author": "u/founder_kate",
        "postUrl": "https://www.reddit.com/r/startups/comments/abc123/...",
        "postTitle": "Onboarding is exhausting",
        "subreddit": "r/startups",
        "seenAt": "2026-07-01T09:14:00.000Z",
        "moves": {
          "content": { "title": "Write: cut setup to 5 minutes", "body": "A walkthrough of a faster first-run." }
        }
      }
    ]
  }
}

pains.sources

Source grounding for one or more pain themes: each pain plus its Reddit source posts and full threads. Omit sovIds to instead get the project's pains ranked by post count, so you can pick which to ground.

  • Scope: project
  • Input: { "sovIds"?: string[] }

List mode — no sovIds:

{ "capability": "pains.sources", "input": {} }
{
  "data": {
    "mode": "list",
    "pains": [
      { "id": "sov_77", "title": "Slow onboarding", "summary": "New users churn before finishing setup.", "painCount": 12 }
    ]
  }
}

Sources mode — with sovIds:

{ "capability": "pains.sources", "input": { "sovIds": ["sov_77"] } }
{
  "data": {
    "mode": "sources",
    "sovs": [
      {
        "id": "sov_77",
        "title": "Slow onboarding",
        "summary": "New users churn before finishing setup.",
        "postCount": 12,
        "firstSeenAt": "2026-06-14T00:00:00.000Z",
        "lastSeenAt": "2026-07-01T09:14:00.000Z",
        "pains": [
          { "signalId": "sig_501", "quote": "It took me 40 minutes just to connect my first tool.", "author": "u/founder_kate", "postUrl": "https://www.reddit.com/r/startups/comments/abc123/...", "postTitle": "Onboarding is exhausting", "subreddit": "r/startups", "seenAt": "2026-07-01T09:14:00.000Z", "moves": {} }
        ]
      }
    ],
    "threads": [
      {
        "id": "post_abc123",
        "permalink": "/r/startups/comments/abc123/onboarding_is_exhausting/",
        "title": "Onboarding is exhausting",
        "selftext": "I have tasks in three apps...",
        "author": "u/founder_kate",
        "score": 84,
        "postCreatedAt": "2026-07-01T08:00:00.000Z",
        "content": "# Onboarding is exhausting\n\nI have tasks in three apps...\n\n## Comments\n...",
        "subreddit": "r/startups"
      }
    ]
  }
}

posts.get

A Reddit post and its full thread, plus the signals the pipeline extracted from it. Look it up by permalink (an absolute URL or a bare path) or by raw post id.

  • Scope: project · supports "render": "text"
  • Input: { "permalink"?: string, "id"?: string } — one of the two is required.
{ "capability": "posts.get", "input": { "permalink": "https://www.reddit.com/r/startups/comments/abc123/onboarding_is_exhausting/" } }
{
  "data": {
    "post": {
      "id": "post_abc123",
      "permalink": "/r/startups/comments/abc123/onboarding_is_exhausting/",
      "title": "Onboarding is exhausting",
      "selftext": "I have tasks in three apps...",
      "author": "u/founder_kate",
      "score": 84,
      "postCreatedAt": "2026-07-01T08:00:00.000Z",
      "content": "# Onboarding is exhausting\n\n...\n\n## Comments\n...",
      "subreddit": "r/startups"
    },
    "signals": [
      { "kind": "pain", "quote": "It took me 40 minutes just to connect my first tool.", "author": "u/founder_kate", "sovId": "sov_77" }
    ]
  }
}

runs.get

One digest run's status, window, and signal counts, by run id.

  • Scope: user
  • Input: { "runId": string }
{ "capability": "runs.get", "input": { "runId": "run_555" } }
{
  "data": {
    "id": "run_555",
    "projectId": "prj_1a2b",
    "projectName": "Acme",
    "windowStart": "2026-07-01T00:00:00.000Z",
    "windowEnd": "2026-07-02T00:00:00.000Z",
    "status": "complete",
    "signalCounts": { "pain": 9, "competitor": 2, "content": 4 },
    "postsAnalyzed": 137
  }
}

competitors.list

Competitors the pipeline flagged — one entry per product — each with its name, link, source posts, and how often it recurred, ranked most-mentioned first. The entry's name is the product; url carries the competitor's link when the product is in your project memory, and is null otherwise.

  • Scope: project · supports "render": "text"
  • Input: {}
{ "capability": "competitors.list", "input": {} }
{
  "data": {
    "competitors": [
      {
        "name": "Linear",
        "url": "https://linear.app",
        "mentions": 3,
        "sources": [
          { "title": "What do you use to track issues?", "url": "https://www.reddit.com/r/startups/comments/def456/...", "quote": "We moved to Linear and never looked back." }
        ]
      }
    ]
  }
}

Write capabilities

Write capabilities change a project's data. They call the same POST /api/v1/invoke endpoint and enforce the same ownership check as reads — you can only write to a project on your own account.

memory.edit

Apply structured edits to the active project's memory: add or remove filter rules, competitors, offerings, or watched subreddits, and set the product or ideal customer. Pass a list of edit operations; they apply in order, and either all of them commit or none do.

  • Scope: project · supports "render": "text"
  • Input: { "edits": Edit[] } — 1–64 operations.

Each edit is one of:

opFieldsWhat it does
set_producttextReplace the product description.
set_icptextReplace the ideal-customer description.
add_competitorname, url, descriptionAdd a tracked competitor.
remove_competitornameRemove a tracked competitor by name.
add_offeringname, descriptionAdd an offering.
remove_offeringnameRemove an offering by name.
add_subredditname, descriptionWatch a subreddit (up to 5).
remove_subredditnameStop watching a subreddit.
add_filter_ruleruleAdd a rule that steers what counts as a signal.
remove_filter_ruleruleRemove a filter rule.
{
  "capability": "memory.edit",
  "input": {
    "edits": [
      { "op": "add_filter_rule", "rule": "Don't treat note-taking apps as competitors" },
      { "op": "remove_competitor", "name": "Obsidian" }
    ]
  }
}
{
  "data": {
    "applied": [
      "Added filter rule: Don't treat note-taking apps as competitors",
      "Removed competitor: Obsidian"
    ]
  }
}

A bad edit comes back as the typed error envelope — invalid for a malformed edit or one that changes nothing, limit if it would push you past the 5-subreddit cap.

A common flow: ground on a pain's source posts

To read a pain theme and the full Reddit threads behind it in one call, use pains.sources with the theme's id:

# 1. Find the pain you care about.
curl -s https://dist0.com/api/v1/invoke \
  -H "Authorization: Bearer $DIST0_TOKEN" -H "Content-Type: application/json" \
  -d '{ "capability": "pains.list", "input": { "type": "pain", "sort": "posts" } }'

# 2. Pull that pain plus its source posts and full threads.
curl -s https://dist0.com/api/v1/invoke \
  -H "Authorization: Bearer $DIST0_TOKEN" -H "Content-Type: application/json" \
  -d '{ "capability": "pains.sources", "input": { "sovIds": ["sov_77"] } }'

The sovs array carries each pain and its signals; the threads array carries the full rendered Reddit threads (content) for every source post, deduped by URL.