# Getting started
> Sign in, plan your first month, and track an expense.
Source: https://docs.spendrock.com/getting-started/
This page is a short outline for now. Step-by-step guides with screenshots are coming.
## 1. Sign in [#1-sign-in]
SpendRock is **invite-only** for now. Sign in with the Google account that was invited (or
that's on the list). Your first sign-in creates your own household, named after you, with an
empty budget.
## 2. Plan the month [#2-plan-the-month]
Open the current month and choose **Start Planning**. SpendRock copies your most recent budget,
or starts you from a simple template if this is your first month.
* Add your **income** for the month.
* Add groups (like *Housing* or *Food*) and items (like *Rent* or *Groceries*) with a **planned**
amount each.
* Keep going until **Left to Budget** reaches zero: every dollar has a job.
## 3. Track what you spend [#3-track-what-you-spend]
Add an expense or income with its amount, date, and merchant, and pick the budget item it
belongs to. You can split one purchase across several items. Each item shows what you've
**spent** and what's **remaining**.
## 4. Invite your household [#4-invite-your-household]
Anyone in a household can invite someone by email from **Settings → Households**. Everyone in
the household edits the same budget.
## What's next [#whats-next]
More help is on the way. In the meantime, the [API docs](/api/) describe everything SpendRock
can do, in detail.
---
# Welcome
> SpendRock is a small zero-based budgeting app for your household.
Source: https://docs.spendrock.com/
The help section is just getting started. More pages (how budgeting works, funds, splits,
households, saving while offline) are on the way. If something here is missing or wrong, tell us.
SpendRock helps you **give every dollar a job**. Each month you plan where your income goes,
then track what you actually spend against that plan. Everyone in your household sees and edits
the same budget.
Sign in, plan your first month, and add an expense.
Build on SpendRock with a personal access token.
## Tips for reading these docs [#tips-for-reading-these-docs]
* Every page is also available as Markdown: add `.md` to its address, or use **Copy Markdown**
at the top of the page.
* Use the search box (or press ⌘ K / Ctrl K) to find
anything across Help and the API docs.
---
# Authentication & scopes
> Personal access tokens, the scopes they carry, and what they can't do.
Source: https://docs.spendrock.com/api/authentication/
## Personal access tokens [#personal-access-tokens]
Scripts authenticate with a **personal access token** (PAT) in the `Authorization` header:
```http
Authorization: Bearer srp_…
```
* Create, list, and revoke tokens in **Account → API tokens**
([open it](https://app.spendrock.com/account/api-tokens)). Each token has a name, optional expiry (30, 90, or 365
days, or never), and a set of scopes.
* The secret is shown **once**, when you create the token. SpendRock stores only a hash of it.
* The list shows each token's name, scopes, when it was created, when it was last used, and when
it expires.
* **A token acts as you.** It sees the households you belong to and can do what you can do there,
limited by its scopes.
* A revoked or expired token gets `401` with the code `unauthenticated`.
The API also accepts the web app's session cookie, but only from the web app itself. Calls from
anywhere else, including the playground in these docs, must use a bearer token. Cross-origin
requests never send or accept cookies.
## Scopes [#scopes]
Every operation needs one scope. The reference shows it on each endpoint (and the spec has it as
`x-spendrock-scope`).
| Scope | Allows |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `budget:read` | Read months and their groups, items, funds, and favorites. |
| `budget:write` | Create and reset months; add, edit, reorder, and delete groups and items; funds, favorites order, and harvest. |
| `transactions:read` | List and read transactions, and merchant suggestions. |
| `transactions:write` | Add, edit, delete, and restore transactions. |
| `households:read` | List households, members, and invites. |
| `households:manage` | Create, rename, delete, archive, and leave households; set the default; transfer ownership; remove members; send, revoke, accept, and decline invites. |
| `account:read` | Read your profile (`/me`), settings, and sign-in methods. |
| `account:write` | Change your settings. |
* A **write** scope includes its **read** scope (and `households:manage` includes
`households:read`).
* When creating a token, the presets are **Read only** (every `*:read` scope) and **Full access**
(every scope). You can also pick scopes one by one.
* A call outside the token's scopes gets `403` with the code `insufficient_scope`. The error
names the scope it needs in `details.required_scope`. For example:
```json
{
"code": "insufficient_scope",
"message": "This token doesn't have the transactions:write scope.",
"details": { "required_scope": "transactions:write" }
}
```
## What tokens can't do [#what-tokens-cant-do]
Some things need you, signed in, in a browser:
* **Managing tokens.** A token can't create, list, or revoke tokens.
* **Changing your password.**
These return `403` with the code `session_required`.
## Sessions [#sessions]
The web app (a cookie) and the mobile app (a bearer session token from signing in) use
**sessions** instead of personal access tokens. Sessions have every scope and aren't subject to
the per-token [rate limit](/api/rate-limits/). You don't need them for scripts; they're
documented in the reference under **auth** for completeness.
## No authentication [#no-authentication]
A few endpoints need no credentials: signing in, and looking up an invite by its link token. The
spec itself is public too (`/openapi.json`, `/openapi.yaml`).
---
# Errors
> The error format, HTTP statuses, and every error code.
Source: https://docs.spendrock.com/api/errors/
## Format [#format]
Every error response has a JSON body with a stable, machine-readable `code` and a human-readable
`message`. Some add `details`.
```json
{
"code": "splits_do_not_sum",
"message": "The splits add up to $50.00, but the amount is $54.20.",
"details": { }
}
```
* **Branch on `code`**, never on `message`. Codes are stable within `/v1`; messages may change.
* `message` is safe to show to a user.
* `details` is an object whose contents depend on the code (for example
`details.required_scope` for `insufficient_scope`). Ignore keys you don't know.
* New codes may be added in `/v1`. Treat an unknown code by its HTTP status.
## Statuses [#statuses]
| Status | Meaning |
| ------ | ---------------------------------------------------------------------------------------------------- |
| `400` | The request didn't parse: malformed JSON, a wrong type, or a bad parameter (`invalid`). |
| `401` | No valid credentials: missing, revoked, or expired token. |
| `403` | Authenticated, but not allowed: missing scope, not a member, not the owner, and so on. |
| `404` | No such thing (or no budget for that month yet). |
| `409` | Conflicts with what exists: a name that's taken, a month that exists, a reused transaction ID. |
| `410` | A link that was valid but isn't any more (an expired or used invite or email link). |
| `422` | Well-formed, but breaks a budgeting rule (the splits don't add up, the item isn't in that month, …). |
| `429` | Too many requests. See [Rate limits](/api/rate-limits/). |
| `500` | Something went wrong on our side (`internal`). It's reported to us automatically; retry later. |
## Codes [#codes]
### Authentication and access [#authentication-and-access]
| Code | Status | Meaning |
| -------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `unauthenticated` | 401 | No credentials, or the token or session is revoked or expired. |
| `insufficient_scope` | 403 | The token lacks the scope this operation needs; `details.required_scope` names it. See [scopes](/api/authentication/#scopes). |
| `session_required` | 403 | Only a signed-in session can do this (managing tokens, changing your password). |
| `not_invited` | 403 | Signing in: the account isn't on the invite list and has no pending invite. |
| `not_a_member` | 403 | You aren't a current member of that household. See [Households](/api/households/). |
| `not_owner` | 403 | Only the household's owner can do this. |
| `rate_limited` | 429 | Too many requests; wait `Retry-After` seconds. |
### Budgets, groups, and items [#budgets-groups-and-items]
| Code | Status | Meaning |
| ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `not_found` | 404 | No such group, item, transaction, household, or invite (or it isn't in this household). |
| `month_not_created` | 404 / 422 | The month has no budget yet: `404` when reading it, `422` when writing to it. Create it with `POST /months/{month}`. |
| `month_exists` | 409 | The month already has a budget. |
| `no_previous_month` | 409 | Resetting with "replace" needs an earlier budget to copy, and there isn't one. |
| `name_taken` | 409 | A group or item with that name already exists (names are unique within a budget). |
| `income_group_protected` | 422 | The Income group can't be deleted. |
| `kind_mismatch` | 422 | The item's kind doesn't allow this: income items can't be funds, favorites, or harvested, can't leave Income; funds can't be harvested; an expense can't be split to an income item. |
| `already_fund` | 422 | The item is already a fund. |
| `not_fund` | 422 | The operation only applies to funds. |
| `nothing_to_harvest` | 422 | The item has nothing left over to move back to Left to Budget. |
| `invalid` | 400 / 422 | Generic validation failure; `message` says what's wrong. |
### Transactions [#transactions]
| Code | Status | Meaning |
| ---------------------- | ------ | ------------------------------------------------------------------------------------------------- |
| `id_conflict` | 409 | That transaction ID was already used with a different body. See [Idempotency](/api/idempotency/). |
| `amount_not_positive` | 422 | Amounts must be greater than zero (the `type` gives the direction). |
| `split_not_positive` | 422 | Every split's amount must be greater than zero. |
| `splits_do_not_sum` | 422 | The splits must add up to the transaction's amount. |
| `duplicate_split_item` | 422 | Two splits point at the same item. |
| `item_not_in_month` | 422 | A split's item doesn't exist in the transaction's budget month. |
### Households and invites [#households-and-invites]
| Code | Status | Meaning |
| ----------------------- | ------ | ---------------------------------------------------------------------------- |
| `already_member` | 409 | That person is already in the household. |
| `invite_email_mismatch` | 403 | The invite was sent to a different email than the one you're signed in with. |
| `invite_unavailable` | 410 | The invite expired, or was revoked, declined, or already accepted. |
| `last_household` | 422 | You can't leave, delete, or archive your only (non-archived) household. |
| `personal_household` | 422 | Your personal household can't be deleted, and its owner can't leave it. |
| `owner_must_transfer` | 422 | Transfer ownership before leaving a household others still belong to. |
| `default_household` | 422 | Your default household can't be archived; pick another default first. |
| `household_archived` | 422 | An archived household can't be your default. |
| `choose_default` | 422 | Leaving your default household: say which household becomes the new default. |
### Email + password accounts [#email--password-accounts]
These apply only where email + password sign-in is turned on.
| Code | Status | Meaning |
| ------------------ | ------ | ------------------------------------------- |
| `bad_credentials` | 401 | Email or password is incorrect. |
| `email_unverified` | 403 | Verify the email address first. |
| `invalid_link` | 410 | The email link expired or was already used. |
### Server [#server]
| Code | Status | Meaning |
| ---------- | ------ | ------------------------------------------------------------------ |
| `internal` | 500 | Unexpected error. Retry later; if it keeps happening, let us know. |
---
# Households
> Pick which household a request acts on with the X-SpendRock-Household header.
Source: https://docs.spendrock.com/api/households/
A **household** owns a budget. You can belong to several: your own personal household (created
the first time you sign in) plus any you've been invited to or created. Your token belongs to
**you**, not to a household.
## Choosing the household [#choosing-the-household]
Budget and transaction calls act on **one** household:
* the one named by the `X-SpendRock-Household` header, if you send it, or
* your **default** household otherwise.
```bash
# your default household
curl -s "$SR_API/months/2026-12" -H "Authorization: Bearer $SR_TOKEN"
# a specific household
curl -s "$SR_API/months/2026-12" \
-H "Authorization: Bearer $SR_TOKEN" \
-H "X-SpendRock-Household: 01J8Z3N4Q5R6S7T8V9W0X1Y2Z3"
```
`GET /me` tells you which household the request acted on (`household`), your default
(`default_household_id`), and every household you belong to (`households`).
If you aren't a current member of the household you name, the request fails with `403` and the
code `not_a_member`. Membership is checked on every request, so someone removed from a household
loses access immediately.
Your default household can change (you can pick a different one in the app). A script that
should always touch the same budget should send `X-SpendRock-Household` explicitly.
## Finding household IDs [#finding-household-ids]
```bash
curl -s "$SR_API/households" -H "Authorization: Bearer $SR_TOKEN"
```
This lists your households, default first, with your `role` (`owner` or `member`) and whether
each is your `personal` household. Archived households are left out unless you pass
`include_archived=true`. (Needs `households:read`.)
## Managing households [#managing-households]
With `households:manage` a token can do what you can do under **Settings → Households**: create
households, rename them (owner only), set your default, archive and unarchive, leave, transfer
ownership (owner only), remove members (owner only), and send, revoke, accept, or decline invites.
The rules are the app's rules. For example:
* you can't leave or delete your **only** household (`last_household`)
* your **personal** household can't be deleted (`personal_household`)
* an owner must transfer ownership before leaving (`owner_must_transfer`)
* only the owner can do owner things (`not_owner`)
See [Errors](/api/errors/) for every code.
---
# Idempotency
> Transaction IDs are generated by the client and double as idempotency keys, so retries never duplicate.
Source: https://docs.spendrock.com/api/idempotency/
Networks fail. If a request to add a transaction times out, you don't know whether it was saved.
SpendRock makes retrying safe: **the client picks the transaction's ID**, and that ID is the
idempotency key.
## How it works [#how-it-works]
When you create a transaction with `POST /transactions`, you send an `id`: a new
[ULID](https://github.com/ulid/spec) you generate.
| You send | You get |
| ----------------------------------------- | --------------------------------------------------------------- |
| A new `id` | `201 Created` with the transaction and the recomputed month(s). |
| The same `id` and the **same** body again | `200 OK` with the **stored** result. Nothing is created twice. |
| The same `id` with a **different** body | `409 Conflict` with the code `id_conflict`. |
So the rule for clients is simple: **generate the ID once, before the first attempt, and reuse it
for every retry** of that same transaction. Never generate a new ID for a retry.
```js
import { ulid } from 'ulid';
const txn = {
id: ulid(), // once, up front
type: 'expense',
amount: 5420,
date: '2026-12-03',
merchant: 'Corner Grocery',
budget_month: '2026-12',
splits: [{ item_id: groceriesId, amount: 5420 }],
};
// Safe to call as many times as needed: at most one transaction is created.
async function save() {
const res = await fetch(`${SR_API}/transactions`, {
method: 'POST',
headers: { Authorization: `Bearer ${SR_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify(txn),
});
if (res.status === 201 || res.status === 200) return res.json();
throw new Error(`${res.status}: ${(await res.json()).code}`);
}
```
This is exactly how the mobile app saves expenses you add while offline: they're stored on the
device with their ID and uploaded when the connection returns, and nothing is lost or duplicated.
## Other requests [#other-requests]
* **Updates** (`PATCH`) set fields to the values you send, so repeating one is harmless.
* **Deleting** a transaction is a soft delete: nothing is lost, and
`POST /transactions/{id}/restore` brings it back.
* **Other creates** (groups, items, households, invites) use server-generated IDs and are **not**
idempotent. If one times out, list or read before retrying. Names of groups and items are unique
within a month, so a duplicate create usually fails with `name_taken` rather than creating a
copy.
---
# API overview
> A JSON API over HTTPS for everything SpendRock does, authenticated with personal access tokens.
Source: https://docs.spendrock.com/api/
The SpendRock API is the same API the web and mobile apps use: every budget, transaction, and
household operation is available to your own scripts. Requests and responses are JSON over
HTTPS.
**Base URL:** `https://app.spendrock.com/api/v1`
Create a token and make your first request in two minutes.
Personal access tokens, what each scope allows, and what tokens can't do.
Every endpoint, generated from the OpenAPI spec, with a playground to try it with your token.
The error format and every error code.
## Conventions at a glance [#conventions-at-a-glance]
* **Money** is an integer number of **cents** (`1234` is $12.34). See [Money & dates](/api/money-and-dates/).
* **Months** are `YYYY-MM`, **dates** are `YYYY-MM-DD`, and **IDs** are [ULIDs](https://github.com/ulid/spec).
* **Households:** budget calls act on your default household unless you send
`X-SpendRock-Household`. See [Households](/api/households/).
* **Transactions have client-generated IDs**, which double as idempotency keys. See [Idempotency](/api/idempotency/).
* **Mutations return the recomputed month**, so you rarely need a second request to see the effect.
* **Errors** are `{ "code", "message", "details"? }` with a stable `code`. See [Errors](/api/errors/).
* **`/v1` only changes additively.** Ignore fields you don't recognize. See [Versioning & changelog](/api/versioning/).
## The OpenAPI spec [#the-openapi-spec]
The reference in these docs is generated from the API's OpenAPI 3.0 spec. The live spec is
public, with no authentication, at `GET /openapi.json` and `GET /openapi.yaml` under the base
URL. Use it to generate a client in your language of choice.
Every page here is available as Markdown (add `.md` to the URL), and
[/llms.txt](/llms.txt) / [/llms-full.txt](/llms-full.txt) index the whole site. Use
**Copy Markdown** or **Open in Claude / ChatGPT** at the top of any page.
---
# Money & dates
> Amounts are integer cents; months are YYYY-MM; dates are YYYY-MM-DD.
Source: https://docs.spendrock.com/api/money-and-dates/
## Money is integer cents [#money-is-integer-cents]
Every amount in the API is an **integer number of US cents** (a 64-bit integer):
| Dollars | In the API |
| --------- | ---------- |
| $12.34 | `1234` |
| $1,500.00 | `150000` |
| $0.05 | `5` |
Never send floats or strings. Convert at the edges of your program, for example
`Math.round(dollars * 100)`, and format for display by dividing by 100.
* **Transaction amounts are always positive.** Whether money comes in or goes out is the
transaction's `type`: `expense` or `income`.
* **Split amounts must add up** to the transaction's amount (`splits_do_not_sum` otherwise), and
each must be positive (`split_not_positive`).
* **Computed figures can be negative.** `left_to_budget` is negative when you've planned more
than your income, and an item's `remaining` is negative when it's overspent. Fund balances can
go negative too.
## Months are `YYYY-MM` [#months-are-yyyy-mm]
A budget covers one calendar month and is addressed by `YYYY-MM`, like `2026-12`:
```
GET /months/2026-12
```
A month either has a budget or doesn't yet. Reading one that doesn't gives `404` with the code
`month_not_created`. Create it with `POST /months/{month}`, which copies the most recent earlier
budget (or a default template).
## Dates are `YYYY-MM-DD` [#dates-are-yyyy-mm-dd]
A transaction's `date` is a plain calendar date with no time or time zone, like `2026-12-03`.
Timestamps like `created_at` are RFC 3339 date-times in UTC.
### Budget month vs. date [#budget-month-vs-date]
A transaction also has a `budget_month`: the month whose budget it counts toward. It's usually
the month of its date, but it doesn't have to be. A rent payment made on November 30th can count
toward December. `budget_month` is required whenever the transaction has splits, and each split's
item must exist in that month (`item_not_in_month` otherwise).
## IDs are ULIDs [#ids-are-ulids]
IDs are [ULIDs](https://github.com/ulid/spec): 26 characters of Crockford base32, like
`01J8Z3N4Q5R6S7T8V9W0X1Y2Z3`. They sort by creation time. Most are generated by the server; the
exception is **transaction IDs**, which you generate (see [Idempotency](/api/idempotency/)).
---
# Pagination
> List transactions page by page with an opaque cursor.
Source: https://docs.spendrock.com/api/pagination/
Lists that can grow without bound are paginated with a **cursor**. Today that's
`GET /transactions`. Other lists (households, members, invites, a month's groups and items) are
small and come back whole.
## Parameters [#parameters]
| Parameter | Meaning |
| --------- | ----------------------------------------------------------------------------------------------- |
| `limit` | Page size, 1 to 200. Default 100. |
| `cursor` | Where to continue. Omit it for the first page; then pass the previous response's `next_cursor`. |
The response has the page's items plus `next_cursor`. When `next_cursor` is `null` (or missing),
you've reached the end. Treat the cursor as an opaque string: don't parse or build it, and don't
reuse it with different filters.
## Example [#example]
```bash
# first page
curl -s "$SR_API/transactions?view=tracked&month=2026-12&limit=50" \
-H "Authorization: Bearer $SR_TOKEN"
```
```json
{
"transactions": [ … 50 transactions, newest first … ],
"next_cursor": "eyJkIjoiMjAyNi0xMi0wMyIsImkiOiIwMUpDWEs2UThXNk01VDdWM04yQjlSNFkxWiJ9",
"untracked_count": 2
}
```
```bash
# next page: same filters, plus the cursor
curl -s "$SR_API/transactions?view=tracked&month=2026-12&limit=50&cursor=eyJkIjoi…" \
-H "Authorization: Bearer $SR_TOKEN"
```
A loop that reads everything:
```js
let cursor;
const all = [];
do {
const url = new URL(`${SR_API}/transactions`);
url.search = new URLSearchParams({ view: 'tracked', month: '2026-12', limit: '200' });
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${SR_TOKEN}` } });
const page = await res.json();
all.push(...page.transactions);
cursor = page.next_cursor;
} while (cursor);
```
## Filters [#filters]
`GET /transactions` needs a `view`: `untracked`, `tracked`, or `deleted` (the three tabs of the
app's Transactions panel). `month` limits `tracked` to one budget month, and `q` searches by
merchant (a case-insensitive substring) or by an exact amount such as `54.20`. Results are newest
first by date.
---
# Quickstart
> Create a personal access token and make your first API request.
Source: https://docs.spendrock.com/api/quickstart/
## 1. Create a personal access token [#1-create-a-personal-access-token]
In SpendRock, open **Account → API tokens** ([open it now](https://app.spendrock.com/account/api-tokens)) and choose
**Create token**:
* **Name:** something that tells you where it's used, like `budget-export script`.
* **Expiry:** 30, 90, or 365 days, or never.
* **Scopes:** **Read only** for scripts that only look, **Full access** for scripts that change
things, or pick exactly the ones you need. See [Authentication & scopes](/api/authentication/).
The token (it starts with `srp_`) is shown **once**. Copy it somewhere safe, like a password
manager or your script's secret store. If you lose it, revoke it and create a new one.
A token acts as you. Don't commit it to a repository or paste it into shared documents.
## 2. Make your first request [#2-make-your-first-request]
Set two shell variables. `SR_API` is the base URL, `https://app.spendrock.com/api/v1`.
```bash
export SR_API=""
export SR_TOKEN="srp_…" # your token
```
Ask who you are:
```bash
curl -s "$SR_API/me" -H "Authorization: Bearer $SR_TOKEN"
```
You get your user, the household you're acting on, and all your households (abbreviated):
```json
{
"user": { "id": "…", "name": "Jacob Barrieault", "email": "jacob@example.com" },
"household": { "id": "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3", "name": "Jacob's Budget", "role": "owner" },
"default_household_id": "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3",
"households": [ … ]
}
```
That needs the `account:read` scope. With a **Read only** or **Full access** token it just works.
## 3. Read this month's budget [#3-read-this-months-budget]
```bash
curl -s "$SR_API/months/2026-12" -H "Authorization: Bearer $SR_TOKEN"
```
The response is the whole month in one call: `left_to_budget`, every group and item with its
planned, spent, and remaining amounts (all in **cents**), and more. If the month has no budget
yet, you get `404` with the code `month_not_created`.
## 4. Add an expense [#4-add-an-expense]
Transactions need an ID that **you** generate: a [ULID](https://github.com/ulid/spec). Sending the
same request twice (say, after a timeout) never creates a duplicate. See
[Idempotency](/api/idempotency/).
```bash
curl -s -X POST "$SR_API/transactions" \
-H "Authorization: Bearer $SR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "01JCXK6Q8W6M5T7V3N2B9R4Y1Z",
"type": "expense",
"amount": 5420,
"date": "2026-12-03",
"merchant": "Corner Grocery",
"budget_month": "2026-12",
"splits": [{ "item_id": "", "amount": 5420 }]
}'
```
That needs `transactions:write`. The response contains the new transaction and the recomputed
month, so you can see the item's new remaining amount right away.
## Next steps [#next-steps]
* Try any endpoint from the browser: every [reference](/api/reference/months/getMonth/) page has a
playground. Paste your token into its **Authorization** field.
* Read about [errors](/api/errors/) and [rate limits](/api/rate-limits/) before you automate
anything.
---
# Rate limits
> 120 requests per minute per token, with bursts, and headers that tell you where you stand.
Source: https://docs.spendrock.com/api/rate-limits/
Each personal access token may make **120 requests per minute**. The limit is a token bucket: a
token starts with a full bucket of 120 requests, so short bursts are fine, and the bucket refills
steadily at 2 requests per second.
## Headers [#headers]
Every response to a request made with a personal access token carries:
| Header | Meaning |
| ----------------------- | --------------------------------------- |
| `X-RateLimit-Limit` | Requests allowed per minute (`120`). |
| `X-RateLimit-Remaining` | Requests left in the bucket right now. |
| `X-RateLimit-Reset` | Seconds until the bucket is full again. |
## Over the limit [#over-the-limit]
When the bucket is empty, the API answers `429 Too Many Requests` with the code `rate_limited`
and a `Retry-After` header: the number of seconds to wait before trying again.
```http
HTTP/2 429
retry-after: 1
x-ratelimit-limit: 120
x-ratelimit-remaining: 0
x-ratelimit-reset: 60
{ "code": "rate_limited", "message": "Too many requests. Try again in a moment." }
```
A well-behaved client:
* waits `Retry-After` seconds before retrying a `429` (retrying [transaction creates](/api/idempotency/)
is always safe)
* slows down when `X-RateLimit-Remaining` gets low, instead of running into the wall
```js
async function call(url, init) {
for (;;) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const wait = Number(res.headers.get('Retry-After') ?? '1');
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
```
Limits are tracked in memory on each server instance, not shared between instances. Under load
you may occasionally get a little more than 120 requests per minute through, and a limit can
reset early. Don't rely on the exact numbers; do respect `429` and `Retry-After`.
## Who is limited [#who-is-limited]
Only **personal access tokens** are limited this way. Browser and mobile-app sessions aren't
(signing in and password requests have their own protections against abuse).
---
# Versioning & changelog
> /v1 only changes additively. Here's what that promises, and what changed.
Source: https://docs.spendrock.com/api/versioning/
## The policy [#the-policy]
The API is versioned in the path: `/api/v1`.
* **`/v1` only changes additively.** New endpoints, new optional request fields, new response
fields, new error codes, and new enum values for things that are clearly open-ended can appear
at any time.
* **Breaking changes mean `/v2`.** Removing or renaming a field, changing a type or its meaning,
or making an optional request field required would all go into a new version, and `/v1` would
keep working alongside it.
## What that means for your client [#what-that-means-for-your-client]
* **Ignore fields you don't recognize.** Don't fail on unknown properties in responses.
* **Handle unknown error codes** by their HTTP status (see [Errors](/api/errors/)).
* **Don't depend on field order** or on the exact wording of `message`.
* **Treat cursors and IDs as opaque strings.**
## Changelog [#changelog]
Newest first.
### 2026-09 [#2026-09]
* **Public API launch.** Personal access tokens with fine-grained scopes, per-token rate limits
(with `X-RateLimit-*` headers), CORS for the API playground, and the public spec at
`/openapi.json` and `/openapi.yaml`.
* These docs: guides, a generated reference with a playground, and Markdown / `llms.txt` versions
of every page.
---
# Exchange a Google ID token for a SpendRock session (AUTH-1, AUTH-2)
Source: https://docs.spendrock.com/api/reference/auth/signInWithGoogle/
## POST /auth/google
`POST https://app.spendrock.com/api/v1/auth/google`
Operation ID: `signInWithGoogle`
Scope: `public`
Exchange a Google ID token for a SpendRock session (AUTH-1, AUTH-2)
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- id_token
- client
properties:
id_token:
type: string
client:
type: string
enum:
- web
- mobile
description: '`web` sets the `sr_session` cookie; `mobile` returns a bearer token.'
```
### Responses
#### 200
Signed in.
```yaml
type: object
required:
- me
properties:
token:
type: string
description: Bearer session token (mobile only). Store it in the Keychain.
me:
type: object
required:
- user
- household
- default_household_id
- households
properties:
user:
type: object
required:
- id
- email
- name
properties:
id:
type: string
description: >-
Account id: a ULID, or the Google subject for accounts created before email + password
sign-in.
email:
type: string
name:
type: string
household:
type: object
description: The household this request acts on (X-SpendRock-Household, else the default).
required:
- id
- name
- role
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
default_household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
households:
type: array
description: Every household I belong to, archived ones included, default first.
items:
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 401
Error.
#### 403
Valid Google account, but not on the allowlist (`not_invited`).
---
# Sign Out
Source: https://docs.spendrock.com/api/reference/auth/signOut/
## POST /auth/logout
`POST https://app.spendrock.com/api/v1/auth/logout`
Operation ID: `signOut`
Scope: `any`
Any credential; no scope needed.
### Responses
#### 204
Session revoked (and the cookie cleared).
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Start an email + password sign-up (ACCT-1)
Source: https://docs.spendrock.com/api/reference/auth/registerWithPassword/
## POST /auth/password/register
`POST https://app.spendrock.com/api/v1/auth/password/register`
Operation ID: `registerWithPassword`
Scope: `public`
Start an email + password sign-up (ACCT-1)
Emails a verification link (valid 24h, single use). If the email already belongs to an
account, that inbox gets a "you already have an account" note instead. The response is the
same either way.
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- email
- password
- name
properties:
email:
type: string
maxLength: 254
password:
type: string
minLength: 10
maxLength: 128
name:
type: string
maxLength: 80
```
### Responses
#### 202
Accepted. The same answer whether or not the email has an account.
```yaml
type: object
required:
- message
properties:
message:
type: string
```
#### 404
Error.
#### 422
Error.
#### 429
Error.
---
# Redeem an email verification link and sign in (ACCT-2)
Source: https://docs.spendrock.com/api/reference/auth/verifyEmail/
## POST /auth/password/verify
`POST https://app.spendrock.com/api/v1/auth/password/verify`
Operation ID: `verifyEmail`
Scope: `public`
Redeem an email verification link and sign in (ACCT-2)
The token is the `token` query parameter of the emailed link. A first verification creates
the account and its personal household (HH-1) if the email is admitted (allowlist, pending
invite, or existing member; HH-7, ACCT-4); otherwise the email is verified but the answer is
403 `not_invited`. 410 `invalid_link` for expired or used links.
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- token
- client
properties:
token:
type: string
client:
type: string
enum:
- web
- mobile
description: '`web` sets the `sr_session` cookie; `mobile` returns a bearer token.'
```
### Responses
#### 200
Signed in.
```yaml
type: object
required:
- me
properties:
token:
type: string
description: Bearer session token (mobile only). Store it in the Keychain.
me:
type: object
required:
- user
- household
- default_household_id
- households
properties:
user:
type: object
required:
- id
- email
- name
properties:
id:
type: string
description: >-
Account id: a ULID, or the Google subject for accounts created before email + password
sign-in.
email:
type: string
name:
type: string
household:
type: object
description: The household this request acts on (X-SpendRock-Household, else the default).
required:
- id
- name
- role
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
default_household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
households:
type: array
description: Every household I belong to, archived ones included, default first.
items:
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
Error.
#### 404
Error.
#### 410
Error.
#### 429
Error.
---
# Email a new verification link to an unverified sign-up
Source: https://docs.spendrock.com/api/reference/auth/resendVerification/
## POST /auth/password/resend
`POST https://app.spendrock.com/api/v1/auth/password/resend`
Operation ID: `resendVerification`
Scope: `public`
Email a new verification link to an unverified sign-up
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- email
properties:
email:
type: string
```
### Responses
#### 202
Accepted. The same answer whether or not the email has an account.
```yaml
type: object
required:
- message
properties:
message:
type: string
```
#### 404
Error.
#### 422
Error.
#### 429
Error.
---
# Sign in with email + password (ACCT-1)
Source: https://docs.spendrock.com/api/reference/auth/signInWithPassword/
## POST /auth/password/login
`POST https://app.spendrock.com/api/v1/auth/password/login`
Operation ID: `signInWithPassword`
Scope: `public`
Sign in with email + password (ACCT-1)
401 `bad_credentials` ("Email or password is incorrect.") for any wrong email or password;
403 `email_unverified` when the email still needs verifying; 403 `not_invited`.
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- email
- password
- client
properties:
email:
type: string
password:
type: string
client:
type: string
enum:
- web
- mobile
description: '`web` sets the `sr_session` cookie; `mobile` returns a bearer token.'
```
### Responses
#### 200
Signed in.
```yaml
type: object
required:
- me
properties:
token:
type: string
description: Bearer session token (mobile only). Store it in the Keychain.
me:
type: object
required:
- user
- household
- default_household_id
- households
properties:
user:
type: object
required:
- id
- email
- name
properties:
id:
type: string
description: >-
Account id: a ULID, or the Google subject for accounts created before email + password
sign-in.
email:
type: string
name:
type: string
household:
type: object
description: The household this request acts on (X-SpendRock-Household, else the default).
required:
- id
- name
- role
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
default_household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
households:
type: array
description: Every household I belong to, archived ones included, default first.
items:
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 401
Error.
#### 403
Error.
#### 404
Error.
#### 429
Error.
---
# Email a password reset link (ACCT-3)
Source: https://docs.spendrock.com/api/reference/auth/requestPasswordReset/
## POST /auth/password/forgot
`POST https://app.spendrock.com/api/v1/auth/password/forgot`
Operation ID: `requestPasswordReset`
Scope: `public`
Email a password reset link (ACCT-3)
Sends a link (valid 1h, single use) if the email has an account. Same answer either way.
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- email
properties:
email:
type: string
```
### Responses
#### 202
Accepted. The same answer whether or not the email has an account.
```yaml
type: object
required:
- message
properties:
message:
type: string
```
#### 404
Error.
#### 422
Error.
#### 429
Error.
---
# Set a new password from a reset link (ACCT-3)
Source: https://docs.spendrock.com/api/reference/auth/resetPassword/
## POST /auth/password/reset
`POST https://app.spendrock.com/api/v1/auth/password/reset`
Operation ID: `resetPassword`
Scope: `public`
Set a new password from a reset link (ACCT-3)
Also marks the email verified (the link proves it) and signs out every session of the
account. It doesn't sign in; sign in with the new password.
No authentication needed.
### Request body (required)
```yaml
type: object
required:
- token
- password
properties:
token:
type: string
password:
type: string
minLength: 10
maxLength: 128
```
### Responses
#### 204
Password changed; every session signed out.
#### 404
Error.
#### 410
Error.
#### 422
Error.
#### 429
Error.
---
# The caller's sign-in methods (ACCT-2, Settings → Sign-in methods)
Source: https://docs.spendrock.com/api/reference/auth/getSignInMethods/
## GET /me/sign-in-methods
`GET https://app.spendrock.com/api/v1/me/sign-in-methods`
Operation ID: `getSignInMethods`
Scope: `account:read`
The caller's sign-in methods (ACCT-2, Settings → Sign-in methods)
Required scope: `account:read` (personal access tokens; sessions have every scope).
### Responses
#### 200
Sign-in methods.
```yaml
type: object
required:
- email
- email_verified
- password
- password_pending
- google
properties:
email:
type: string
email_verified:
type: boolean
password:
type: boolean
description: The account has a password.
password_pending:
type: boolean
description: The password works once the email is verified.
google:
type: array
items:
type: object
required:
- email
- linked_at
properties:
email:
type: string
linked_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Add or change the caller's password (ACCT-2)
Source: https://docs.spendrock.com/api/reference/auth/setPassword/
## PUT /me/password
`PUT https://app.spendrock.com/api/v1/me/password`
Operation ID: `setPassword`
Scope: `session`
Add or change the caller's password (ACCT-2)
Changing an existing password needs `current_password` (401 `bad_credentials`). If the
account's email isn't verified yet, a verification link is emailed and the password works
once it's verified (`verification_sent`).
Needs a signed-in session: personal access tokens get 403 `session_required`.
### Request body (required)
```yaml
type: object
required:
- password
properties:
password:
type: string
minLength: 10
maxLength: 128
current_password:
type: string
```
### Responses
#### 200
Password saved.
```yaml
type: object
required:
- verification_sent
properties:
verification_sent:
type: boolean
```
#### 401
Error.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 422
Error.
#### 429
Error.
---
# Get Me
Source: https://docs.spendrock.com/api/reference/auth/getMe/
## GET /me
`GET https://app.spendrock.com/api/v1/me`
Operation ID: `getMe`
Scope: `account:read`
Required scope: `account:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
### Responses
#### 200
Current user and household.
```yaml
type: object
required:
- user
- household
- default_household_id
- households
properties:
user:
type: object
required:
- id
- email
- name
properties:
id:
type: string
description: >-
Account id: a ULID, or the Google subject for accounts created before email + password
sign-in.
email:
type: string
name:
type: string
household:
type: object
description: The household this request acts on (X-SpendRock-Household, else the default).
required:
- id
- name
- role
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
default_household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
households:
type: array
description: Every household I belong to, archived ones included, default first.
items:
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 401
Error.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Personal access tokens (AUTH-5)
Source: https://docs.spendrock.com/api/reference/auth/listTokens/
## GET /tokens
`GET https://app.spendrock.com/api/v1/tokens`
Operation ID: `listTokens`
Scope: `session`
Personal access tokens (AUTH-5)
Needs a signed-in session: personal access tokens get 403 `session_required`.
### Responses
#### 200
Tokens (secrets are never returned after creation).
```yaml
type: object
required:
- tokens
properties:
tokens:
type: array
items:
type: object
required:
- id
- name
- scopes
- created_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
scopes:
type: array
items:
type: string
description: >
A personal access token scope (DEV-2). A `*:write` / `households:manage` scope
includes
the matching read scope. Browser and mobile sessions have every scope.
enum:
- budget:read
- budget:write
- transactions:read
- transactions:write
- households:read
- households:manage
- account:read
- account:write
created_at:
type: string
format: date-time
last_used_at:
type:
- string
- 'null'
format: date-time
description: Updated at most about once a minute.
expires_at:
type:
- string
- 'null'
format: date-time
description: 'Null: never expires.'
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Create a personal access token (DEV-1, DEV-2)
Source: https://docs.spendrock.com/api/reference/auth/createToken/
## POST /tokens
`POST https://app.spendrock.com/api/v1/tokens`
Operation ID: `createToken`
Scope: `session`
Create a personal access token (DEV-1, DEV-2)
`scopes` defaults to every scope (full access) when omitted. `expires_in_days` omitted
means the token never expires; an expired token gets 401 `unauthenticated`.
Needs a signed-in session: personal access tokens get 403 `session_required`.
### Request body (required)
```yaml
type: object
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 100
scopes:
type: array
minItems: 1
items:
type: string
description: |
A personal access token scope (DEV-2). A `*:write` / `households:manage` scope includes
the matching read scope. Browser and mobile sessions have every scope.
enum:
- budget:read
- budget:write
- transactions:read
- transactions:write
- households:read
- households:manage
- account:read
- account:write
expires_in_days:
type: integer
minimum: 1
maximum: 365
```
### Responses
#### 201
Created. `secret` is shown only once.
```yaml
allOf:
- type: object
required:
- id
- name
- scopes
- created_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
scopes:
type: array
items:
type: string
description: |
A personal access token scope (DEV-2). A `*:write` / `households:manage` scope includes
the matching read scope. Browser and mobile sessions have every scope.
enum:
- budget:read
- budget:write
- transactions:read
- transactions:write
- households:read
- households:manage
- account:read
- account:write
created_at:
type: string
format: date-time
last_used_at:
type:
- string
- 'null'
format: date-time
description: Updated at most about once a minute.
expires_at:
type:
- string
- 'null'
format: date-time
description: 'Null: never expires.'
- type: object
required:
- secret
properties:
secret:
type: string
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Delete Token
Source: https://docs.spendrock.com/api/reference/auth/deleteToken/
## DELETE /tokens/{tokenId}
`DELETE https://app.spendrock.com/api/v1/tokens/{tokenId}`
Operation ID: `deleteToken`
Scope: `session`
Needs a signed-in session: personal access tokens get 403 `session_required`.
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `tokenId` | path | yes | string | ULID. |
### Responses
#### 204
Revoked.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Which months have budgets (for the month picker, NAV-2)
Source: https://docs.spendrock.com/api/reference/months/listMonths/
## GET /months
`GET https://app.spendrock.com/api/v1/months`
Operation ID: `listMonths`
Scope: `budget:read`
Which months have budgets (for the month picker, NAV-2)
Required scope: `budget:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `from` | query | yes | string | |
| `to` | query | yes | string | |
### Responses
#### 200
One entry per month in [from, to] (max 36 months).
```yaml
type: object
required:
- months
properties:
months:
type: array
items:
type: object
required:
- month
- exists
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
exists:
type: boolean
```
#### 400
Error.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# The full budget for a month (one call renders the whole budget screen, PERF-4)
Source: https://docs.spendrock.com/api/reference/months/getMonth/
## GET /months/{month}
`GET https://app.spendrock.com/api/v1/months/{month}`
Operation ID: `getMonth`
Scope: `budget:read`
The full budget for a month (one call renders the whole budget screen, PERF-4)
Required scope: `budget:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Responses
#### 200
The month's budget.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
No budget for this month yet (`month_not_created`). Shows the CREATE-1 empty state.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Create the month by copying the most recent earlier budget, or the default template (CREATE-2, CREATE-4, CREATE-5)
Source: https://docs.spendrock.com/api/reference/months/createMonth/
## POST /months/{month}
`POST https://app.spendrock.com/api/v1/months/{month}`
Operation ID: `createMonth`
Scope: `budget:write`
Create the month by copying the most recent earlier budget, or the default template (CREATE-2, CREATE-4, CREATE-5)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Responses
#### 201
Created.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Reset Budget (CREATE-6). `replace` untracks every transaction in the month.
Source: https://docs.spendrock.com/api/reference/months/resetMonth/
## POST /months/{month}/reset
`POST https://app.spendrock.com/api/v1/months/{month}/reset`
Operation ID: `resetMonth`
Scope: `budget:write`
Reset Budget (CREATE-6). `replace` untracks every transaction in the month.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Request body (required)
```yaml
type: object
required:
- mode
properties:
mode:
type: string
enum:
- zero
- replace
```
### Responses
#### 200
Reset.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 409
`replace` with no earlier month to copy (`no_previous_month`).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Add a group to this month (GROUP-1)
Source: https://docs.spendrock.com/api/reference/groups/createGroup/
## POST /months/{month}/groups
`POST https://app.spendrock.com/api/v1/months/{month}/groups`
Operation ID: `createGroup`
Scope: `budget:write`
Add a group to this month (GROUP-1)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Request body (required)
```yaml
type: object
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 80
```
### Responses
#### 201
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Reorder this month's groups (GROUP-4). Income is always first; list non-income groups only.
Source: https://docs.spendrock.com/api/reference/groups/setGroupOrder/
## PUT /months/{month}/group-order
`PUT https://app.spendrock.com/api/v1/months/{month}/group-order`
Operation ID: `setGroupOrder`
Scope: `budget:write`
Reorder this month's groups (GROUP-4). Income is always first; list non-income groups only.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Request body (required)
```yaml
type: object
required:
- group_ids
properties:
group_ids:
type: array
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
```
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Delete a group and its items from THIS month only; their transactions become untracked (GROUP-3)
Source: https://docs.spendrock.com/api/reference/groups/deleteGroupFromMonth/
## DELETE /months/{month}/groups/{groupId}
`DELETE https://app.spendrock.com/api/v1/months/{month}/groups/{groupId}`
Operation ID: `deleteGroupFromMonth`
Scope: `budget:write`
Delete a group and its items from THIS month only; their transactions become untracked (GROUP-3)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `groupId` | path | yes | string | ULID. |
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 422
The Income group can't be deleted (`income_group_protected`).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Rename or recolor a group. Applies to EVERY month (linked identity, GROUP-2).
Source: https://docs.spendrock.com/api/reference/groups/updateGroup/
## PATCH /groups/{groupId}
`PATCH https://app.spendrock.com/api/v1/groups/{groupId}`
Operation ID: `updateGroup`
Scope: `budget:write`
Rename or recolor a group. Applies to EVERY month (linked identity, GROUP-2).
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `groupId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
properties:
name:
type: string
minLength: 1
maxLength: 80
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
```
### Responses
#### 200
Updated.
```yaml
type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
Name already used (`name_taken`, GROUP-5).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Set the order of items within a group; an item listed here that belongs to another group is moved into this one (ITEM-5)
Source: https://docs.spendrock.com/api/reference/items/setItemOrder/
## PUT /months/{month}/groups/{groupId}/item-order
`PUT https://app.spendrock.com/api/v1/months/{month}/groups/{groupId}/item-order`
Operation ID: `setItemOrder`
Scope: `budget:write`
Set the order of items within a group; an item listed here that belongs to another group is moved into this one (ITEM-5)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `groupId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
required:
- item_ids
properties:
item_ids:
type: array
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
```
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
e.g. moving an expense item into Income (`kind_mismatch`).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Add an item to a group in this month (ITEM-1). Kind follows the group (income vs expense).
Source: https://docs.spendrock.com/api/reference/items/createItem/
## POST /months/{month}/items
`POST https://app.spendrock.com/api/v1/months/{month}/items`
Operation ID: `createItem`
Scope: `budget:write`
Add an item to a group in this month (ITEM-1). Kind follows the group (income vs expense).
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
### Request body (required)
```yaml
type: object
required:
- group_id
- name
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
minLength: 1
maxLength: 80
planned:
type: integer
format: int64
description: US cents.
```
### Responses
#### 201
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
`name_taken` (ITEM-6).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Change this month's planned amount or note (ITEM-2, ITEM-8)
Source: https://docs.spendrock.com/api/reference/items/updateMonthItem/
## PATCH /months/{month}/items/{itemId}
`PATCH https://app.spendrock.com/api/v1/months/{month}/items/{itemId}`
Operation ID: `updateMonthItem`
Scope: `budget:write`
Change this month's planned amount or note (ITEM-2, ITEM-8)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `itemId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
properties:
planned:
type: integer
format: int64
description: US cents.
note:
type: string
maxLength: 2000
```
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Delete the item from THIS month only; its transactions in this month become untracked (ITEM-4)
Source: https://docs.spendrock.com/api/reference/items/deleteItemFromMonth/
## DELETE /months/{month}/items/{itemId}
`DELETE https://app.spendrock.com/api/v1/months/{month}/items/{itemId}`
Operation ID: `deleteItemFromMonth`
Scope: `budget:write`
Delete the item from THIS month only; its transactions in this month become untracked (ITEM-4)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `itemId` | path | yes | string | ULID. |
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# "Make This a Fund" (FUND-1). current_balance becomes the carry-in for this month.
Source: https://docs.spendrock.com/api/reference/items/makeFund/
## POST /months/{month}/items/{itemId}/fund
`POST https://app.spendrock.com/api/v1/months/{month}/items/{itemId}/fund`
Operation ID: `makeFund`
Scope: `budget:write`
"Make This a Fund" (FUND-1). current_balance becomes the carry-in for this month.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `itemId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
properties:
current_balance:
type: integer
format: int64
description: US cents.
target:
type:
- integer
- 'null'
format: int64
```
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
Income items can't be funds (`kind_mismatch`); already a fund (`already_fund`).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Set the fund's current balance; records a "Balance Updated" adjustment for the difference (FUND-5). Never counts as Spent.
Source: https://docs.spendrock.com/api/reference/items/setFundBalance/
## POST /months/{month}/items/{itemId}/fund-balance
`POST https://app.spendrock.com/api/v1/months/{month}/items/{itemId}/fund-balance`
Operation ID: `setFundBalance`
Scope: `budget:write`
Set the fund's current balance; records a "Balance Updated" adjustment for the difference (FUND-5). Never counts as Spent.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `itemId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
required:
- current_balance
properties:
current_balance:
type: integer
format: int64
description: US cents.
```
### Responses
#### 200
The recomputed month.
```yaml
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
color:
type: string
pattern: ^#[0-9a-fA-F]{6}$
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
$ref: Id
name:
type: string
kind:
$ref: ItemKind
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
$ref: NullableCents
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
planned:
type: integer
format: int64
description: US cents.
spent:
allOf:
- type: integer
format: int64
description: US cents.
description: Expense items; net of refunds.
received:
allOf:
- type: integer
format: int64
description: US cents.
description: Income items.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
type: integer
format: int64
description: US cents.
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
type: integer
format: int64
description: US cents.
at:
type: string
format: date-time
by:
type: string
ending:
type: integer
format: int64
description: US cents.
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
received:
type: integer
format: int64
description: US cents.
remaining:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# "Harvest $X" (HARV-1..4): set this month's Planned = Spent so the remainder goes back to Left to Budget.
Source: https://docs.spendrock.com/api/reference/items/harvestRemainder/
## POST /months/{month}/items/{itemId}/harvest
`POST https://app.spendrock.com/api/v1/months/{month}/items/{itemId}/harvest`
Operation ID: `harvestRemainder`
Scope: `budget:write`
"Harvest $X" (HARV-1..4): set this month's Planned = Spent so the remainder goes back to Left to Budget.
Only for regular expense items whose Remaining is above $0. `harvested` is the amount moved back
to Left to Budget. Undo by sending `previous_planned` to `PATCH /months/{month}/items/{itemId}`.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `month` | path | yes | string | |
| `itemId` | path | yes | string | ULID. |
### Responses
#### 200
The recomputed month, with what was harvested.
```yaml
type: object
required:
- month
- harvested
- previous_planned
properties:
month:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
harvested:
allOf:
- type: integer
format: int64
description: US cents.
description: The amount moved back to Left to Budget (HARV-2).
previous_planned:
allOf:
- type: integer
format: int64
description: US cents.
description: The item's planned amount before harvesting, for Undo (HARV-3).
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 422
Nothing remains (`nothing_to_harvest`); funds and income items can't be harvested (`kind_mismatch`); `month_not_created`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Identity-level changes that apply to EVERY month (ITEM-3, ITEM-7, FUND-6)
Source: https://docs.spendrock.com/api/reference/items/updateItem/
## PATCH /items/{itemId}
`PATCH https://app.spendrock.com/api/v1/items/{itemId}`
Operation ID: `updateItem`
Scope: `budget:write`
Identity-level changes that apply to EVERY month (ITEM-3, ITEM-7, FUND-6)
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `itemId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
properties:
name:
type: string
minLength: 1
maxLength: 80
favorite:
type: boolean
description: Income items can't be favorited.
fund_target:
type:
- integer
- 'null'
format: int64
```
### Responses
#### 200
Updated.
```yaml
type: object
description: Month-independent item attributes (shared by every month).
required:
- id
- name
- kind
- is_fund
- favorite
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
kind:
type: string
enum:
- income
- expense
is_fund:
type: boolean
favorite:
type: boolean
fund_target:
type:
- integer
- 'null'
format: int64
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
`name_taken`.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Reorder the Favorites section (FAV-1). Independent of each item's order within its group.
Source: https://docs.spendrock.com/api/reference/items/setFavoritesOrder/
## PUT /favorites/order
`PUT https://app.spendrock.com/api/v1/favorites/order`
Operation ID: `setFavoritesOrder`
Scope: `budget:write`
Reorder the Favorites section (FAV-1). Independent of each item's order within its group.
`item_ids` may list all favorites or only some (e.g. the favorites in the month being
viewed). The listed favorites are placed, in the given order, into the positions they
held between them in the household's current Favorites order; favorites that are not
listed keep their positions. For example, with Favorites `[A, B, C, D]`, sending
`[D, B]` gives `[A, D, C, B]`. Every id must be a current favorite, listed once;
otherwise the request fails with `invalid`.
Required scope: `budget:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
### Request body (required)
```yaml
type: object
required:
- item_ids
properties:
item_ids:
type: array
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
```
### Responses
#### 204
Saved.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# The Transactions panel tabs (TXN-8, TXN-2a, TXN-4a)
Source: https://docs.spendrock.com/api/reference/transactions/listTransactions/
## GET /transactions
`GET https://app.spendrock.com/api/v1/transactions`
Operation ID: `listTransactions`
Scope: `transactions:read`
The Transactions panel tabs (TXN-8, TXN-2a, TXN-4a)
Required scope: `transactions:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `view` | query | yes | untracked \\| tracked \\| deleted | |
| `month` | query | no | string | For `tracked`: limit to this budget month. |
| `q` | query | no | string | Search by merchant (case-insensitive substring) or exact amount (e.g. `54.20`). |
| `cursor` | query | no | string | |
| `limit` | query | no | integer | |
### Responses
#### 200
Newest first by date.
```yaml
type: object
required:
- transactions
- untracked_count
properties:
transactions:
type: array
items:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
next_cursor:
type:
- string
- 'null'
untracked_count:
type: integer
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Add an expense or income (TXN-1). Idempotent on the client-generated id (SYNC-1).
Source: https://docs.spendrock.com/api/reference/transactions/createTransaction/
## POST /transactions
`POST https://app.spendrock.com/api/v1/transactions`
Operation ID: `createTransaction`
Scope: `transactions:write`
Add an expense or income (TXN-1). Idempotent on the client-generated id (SYNC-1).
Required scope: `transactions:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
### Request body (required)
```yaml
type: object
required:
- id
- type
- amount
- date
- merchant
- splits
properties:
id:
allOf:
- type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
description: Client-generated ULID; also the idempotency key.
type:
type: string
enum:
- expense
- income
amount:
allOf:
- type: integer
format: int64
description: US cents.
description: Always positive; direction comes from `type`.
date:
type: string
format: date
merchant:
type: string
maxLength: 120
note:
type: string
maxLength: 2000
budget_month:
description: Required when splits are non-empty. Defaults to the viewed month in clients (TXN-5).
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
description: Empty = untracked (TXN-2). Otherwise they must sum to amount.
items:
type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
location:
description: Mobile only, and only if opted in (TXN-13).
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
```
### Responses
#### 200
Replay of an identical earlier create (same id + same body). Returns the stored result.
```yaml
type: object
required:
- transaction
- months
- untracked_count
properties:
transaction:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
months:
type: array
description: Every month whose figures changed (0–2), recomputed.
items:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over
budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
untracked_count:
type: integer
```
#### 201
The transaction and every month it affected, recomputed.
```yaml
type: object
required:
- transaction
- months
- untracked_count
properties:
transaction:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
months:
type: array
description: Every month whose figures changed (0–2), recomputed.
items:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over
budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
untracked_count:
type: integer
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 409
Same id, different body (`id_conflict`).
#### 422
Validation: `amount_not_positive`, `splits_do_not_sum`, `split_not_positive`, `duplicate_split_item`, `item_not_in_month` (SYNC-4), `month_not_created`, `kind_mismatch`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Get Transaction
Source: https://docs.spendrock.com/api/reference/transactions/getTransaction/
## GET /transactions/{transactionId}
`GET https://app.spendrock.com/api/v1/transactions/{transactionId}`
Operation ID: `getTransaction`
Scope: `transactions:read`
Required scope: `transactions:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `transactionId` | path | yes | string | ULID. |
### Responses
#### 200
The transaction.
```yaml
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Edit amount/date/merchant/note/type/splits/budget month, untrack (empty splits), or remove the location (TXN-3, TXN-4, TXN-5)
Source: https://docs.spendrock.com/api/reference/transactions/updateTransaction/
## PATCH /transactions/{transactionId}
`PATCH https://app.spendrock.com/api/v1/transactions/{transactionId}`
Operation ID: `updateTransaction`
Scope: `transactions:write`
Edit amount/date/merchant/note/type/splits/budget month, untrack (empty splits), or remove the location (TXN-3, TXN-4, TXN-5)
Required scope: `transactions:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `transactionId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
description: 'Only the fields present are changed. `splits: []` untracks. `location: null` removes the location.'
properties:
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
maxLength: 120
note:
type: string
maxLength: 2000
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
```
### Responses
#### 200
The transaction and every month it affected, recomputed.
```yaml
type: object
required:
- transaction
- months
- untracked_count
properties:
transaction:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
months:
type: array
description: Every month whose figures changed (0–2), recomputed.
items:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over
budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
untracked_count:
type: integer
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Soft delete, no confirmation; the client offers Undo (TXN-4)
Source: https://docs.spendrock.com/api/reference/transactions/deleteTransaction/
## DELETE /transactions/{transactionId}
`DELETE https://app.spendrock.com/api/v1/transactions/{transactionId}`
Operation ID: `deleteTransaction`
Scope: `transactions:write`
Soft delete, no confirmation; the client offers Undo (TXN-4)
Required scope: `transactions:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `transactionId` | path | yes | string | ULID. |
### Responses
#### 200
The transaction and every month it affected, recomputed.
```yaml
type: object
required:
- transaction
- months
- untracked_count
properties:
transaction:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
months:
type: array
description: Every month whose figures changed (0–2), recomputed.
items:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over
budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
untracked_count:
type: integer
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Restore with original tracking; splits whose item no longer exists in that month are dropped (TXN-4a)
Source: https://docs.spendrock.com/api/reference/transactions/restoreTransaction/
## POST /transactions/{transactionId}/restore
`POST https://app.spendrock.com/api/v1/transactions/{transactionId}/restore`
Operation ID: `restoreTransaction`
Scope: `transactions:write`
Restore with original tracking; splits whose item no longer exists in that month are dropped (TXN-4a)
Required scope: `transactions:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
| `transactionId` | path | yes | string | ULID. |
### Responses
#### 200
The transaction and every month it affected, recomputed.
```yaml
type: object
required:
- transaction
- months
- untracked_count
properties:
transaction:
type: object
required:
- id
- type
- amount
- date
- merchant
- note
- budget_month
- splits
- status
- created_at
- updated_at
- created_by
- created_by_name
- updated_by
- updated_by_name
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
type:
type: string
enum:
- expense
- income
amount:
type: integer
format: int64
description: US cents.
date:
type: string
format: date
merchant:
type: string
note:
type: string
budget_month:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
splits:
type: array
items:
allOf:
- type: object
required:
- item_id
- amount
properties:
item_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
amount:
type: integer
format: int64
description: US cents.
- type: object
required:
- item_name
properties:
item_name:
type: string
status:
type: string
enum:
- tracked
- untracked
- deleted
deleted_at:
type:
- string
- 'null'
format: date-time
location:
anyOf:
- type: object
required:
- lat
- lng
properties:
lat:
type: number
format: double
minimum: -90
maximum: 90
lng:
type: number
format: double
minimum: -180
maximum: 180
accuracy_m:
type: number
format: double
minimum: 0
- type: 'null'
created_by:
type: string
description: User id (Google subject) of whoever added it (HH-13).
created_by_name:
type: string
description: Their first name. Show it only when the household has more than one member (HH-13).
updated_by:
type:
- string
- 'null'
description: Who last edited it; null if never edited.
updated_by_name:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
months:
type: array
description: Every month whose figures changed (0–2), recomputed.
items:
type: object
required:
- month
- left_to_budget
- groups
- favorites
- center
- untracked_count
- copied_from
- created_at
properties:
month:
type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
left_to_budget:
allOf:
- type: integer
format: int64
description: US cents.
description: >-
CALC-1. Positive = left to budget; 0 = "It's a SpendRock budget!"; negative = over
budget.
groups:
type: array
description: Income first, then user order.
items:
allOf:
- type: object
required:
- id
- name
- color
- is_income
properties:
id:
$ref: Id
name:
type: string
color:
$ref: Color
is_income:
type: boolean
- type: object
required:
- items
- totals
properties:
items:
type: array
items:
description: >-
An item as it appears in one month, with computed figures (PLAN §4,
spec/calc-fixtures).
allOf:
- $ref: ItemIdentity
- type: object
required:
- group_id
- planned
- spent
- received
- remaining
- note
properties:
group_id:
$ref: Id
planned:
$ref: Cents
spent:
allOf:
- $ref: Cents
description: Expense items; net of refunds.
received:
allOf:
- $ref: Cents
description: Income items.
remaining:
allOf:
- $ref: Cents
description: >-
Row value. Funds: the running balance. Income: planned − received
(negative = over-received, shown as +$X).
note:
type: string
fund:
type:
- object
- 'null'
description: Present when is_fund.
required:
- carry_in
- adjustments
- ending
properties:
carry_in:
$ref: Cents
adjustments:
type: array
description: '"Balance Updated" entries this month (FUND-5).'
items:
type: object
required:
- amount
- at
properties:
amount:
$ref: Cents
at:
type: string
format: date-time
by:
type: string
ending:
$ref: Cents
totals:
type: object
required:
- planned
- spent
- received
- remaining
properties:
planned:
$ref: Cents
spent:
$ref: Cents
received:
$ref: Cents
remaining:
allOf:
- $ref: Cents
description: >-
Month-only (planned − spent; funds exclude carry-in). Income: planned −
received.
favorites:
type: array
description: Favorited item ids present in this month, in Favorites order (FAV-1).
items:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
center:
type: object
description: Summary doughnut center (SUM-2).
required:
- planned
- spent
- remaining
properties:
planned:
type: integer
format: int64
description: US cents.
spent:
type: integer
format: int64
description: US cents.
remaining:
type: integer
format: int64
description: US cents.
untracked_count:
type: integer
description: Household-wide (TXN-2a badge).
copied_from:
anyOf:
- type: string
pattern: ^[0-9]{4}-(0[1-9]|1[0-2])$
- type: 'null'
created_at:
type: string
format: date-time
untracked_count:
type: integer
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# The full merchant → last-used item map, cached on clients (TXN-9, PERF-5)
Source: https://docs.spendrock.com/api/reference/suggestions/listMerchantHints/
## GET /suggestions/merchants
`GET https://app.spendrock.com/api/v1/suggestions/merchants`
Operation ID: `listMerchantHints`
Scope: `transactions:read`
The full merchant → last-used item map, cached on clients (TXN-9, PERF-5)
Required scope: `transactions:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
### Responses
#### 200
All hints for the household.
```yaml
type: object
required:
- hints
properties:
hints:
type: array
items:
type: object
required:
- merchant
- type
- item_id
properties:
merchant:
type: string
description: Display form of the most recent use.
type:
type: string
enum:
- expense
- income
item_id:
allOf:
- type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
description: >-
The largest split's item on the most recent tracked transaction with this merchant
(TXN-9).
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Get Settings
Source: https://docs.spendrock.com/api/reference/settings/getSettings/
## GET /settings
`GET https://app.spendrock.com/api/v1/settings`
Operation ID: `getSettings`
Scope: `account:read`
Required scope: `account:read` (personal access tokens; sessions have every scope).
### Responses
#### 200
Current user's settings.
```yaml
type: object
required:
- column_view
- location_opt_in
- theme
properties:
column_view:
type: string
enum:
- remaining
- spent
description: LAYOUT-9 global toggle. `spent` shows Spent on expense groups and Received on Income.
location_opt_in:
type: boolean
description: TXN-13; off by default.
theme:
type: string
enum:
- light
- dark
- system
description: UI-20 web color scheme. `system` follows the browser's prefers-color-scheme.
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Update Settings
Source: https://docs.spendrock.com/api/reference/settings/updateSettings/
## PATCH /settings
`PATCH https://app.spendrock.com/api/v1/settings`
Operation ID: `updateSettings`
Scope: `account:write`
Required scope: `account:write` (personal access tokens; sessions have every scope).
### Request body (required)
```yaml
type: object
properties:
column_view:
type: string
enum:
- remaining
- spent
description: LAYOUT-9 global toggle. `spent` shows Spent on expense groups and Received on Income.
location_opt_in:
type: boolean
theme:
type: string
enum:
- light
- dark
- system
description: UI-20 web color scheme. `system` follows the browser's prefers-color-scheme.
```
### Responses
#### 200
Updated.
```yaml
type: object
required:
- column_view
- location_opt_in
- theme
properties:
column_view:
type: string
enum:
- remaining
- spent
description: LAYOUT-9 global toggle. `spent` shows Spent on expense groups and Received on Income.
location_opt_in:
type: boolean
description: TXN-13; off by default.
theme:
type: string
enum:
- light
- dark
- system
description: UI-20 web color scheme. `system` follows the browser's prefers-color-scheme.
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# "Delete all saved locations" (TXN-13)
Source: https://docs.spendrock.com/api/reference/settings/deleteAllLocations/
## DELETE /settings/locations
`DELETE https://app.spendrock.com/api/v1/settings/locations`
Operation ID: `deleteAllLocations`
Scope: `account:write`
"Delete all saved locations" (TXN-13)
Required scope: `account:write` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `X-SpendRock-Household` | header | no | string | The household to act on (HH-12). Defaults to the user's default household. 403 `not_a_member` if the caller isn't a current member (HH-16). |
### Responses
#### 204
All transaction locations removed.
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# My households, default first (HH-11). Archived ones only with include_archived (HH-19).
Source: https://docs.spendrock.com/api/reference/households/listHouseholds/
## GET /households
`GET https://app.spendrock.com/api/v1/households`
Operation ID: `listHouseholds`
Scope: `households:read`
My households, default first (HH-11). Archived ones only with include_archived (HH-19).
Required scope: `households:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `include_archived` | query | no | boolean | |
### Responses
#### 200
Households.
```yaml
type: object
required:
- households
properties:
households:
type: array
items:
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Create an additional household that I own (HH-11)
Source: https://docs.spendrock.com/api/reference/households/createHousehold/
## POST /households
`POST https://app.spendrock.com/api/v1/households`
Operation ID: `createHousehold`
Scope: `households:manage`
Create an additional household that I own (HH-11)
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Request body (required)
```yaml
type: object
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 80
```
### Responses
#### 201
Created.
```yaml
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Get Household
Source: https://docs.spendrock.com/api/reference/households/getHousehold/
## GET /households/{householdId}
`GET https://app.spendrock.com/api/v1/households/{householdId}`
Operation ID: `getHousehold`
Scope: `households:read`
Required scope: `households:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 200
The household, from my point of view.
```yaml
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Rename (owner only, HH-2)
Source: https://docs.spendrock.com/api/reference/households/renameHousehold/
## PATCH /households/{householdId}
`PATCH https://app.spendrock.com/api/v1/households/{householdId}`
Operation ID: `renameHousehold`
Scope: `households:manage`
Rename (owner only, HH-2)
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 80
```
### Responses
#### 200
Renamed.
```yaml
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Delete for all members (owner only; never a personal household). A soft delete; members whose default it was fall back to their personal household (HH-18).
Source: https://docs.spendrock.com/api/reference/households/deleteHousehold/
## DELETE /households/{householdId}
`DELETE https://app.spendrock.com/api/v1/households/{householdId}`
Operation ID: `deleteHousehold`
Scope: `households:manage`
Delete for all members (owner only; never a personal household). A soft delete; members whose default it was fall back to their personal household (HH-18).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 204
Deleted.
#### 403
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Make it my default (HH-10). Not an archived household (`household_archived`).
Source: https://docs.spendrock.com/api/reference/households/setDefaultHousehold/
## POST /households/{householdId}/default
`POST https://app.spendrock.com/api/v1/households/{householdId}/default`
Operation ID: `setDefaultHousehold`
Scope: `households:manage`
Make it my default (HH-10). Not an archived household (`household_archived`).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 204
Default changed.
#### 403
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Hide it from my lists (per user; never deletes anything; HH-19). Not my default (`default_household`), not my last non-archived one (`last_household`).
Source: https://docs.spendrock.com/api/reference/households/archiveHousehold/
## POST /households/{householdId}/archive
`POST https://app.spendrock.com/api/v1/households/{householdId}/archive`
Operation ID: `archiveHousehold`
Scope: `households:manage`
Hide it from my lists (per user; never deletes anything; HH-19). Not my default (`default_household`), not my last non-archived one (`last_household`).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 204
Archived.
#### 403
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Show it in my lists again (HH-19)
Source: https://docs.spendrock.com/api/reference/households/unarchiveHousehold/
## POST /households/{householdId}/unarchive
`POST https://app.spendrock.com/api/v1/households/{householdId}/unarchive`
Operation ID: `unarchiveHousehold`
Scope: `households:manage`
Show it in my lists again (HH-19)
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 204
Unarchived.
#### 403
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Leave (HH-2, HH-3). Owners must transfer first unless alone (a sole owner leaving deletes it); a personal household's owner can't leave it.
Source: https://docs.spendrock.com/api/reference/households/leaveHousehold/
## POST /households/{householdId}/leave
`POST https://app.spendrock.com/api/v1/households/{householdId}/leave`
Operation ID: `leaveHousehold`
Scope: `households:manage`
Leave (HH-2, HH-3). Owners must transfer first unless alone (a sole owner leaving deletes it); a personal household's owner can't leave it.
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Request body
```yaml
type: object
properties:
new_default_household_id:
allOf:
- type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
description: >-
Required (`choose_default`) when leaving my default and more than one other household could
replace it.
```
### Responses
#### 204
Left.
#### 403
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Make another member the owner (owner only, HH-2). Not for personal households.
Source: https://docs.spendrock.com/api/reference/households/transferOwnership/
## POST /households/{householdId}/transfer
`POST https://app.spendrock.com/api/v1/households/{householdId}/transfer`
Operation ID: `transferOwnership`
Scope: `households:manage`
Make another member the owner (owner only, HH-2). Not for personal households.
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
required:
- user_id
properties:
user_id:
type: string
```
### Responses
#### 204
Transferred.
#### 403
Error.
#### 404
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# List Members
Source: https://docs.spendrock.com/api/reference/households/listMembers/
## GET /households/{householdId}/members
`GET https://app.spendrock.com/api/v1/households/{householdId}/members`
Operation ID: `listMembers`
Scope: `households:read`
Required scope: `households:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 200
Current members, oldest first.
```yaml
type: object
required:
- members
properties:
members:
type: array
items:
type: object
required:
- user_id
- name
- first_name
- email
- role
- joined_at
properties:
user_id:
type: string
name:
type: string
first_name:
type: string
description: Shown on transactions (HH-13).
email:
type: string
role:
type: string
enum:
- owner
- member
joined_at:
type: string
format: date-time
```
#### 403
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Remove a member (owner only, HH-2). They lose access on their next request (HH-16).
Source: https://docs.spendrock.com/api/reference/households/removeMember/
## DELETE /households/{householdId}/members/{userId}
`DELETE https://app.spendrock.com/api/v1/households/{householdId}/members/{userId}`
Operation ID: `removeMember`
Scope: `households:manage`
Remove a member (owner only, HH-2). They lose access on their next request (HH-16).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
| `userId` | path | yes | string | The member's user id (Google subject). |
### Responses
#### 204
Removed.
#### 403
Error.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Pending, unexpired invites with their links (HH-9)
Source: https://docs.spendrock.com/api/reference/invites/listHouseholdInvites/
## GET /households/{householdId}/invites
`GET https://app.spendrock.com/api/v1/households/{householdId}/invites`
Operation ID: `listHouseholdInvites`
Scope: `households:read`
Pending, unexpired invites with their links (HH-9)
Required scope: `households:read` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Responses
#### 200
Invites.
```yaml
type: object
required:
- invites
properties:
invites:
type: array
items:
type: object
required:
- id
- household_id
- household_name
- email
- inviter_id
- inviter_name
- created_at
- expires_at
- status
- member_names
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_name:
type: string
email:
type: string
description: Normalized (lower case).
inviter_id:
type: string
inviter_name:
type: string
description: First name.
created_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
status:
type: string
enum:
- pending
- accepted
- declined
- revoked
- expired
token:
type: string
description: The link secret. Only for the household's members and the invitee.
path:
type: string
description: '`/invite/{token}`, relative to the web origin. Present with `token`.'
member_names:
type: array
description: Current members' first names (for the HH-8 explanation).
items:
type: string
```
#### 403
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Invite by email (any member, HH-4). Expires in 7 days; re-inviting the same email replaces the pending invite (HH-9).
Source: https://docs.spendrock.com/api/reference/invites/createInvite/
## POST /households/{householdId}/invites
`POST https://app.spendrock.com/api/v1/households/{householdId}/invites`
Operation ID: `createInvite`
Scope: `households:manage`
Invite by email (any member, HH-4). Expires in 7 days; re-inviting the same email replaces the pending invite (HH-9).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
### Request body (required)
```yaml
type: object
required:
- email
properties:
email:
type: string
maxLength: 254
```
### Responses
#### 201
Created. `token`/`path` form the copyable link.
```yaml
type: object
required:
- id
- household_id
- household_name
- email
- inviter_id
- inviter_name
- created_at
- expires_at
- status
- member_names
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_name:
type: string
email:
type: string
description: Normalized (lower case).
inviter_id:
type: string
inviter_name:
type: string
description: First name.
created_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
status:
type: string
enum:
- pending
- accepted
- declined
- revoked
- expired
token:
type: string
description: The link secret. Only for the household's members and the invitee.
path:
type: string
description: '`/invite/{token}`, relative to the web origin. Present with `token`.'
member_names:
type: array
description: Current members' first names (for the HH-8 explanation).
items:
type: string
```
#### 403
Error.
#### 409
Error.
#### 422
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Revoke (the owner, or whoever sent it; HH-2)
Source: https://docs.spendrock.com/api/reference/invites/revokeInvite/
## DELETE /households/{householdId}/invites/{inviteId}
`DELETE https://app.spendrock.com/api/v1/households/{householdId}/invites/{inviteId}`
Operation ID: `revokeInvite`
Scope: `households:manage`
Revoke (the owner, or whoever sent it; HH-2)
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `householdId` | path | yes | string | ULID. |
| `inviteId` | path | yes | string | ULID. |
### Responses
#### 204
Revoked.
#### 403
Error.
#### 404
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Pending invites addressed to my email (the HH-5 banner)
Source: https://docs.spendrock.com/api/reference/invites/listMyInvites/
## GET /invites
`GET https://app.spendrock.com/api/v1/invites`
Operation ID: `listMyInvites`
Scope: `households:read`
Pending invites addressed to my email (the HH-5 banner)
Required scope: `households:read` (personal access tokens; sessions have every scope).
### Responses
#### 200
Invites.
```yaml
type: object
required:
- invites
properties:
invites:
type: array
items:
type: object
required:
- id
- household_id
- household_name
- email
- inviter_id
- inviter_name
- created_at
- expires_at
- status
- member_names
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_name:
type: string
email:
type: string
description: Normalized (lower case).
inviter_id:
type: string
inviter_name:
type: string
description: First name.
created_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
status:
type: string
enum:
- pending
- accepted
- declined
- revoked
- expired
token:
type: string
description: The link secret. Only for the household's members and the invitee.
path:
type: string
description: '`/invite/{token}`, relative to the web origin. Present with `token`.'
member_names:
type: array
description: Current members' first names (for the HH-8 explanation).
items:
type: string
```
#### 403
The caller can't do this: e.g. `insufficient_scope` (a personal access token without the
scope in `details.required_scope`), `session_required`, `not_a_member`.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Look up an invite link in any status (HH-6). No sign-in needed; the token isn't echoed.
Source: https://docs.spendrock.com/api/reference/invites/getInvite/
## GET /invites/{token}
`GET https://app.spendrock.com/api/v1/invites/{token}`
Operation ID: `getInvite`
Scope: `public`
Look up an invite link in any status (HH-6). No sign-in needed; the token isn't echoed.
No authentication needed.
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `token` | path | yes | string | The secret from the invite link (`/invite/{token}`). |
### Responses
#### 200
The invite.
```yaml
type: object
required:
- id
- household_id
- household_name
- email
- inviter_id
- inviter_name
- created_at
- expires_at
- status
- member_names
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
household_name:
type: string
email:
type: string
description: Normalized (lower case).
inviter_id:
type: string
inviter_name:
type: string
description: First name.
created_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
status:
type: string
enum:
- pending
- accepted
- declined
- revoked
- expired
token:
type: string
description: The link secret. Only for the household's members and the invitee.
path:
type: string
description: '`/invite/{token}`, relative to the web origin. Present with `token`.'
member_names:
type: array
description: Current members' first names (for the HH-8 explanation).
items:
type: string
```
#### 404
Error.
---
# Join (HH-8). My email must match the invite (`invite_email_mismatch`).
Source: https://docs.spendrock.com/api/reference/invites/acceptInvite/
## POST /invites/{token}/accept
`POST https://app.spendrock.com/api/v1/invites/{token}/accept`
Operation ID: `acceptInvite`
Scope: `households:manage`
Join (HH-8). My email must match the invite (`invite_email_mismatch`).
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `token` | path | yes | string | The secret from the invite link (`/invite/{token}`). |
### Request body
```yaml
type: object
properties:
make_default:
type: boolean
default: false
description: Make it my default household.
archive_current:
type: boolean
default: false
description: Archive my current default. Implies make_default.
```
### Responses
#### 200
Joined.
```yaml
type: object
required:
- id
- name
- role
- personal
- default
- archived
- member_count
- joined_at
properties:
id:
type: string
description: ULID.
pattern: ^[0-9A-HJKMNP-TV-Z]{26}$
name:
type: string
role:
type: string
enum:
- owner
- member
personal:
type: boolean
description: Created at its owner's first sign-in; can't be deleted (HH-18).
default:
type: boolean
description: My default (HH-10).
archived:
type: boolean
description: Archived by me (per user
HH-19).: null
member_count:
type: integer
joined_at:
type: string
format: date-time
```
#### 403
Error.
#### 404
Error.
#### 410
Expired, revoked, declined, or already accepted (`invite_unavailable`, `details.status`).
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.
---
# Decline Invite
Source: https://docs.spendrock.com/api/reference/invites/declineInvite/
## POST /invites/{token}/decline
`POST https://app.spendrock.com/api/v1/invites/{token}/decline`
Operation ID: `declineInvite`
Scope: `households:manage`
Required scope: `households:manage` (personal access tokens; sessions have every scope).
### Parameters
| Name | In | Required | Schema | Description |
|---|---|---|---|---|
| `token` | path | yes | string | The secret from the invite link (`/invite/{token}`). |
### Responses
#### 204
Declined.
#### 403
Error.
#### 404
Error.
#### 410
Error.
#### 429
Too many requests (`rate_limited`). Retry after `Retry-After` seconds.