Ascanti Public API v1

A small, stable HTTP API over the Ascanti data model — goals, projects, tasks, weeks, habits, time blocks, the daily Top three. Built for trusted first-party integrations (Cortex, email-brain, internal tooling).

Base URL

https://ascanti.io/api/v1

Authentication

Every request must carry an API key:

X-API-Key: ascanti_<48 hex chars>

Keys issued before October 2026 start with an older prefix; they keep working unchanged. Rotate to an ascanti_ key whenever convenient.

Generate keys in Settings → Integrations → API keys (you confirm with your password; up to 10 keys). Each key is tagged with a consumer label (e.g. cortex, email-brain); revoking one leaves the others working. Keys are stored as SHA-256 hashes — once shown, they cannot be recovered.

Resetting or changing a password keeps keys and webhooks working; the owner is emailed a list of them and can revoke any in Settings → Security → Connected apps. If the owner turned on extra protection after account recovery, a reset pauses them instead: requests answer 403 with {"code": "integration_paused"} and a Retry-After header. Keep the key, wait and retry later; it works again once the owner confirms it is them. Webhook events raised while paused are not sent, so catch up with /whats-new.

User-Agent (recommended)

Send a descriptive User-Agent on every request:

User-Agent: my-integration/1.0 (+https://example.internal/my-integration)

Cloudflare's edge WAF historically blocked unidentified library-default UAs; sending a real one keeps your traffic identifiable in access logs and out of any future automated rules.

CSRF / cookies

v1 is API-key-only — cookies and CSRF tokens are not used. The router ignores any Cookie header.

Rate limits

Every response includes:

X-RateLimit-Limit:     60
X-RateLimit-Remaining: 42
X-RateLimit-Reset:     37    # seconds until the window resets

Exceeding any limit returns 429 with a Retry-After header (in seconds). Wait at least that long before retrying.

Endpoints — Reads

GET/goals

List all goals for the authenticated user.

GET/projects

List all projects.

GET/tasks?status=&goal_id=&project_id=&due_before=&limit=&offset=

List tasks with filters. Returns {tasks: [...], total: N}. status is open (default), completed, or all.

GET/tasks/:id

Single task. :id is the local tt-... PK or the task's legacy task id (todoist_id: a local_t-... id, or a numeric id on older imported tasks).

GET/inbox?status=&limit=&offset=

Tasks with no project_id — the triage bucket. Same {tasks, total} shape as /tasks.

GET/whats-new?since=<iso>

Goals, projects, and tasks modified since the given ISO 8601 timestamp. Response: {since, now, goals, projects, tasks}. Poll on a cadence and advance since to now from the response.

GET/weeks/current

Current week (theme, top priorities, risks).

GET/weeks/:weekId

Specific week, e.g. 2026-W17.

GET/daily-focus?date=YYYY-MM-DD&weekId=...

The day's Top three items (stored as daily focus). Returns 404 when no focus exists for the date — distinguishes "nothing planned" from "planned but empty".

GET/habits

List habits.

GET/commitments?weekId=YYYY-Www

List weekly commitments. weekId defaults to the current week.

GET/timeblocks?date=YYYY-MM-DD

List time blocks for a date (date required).

Endpoints — Writes

POST/goals

Create a goal. Body: {title, domain?, timeframe?, why?, successDefinition?}.

PATCH/goals/:id

Partial update. Any subset of title, domain, timeframe, why, successDefinition/success_definition, status, category, starred, progressMethod. Use /goal-progress for percent updates.

POST/projects

Create a project. Body: {name (required), description?, area_id?, goal_id?, color?, status?}.

PATCH/projects/:id

Partial update. Any subset of name, description, status, color, goal_id (null to unlink), area_id (null to unlink).

POST/tasks

Create a task. Body: {content (required), description?, goal_id?, project_id?, due_at? OR due_string?, priority?, labels?}. priority accepts 1..4 or "P0".."P3"; due_string is parsed by chrono-node ("tomorrow 9am", "friday").

PATCH/tasks/:id

Partial update. Any subset of content, description, project_id, goal_id, due_at OR due_string, priority, labels.

POST/tasks/:id/complete

Mark a task done. Returns {ok: true}.

POST/goal-progress

Update goal progress and/or log evidence. Body: {goal_id (required), value? (0–100 absolute), delta? (signed), evidence_url?, note?}. At least one of value/delta/evidence_url/note is required.

Webhooks

Configure outbound webhooks in Settings → Integrations → Webhooks. Each webhook subscribes to one or more event names; deliveries are HMAC-signed and retry on failure.

Event names

Delivery body

{
  "event": "task.created",
  "delivered_at": "2026-04-25T17:53:06Z",
  "data": { /* normalized v1 entity payload */ }
}

Signature header

X-Ascanti-Signature: t=<unix-seconds>,v1=<hex-hmac-sha256>

The signed payload is ${unix}.${json_body}, HMAC-SHA256'd with your webhook's signing secret (returned once at registration). Reject deliveries where the signature doesn't verify or t is more than ±5 minutes from your server clock.

Delivery semantics

Errors

StatusMeaning
400Malformed request — missing field, bad JSON, bad date, mutually exclusive fields together
401Missing or invalid X-API-Key
404Resource not found OR not owned by this key (intentionally indistinguishable)
409Conflict — e.g. duplicate consumer label on key generation
429Rate limit exceeded (per-key, per-tenant, or per-IP). Honour Retry-After
500Server error — safe to retry with backoff

All errors share the shape {"error": "human-readable message"}. The only 403 on v1 is {"code": "integration_paused"} (the account is paused after an account recovery: keep the key and retry after Retry-After). Otherwise either the key is valid and the resource is accessible, or it isn't.

Quick example

curl -sfS \
  -H "X-API-Key: $ASCANTI_API_KEY" \
  -H "User-Agent: my-integration/0.1" \
  https://ascanti.io/api/v1/goals
Need the full reference, including the Python client and end-to-end flows? The detailed contract lives in docs/api.md and docs/INTEGRATION_GUIDE.md in the Ascanti repo.