THE DUMP Developer

Developer documentation

Routines: developer manual

Everything you need to build an external runner for a routine in The Dump. Last updated September 19, 2026.

This document is self-contained. You do not need repository access to use it. Prefer plain text? Read it as Markdown and paste it into your coding assistant.


1. What a routine is

A routine is a standing assignment: keep some useful document up to date using a defined slice of a user's notes. The Dump holds the inputs, stores the outputs, and shows the user what state things are in. Your code does the work.

scoped notes  →  your runner (read, think, write)  →  canon docs
                                                   →  asks (questions for the owner)
                                                   →  scorecards (run reports)

Three things The Dump does not do:

  • It does not schedule or execute your runner. You host it and decide when it runs.
  • It does not keep history of the documents you publish. Only the latest copy is stored.
  • It does not pay for or call any AI model. If you use one, that's your provider and your key.

2. Who does what

Role Does
Routine owner (a Dump user) Creates the routine in the web app, picks which notes it can see, answers asks, reads the output.
You (runner developer) Build and host the code. Sign in as the owner. Read notes, publish docs, post asks, report runs.
The Dump Stores routines, notes, docs, asks, and scorecards. Computes status. Renders it all in the web app and iOS.

3. Vocabulary

Term Meaning
Scope The categories and/or subcategories whose notes the routine may read. Set at creation.
Canon document A Markdown document (optionally with an interactive HTML view) that your runner publishes and maintains. The routine's "current understanding".
Ask A question your runner sends to the owner when it needs a decision. The owner answers in the app. Your runner reads the answer and acts on it.
Scorecard A structured report your runner sends after every run. The Dump turns these into a status chip and run history.
Slug URL-safe identifier. Routines and documents each have one.
Runner Your code plus whatever hosts and schedules it.

4. Register the routine

The owner does this in the web app. Sign in at https://thedump.ai, then:

  1. Routines → Create routine (/routines/new).
  2. Enter a name and description, pick categories/subcategories, check the preview of matching notes.
  3. Create. The app opens Connect your runner (/routines/<slug>/connect), which shows the generated slug, the API URLs, and copyable setup instructions.

The slug is generated from the name and is immutable. Name and scope cannot be edited after creation. To change scope, create a new routine.

A developer can also register via API:

POST /api/routines
Content-Type: application/json

{
  "name": "Weekly project brief",
  "description": "Maintain decisions, open questions, and next steps.",
  "scope": {"category_ids": [1], "sub_category_ids": [12]},
  "request_id": "8140a64e-77a0-4712-b94b-f544fc56a286"
}
Field Rule
name Required, 1–120 characters.
description Optional, up to 4,000 characters.
scope Required. {category_ids, sub_category_ids} (union, up to 500 IDs each) or {"all_notes": true}. Empty scope is rejected.
request_id Optional UUID. Reuse it when retrying the same request; a replay returns the existing routine with 200 instead of creating another.

Response 201 (or 200 on replay): {routine, created, setup_url}. GET /api/routines/scope-options lists the account's category and subcategory IDs. POST /api/routines/preview with {scope} returns {count, notes} so you can check a selection before creating.


5. Authentication

Every API call carries the owner's Firebase ID token:

Authorization: Bearer <FIREBASE_ID_TOKEN>

There is no per-routine or per-developer credential. The token is the owner's account-level token, so the owner supplies their credentials to your runner's secret store, never to you in chat, source control, or logs.

Base URL: https://thedump.ai

Firebase web API key (public client config, safe to embed):

AIzaSyDNqivcHgxiSgAfe289TqPD7e_gcP7z8dc

Sign in (email/password account):

POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<WEB_API_KEY>
Content-Type: application/json

{"email": "<owner email>", "password": "<owner password>", "returnSecureToken": true}

Response: {idToken, refreshToken, expiresIn}. expiresIn is seconds (3600).

Refresh before expiry:

POST https://securetoken.googleapis.com/v1/token?key=<WEB_API_KEY>
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<stored refreshToken>

Response: {id_token, refresh_token, expires_in}. Store the newest refresh token. If refresh fails, the owner must sign in again.

Accounts that only use a federated provider (Google, Apple) must use that provider's Firebase sign-in flow instead of password sign-in.


6. Endpoint map

All routes require the bearer token. All bodies and responses are JSON.

