Tixbae Developer API
API Reference
A read-only HTTP API over the RSVP campaigns your key has been granted. Every endpoint is a GET. Nothing you can do with a key changes an organizer record.
- Base URL
- https://api.tixbae.com/developer/v1
- Auth header
- Authorization: Bearer tbk_live_…
- Rate limit
- 120 requests / minute / key
- Page size
- limit default 50, max 200
Machine-readable
Point your tooling at these rather than scraping this page.
openapi.json
OpenAPI 3 document, generated from the running code so it cannot drift. Feed it to a client generator.
Open specllms.txt
Plain-text brief: base URL, auth, endpoints, field semantics, errors, worked examples. The format LLMs ingest most reliably.
Open llms.txtskill.zip
A downloadable Claude skill (tixbae-rsvp-api). Carries the conventions below, not just the endpoint list.
Download skillQuickstart
Swap in your key and run it.
curl -sS \
-H "Authorization: Bearer tbk_live_…" \
"https://api.tixbae.com/developer/v1/campaigns"# Page to exhaustion — stop on meta.totalPages, never on a short page.
curl -sS \
-H "Authorization: Bearer tbk_live_…" \
"https://api.tixbae.com/developer/v1/campaigns/CAMPAIGN_ID/respondents?page=1&limit=200&since=2026-08-01T00:00:00.000Z"Conventions
Four rules. These are the ones integrations get wrong.
The field set varies per key
Each key carries its own allow-list of fields, per campaign. A field outside that list is OMITTED from the response object — it is never returned as null. Absence is therefore unambiguous, and your code must not assume any given field exists. Read what is present; do not index blindly.
Page to exhaustion with meta.totalPages
Keep requesting until page reaches meta.totalPages. Do NOT stop at the first page that returns fewer rows than limit — a short page is not the last page, and treating it as one silently truncates the import. Ordering is submittedAt ASC, id ASC, which is stable while new responses arrive.
On 429, honour Retry-After
The limit is 120 requests per minute per key. A 429 carries a Retry-After header in seconds. Sleep for that long before retrying. A tight retry loop that ignores it will stay rate-limited and can get the key throttled harder.
submittedAt is a LAST-MODIFIED timestamp
It is bumped when a respondent edits their RSVP. A since sync therefore re-delivers rows you have already seen, with updated content and the SAME id. Upsert by id. Appending blindly duplicates every edited respondent.
// submittedAt is LAST-MODIFIED. An edited RSVP comes back in a later
// `since` window with the same id — upsert, never append.
for (const row of page.data) {
store.upsert(row.id, row); // keyed by id, not by arrival order
}Endpoints
Reference copy — the live spec could not be loaded.
Loading the live spec…
/developer/v1/campaignsList the campaigns this API key may read.
Returns only the campaigns scoped to the presented key. A campaign that exists but is not scoped to the key is not listed.
No parameters.
Responses
- 200Campaigns in scope.
{
"data": [
{
"id": "cmp_7f3a…",
"slug": "cutting-edge-academy-graduation-2026",
"title": "Cutting Edge Academy — Graduation 2026",
"eventDate": "2026-09-12T02:00:00.000Z",
"location": "The House Main Hall, Bandung",
"responseCount": 128
}
]
}/developer/v1/campaigns/{id}/summaryAttendance totals and breakdowns for one campaign.
Breakdowns cover allow-listed RADIO and SELECT fields only. A field outside the allow-list produces no breakdown entry.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
idrequired | path | string | Campaign id, as returned by /campaigns. |
Responses
- 200Totals and per-field breakdowns.
- 403Campaign exists but is not in scope.
{
"campaign": {
"id": "cmp_7f3a…",
"slug": "cutting-edge-academy-graduation-2026",
"title": "Cutting Edge Academy — Graduation 2026"
},
"totals": { "responses": 128, "attending": 120, "notAttending": 8 },
"breakdowns": {
"onsite_online": {
"Onsite": 96,
"Online (for overseas students only)": 24
}
}
}- attendanceStatus is a first-class column, so attending and notAttending are always populated — they are never subject to the field allow-list.
/developer/v1/campaigns/{id}/respondentsPaginated respondents, limited to the allow-listed fields.
Ordering is submittedAt ASC, id ASC — stable while new responses arrive.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
idrequired | path | string | Campaign id, as returned by /campaigns. |
page | query | integer (min 1, default 1) | 1-based page number. |
limit | query | integer (1–200, default 50) | Page size. Values above 200 are rejected with 422. |
since | query | string (date-time) | ISO-8601. Returns responses with submittedAt strictly greater than this value. See convention 4 — submittedAt is a last-modified timestamp. |
Responses
- 200A page of respondents plus meta.
- 422Bad pagination parameters.
- 429Rate limited — honour Retry-After.
{
"data": [
{
"id": "rsp_91c2…",
"submittedAt": "2026-08-05T09:12:00.000Z",
"attendanceStatus": "YES",
"fields": {
"name": "Rina",
"email": "rina@example.com"
}
}
],
"meta": { "total": 128, "page": 1, "limit": 50, "totalPages": 3 }
}- fields contains only the keys allow-listed on your key. A key outside that list is absent from the object entirely — it is never present with a null value.
- Ordering is submittedAt ASC, id ASC. The id tiebreaker is what keeps paging stable when two responses share a timestamp.
- attendanceStatus is always present. It is a column on the response, not a custom form field.
Errors
Every error body is { error: { code, message } }.
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 401 | unauthorized | Key missing, malformed, revoked, or expired. | Re-issue or rotate the key in the portal. Do not retry. |
| 403 | forbidden | The campaign exists but is not scoped to this key. | Ask your Tixbae contact to add the campaign to the key scope. |
| 404 | not_found | No such campaign. | Check the id against GET /campaigns. 403 and 404 differ deliberately, so the API is not an oracle for which ids exist. |
| 422 | unprocessable_entity | Bad pagination — page below 1, limit above 200, unknown query param. | Fix the request. Unknown query parameters are rejected, not ignored. |
| 429 | rate_limited | Over 120 requests per minute for this key. | Sleep for the number of seconds in the Retry-After header, then retry. |
Keys are issued by Tixbae and scoped by Tixbae — a key cannot widen its own access. If you need another campaign or another field, talk to your Tixbae contact.