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
| Method | Path | Scope |
|---|---|---|
GET | /api/public/v1/me | ledger:read |
GET | /api/public/v1/categories | ledger:read |
GET | /api/public/v1/accounts | ledger:read |
POST | /api/public/v1/accounts | accounts:write |
GET | /api/public/v1/accounts/{id} | ledger:read |
PATCH | /api/public/v1/accounts/{id} | accounts:write |
GET | /api/public/v1/snapshots | ledger:read |
POST | /api/public/v1/snapshots | snapshots: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/bulk | snapshots:write |
GET | /api/public/v1/openapi.json | none |
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.
| Field | Required | Notes |
|---|---|---|
name | yes | 1–120 characters. |
category_id | yes | A UUID from GET /categories. |
institution_name | no | 1–120 characters. Defaults to "Public API". |
visibility | no | private (default), shared_read_only, shared_full_access. |
liquidity_tier | no | auto (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.
| Field | Notes |
|---|---|
name | 1–120 characters. |
institution_name | 1–120 characters. |
category_id | A UUID from GET /categories. |
visibility | private, shared_read_only, shared_full_access. |
lifecycle_status | active or archived. |
auto_carry_forward | Boolean. |
liquidity_tier | Same 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
| Parameter | Notes |
|---|---|
account_id | Optional. Omit to return snapshots across every visible standard account. |
from | Optional YYYY-MM-DD lower bound. |
to | Optional 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
}201when the snapshot was created,200when an existing one was updated;actionsays which.- A balance written through the API is always confirmed — writing over an estimated balance clears the estimated mark.
422if the account doesn’t exist or isn’t writable by this token.- Fires the
snapshot.upsertedwebhook.
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
}skippedreports 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
422with the same per-row detail. - One
snapshot.upsertedwebhook 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.