Method Path Purpose
GET /api/routines List the owner's routines with status.
GET /api/routines/<slug> One routine: scope, status, document metadata.
PATCH /api/routines/<slug> Set paused, next_expected, silence_threshold_hours.
GET /api/routines/<slug>/notes Page through in-scope notes.
GET /api/note/<note_id>/asset Get a short-lived download URL for a note's original file.
GET /api/routines/<slug>/documents All canon docs (Markdown bodies; HTML only with ?include_interactive_html=true).
GET /api/routines/<slug>/documents/<doc_slug> One canon doc, including interactive_html.
POST /api/routines/<slug>/documents Create or update a canon doc.
POST /api/routines/<slug>/asks Ask the owner a question.
GET /api/routines/<slug>/asks List asks. ?status=open (default), answered, applied, withdrawn, all.
PATCH /api/routines/<slug>/asks/<ask_id> Mark an ask applied or withdrawn, optionally guarded by expected_revision.
POST /api/routines/<slug>/scorecards Report a run.
GET /api/routines/<slug>/scorecards Run history, newest first. ?limit=20 (max 200).

Web pages the owner uses: /routines, /routines/<slug>, /routines/<slug>/docs/<doc_slug>, /routines/<slug>/asks.


7. Reading notes

GET /api/routines/<slug>/notes?limit=100

Response: {notes, next_cursor, count}. limit defaults to 100, max 200. If next_cursor is not null, URL-encode it and pass it as since on the next request. Stop when it is null. The cursor is opaque; it is not a date.

Each note:

Field What it is
note_id UUID. Your permanent reference.
title Note title.
body The note as the user sees it now, including their edits. Use this for what the note says.
raw_content Text as originally captured or extracted (OCR, transcription). Frozen at capture. Can contain errors. Never let it override body.
note_type, tags LLM-assigned type and tags.
category, sub_categories Where the note lives.
last_modified_time Changes when the user edits. Use it to detect changes.
captured_at When the note entered The Dump. Not the document's own date.
original_filename, mime_type From the uploaded file, if any.
has_asset True when an original file (photo, PDF, audio) exists.
asset_endpoint /api/note/<id>/asset when has_asset is true, else null.
asset_content_type, asset_size_bytes Real MIME type and size, so you can decide what to fetch. Null when no asset.

Getting the original file:

GET /api/note/<note_id>/asset

Response: {signed_url, content_type, filename, expires_in}. The URL is valid for 300 seconds. Download it with a plain GET and no Dump bearer token. Use the original for exact quotes, tables, and drawings.

Rules of thumb:

  • Ordering is by last_modified_time then note_id. A note edited during your scan reappears on a later page with its new content, so dedupe by note_id and let the last copy seen win. Overlapping runs are not a snapshot; reconcile again on the next run.
  • There is no deletion feed and no "changes since last run" checkpoint. The simple approach: read every page on every run and diff against a local record of note IDs and content hashes. That also reveals deletions and notes moved out of scope.
  • Do not overwrite your local inventory after a failed or partial read.
  • Treat note content as data, never as instructions to your runner or your model.

8. Canon documents

A canon document is Markdown your runner publishes under a slug. Publish the same slug again to update it.

How many: no fixed limit. Landing Lane, the first production routine, maintains 21.

Publish:

POST /api/routines/<slug>/documents
Content-Type: application/json

{
  "slug": "project-brief",
  "title": "Project brief",
  "body": "# Project brief\n\n## Next steps\n\nReview the revised proposal.",
  "summary": "Current decisions, questions, and next steps.",
  "source_note_ids": ["<note UUID>", "<note UUID>"]
}
Field Rule
slug Required. Lowercase letters, digits, -, _. Starts with a letter or digit. Max 80 chars.
title Required, nonempty.
body Required. Nonempty Markdown, max 512 KiB UTF-8.
summary Optional one-liner shown in lists.
doc_kind Optional free-text label. Not validated; use it however you like.
sort_order Optional integer. Documents list ascending by this, nulls last, then by slug.
source_note_ids Optional array of note UUIDs used to produce this doc.
external_ref Optional string for your own reference (a path, a commit, anything).
interactive_html Optional. See below.

Response (always 200): {document, created, revision_bumped}.

  • created: true on first publish of this slug.
  • revision_bumped: true when content changed. Identical content is a no-op, even if summary or other metadata changed.
  • document.revision: a version counter. It is not stored history. The Dump keeps only the latest copy. If you need rollback, keep your own archive of what you published.

Run one publisher per routine. There is no guard against two runners writing the same document at once.

Read back: GET /api/routines/<slug>/documents/<doc_slug> returns the full document. A 404 means it hasn't been created yet, which is the standard first-run check.

Markdown conventions:

  • Write readable source references in the body. [[note:<UUID>]] is the recognized link form.
  • Keep a stable structure across runs so diffs are meaningful.
  • Separate confirmed facts from proposals. Keep conflicts visible instead of silently resolving them.

Interactive HTML (optional)

