Four reads and one write. The examples assume the key is in an environment variable named PACE_KEY. Amounts are US dollars as decimal numbers. Dates are YYYY-MM-DD in the user's timezone; timestamps are ISO 8601 in UTC.
GET/v1/me
Who the key belongs to.
Returns the first name if the user entered one, the timezone the week is computed in, the subscription status, and the display prefix of the key you are holding. Nothing here identifies a bank account.
Request
curl https://api.pace.money/api/v1/me \
-H "Authorization: Bearer $PACE_KEY"
Response
{
"first_name": "Sam",
"timezone": "America/Chicago",
"currency": "USD",
"subscription": { "status": "active" },
"key": {
"prefix": "pace_live_a1b2",
"name": "Claude",
"created_at": "2026-09-19T14:02:11Z"
}
}
GET/v1/number
The weekly number. This is the one that matters.
left_this_week is what is safe to spend for the rest of the current week after bills and savings goals are counted, floored at zero once the week is overspent. It is the same computation the app shows on its home screen. Weeks run Monday to Sunday in the subscriber's timezone. status is one of ahead, on_pace, behind, or off_track. If the subscriber has not finished setting up a budget in the app, this endpoint answers 409 starter_budget_missing.
Request
curl https://api.pace.money/api/v1/number \
-H "Authorization: Bearer $PACE_KEY"
Response
{
"left_this_week": 212.40,
"weekly_number": 350.00,
"spent_this_week": 137.60,
"week_start": "2026-09-14",
"week_end": "2026-09-20",
"status": "on_pace",
"bills_reserved_this_month": 1840.00,
"savings_reserved_this_month": 400.00,
"as_of": "2026-09-19T14:02:11Z"
}
GET/v1/bills
Upcoming bills.
Recurring bills the app has forecast for the current month. The optional until=YYYY-MM-DD query narrows the window to bills due on or before that date; without it the window runs to the last day of the current month, and it cannot extend past the month. The response echoes the until actually applied. status is upcoming, overdue, or paid. An until that is not a real YYYY-MM-DD date, including an impossible one such as 2026-02-31, gets 400 invalid_until; a subscriber with no budget set up yet gets 409 starter_budget_missing.
Request
curl "https://api.pace.money/api/v1/bills?until=2026-09-30" \
-H "Authorization: Bearer $PACE_KEY"
Response
{
"bills": [
{ "name": "Electric", "amount": 92.15, "due_date": "2026-09-22", "status": "upcoming" },
{ "name": "Internet", "amount": 65.00, "due_date": "2026-09-25", "status": "upcoming" }
],
"total_upcoming": 157.15,
"until": "2026-09-30"
}
GET/v1/spending
Recent transactions.
The optional days query sets how far back to look, counting today: default 7, minimum 1, maximum 90. Transactions come back newest first, at most 100 of them, and the response states the from and to dates actually covered. source is bank for transactions that arrived through a linked account and manual for ones the user typed or an assistant logged. classification is need, want, or null when the user has not sorted it yet. A negative amount is a refund or credit. Bank rows carry the cleaned merchant name only; nothing about the institution or account.
Request
curl "https://api.pace.money/api/v1/spending?days=7" \
-H "Authorization: Bearer $PACE_KEY"
Response
{
"transactions": [
{
"id": "txn_01j8f3k9m2",
"date": "2026-09-18",
"merchant": "Trader Joe's",
"amount": 64.20,
"source": "bank",
"classification": "need"
},
{
"id": "txn_01j8f1q7c4",
"date": "2026-09-17",
"merchant": "Coffee",
"amount": 5.75,
"source": "manual",
"classification": "want"
}
],
"total": 137.60,
"from": "2026-09-13",
"to": "2026-09-19"
}
POST/v1/purchases
Log a purchase. The only write.
Creates a manual transaction, exactly as if the user had typed it into the app, and answers 201 with the updated weekly number in the same shape as /v1/number so the assistant can say what the purchase did to the week in one round trip. amount and merchant (up to 120 characters) are required. amount is in dollars, rounded to cents, and must come to at least 0.01 after rounding. date defaults to today in the subscriber's timezone and cannot be later than that day; tomorrow is rejected with invalid_date, and so is a date that does not exist on the calendar, such as 2026-02-31. note is optional free text up to 280 characters; a longer note is rejected with invalid_note, not truncated. If the subscriber has not finished setting up a budget in the app there is no weekly number to log against, so the call answers 409 starter_budget_missing and records nothing. Every call needs an Idempotency-Key header; see the retries note below.
Request
curl -X POST https://api.pace.money/api/v1/purchases \
-H "Authorization: Bearer $PACE_KEY" \
-H "Idempotency-Key: 6f1c2a9e-3b7d-4e58-9c0a-2d5b8f7e1a43" \
-H "Content-Type: application/json" \
-d '{ "amount": 18.50, "merchant": "Lunch", "note": "with Priya" }'
Response
{
"transaction": {
"id": "txn_01j8f5z2t8",
"date": "2026-09-19",
"merchant": "Lunch",
"amount": 18.50,
"source": "manual",
"classification": null
},
"number": {
"left_this_week": 193.90,
"weekly_number": 350.00,
"spent_this_week": 156.10,
"week_start": "2026-09-14",
"week_end": "2026-09-20",
"status": "on_pace",
"bills_reserved_this_month": 1840.00,
"savings_reserved_this_month": 400.00,
"as_of": "2026-09-19T14:05:40Z"
}
}
Retries and the Idempotency-Key header
A network timeout on a write leaves you unsure whether the purchase was logged, and logging it twice would move the number twice. So every POST /v1/purchases needs an Idempotency-Key header: a value you choose, 1 to 128 characters, that identifies this one purchase. A UUID is fine. Send the same key on every retry of the same purchase. If Pace has already seen that key for this subscriber, it returns the original { transaction, number } with status 201 and logs nothing new. The replay is the exact body the first write returned, even if the subscriber has since reclassified or edited that transaction in the app; read it as a receipt, not as the current state. The Idempotency-Replayed response header is true on a replay and false on the first write. A different purchase needs a different key. A missing header gets 400 idempotency_key_required.