API quickstart
End-to-end curl walkthrough — create a signup, add slots, publish, manage participants.
A complete signup flow against the REST API in curl. Every command works as written; paste them into your shell after exporting an API key.
0. Set up auth
Generate an API key and export it. Use sgn_test_… against the sandbox while you're learning; switch to sgn_live_… for production.
export SIGNUP_API_KEY=sgn_test_a2f9_8e7d4c1b6a59f8e3d7c2b1a09f8e7d6c5
export BASE=https://thesignup.app/api/v1Confirm the key works:
curl -sS -H "Authorization: Bearer $SIGNUP_API_KEY" "$BASE/me" | jq{
"id": "usr_01J7K4P9N6QR8E2T5M3VHN8WK1",
"email": "you@example.com",
"organization": {
"id": "org_01J7K4P9N6QR8E2T5M3VHN8WK1",
"name": "Acme Soccer",
"slug": "acme-soccer",
"tier": "pro"
}
}1. List existing signups
curl -sS -H "Authorization: Bearer $SIGNUP_API_KEY" \
"$BASE/signups?limit=5" | jq{
"data": [
{
"id": "sg_01J7K5RXM2YD0FQ9X3GHCK4N8M",
"slug": "fall-fundraiser-a1b2",
"title": "Fall fundraiser",
"status": "published",
"eventDate": "2026-09-15T17:00:00Z",
...
}
],
"nextCursor": "eyJpZCI6InNnXzAxSjdLNVJYTTJZRDBGUTlYM0dIQ0s0TjhNIn0="
}Pass nextCursor back as ?cursor=… to advance pages.
2. Create a draft signup
Mutations accept an Idempotency-Key header — use a UUID per logical operation so retries are safe.
curl -sS -X POST "$BASE/signups" \
-H "Authorization: Bearer $SIGNUP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"title": "Spring potluck",
"description": "Sides and desserts for 25.",
"eventDate": "2026-04-12T17:00:00Z",
"location": "Roosevelt Park, picnic shelter 3",
"maxParticipants": 25,
"requiresPhone": false
}' | jq{
"id": "sg_01J7K6N8DQHX5T2B1ME7VK9W3R",
"slug": "spring-potluck-c4d8",
"title": "Spring potluck",
"status": "draft",
"eventDate": "2026-04-12T17:00:00Z",
...
}Save the id:
export SIGNUP_ID=sg_01J7K6N8DQHX5T2B1ME7VK9W3R3. Publish
The signup is in draft state — invisible to participants until you publish:
curl -sS -X POST "$BASE/signups/$SIGNUP_ID/publish" \
-H "Authorization: Bearer $SIGNUP_API_KEY" | jq '.status'"published"publish is idempotent — calling it on an already-published signup is a no-op. It rejects with 409 conflict if the signup has been closed.
4. List participants
curl -sS "$BASE/signups/$SIGNUP_ID/participants" \
-H "Authorization: Bearer $SIGNUP_API_KEY" | jq '.data | length'05. Register a participant
The POST /signups/{id}/participants endpoint is anonymous-friendly — no Authorization header required (it backs the public link-share flow). Authenticated calls work the same way.
curl -sS -X POST "$BASE/signups/$SIGNUP_ID/participants" \
-H "Content-Type: application/json" \
-d '{
"name": "Sam Rivers",
"email": "sam@example.com",
"phone": "+15555550123",
"selections": []
}' | jq{
"success": true,
"confirmationToken": "cnf_01J7K7P8DQHX5T2B1ME7VK9W3R",
"confirmed": [],
"failed": []
}The confirmationToken is what the participant uses later to edit or cancel their registration without re-authenticating. Surface it to them; agents acting on someone's behalf should preserve it.
6. Generate a signup from a description (AI)
If you have the ai:draft scope, you can hand the AI a free-text description and get back a structured draft:
curl -sS -X POST "$BASE/signups/from-description" \
-H "Authorization: Bearer $SIGNUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Saturday potluck for the U10 soccer team, around 20 people. Need salads, drinks, and 2 desserts."
}' | jq '.draft.title, .draft.items[].name'"U10 soccer team potluck"
"Salad"
"Salad"
"Drinks"
"Drinks"
"Dessert"
"Dessert"This consumes one draft from the org's monthly quota on success. Returns the draft only — call POST /signups + slot/item endpoints to actually create it.
7. Send reminders
For published signups, manually fire an SMS reminder pass:
curl -sS -X POST "$BASE/signups/$SIGNUP_ID/reminders/send" \
-H "Authorization: Bearer $SIGNUP_API_KEY" | jq{ "eligible": 12, "sent": 12, "failed": 0 }Skips participants who've already been reminded for this signup. Subject to the org's per-day SMS ceiling.
Errors you'll hit
All errors are application/problem+json — switch on the code field, not on title or detail.
# Missing key
$ curl -sS "$BASE/me" | jq
{
"type": "https://thesignup.app/problems/missing-credentials",
"title": "Authentication required",
"status": 401,
"code": "missing_credentials"
}
# Invalid body
$ curl -sS -X POST "$BASE/signups" \
-H "Authorization: Bearer $SIGNUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "" }' | jq
{
"type": "https://thesignup.app/problems/validation-failed",
"title": "Validation failed",
"status": 422,
"code": "validation_failed",
"errors": [
{ "path": "title", "message": "String must contain at least 1 character(s)" },
{ "path": "eventDate", "message": "Required" }
]
}Field-level errors land in errors[] on validation_failed.
Next steps
- Error reference — every
codevalue with an example shape - OAuth — for third-party tools acting on behalf of your users
- MCP — connect Claude, Cursor, or any MCP client to thesignup