SpendRock Docs
Items

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

POST
/months/{month}/items/{itemId}/harvest

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

Authorization

bearerAuth
AuthorizationBearer <token>

Authorization: Bearer <token>: a mobile session token, or a personal access token (srp_…) created in Account → API tokens (DEV-1).

Personal access tokens:

  • Act as their user. The household comes from X-SpendRock-Household, else the user's default.
  • Carry scopes (DEV-2), and each operation's description names the one it needs: budget:read / budget:write (months, groups, items, funds, favorites, harvest), transactions:read / transactions:write (transactions, merchant suggestions), households:read / households:manage (households, members, invites), account:read / account:write (me, settings). A write scope includes its read scope. A call outside the token's scopes gets 403 insufficient_scope, with the needed scope in details.required_scope.
  • Can't manage tokens or passwords (403 session_required).
  • May expire (401 unauthenticated afterwards).
  • Are rate-limited (DEV-3): 120 requests/minute per token with bursts up to 120. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (seconds until the bucket is full again); over the limit the answer is 429 rate_limited with Retry-After (seconds). Limits are kept per server instance, so they're approximate.

Sessions (web cookie or mobile bearer) have every scope and aren't limited this way. Cross-origin browser calls (CORS, for the API docs playground) must use a bearer token; cookies are never accepted cross-origin.

In: header

Path Parameters

month*string
Match^[0-9]{4}-(0[1-9]|1[0-2])$
itemId*string

ULID.

Match^[0-9A-HJKMNP-TV-Z]{26}$

Header Parameters

X-SpendRock-Household?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).

Match^[0-9A-HJKMNP-TV-Z]{26}$

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/months/2026-12/items/01J8Z3N4Q5R6S7T8V9W0X1Y2Z3/harvest"
{  "month": {    "month": "2026-12",    "left_to_budget": 1234,    "groups": [      {        "id": "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3",        "name": "string",        "color": "string",        "is_income": true,        "items": [          {            "id": "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3",            "name": "string",            "kind": "income",            "is_fund": true,            "favorite": true,            "fund_target": 0,            "group_id": "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3",            "planned": 1234,            "spent": 1234,            "received": 1234,            "remaining": 1234,            "note": "string",            "fund": {              "carry_in": 1234,              "adjustments": [                {                  "amount": 1234,                  "at": "2019-08-24T14:15:22Z",                  "by": "string"                }              ],              "ending": 1234            }          }        ],        "totals": {          "planned": 1234,          "spent": 1234,          "received": 1234,          "remaining": 1234        }      }    ],    "favorites": [      "01J8Z3N4Q5R6S7T8V9W0X1Y2Z3"    ],    "center": {      "planned": 1234,      "spent": 1234,      "remaining": 1234    },    "untracked_count": 0,    "copied_from": "2026-12",    "created_at": "2019-08-24T14:15:22Z"  },  "harvested": 1234,  "previous_planned": 1234}