BuddyPad API
Quickstart Reference Events and webhooks Errors Rate limits Versioning and changelog Security
Reference
Every call goes to https://api.buddypad.builtbymo.dev/v1 with Authorization: Bearer bp_.... It takes JSON and answers JSON. The examples below are real answers from a test account.
Error codes
Every error has the same shape. See Errors .
unauthorized forbidden_scope not_found conflict invalid rate_limited cursor_expired internal
Event types
item.created item.updated item.completed item.deleted item.shared item.unshared comment.created reaction.set nudge.received checkin.received checkin.answered suggestion.received suggestion.accepted suggestion.declined pair.started pair.ended
Profile
GET /v1/me
Your profile
Permission: read
Example
curl https://api.buddypad.builtbymo.dev/v1/me \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"id": "00000000-0000-4000-8000-00000000a001",
"display_name": "Ana",
"avatar_path": null
}
Responses
200Your profile 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
Pairs
GET /v1/pairs
Your active buddy pairs, each with the buddy's name and the pair's streak
Permission: read
Parameters Name In limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl https://api.buddypad.builtbymo.dev/v1/pairs \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"week_start": 1,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"buddy": {
"id": "00000000-0000-4000-8000-00000000a002",
"display_name": "Ben",
"avatar_path": null
},
"streak": 1,
"current_week": "2026-11-02"
},
{
"id": "e6317f9e-5358-4c3d-8352-80fc7f6cb815",
"week_start": 1,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"buddy": {
"id": "00000000-0000-4000-8000-00000000a003",
"display_name": "Cy",
"avatar_path": null
},
"streak": 0,
"current_week": "2026-11-02"
}
],
"next_cursor": null
}
Responses
200A page of pairs 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/pairs/{pair_id}
One active pair
Permission: read
Parameters Name In pair_idpath required
Example
curl https://api.buddypad.builtbymo.dev/v1/pairs/6d8afa93-4409-4e49-a788-76e4a84a75f5 \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"week_start": 1,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"buddy": {
"id": "00000000-0000-4000-8000-00000000a002",
"display_name": "Ben",
"avatar_path": null
},
"streak": 1,
"current_week": "2026-11-02"
}
Responses
200The pair 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
Goals
GET /v1/items
Goals you can see (yours, and your buddies' goals shared into a pair you are in)
Permission: read
Parameters Name In pair_idquery optional. Only goals shared into this pair. ownerquery optional. Only your goals (me) or only your buddies' (buddy). limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl "https://api.buddypad.builtbymo.dev/v1/items?pair_id=6d8afa93-4409-4e49-a788-76e4a84a75f5" \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"owner_id": "00000000-0000-4000-8000-00000000a002",
"title": "Send 10 applications",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"updated_at": "2026-11-02T07:42:58.347663+00:00",
"deleted_at": null
},
{
"id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Run 3x",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"updated_at": "2026-11-02T07:42:58.347663+00:00",
"deleted_at": null
}
],
"next_cursor": null
}
Responses
200A page of goals 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items
Add a goal, optionally shared into a pair
Send your own id, or one is derived from the Idempotency-Key, so a retry creates the goal once. A replay returns the goal as it is now, with the header Idempotent-Replayed. The status cannot be done: complete it.
Permission: goals
Parameters Name In Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type idstring (uuid) optional pair_idstring (uuid) optional titlestring required contentarray of object optional statusstring optional blocked_reasonstring or null optional due_atstring or null (date-time) optional recurrenceobject or null optional progressobject or null optional
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"title":"Read 20 pages","pair_id":"6d8afa93-4409-4e49-a788-76e4a84a75f5"}'
Answers 201:
{
"id": "f62cf6ea-b23b-5355-b541-5a1cf28420b1",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Read 20 pages",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.408067+00:00",
"updated_at": "2026-11-02T07:43:00.408067+00:00",
"deleted_at": null
}
Responses
201The goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/items/{item_id}
One goal
Permission: read
Parameters Name In item_idpath required
Example
curl https://api.buddypad.builtbymo.dev/v1/items/d62e1332-8734-4daf-b7b8-01c942af9821 \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Run 3x",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"updated_at": "2026-11-02T07:42:58.347663+00:00",
"deleted_at": null
}
Responses
200The goal 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
PATCH /v1/items/{item_id}
Change your own goal
Changes only the fields you send. status may be todo, doing or blocked; completing is POST /items/{item_id}/complete. blocked_reason must be null unless the status is blocked, so send "blocked_reason": null when you leave blocked.
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type titlestring optional contentarray of object optional statusstring optional blocked_reasonstring or null optional due_atstring or null (date-time) optional recurrenceobject or null optional progressobject or null optional
Example
curl -X PATCH https://api.buddypad.builtbymo.dev/v1/items/6f5aafd7-47ae-573f-85de-39af31cebd5a \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"title":"Renamed"}'
Answers 200:
{
"id": "6f5aafd7-47ae-573f-85de-39af31cebd5a",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Renamed",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.486441+00:00",
"updated_at": "2026-11-02T07:43:00.504809+00:00",
"deleted_at": null
}
Responses
200The goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
DELETE /v1/items/{item_id}
Delete your own goal
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X DELETE https://api.buddypad.builtbymo.dev/v1/items/c47747ae-f55d-59fd-9683-bfd135e36a33 \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 204 with no body.
Responses
204Deleted 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/complete
Complete your own goal (a recurring goal rolls forward to its next date)
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/8453da75-ff76-503b-a8cb-7058ae16b55d/complete \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"id": "8453da75-ff76-503b-a8cb-7058ae16b55d",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Contract goal",
"content": [],
"status": "done",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.58146+00:00",
"updated_at": "2026-11-02T07:43:00.59861+00:00",
"deleted_at": null
}
Responses
200The goal after completing 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/share
Share your own goal into a pair
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type pair_idstring (uuid) required
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/9c2d75df-d29a-5c96-88f9-0b32fc39c660/share \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pair_id":"e6317f9e-5358-4c3d-8352-80fc7f6cb815"}'
Answers 200:
{
"id": "9c2d75df-d29a-5c96-88f9-0b32fc39c660",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Contract goal",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.620191+00:00",
"updated_at": "2026-11-02T07:43:00.620191+00:00",
"deleted_at": null
}
Responses
200The goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
DELETE /v1/items/{item_id}/share/{pair_id}
Stop sharing your own goal into a pair
Permission: goals
Parameters Name In item_idpath required pair_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X DELETE https://api.buddypad.builtbymo.dev/v1/items/1627cd14-8fb0-52e8-b264-2f49caf66f0b/share/6d8afa93-4409-4e49-a788-76e4a84a75f5 \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"id": "1627cd14-8fb0-52e8-b264-2f49caf66f0b",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Contract goal",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.647088+00:00",
"updated_at": "2026-11-02T07:43:00.647088+00:00",
"deleted_at": null
}
Responses
200The goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/copy
Copy your own goal into a pair as a new goal
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type pair_idstring (uuid) required new_idstring (uuid) optional
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/73b2bf69-d68c-5c0d-96f9-fb1368c55490/copy \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pair_id":"e6317f9e-5358-4c3d-8352-80fc7f6cb815"}'
Answers 201:
{
"id": "2ce181fd-6931-5e44-aca8-3ee4b8bebeb8",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Contract goal",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.712169+00:00",
"updated_at": "2026-11-02T07:43:00.712169+00:00",
"deleted_at": null
}
Responses
201The new goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/move
Move your own goal from one pair to another
Permission: goals
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type from_pair_idstring (uuid) required to_pair_idstring (uuid) required
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/9518aeb5-fd72-577d-9299-61377932e713/move \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"from_pair_id":"6d8afa93-4409-4e49-a788-76e4a84a75f5","to_pair_id":"e6317f9e-5358-4c3d-8352-80fc7f6cb815"}'
Answers 200:
{
"id": "9518aeb5-fd72-577d-9299-61377932e713",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Contract goal",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:43:00.728515+00:00",
"updated_at": "2026-11-02T07:43:00.728515+00:00",
"deleted_at": null
}
Responses
200The goal 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/suggestions/{suggestion_id}/accept
Accept a suggestion on your own goal (the goal changes)
Permission: goals
Parameters Name In suggestion_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/suggestions/00000000-0000-4000-8000-0000000c5001/accept \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"owner_id": "00000000-0000-4000-8000-00000000a001",
"title": "Run 4x",
"content": [],
"status": "todo",
"blocked_reason": null,
"due_at": null,
"recurrence": null,
"progress": null,
"created_at": "2026-11-02T07:42:58.347663+00:00",
"updated_at": "2026-11-02T07:43:00.95491+00:00",
"deleted_at": null
}
Responses
200The goal after the change 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/suggestions/{suggestion_id}/decline
Decline a suggestion on your own goal
Permission: goals
Parameters Name In suggestion_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/suggestions/00000000-0000-4000-8000-0000000c5002/decline \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"id": "00000000-0000-4000-8000-0000000c5002",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"suggester_id": "00000000-0000-4000-8000-00000000a002",
"patch": {
"title": "Run 5x"
},
"status": "declined",
"created_at": "2026-11-02T07:42:58.347663+00:00",
"decided_at": "2026-11-02T07:43:00.967893+00:00",
"updated_at": "2026-11-02T07:43:00.972025+00:00"
}
Responses
200The suggestion 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
Buddy
POST /v1/pairs/{pair_id}/nudges
Nudge your buddy about one of their shared goals (the goal owner's nudge limit applies)
The Idempotency-Key is the nudge's id. A retry with the same key returns the same nudge.
Permission: buddy
Parameters Name In pair_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type item_idstring (uuid) required messagestring required
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/pairs/6d8afa93-4409-4e49-a788-76e4a84a75f5/nudges \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"item_id":"a37d1e68-06e6-4a5f-bbcf-2080b74f201f","message":"You got this"}'
Answers 201:
{
"id": "6c4b5550-81e8-4383-bff9-9bf07e82452b",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"sender_id": "00000000-0000-4000-8000-00000000a001",
"message": "You got this",
"created_at": "2026-11-02T07:43:00.306859+00:00"
}
Responses
201The nudge 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
PUT /v1/items/{item_id}/reaction
Set or clear your reaction on a goal shared into a pair you are in
Permission: buddy
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type pair_idstring (uuid) required emojistring required activeboolean required
Example
curl -X PUT https://api.buddypad.builtbymo.dev/v1/items/a37d1e68-06e6-4a5f-bbcf-2080b74f201f/reaction \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pair_id":"6d8afa93-4409-4e49-a788-76e4a84a75f5","emoji":"🔥","active":true}'
Answers 200:
{
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"user_id": "00000000-0000-4000-8000-00000000a001",
"emoji": "🔥",
"active": true,
"updated_at": "2026-11-02T07:43:00.815037+00:00"
}
Responses
200The reaction 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/checkins
Ask your buddy how a shared goal is going (one open check-in per goal per pair)
Permission: buddy
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type idstring (uuid) optional pair_idstring (uuid) required
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/a37d1e68-06e6-4a5f-bbcf-2080b74f201f/checkins \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pair_id":"6d8afa93-4409-4e49-a788-76e4a84a75f5"}'
Answers 201:
{
"id": "7f890d6b-d5e6-57bd-8204-49e4219bdafd",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"asker_id": "00000000-0000-4000-8000-00000000a001",
"asked_at": "2026-11-02T07:43:00.826134+00:00",
"response": null,
"note": null,
"responded_at": null,
"updated_at": "2026-11-02T07:43:00.826134+00:00"
}
Responses
201The check-in 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/items/{item_id}/suggestions
Suggest an edit to your buddy's shared goal (a new title, a new due date, or both)
Permission: buddy
Parameters Name In item_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type idstring (uuid) optional pair_idstring (uuid) required patchobject required
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/items/a37d1e68-06e6-4a5f-bbcf-2080b74f201f/suggestions \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"pair_id":"6d8afa93-4409-4e49-a788-76e4a84a75f5","patch":{"title":"Send 12 applications"}}'
Answers 201:
{
"id": "29f861dd-80ff-58a9-8e5f-838f291fb57b",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"suggester_id": "00000000-0000-4000-8000-00000000a001",
"patch": {
"title": "Send 12 applications"
},
"status": "pending",
"created_at": "2026-11-02T07:43:00.85656+00:00",
"decided_at": null,
"updated_at": "2026-11-02T07:43:00.85656+00:00"
}
Responses
201The suggestion 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/nudges
Nudges on goals shared into your pairs, sent by you or to you
Permission: read
Parameters Name In pair_idquery optional item_idquery optional limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl https://api.buddypad.builtbymo.dev/v1/nudges \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "08438569-a440-400a-b8bb-51ec0d53a78b",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"sender_id": "00000000-0000-4000-8000-00000000a002",
"message": "Go Ana",
"created_at": "2026-11-02T07:42:58.347663+00:00"
},
{
"id": "6c4b5550-81e8-4383-bff9-9bf07e82452b",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "a37d1e68-06e6-4a5f-bbcf-2080b74f201f",
"sender_id": "00000000-0000-4000-8000-00000000a001",
"message": "You got this",
"created_at": "2026-11-02T07:43:00.306859+00:00"
}
],
"next_cursor": null
}
Responses
200A page of nudges 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/checkins
Check-ins on goals shared into your pairs
Permission: read
Parameters Name In pair_idquery optional item_idquery optional limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl "https://api.buddypad.builtbymo.dev/v1/checkins?item_id=d62e1332-8734-4daf-b7b8-01c942af9821" \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "00000000-0000-4000-8000-0000000c0001",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"asker_id": "00000000-0000-4000-8000-00000000a002",
"asked_at": "2026-11-02T07:42:58.347663+00:00",
"response": null,
"note": null,
"responded_at": null,
"updated_at": "2026-11-02T07:42:58.347663+00:00"
}
],
"next_cursor": null
}
Responses
200A page of check-ins 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/checkins/{checkin_id}/response
Answer a check-in on your own goal
Permission: buddy
Parameters Name In checkin_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type responsestring required notestring or null optional
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/checkins/00000000-0000-4000-8000-0000000c0001/response \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"response":"on_track","note":"Twice so far"}'
Answers 200:
{
"id": "00000000-0000-4000-8000-0000000c0001",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"asker_id": "00000000-0000-4000-8000-00000000a002",
"asked_at": "2026-11-02T07:42:58.347663+00:00",
"response": "on_track",
"note": "Twice so far",
"responded_at": "2026-11-02T07:43:00.916697+00:00",
"updated_at": "2026-11-02T07:43:00.921277+00:00"
}
Responses
200The check-in 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/suggestions
Edit suggestions on goals shared into your pairs
Permission: read
Parameters Name In pair_idquery optional item_idquery optional limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl https://api.buddypad.builtbymo.dev/v1/suggestions \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "00000000-0000-4000-8000-0000000c5001",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"suggester_id": "00000000-0000-4000-8000-00000000a002",
"patch": {
"title": "Run 4x"
},
"status": "pending",
"created_at": "2026-11-02T07:42:58.347663+00:00",
"decided_at": null,
"updated_at": "2026-11-02T07:42:58.347663+00:00"
},
{
"id": "00000000-0000-4000-8000-0000000c5002",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"item_id": "d62e1332-8734-4daf-b7b8-01c942af9821",
"suggester_id": "00000000-0000-4000-8000-00000000a002",
"patch": {
"title": "Run 5x"
},
"status": "pending",
"created_at": "2026-11-02T07:42:58.347663+00:00",
"decided_at": null,
"updated_at": "2026-11-02T07:42:58.347663+00:00"
}
],
"next_cursor": null
}
Responses
200A page of suggestions 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
Events
GET /v1/events
What changed, in order, exactly once
Thin events (a type, ids, the actor and a time; never a copy of the object). Fetch the object to see it: you get it only if you can still see it. Keep next_cursor and pass it as after. Omit after to start from the oldest event you still have; pass after=head to start from now. Events are kept 30 days. When events after your cursor have aged out you get 410 cursor_expired: re-read the current state, then continue from after=head.
Permission: read
Parameters Name In afterquery optional. A next_cursor from an earlier page, or head. limitquery optional
Example
curl https://api.buddypad.builtbymo.dev/v1/events \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "1",
"type": "pair.started",
"actor_id": "00000000-0000-4000-8000-00000000a002",
"object_type": "pair",
"object_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"pair_id": "6d8afa93-4409-4e49-a788-76e4a84a75f5",
"occurred_at": "2026-11-02T07:42:58.347663+00:00"
},
{
"id": "3",
"type": "pair.started",
"actor_id": "00000000-0000-4000-8000-00000000a003",
"object_type": "pair",
"object_id": "e6317f9e-5358-4c3d-8352-80fc7f6cb815",
"pair_id": "e6317f9e-5358-4c3d-8352-80fc7f6cb815",
"occurred_at": "2026-11-02T07:42:58.347663+00:00"
}
],
"next_cursor": "e1.WyIxNTQyIiwiNzQiXQ"
}
Responses
200A page of events 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 410Events after this cursor were deleted (code cursor_expired). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
Webhooks
GET /v1/webhooks
Your webhooks. A secret is never listed.
Permission: webhooks
Parameters Name In limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl https://api.buddypad.builtbymo.dev/v1/webhooks \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [
{
"id": "909aec92-ab10-5af8-96c4-89975f6641db",
"url": "https://example.com/buddypad",
"event_types": [
"item.completed"
],
"active": true,
"paused_reason": null,
"failing_since": null,
"last_delivered_at": null,
"previous_secret_expires_at": null,
"created_at": "2026-11-02T07:43:01.01442+00:00",
"updated_at": "2026-11-02T07:43:01.01442+00:00"
}
],
"next_cursor": null
}
Responses
200A page of webhooks 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/webhooks
Register a webhook. The signing secret comes back once, in this response.
The url must be https and must point at a public address. At most 10 webhooks per person. Only events that happen after this call are delivered; read GET /events for anything earlier. A retry with the same Idempotency-Key answers with the same webhook and "secret": null, because a secret is shown once. If that response was lost, call rotate-secret.
Permission: webhooks
Parameters Name In Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Body Field Type idstring (uuid) optional urlstring required. https, and a public address. No redirects are followed. event_typesarray of string required. The event types to send (see Event).
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/webhooks \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/buddypad","event_types":["item.completed","nudge.received"]}'
Answers 201:
{
"id": "c44090e3-648d-53eb-a7ed-af825c0a14e3",
"url": "https://example.com/buddypad",
"active": true,
"secret": "whsec_0123456789abcdefghijklmnopqrstuvwxyzABCDEFG",
"created_at": "2026-11-02T07:43:01.052089+00:00",
"updated_at": "2026-11-02T07:43:01.052089+00:00",
"event_types": [
"item.completed",
"nudge.received"
],
"failing_since": null,
"paused_reason": null,
"last_delivered_at": null,
"previous_secret_expires_at": null
}
Responses
201The webhook and its secret 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
DELETE /v1/webhooks/{webhook_id}
Delete a webhook and its secrets
Permission: webhooks
Parameters Name In webhook_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X DELETE https://api.buddypad.builtbymo.dev/v1/webhooks/dcfa8529-8822-5cb0-9bcd-3021d7fc7eed \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 204 with no body.
Responses
204Deleted 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/webhooks/{webhook_id}/rotate-secret
Get a new signing secret. The old one keeps working for 24 hours.
For those 24 hours each delivery carries two v1 signatures, so a receiver on either secret verifies. A retry with the same Idempotency-Key rotates nothing and answers with "secret": null.
Permission: webhooks
Parameters Name In webhook_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/webhooks/f39b0cf3-3757-5e0c-a779-f853e9ad0964/rotate-secret \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"secret": "whsec_0123456789abcdefghijklmnopqrstuvwxyzABCDEFG",
"previous_secret_expires_at": "2026-11-03T07:43:01.185163+00:00"
}
Responses
200The new secret 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/webhooks/{webhook_id}/test
Send a signed webhook.test event to your receiver, within about a minute
One a minute per webhook. It works on a paused webhook, moves no cursor and does not resume anything. The result shows up in the delivery log with event_type webhook.test.
Permission: webhooks
Parameters Name In webhook_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/webhooks/139bdf42-9190-55a1-b68f-de8dbd86f389/test \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 202:
{
"queued": true
}
Responses
202Queued 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
POST /v1/webhooks/{webhook_id}/resume
Turn a paused webhook back on, from now
A webhook pauses after 3 days of failed deliveries. Resuming starts from now. Read GET /events to catch up on what happened while it was paused. Resuming an active webhook changes nothing.
Permission: webhooks
Parameters Name In webhook_idpath required Idempotency-Keyheader optional. A UUID. A retry with the same key never acts twice. Echoed in the response.
Example
curl -X POST https://api.buddypad.builtbymo.dev/v1/webhooks/d20f2263-c1e4-502d-a0a7-894845db38b5/resume \
-H "Authorization: Bearer $BUDDYPAD_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Answers 200:
{
"id": "d20f2263-c1e4-502d-a0a7-894845db38b5",
"url": "https://example.com/buddypad",
"active": true,
"created_at": "2026-11-02T07:43:01.273577+00:00",
"updated_at": "2026-11-02T07:43:01.273577+00:00",
"event_types": [
"item.completed"
],
"failing_since": null,
"paused_reason": null,
"last_delivered_at": null,
"previous_secret_expires_at": null
}
Responses
200The webhook 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 409Already exists, an app limit (nudge limit, one open check-in), or a collision to retry (code conflict). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).
GET /v1/webhooks/{webhook_id}/deliveries
The delivery log, newest first, kept 30 days
Permission: webhooks
Parameters Name In webhook_idpath required limitquery optional cursorquery optional. A next_cursor from an earlier page.
Example
curl https://api.buddypad.builtbymo.dev/v1/webhooks/99c13339-a3f6-5c8e-a118-2de574bcb0bf/deliveries \
-H "Authorization: Bearer $BUDDYPAD_TOKEN"
Answers 200:
{
"data": [],
"next_cursor": null
}
Responses
200A page of attempts 400The request is not valid (code invalid). 401No valid token (code unauthorized). 403The token lacks this route's permission (code forbidden_scope). 404Does not exist, or is not yours to see (the same answer; code not_found). 429Over the token's limit, or too many failed authentications from this address (code rate_limited). See Retry-After. 500Something failed on our side (code internal). 503The request ran too long (code internal).