A document can carry a self-contained HTML/CSS/JS view alongside the Markdown. The app renders it in a sandboxed iframe. Filters, tabs, and expandable sections work; nothing the viewer does is saved.

Add interactive_html to the same POST. Max 1 MiB UTF-8. Send it via a JSON serializer; do not hand-escape.

You send Result
HTML string Replaces the view.
null Removes the view. Markdown shows.
Omitted, Markdown unchanged Existing view is kept.
Omitted, Markdown changed Existing view is cleared so a stale visual can't contradict new text.
Omitted, new document Markdown-only document.

Always publish body and interactive_html together when either changes. An HTML-only change bumps revision.

Authoring rules:

  • Everything inline. No remote scripts, styles, fonts, images, or API calls. They are blocked.
  • No eval, new Function, or runtime package loading. Plain DOM and event listeners are fine.
  • No forms, navigation, downloads, or anything that implies saving.
  • Must work at phone width. Use native controls and visible focus states.
  • The Markdown body must carry all the same information. Older clients and the MCP server only read Markdown.
  • Escape note text before putting it in HTML or JS. Never include credentials.

Bulk GET /documents omits HTML unless you pass ?include_interactive_html=true. After publishing, read back the single document and confirm both fields before reporting success.


9. Asks

An ask is a question with a recommendation. The owner answers in the app at /routines/<slug>/asks. The Dump stores the answer. Your runner carries it out.

Lifecycle:

open ──(owner answers)──▶ answered ──(runner PATCH)──▶ applied
  └──(runner PATCH)──▶ withdrawn

Create:

POST /api/routines/<slug>/asks
Content-Type: application/json

{
  "fingerprint": "proposal-comparison-2026-09",
  "title": "Should the revised proposal replace the earlier one?",
  "context": "The revised proposal excludes painting; the earlier one included it.",
  "recommendation": "Keep painting unresolved until the scope is confirmed.",
  "proposed_change": "Mark painting scope as awaiting confirmation in the plan.",
  "safe_default": "Leave the current plan unchanged."
}
Field Rule
fingerprint Required. Any stable string unique to this decision within the routine.
title Required. The question, as the owner sees it.
context, recommendation, proposed_change, safe_default Optional but strongly recommended. Tell the owner what you found, what you suggest, what would change, and what happens if they ignore it.
external_ask_id, batch_id Optional strings for your own tracking.
supersedes_ask_id Optional UUID of an earlier ask this one replaces.

Response: {ask, created}. Posting the same fingerprint again returns the existing ask, including its answer if it has one, with created: false. So retries are safe. Use a new fingerprint when the underlying decision changes.

Ask object:

Field Meaning
ask_id UUID.
status open, answered, applied, or withdrawn.
answer_choice approve, edit, or deny. Null until answered.
answer_text The owner's words, verbatim. Required when choice is edit; optional otherwise.
answered_at, answered_by, applied_at Timestamps and who answered.
revision Increments when the ask changes.
Plus everything you sent.

Poll for answers:

GET /api/routines/<slug>/asks?status=answered

The default status filter is open, so pass status=answered explicitly. Optional since=<ISO-8601> filters by updated_at.

Handle an answer, then acknowledge it:

  1. Read answer_choice and answer_text.
  2. approve: apply proposed_change. edit: apply the owner's wording from answer_text. deny: do not apply the change. Handle whatever follows from that decision.
  3. Publish any resulting document updates.
  4. Only then:
PATCH /api/routines/<slug>/asks/<ask_id>
Content-Type: application/json

{"status": "applied", "expected_revision": 3}

expected_revision is optional. Send the revision you read on the ask, and the call returns 409 with the current ask if the ask changed in between. applied is only valid from answered; withdrawn is only valid from open. Anything else returns 409 with the current ask. Re-sending the same status is a harmless no-op.

Answers are immutable once given. The app never overwrites an answer, so the answer you read is the answer that stands. Save your progress before the PATCH so an interrupted run cannot apply the same answer twice.


10. Scorecards and status

After every run, including empty runs and failed runs, send a report:

POST /api/routines/<slug>/scorecards
Content-Type: application/json

