Version: 1.0
Status: ✅ Implemented
Authentication: Bearer Token (API Key)
Base URL: /api/v1
The Choreography REST API provides programmatic access to family chore and prize data. All requests are family-scoped and require a valid API key issued through the Admin Settings UI.
- Family-Scoped: All data is isolated per family
- One Active Key Per Family: Only one API key can be active at a time
- Bearer Token Auth: Use
Authorization: Bearer <key>header - Status-Based Workflow: Redemptions use status updates (no hard deletes)
- Pagination: List endpoints support limit/offset parameters
- Sign in to your family account
- Navigate to Admin → Settings
- Click "Generate API Key"
- Copy the key and save it securely (shown only once)
Include the key in all requests:
curl -H "Authorization: Bearer choreo_xyzabc..." https://yourapp.com/api/v1/choresTo rotate your API key:
- Go to Admin → Settings
- Click "Rotate Key"
- A new key is generated; the old key immediately stops working
- Update any integrations with the new key
- Go to Admin → Settings
- Click "Delete" next to your API key
- The key is revoked immediately
All responses follow this structure:
{
"success": true,
"data": {
"id": "...",
"name": "Wash Dishes",
...
}
}Or on error:
{
"success": false,
"error": "Chore not found",
"code": "NOT_FOUND"
}| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (POST) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (missing/invalid key) |
| 404 | Not Found |
| 405 | Method Not Allowed |
| 500 | Server Error |
GET /api/v1/chores
Response:
{
"success": true,
"data": [
{
"id": "ulid1",
"familyId": "...",
"name": "Wash Dishes",
"description": "Wash all dishes in the sink",
"emoji": "πŸ›'",
"createdAt": "2026-04-18T...",
"updatedAt": "2026-04-18T..."
}
]
}GET /api/v1/chores?id=<choreId>
POST /api/v1/chores
Content-Type: application/json
{
"name": "Wash Dishes",
"description": "Wash all dishes in the sink",
"emoji": "πŸ›'"
}
Response: 201 Created
PUT /api/v1/chores?id=<choreId>
Content-Type: application/json
{
"name": "Updated Name",
"description": "Updated description",
"emoji": "πŸ§'β€πŸ³"
}
DELETE /api/v1/chores?id=<choreId>
GET /api/v1/prizes
Response:
{
"success": true,
"data": [
{
"id": "ulid1",
"familyId": "...",
"name": "Ice Cream",
"description": "Get a free scoop",
"emoji": "πŸ¨",
"costPoints": 100,
"createdAt": "2026-04-18T...",
"updatedAt": "2026-04-18T..."
}
]
}GET /api/v1/prizes?id=<prizeId>
POST /api/v1/prizes
Content-Type: application/json
{
"name": "Ice Cream",
"description": "Get a free scoop",
"emoji": "πŸ¨",
"costPoints": 100
}
Response: 201 Created
PUT /api/v1/prizes?id=<prizeId>
Content-Type: application/json
{
"name": "Updated Prize",
"costPoints": 150
}
DELETE /api/v1/prizes?id=<prizeId>
GET /api/v1/members
Response:
{
"success": true,
"data": [
{
"memberId": "user-id",
"familyId": "...",
"displayName": "Alice",
"memberRole": "admin",
"createdAt": "2026-04-18T...",
"user": {
"id": "user-id",
"email": "alice@example.com",
"displayName": "Alice",
"isActive": true
}
}
]
}GET /api/v1/members?id=<memberId>
POST /api/v1/members/reactivate
Content-Type: application/json
{
"memberId": "user-id"
}
Reactivates an inactive member account (isActive: true) in your family so they can sign in again.
GET /api/v1/redemptions
GET /api/v1/redemptions?status=available
GET /api/v1/redemptions?status=pending
GET /api/v1/redemptions?status=fulfilled
GET /api/v1/redemptions?status=dismissed
Response:
{
"success": true,
"data": [
{
"id": "ulid1",
"familyId": "...",
"memberId": "user-id",
"prizeId": "prize-id",
"status": "available",
"createdAt": "2026-04-18T...",
"updatedAt": "2026-04-18T...",
"fulfilledAt": null,
"dismissedAt": null
}
]
}GET /api/v1/redemptions?id=<redemptionId>
POST /api/v1/redemptions
Content-Type: application/json
{
"memberId": "user-id",
"prizeId": "prize-id"
}
Response: 201 Created
Initial Status: available
PUT /api/v1/redemptions?id=<redemptionId>
Content-Type: application/json
{
"status": "pending"
}
Valid Statuses:
available- Initial statepending- Being preparedfulfilled- Completed (setsfulfilledAt)dismissed- Cancelled/ignored (setsdismissedAt)
Important: Status updates persist timestamps. Dismissals don't delete records—they mark status as dismissed and record the time.
GET /api/v1/completions?limit=50&offset=0
Response:
{
"success": true,
"data": {
"completions": [
{
"id": "ulid1",
"choreId": "chore-id",
"choreName": "Wash Dishes",
"choreEmoji": "πŸ›'",
"memberId": "user-id",
"memberDisplayName": "Alice",
"completedAt": "2026-04-18T...",
"pointsEarned": 10
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 42,
"hasMore": false
}
}
}Parameters:
limit(optional): Max 100, default 50offset(optional): Default 0
GET /api/v1/activity?limit=50&offset=0
Response:
{
"success": true,
"data": {
"events": [
{
"type": "completion",
"id": "ulid1",
"choreName": "Wash Dishes",
"choreEmoji": "πŸ›'",
"memberDisplayName": "Alice",
"timestamp": "2026-04-18T...",
"pointsEarned": 10
},
{
"type": "redemption",
"id": "ulid2",
"prizeName": "Ice Cream",
"prizeEmoji": "πŸ¨",
"memberDisplayName": "Bob",
"timestamp": "2026-04-18T...",
"status": "fulfilled"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 156,
"hasMore": true
}
}
}Parameters:
limit(optional): Max 100, default 50offset(optional): Default 0
Event Types:
completion- Chore completedredemption- Prize redeemed
| Code | Meaning |
|---|---|
UNAUTHORIZED |
Missing or invalid API key |
NOT_FOUND |
Resource not found or belongs to different family |
INVALID_INPUT |
Validation error (missing field, wrong type) |
INVALID_JSON |
Request body is not valid JSON |
METHOD_NOT_ALLOWED |
HTTP method not supported for endpoint |
INTERNAL_ERROR |
Server-side error |
The development server uses a self-signed SSL certificate. When testing with curl, add the -k or --insecure flag to skip certificate verification:
curl -k -H "Authorization: Bearer <your-key>" https://localhost:5173/api/v1/choresOr use the insecure flag explicitly:
curl --insecure -H "Authorization: Bearer <your-key>" https://localhost:5173/api/v1/choresFor Production: Always use valid SSL certificates and proper HTTPS.
# Create chore
curl -k -X POST https://localhost:5173/api/v1/chores \
-H "Authorization: Bearer choreo_xyzabc..." \
-H "Content-Type: application/json" \
-d '{
"name": "Vacuum Living Room",
"emoji": "🧹"
}'
# Response:
# {"success":true,"data":{"id":"ulid...","name":"Vacuum Living Room",...}}# Create prize
curl -k -X POST https://localhost:5173/api/v1/prizes \
-H "Authorization: Bearer choreo_xyzabc..." \
-H "Content-Type: application/json" \
-d '{
"name": "Movie Night",
"costPoints": 200,
"emoji": "🎬"
}'
# Create redemption
curl -k -X POST https://localhost:5173/api/v1/redemptions \
-H "Authorization: Bearer choreo_xyzabc..." \
-H "Content-Type: application/json" \
-d '{
"memberId": "user123",
"prizeId": "prize456"
}'
# Update status to fulfilled
curl -k -X PUT "https://localhost:5173/api/v1/redemptions?id=redemption789" \
-H "Authorization: Bearer choreo_xyzabc..." \
-H "Content-Type: application/json" \
-d '{"status": "fulfilled"}'curl -k https://localhost:5173/api/v1/activity?limit=20 \
-H "Authorization: Bearer choreo_xyzabc..."
# Response:
# {"success":true,"data":{"events":[...]},"pagination":{...}}Currently, there is no built-in rate limiting. Consider adding it if needed for public APIs.
API changes will be announced with at least 30 days notice. Breaking changes will use new API versions (e.g., /api/v2).
For issues, questions, or feature requests, please create an issue in the project repository.