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.

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.

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