Developer & APIAPI Reference

API Reference

Base URL: https://app.worthsync.com. Every endpoint below requires an Authorization: Bearer wsk_… header — see Developer & API for creating a token.

All request and response bodies are JSON, and all field names are snake_case.

Endpoints at a glance

MethodPathScope
GET/api/public/v1/meledger:read
GET/api/public/v1/categoriesledger:read
GET/api/public/v1/accountsledger:read
POST/api/public/v1/accountsaccounts:write
GET/api/public/v1/accounts/{id}ledger:read
PATCH/api/public/v1/accounts/{id}accounts:write
GET/api/public/v1/snapshotsledger:read
POST/api/public/v1/snapshotssnapshots:write
GET/api/public/v1/snapshots/{id}ledger:read
PATCH/api/public/v1/snapshots/{id}snapshots:write
DELETE/api/public/v1/snapshots/{id}snapshots:write
POST/api/public/v1/snapshots/bulksnapshots:write
GET/api/public/v1/openapi.jsonnone

Identity

GET /api/public/v1/me

Confirms a token works and reports what it resolves to. Useful as a health check.

curl https://app.worthsync.com/api/public/v1/me \
  -H "Authorization: Bearer wsk_..."
{
  "user_id": "user_...",
  "household_id": "0f2c...",
  "token_id": "8a41..."
}

household_id is resolved from your current membership at request time, not from whatever household existed when the token was created.


Categories

GET /api/public/v1/categories

Lists the categories available to you — your own plus the shared defaults — sorted by type then name. You need a category_id from here to create an account.

{
  "categories": [
    { "id": "3f9d...", "name": "Brokerage", "type": "asset" },
    { "id": "c710...", "name": "Credit Card", "type": "liability" }
  ]
}

Accounts

Only standard accounts are exposed. Tangible-asset vault items are not reachable through the public API.

GET /api/public/v1/accounts

Lists every standard account visible to you, including archived ones. There are no query parameters and no pagination.

{
  "accounts": [
    {
      "id": "1b6e...",
      "name": "Brokerage",
      "institution_name": "Fidelity",
      "category_id": "3f9d...",
      "category_name": "Brokerage",
      "category_type": "asset",
      "visibility": "private",
      "lifecycle_status": "active",
      "liquidity_tier": "taxable_investment"
    }
  ]
}

POST /api/public/v1/accounts

Creates a standard account. Returns 201.

FieldRequiredNotes
nameyes1–120 characters.
category_idyesA UUID from GET /categories.
institution_nameno1–120 characters. Defaults to "Public API".
visibilitynoprivate (default), shared_read_only, shared_full_access.
liquidity_tiernoauto (default), cash, taxable_investment, retirement, property, other_illiquid, debt.
curl https://app.worthsync.com/api/public/v1/accounts \
  -H "Authorization: Bearer wsk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Brokerage",
    "institution_name": "Manual",
    "category_id": "CATEGORY_UUID",
    "visibility": "private",
    "liquidity_tier": "taxable_investment"
  }'

Anything other than private requires a plan that includes sharing and returns 402 otherwise. An unknown or inaccessible category_id returns 422.

Leaving liquidity_tier on auto lets WorthSync infer the label from the category and account type — see How the Numbers Work.

GET /api/public/v1/accounts/{id}

Returns one account, or 404 if it isn’t visible to this token. The response also carries auto_carry_forward.

PATCH /api/public/v1/accounts/{id}

Updates an account. Send at least one field; anything you omit is left alone.

FieldNotes
name1–120 characters.
institution_name1–120 characters.
category_idA UUID from GET /categories.
visibilityprivate, shared_read_only, shared_full_access.
lifecycle_statusactive or archived.
auto_carry_forwardBoolean.
liquidity_tierSame values as on create.

404 if the account isn’t editable by this token; 402 if you set a shared visibility without a plan that includes sharing.

There is no DELETE for accounts. Deleting an account destroys its entire balance history, so it stays a deliberate action in the app. Set lifecycle_status to archived instead — archived accounts stay in your net-worth math.


Snapshots

A snapshot is one dated balance for one account. The pair (account_id, snapshot_date) is unique, which is what makes writes idempotent.

GET /api/public/v1/snapshots

ParameterNotes
account_idOptional. Omit to return snapshots across every visible standard account.
fromOptional YYYY-MM-DD lower bound.
toOptional YYYY-MM-DD upper bound.

A malformed from or to returns 400. There is no pagination, so narrow wide queries with a date range.

{
  "snapshots": [
    {
      "id": "77aa...",
      "account_id": "1b6e...",
      "snapshot_date": "2026-07-14",
      "balance": 12500.25,
      "is_estimated": false
    }
  ]
}

is_estimated marks a balance WorthSync produced (carried forward or fed from a valuation source) rather than one that was confirmed.

POST /api/public/v1/snapshots

Creates or updates the snapshot for that account and date.

curl https://app.worthsync.com/api/public/v1/snapshots \
  -H "Authorization: Bearer wsk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "ACCOUNT_UUID",
    "snapshot_date": "2026-07-14",
    "balance": 12500.25
  }'
{
  "snapshot": { "id": "77aa...", "account_id": "1b6e...", "snapshot_date": "2026-07-14", "balance": 12500.25, "is_estimated": false },
  "action": "inserted",
  "webhooks_attempted": 1
}
  • 201 when the snapshot was created, 200 when an existing one was updated; action says which.
  • A balance written through the API is always confirmed — writing over an estimated balance clears the estimated mark.
  • 422 if the account doesn’t exist or isn’t writable by this token.
  • Fires the snapshot.upserted webhook.

GET /api/public/v1/snapshots/{id}

Returns one snapshot, or 404.

PATCH /api/public/v1/snapshots/{id}

Send balance, snapshot_date, or both — at least one is required. Returns the updated snapshot and fires the snapshot.upserted webhook. 404 if it isn’t editable by this token.

DELETE /api/public/v1/snapshots/{id}

{ "deleted": true, "id": "77aa..." }

404 if it isn’t deletable by this token. Deletions do not fire a webhook.

POST /api/public/v1/snapshots/bulk

Upserts up to 5,000 snapshots in one call — the right endpoint for backfilling history. The response accounts for every row you sent, so you can tell exactly what landed.

curl https://app.worthsync.com/api/public/v1/snapshots/bulk \
  -H "Authorization: Bearer wsk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "snapshots": [
      { "account_id": "ACCOUNT_UUID", "snapshot_date": "2026-07-14", "balance": 12500.25 }
    ]
  }'
{
  "received": 3,
  "inserted": 2,
  "updated": 0,
  "skipped": [{ "index": 2, "reason": "account not found or not writable" }],
  "webhooks_attempted": 1
}
  • skipped reports the zero-based index of each rejected row with a reason, so you can fix and resend just those.
  • If nothing at all was written, the call returns 422 with the same per-row detail.
  • One snapshot.upserted webhook carries every row that was written, not one per row.
⚠️

Always check skipped — a bulk call that writes only some of its rows still returns a success status. If a large batch reports many skipped rows, resend them as a smaller batch and inspect the reasons rather than assuming they were all bad.


OpenAPI document

GET /api/public/v1/openapi.json

An OpenAPI 3.1 description of the endpoints above, served without authentication and generated by the running app. Point a client generator at it rather than transcribing this page.