Docs

API Reference

REST API for managing your Abstria goal data (YAP / MAP / WAP / DAP). List, bulk-create, export, and delete your plans, plus add, update, and delete individual DAPs (daily tasks).

Updated August 19, 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.

CodeNameGranularity
YAPYear Action PlanA yearly goal
MAPMonth Action PlanA monthly goal (belongs to a YAP)
WAPWeek Action PlanA weekly goal (belongs to a MAP)
DAPDay 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

StatuserrorMeaning
401unauthorizedSession is invalid or you're not logged in
401invalid_api_key_formatToken isn't in abs_live_ format
401invalid_api_keyKey doesn't exist or was revoked
401expired_api_keyKey has expired

Rate limits

Requests authenticated with an API key are subject to a per-key fixed-window limit (cookie sessions are exempt).

ItemValue
Limit60 requests / 60 seconds (per key)
MethodFixed window
On exceed429 { "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": { "...": "..." } }
StatusCommon errorMeaning
400invalid_json, invalid_idMalformed request
401(see Authentication)Authentication failed
404not_foundTarget doesn't exist or isn't yours
422validation_failedInput validation failed (details has specifics)
422constraint_violationDB constraint violated (limit exceeded, duplicate, etc.)
429rate_limitedRate limit exceeded
500query_failed, rpc_failed, export_failedServer-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.

QueryValueDescription
includetreeNest 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 mapswapsdaps. 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 / waps can't be orphaned because their parents are NOT NULL + cascade.
  • tasks_unlinked is 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.

QueryValueDescription
formatjson (default)A single restorable JSON, compatible with the imports restore format
formatcsvA 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 mapswapsdaps.
  • 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

LevelRequiredOptional
yaptitle, start_dateend_date (defaults to Dec 31 of start_date's year), description, sort_order, status, maps
maptitle, start_date, end_datedescription, sort_order, status, waps
waptitle, start_date, end_datedescription, sort_order, status, is_urgent, is_important, daps
dap (task)titledescription, status, scheduled_date, start_time, end_time, sort_order, due_date, recurrence_*, is_urgent, is_important
  • Dates are YYYY-MM-DD; times are HH:MM or HH:MM:SS strings only.
  • status is one of pending / in_progress / done.
  • Any user_id in the payload is ignored; the user_id resolved from auth is always used.
  • Omitting sort_order auto-assigns it (MAP uses the month index 0–11 of start_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.

Add a DAP — POST /api/v1/waps/{id}/daps

Adds a single DAP (daily task) to an existing WAP. While bulk create only builds new YAP trees, this endpoint is for adding tasks to a weekly plan you already have.

The body accepts the same fields as the dap level of bulk create (only title is required).

curl -X POST "https://abstria.app/api/v1/waps/<wap_id>/daps" \
  -H "Authorization: Bearer $ABSTRIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Prepare slides","scheduled_date":"2026-08-20","start_time":"09:00","end_time":"11:00"}'
{
  "ok": true,
  "dap": {
    "id": "uuid",
    "title": "Prepare slides",
    "status": "pending",
    "scheduled_date": "2026-08-20",
    "start_time": "09:00:00",
    "end_time": "11:00:00",
    "sort_order": 3,
    "completed_at": null
  }
}
  • Returns 201 Created on success.
  • If sort_order is omitted, the task is appended to the end of the WAP (max + 1).
  • You can look up WAP ids with GET /api/v1/yaps?include=tree.
  • Returns 422 constraint_violation if you have reached your plan's active item limit.

List DAPs of a WAP — GET /api/v1/waps/{id}/daps

Returns the DAPs linked to a WAP in sort_order order. Useful for looking up ids to update or delete.

curl -sS "https://abstria.app/api/v1/waps/<wap_id>/daps" \
  -H "Authorization: Bearer $ABSTRIA_API_KEY"
{
  "wap": { "id": "uuid", "title": "Week 3" },
  "daps": [{ "id": "uuid", "title": "Prepare slides", "status": "pending" }]
}

Update a DAP — PATCH /api/v1/daps/{id}

Partially updates a DAP. Only the fields you pass are changed (at least one field is required; unknown fields return 422).

curl -X PATCH "https://abstria.app/api/v1/daps/<dap_id>" \
  -H "Authorization: Bearer $ABSTRIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"done"}'
{
  "ok": true,
  "dap": { "id": "uuid", "status": "done", "completed_at": "2026-08-19T09:00:00.000Z" }
}
  • Setting status to done records completed_at automatically and propagates completion to the weekly and monthly plans, just like in the app.
  • The same fields as on create are accepted (title, description, status, scheduled_date, start_time, end_time, due_date, sort_order, is_urgent, is_important, recurrence_*).

Delete a DAP — DELETE /api/v1/daps/{id}

Deletes a DAP.

curl -X DELETE "https://abstria.app/api/v1/daps/<dap_id>" \
  -H "Authorization: Bearer $ABSTRIA_API_KEY" -w "\n[HTTP %{http_code}]\n"
StatusMeaning
204 No ContentDeleted
400 invalid_idMalformed UUID
404 not_foundMissing or not yours (existence is not revealed)

Note: the task itself is deleted, so a DAP linked to multiple WAPs disappears everywhere (unlinking from a single WAP only is planned for a future release).

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).

QueryValueDescription
dry_runtrueReturn 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"
StatusMeaning
204 No ContentDeleted
400 invalid_idMalformed UUID
404 not_foundDoesn'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.

StatusMeaning
200Success ({ok,deleted} or {ok,dry_run,...})
404 not_foundIncludes a missing / someone else's id (listed in missing; nothing is deleted)
422 validation_failedids 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