Docs
API Reference
REST API for managing your Abstria goal data (YAP / MAP / WAP / DAP). List, bulk-create, export, and delete your plans programmatically.
Updated June 16, 2026
The Abstria REST API lets you read and write your goal data programmatically. It's designed for seeding your own data, backups, and integrations with external tools (CLI, ChatGPT, Zapier, and so on).
Overview
Every endpoint is relative to the following base URL.
https://abstria.app/api/v1
Responses are JSON (Content-Type: application/json) unless noted. Only the export endpoint may return a ZIP archive.
Data model
An Abstria goal is a four-level tree.
| Code | Name | Granularity |
|---|---|---|
| YAP | Year Action Plan | A yearly goal |
| MAP | Month Action Plan | A monthly goal (belongs to a YAP) |
| WAP | Week Action Plan | A weekly goal (belongs to a MAP) |
| DAP | Day Action Plan (task) | A daily task (linked to a WAP) |
YAP → MAP → WAP form a strict tree with NOT NULL parents. DAPs (tasks) live in their own table and can be linked to multiple WAPs through task_wap_links.
Authentication
Every endpoint requires authentication. There are two ways to authenticate.
API key (for external tools)
Pass your API key as a Bearer token in the Authorization header.
Authorization: Bearer abs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API keys are created, rotated, and revoked in the "API" section of Settings (Settings → API, Pro plan only). A new key is shown only once at creation time; the server stores only a SHA-256 hash. If you lose a key, create a new one.
- Each user can have up to 10 active keys.
- Expiry can be set to never, 30 days, 90 days, or 365 days.
Cookie session
When calling from the browser with fetch(..., { credentials: "include" }), your logged-in session cookie is used automatically. From external tools, use an API key instead.
Authentication errors
| Status | error | Meaning |
|---|---|---|
| 401 | unauthorized | Session is invalid or you're not logged in |
| 401 | invalid_api_key_format | Token isn't in abs_live_ format |
| 401 | invalid_api_key | Key doesn't exist or was revoked |
| 401 | expired_api_key | Key has expired |
Rate limits
Requests authenticated with an API key are subject to a per-key fixed-window limit (cookie sessions are exempt).
| Item | Value |
|---|---|
| Limit | 60 requests / 60 seconds (per key) |
| Method | Fixed window |
| On exceed | 429 { "error": "rate_limited" } |
When you exceed the limit you get a 429 with a Retry-After header containing the number of seconds to wait. Wait that long before retrying.
Errors
All error responses are JSON with an error field holding the error code.
{ "error": "validation_failed", "details": { "...": "..." } }
| Status | Common error | Meaning |
|---|---|---|
| 400 | invalid_json, invalid_id | Malformed request |
| 401 | (see Authentication) | Authentication failed |
| 404 | not_found | Target doesn't exist or isn't yours |
| 422 | validation_failed | Input validation failed (details has specifics) |
| 422 | constraint_violation | DB constraint violated (limit exceeded, duplicate, etc.) |
| 429 | rate_limited | Rate limit exceeded |
| 500 | query_failed, rpc_failed, export_failed | Server-side error |
Write operations (create, delete) roll back entirely on error, so you never end up in a half-applied state (all-or-nothing).
List — GET /api/v1/yaps
Returns the authenticated user's YAPs. Use it to confirm ids before deleting, or as a lightweight export.
| Query | Value | Description |
|---|---|---|
include | tree | Nest the MAPs / WAPs / DAPs underneath |
# Flat (YAPs only)
curl -sS "https://abstria.app/api/v1/yaps" \
-H "Authorization: Bearer $ABSTRIA_API_KEY"
{
"yaps": [
{
"id": "uuid",
"title": "2026 goals",
"description": null,
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"status": "in_progress",
"sort_order": 0,
"created_at": "2026-01-01T00:00:00.000Z"
}
]
}
With ?include=tree, each YAP nests maps → waps → daps. Each level is sorted by sort_order then created_at ascending. A task linked to multiple WAPs appears under each of those WAPs.
Stats — GET /api/v1/stats
Returns counts of all of the authenticated user's entities. Useful for verifying state after a delete.
curl -sS "https://abstria.app/api/v1/stats" \
-H "Authorization: Bearer $ABSTRIA_API_KEY"
{
"ok": true,
"counts": {
"yaps": 1,
"maps": 12,
"waps": 48,
"tasks": 120,
"tasks_unlinked": 8
}
}
maps/wapscan't be orphaned because their parents areNOT NULL+ cascade.tasks_unlinkedis the number of tasks not linked to any WAP (chores, or tasks left after unlinking). They aren't visible from a YAP-rooted listing, so check them here.
Export — GET /api/v1/export
Returns all of the authenticated user's data (YAPs / MAPs / WAPs / tasks / links) as a download.
| Query | Value | Description |
|---|---|---|
format | json (default) | A single restorable JSON, compatible with the imports restore format |
format | csv | A ZIP archive of per-table CSV files |
# JSON (restorable via imports)
curl -sS "https://abstria.app/api/v1/export?format=json" \
-H "Authorization: Bearer $ABSTRIA_API_KEY" \
-o abstria-export.json
# CSV (ZIP)
curl -sS "https://abstria.app/api/v1/export?format=csv" \
-H "Authorization: Bearer $ABSTRIA_API_KEY" \
-o abstria-export.zip
The response comes with Content-Disposition: attachment. The format=json output can be fed straight back into POST /api/v1/imports as a restore payload.
Bulk create — POST /api/v1/imports
Creates nested JSON in a single request. The mode is auto-detected from the shape of the request body.
- Tree format — create from scratch by nesting
maps→waps→daps. - Restore format — pass an export JSON as-is to restore from a backup (flat arrays that include
id).
Here's a tree-format example.
{
"yaps": [
{
"title": "2026 goals",
"start_date": "2026-01-01",
"end_date": "2026-12-31",
"status": "pending",
"maps": [
{
"title": "May",
"start_date": "2026-05-01",
"end_date": "2026-05-31",
"waps": [
{
"title": "Week 3",
"start_date": "2026-05-11",
"end_date": "2026-05-17",
"is_important": true,
"daps": [
{
"title": "Draft the deck",
"scheduled_date": "2026-05-15",
"start_time": "09:00",
"end_time": "11:00"
},
{ "title": "Review" }
]
}
]
}
]
}
]
}
Fields
| Level | Required | Optional |
|---|---|---|
| yap | title, start_date | end_date (defaults to Dec 31 of start_date's year), description, sort_order, status, maps |
| map | title, start_date, end_date | description, sort_order, status, waps |
| wap | title, start_date, end_date | description, sort_order, status, is_urgent, is_important, daps |
| dap (task) | title | description, status, scheduled_date, start_time, end_time, sort_order, due_date, recurrence_*, is_urgent, is_important |
- Dates are
YYYY-MM-DD; times areHH:MMorHH:MM:SSstrings only. statusis one ofpending/in_progress/done.- Any
user_idin the payload is ignored; the user_id resolved from auth is always used. - Omitting
sort_orderauto-assigns it (MAP uses the month index 0–11 ofstart_date; WAP / DAP / YAP use their array order). An explicit value takes precedence.
Limits
- Up to 10 YAPs per request.
- Up to 12 MAPs per YAP, 8 WAPs per MAP, 200 DAPs per WAP.
- MAPs with a duplicate
(yap_id, start_date)within the same YAP are rejected. - Per-plan ownership caps (Free: 3, Pro: capped) apply separately.
Response
{
"ok": true,
"mode": "tree",
"result": {
"yap_ids": ["uuid"],
"map_ids": ["uuid"],
"wap_ids": ["uuid"],
"dap_ids": ["uuid", "uuid"],
"counts": { "yaps": 1, "maps": 1, "waps": 1, "daps": 2 }
}
}
Returns 201 Created on success. The returned yap_ids can be passed directly to bulk delete.
Delete a YAP — DELETE /api/v1/yaps/{id}
Deletes a single YAP. Its MAPs / WAPs cascade away, and DAP tasks that belong only to that subtree are deleted too (tasks linked to other APs are merely unlinked and survive).
| Query | Value | Description |
|---|---|---|
dry_run | true | Return the impact counts without deleting (preview) |
dry-run (preview)
Because this is destructive, you can check the impact before deleting.
curl -X DELETE "https://abstria.app/api/v1/yaps/<id>?dry_run=true" \
-H "Authorization: Bearer $ABSTRIA_API_KEY"
{
"ok": true,
"dry_run": true,
"yap": { "id": "...", "title": "..." },
"impact": { "maps": 12, "waps": 72, "tasks_to_delete": 0, "tasks_to_unlink": 0 }
}
tasks_to_delete— tasks linked only to this YAP's subtree (will be deleted)tasks_to_unlink— tasks also linked to WAPs outside the subtree (only unlinked; they survive)
Actual delete
curl -X DELETE "https://abstria.app/api/v1/yaps/<id>" \
-H "Authorization: Bearer $ABSTRIA_API_KEY" -w "\n[HTTP %{http_code}]\n"
| Status | Meaning |
|---|---|
| 204 No Content | Deleted |
400 invalid_id | Malformed UUID |
404 not_found | Doesn't exist / not your YAP (existence isn't disclosed) |
Bulk delete YAPs — DELETE /api/v1/yaps
Deletes several YAPs at once. Behavior matches the single version, and all ids are deleted in one transaction (all-or-nothing). You can pass the yap_ids returned by an import directly.
curl -X DELETE "https://abstria.app/api/v1/yaps" \
-H "Authorization: Bearer $ABSTRIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ids":["<uuid1>","<uuid2>"]}'
{ "ok": true, "deleted": ["<uuid1>", "<uuid2>"] }
Add ?dry_run=true to return totals and a per-YAP breakdown without deleting.
| Status | Meaning |
|---|---|
| 200 | Success ({ok,deleted} or {ok,dry_run,...}) |
404 not_found | Includes a missing / someone else's id (listed in missing; nothing is deleted) |
422 validation_failed | ids is invalid (must be 1–100 UUIDs) |
If even one id is missing or not owned by you, the whole request is aborted with a 404 for safety (no partial deletes).
Quickstart
Once you've created an API key, start by checking your counts.
export ABSTRIA_API_KEY="abs_live_..."
# Check current counts
curl -sS "https://abstria.app/api/v1/stats" \
-H "Authorization: Bearer $ABSTRIA_API_KEY"
# Bulk create from a JSON file
curl -X POST "https://abstria.app/api/v1/imports" \
-H "Authorization: Bearer $ABSTRIA_API_KEY" \
-H "Content-Type: application/json" \
-d @goals.json