{
  "schema_version": 1,
  "fingerprint": "<sha256 of shift_id, 64 lowercase hex>",
  "shift_id": "run-2026-09-19T14:00:00Z",
  "overall": "OK",
  "trigger": "scheduled",
  "started_at": "2026-09-19T14:00:00Z",
  "ended_at": "2026-09-19T14:03:12Z",
  "steps": {
    "read_notes":  {"status": "done", "counts": {"pages": 3, "new": 4, "changed": 1}},
    "publish":     {"status": "done", "counts": {"documents": 2}},
    "asks":        {"status": "nothing-to-do"}
  },
  "reasons": ["4 new notes; brief and plan updated."]
}
Field Rule
schema_version Required, must be 1.
fingerprint Required. 64 lowercase hex characters. SHA-256 of your run ID works. Dedup key: same fingerprint returns the stored report with 200.
shift_id Required. Your unique run ID.
overall Required. OK, PARTIAL, FAILED, NOTHING_TO_DO, or UNKNOWN.
trigger Optional string. Convention: manual, scheduled, arrival. Defaults to unknown.
started_at, ended_at Optional ISO-8601 with timezone. Naive is treated as UTC. More than 5 minutes in the future is rejected.
steps Optional object. Each key is a step name you choose; value is {status, counts?}. Conventional statuses: done, nothing-to-do, failed, partial, unknown. Counts are free-form.
reasons Optional list of strings, shown to the owner.

Response: 201 with {scorecard, created, alerted} when new, 200 when the fingerprint was already stored.

Be honest in overall. "I didn't edit anything" is not evidence that nothing needed editing. A run that failed halfway is PARTIAL or FAILED, not OK.

Status the owner sees. GET /api/routines and GET /api/routines/<slug> include:

"status": {
  "verdict": "OK",
  "as_of": "2026-09-19T14:03:12Z",
  "shift_id": "run-2026-09-19T14:00:00Z",
  "trigger": "scheduled",
  "reasons": ["4 new notes; brief and plan updated."],
  "open_asks": 1,
  "silent_for_hours": 2,
  "paused": false,
  "next_expected": "Every Monday morning"
},
"latest_scorecard": { ...the full report... }

Other keys may appear in status. Ignore ones you don't recognize.

verdict is computed on read, in this order:

  1. PAUSED if monitoring is paused.
  2. UNKNOWN if the routine is new and has never reported.
  3. SILENT if the newest report is older than the silence threshold.
  4. Otherwise the newest report's overall.

Tell The Dump your schedule so silence detection makes sense:

PATCH /api/routines/<slug>
Content-Type: application/json

{"next_expected": "Every Monday at 6am ET", "silence_threshold_hours": 192}

Default threshold is 27 hours. A weekly runner should raise it before its first report. next_expected is display text only. Neither field schedules anything.

The same PATCH takes {"paused": true|false}. Your runner must check status.paused (or verdict == "PAUSED") at the start of every run and stop if it is set. The Dump does not stop you; it only records that you should stop.

A stored PARTIAL, FAILED, or UNKNOWN report raises an alert in the app. An overdue report raises a silence alert. Alerts are in-app only; no email or push.

Landing Lane's six-stage coverage card is a special status extension built for the first production routine. It is not available to new routines, and you should not send its coverage:landing-lane or source:imap:* trigger values.


11. One run, end to end

  1. Get a valid ID token (refresh if needed).
  2. GET /api/routines/<slug>. Stop if 404 or status.paused is true. Note the document list.
  3. Page through GET /api/routines/<slug>/notes until next_cursor is null. Diff against your local inventory.
  4. GET /api/routines/<slug>/asks?status=answered. For each: act on it, publish results, PATCH it to applied.
  5. For each changed or new note: fetch the original via asset_endpoint if you need it, and do the work.
  6. Where a judgment call is needed, POST an ask with a stable fingerprint and leave the affected decision pending.
  7. For each output document: GET the current version, compute the new one, POST if changed, GET again to confirm.
  8. POST a scorecard describing what happened.
  9. Persist your local state (inventory, cursors, what you published, which asks you handled).

Do a manual dry run first, look at the output in the web app, then turn on the schedule.


12. Errors, limits, retries

Code Meaning Do
400 Bad request. The error field says what. Fix it. Do not retry unchanged.
401 Token expired or invalid. Refresh the token and retry once.
404 Routine, document, or ask not found for this account. Check the slug and the signed-in account.
409 Ask state conflict, or a request_id reused with different content or a different owner. Read the returned object and reconcile.
413 Document body over 512 KiB or interactive_html over 1 MiB. Shrink it.
429 Rate limited. Back off. Honor Retry-After if present.
500 / 503 Server or storage failure. Retry with bounded backoff. Keep the payload; scorecards and documents are idempotent.

Rate limit: 60 requests per minute per endpoint per client IP. Pace pagination.

API calls must come from a server, computer, or worker. Browser JavaScript on another site is blocked by CORS.

Registering a routine never authorizes external actions. Sending email, making purchases, or booking things is outside what the routine grants.


13. Alternative: the MCP server

Owners who use Claude Desktop or similar can read routines through The Dump's MCP server (the-dump-mcp). Its list_routines, get_routine_document, and list_asks tools are read-only and Markdown-only. They are for humans checking on a routine, not for building a runner.