RoundUp API v1.0.0
The RoundUp public API gives a center's own integrations read access to its roster, attendance, billing and incidents, lets them create enrollment inquiries, and delivers real-time events by webhook.
Authentication. A director creates an API key in the director portal or the RoundUp app (Integrations → API keys), choosing its scopes. Send it as Authorization: Bearer rk_live_…. A key only ever sees its own center.
Rate limits. 120 requests per minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 carries Retry-After.
Errors. { "error": "<code>", "message": "…" } with codes invalid_api_key (401), insufficient_scope (403, plus requiredScope), not_found (404), invalid_request (400), rate_limited (429).
Versioning. Breaking changes ship as a new version path (/v2); fields may be added to v1 responses at any time.
API keys
Create a key. In the director portal or the RoundUp app, a director or admin opens Integrations → API keys and chooses Create API key. Give it a name you will recognize later (for example "Zapier"), pick the scopes it needs, and optionally an expiry (1–730 days). The key — rk_live_… — is shown once: copy it into the tool you are connecting straight away. RoundUp stores only a hash and cannot show it again.
Which scopes. Grant only what the integration uses. For Zapier: write:webhooks for every "New …" trigger, read:children for Find Child, read:billing for Find Invoice and its invoice list, and write:inquiries for Create Inquiry. A request outside the key's scopes is answered 403 with the missing requiredScope.
Rotate or revoke. Rotate issues a replacement with the same name and scopes; the old key keeps working for a grace period you choose (up to 7 days, 24 hours by default) so you can swap it without downtime. Revoke stops a key on its very next request. A center can have up to 10 active keys, and every key only ever sees its own center.
Scopes
| Scope | Grants |
|---|---|
read:center | Read the center's name, time zone and settings summary |
read:children | Read the child roster |
read:classrooms | Read classrooms |
read:attendance | Read daily check-in and check-out times |
read:billing | Read invoices |
read:incidents | Read incident type, severity and dates |
read:staff | Read staff names, roles and classrooms |
write:inquiries | Create enrollment inquiries |
write:webhooks | Create and delete webhook subscriptions |
Endpoints
Base URL: https://api.roundupece.com/api
GET /v1/me
The calling key and its center Works with any valid key, whatever its scopes. Use it to test a connection.
Scope: any valid key
Responses
200OK →Me401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/me \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/center
The key's center
Scope: read:center
Responses
200OK →Center401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/center \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/children
List children
Scope: read:children
Parameters
status(query)"ACTIVE" | "INACTIVE" | "WITHDRAWN"limit(query)integercursor(query)string— ThenextCursorfrom the previous page.
Responses
200OK →data, nextCursor401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/children \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/children/{id}
Get a child
Scope: read:children
Parameters
id(path, required)integer— 6-digit public id
Responses
200OK →Child401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error404Not found at this key's center →Error409Two children at the center share this id (rare); list /v1/children instead.429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/children/ID \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/classrooms
List classrooms
Scope: read:classrooms
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/classrooms \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/attendance
Attendance for one day
Scope: read:attendance
Parameters
date(query)string— Defaults to today in the center's time zone.
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/attendance \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/invoices
List invoices, newest first
Scope: read:billing
Parameters
status(query)"DRAFT" | "OPEN" | "PARTIAL" | "PAID" | "VOID"limit(query)integercursor(query)string— ThenextCursorfrom the previous page.
Responses
200OK →data, nextCursor401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/invoices \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/invoices/{id}
Get an invoice
Scope: read:billing
Parameters
id(path, required)string
Responses
200OK →Invoice401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error404Not found at this key's center →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/invoices/ID \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/incidents
List incidents
Scope: read:incidents
Parameters
from(query)stringto(query)string
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/incidents \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/staff
List staff
Scope: read:staff
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/staff \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
POST /v1/inquiries
Create an enrollment inquiry Adds a prospective family to the center's waitlist and emails the guardian a confirmation — the same as the center's public inquiry form.
Scope: write:inquiries
Request body
InquiryInput
Responses
201Created →id, status, createdAt400Invalid request →Error401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/inquiries -X POST \
-H "Authorization: Bearer $ROUNDUP_API_KEY" \
-H "Content-Type: application/json" -d '{…}'
GET /v1/webhooks
List the center's webhook subscriptions
Scope: write:webhooks
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/webhooks \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
POST /v1/webhooks
Subscribe a URL to events (REST hook) The response carries the signing secret, once. Subscriptions created here appear in the director's Webhooks screen.
Scope: write:webhooks
Request body
object
Responses
201Created →object400Invalid request →Error401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/webhooks -X POST \
-H "Authorization: Bearer $ROUNDUP_API_KEY" \
-H "Content-Type: application/json" -d '{…}'
DELETE /v1/webhooks/{id}
Unsubscribe (REST hook) Only subscriptions created through the API can be deleted with a key.
Scope: write:webhooks
Parameters
id(path, required)string
Responses
200OK →ok401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error404Not found at this key's center →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/webhooks/ID -X DELETE \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
GET /v1/events/sample
An example event envelope for a type
Scope: write:webhooks
Parameters
type(query, required)EventType
Responses
200OK →data401Missing, invalid, revoked or expired API key →Error403The key lacks the operation's scope (see requiredScope) →Error429Over 120 requests per minute; see Retry-After →Error
curl https://api.roundupece.com/api/v1/events/sample \
-H "Authorization: Bearer $ROUNDUP_API_KEY"
Webhooks
A director adds webhook endpoints in the RoundUp app (Integrations → Webhooks), or an integration subscribes with POST /v1/webhooks. Each delivery is a POST of this envelope:
{ "id": "evt_…", "type": "child.checked_in", "createdAt": "…", "centerId": "…", "data": { … } }
Verifying signatures
Every delivery is signed with the subscription's secret (whsec_…, shown once when the webhook is created):
RoundUp-Signature: t=1759680000,v1=<64 hex characters>
To verify: take t, build the string <t>.<raw request body>, compute HMAC-SHA256 of it with the secret, and compare it (constant-time) to v1. Reject the request when t is more than 300 seconds from your clock — that is what stops a captured request being replayed. Always verify against the raw bytes you received, before parsing the JSON.
Deliveries also carry RoundUp-Event, RoundUp-Event-Id, RoundUp-Delivery-Id and RoundUp-Attempt. Delivery is at-least-once: dedupe on the event id. Answer with any 2xx within 10 seconds; anything else is retried after 1 min, 5 min, 30 min, 2 h, 5 h, 10 h and 24 h, then kept as a failed delivery a director can retry. Answer 410 Gone to unsubscribe. An endpoint that fails continuously for 3 days is turned off and the center's directors are notified.
Deprecated: X-RoundUp-Signature (sha256=<HMAC of the body alone>) and X-RoundUp-Event are still sent until 2027-01-31 for receivers built before timestamped signatures. Move to RoundUp-Signature.
A worked example you can check: with secret whsec_example_do_not_use, timestamp 1759680000 and body
{"id":"evt_2b6f0c4e-8d1a-4c7e-9b1f-0a5e6d7c8b9a","type":"child.checked_in","createdAt":"2025-10-05T16:00:00.000Z","centerId":"c0ffee00-0000-4000-8000-000000000000","data":{"childId":482913,"at":"2026-10-05T15:02:11.000Z","date":"2026-10-05"}}
the header is
RoundUp-Signature: t=1759680000,v1=5b36dbe81b8f6263ba5e996378bf3bdca40b8f8dac04d688e869293883fa8744
Node.js
const crypto = require("crypto");
function verifyRoundUp(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hmac, hashlib, time
def verify_roundup(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Event catalog
Payloads carry ids, timestamps, amounts and short codes — never names, notes, message text or photos. Children and staff appear by their 6-digit public id, the same id the API's /v1/children and /v1/staff return.
child.checked_in
A child was checked in.
{
"childId": 482913,
"at": "2026-10-05T15:02:11.000Z",
"date": "2026-10-05"
}child.checked_out
A child was checked out.
{
"childId": 482913,
"at": "2026-10-05T23:31:40.000Z",
"date": "2026-10-05"
}child.enrolled
A child became actively enrolled (a new child, or a re-enrollment).
{
"childId": 482913,
"classroom": "Sunflowers",
"enrolledAt": "2026-10-05T16:00:00.000Z"
}child.withdrawn
An enrolled child was withdrawn.
{
"childId": 482913,
"exitReason": "MOVED",
"exitDate": "2026-10-31",
"withdrawnAt": "2026-10-05T16:00:00.000Z"
}invoice.created
An invoice was issued (draft or open).
{
"invoiceId": "inv_3f0c7a1e",
"familyId": "fam_8a21",
"number": "INV-1042",
"status": "OPEN",
"amountDueCents": 125000,
"dueDate": "2026-11-01",
"createdAt": "2026-10-05T16:00:00.000Z"
}invoice.paid
An invoice was paid in full, by any payment method.
{
"invoiceId": "inv_3f0c7a1e",
"familyId": "fam_8a21",
"number": "INV-1042",
"amountDueCents": 125000,
"amountPaidCents": 125000,
"paidAt": "2026-10-06T09:12:00.000Z"
}invoice.payment_failed
A payment on an invoice failed: a declined autopay charge or a returned bank debit.
{
"invoiceId": "inv_3f0c7a1e",
"familyId": "fam_8a21",
"number": "INV-1042",
"amountCents": 125000,
"methodType": "us_bank_account",
"failureCode": "insufficient_funds",
"failedAt": "2026-10-06T09:12:00.000Z"
}incident.created
Staff logged an incident. Type, severity and dates only — never the description.
{
"incidentId": "inc_51b2",
"childId": 482913,
"type": "Injury",
"severity": "Minor",
"incidentDate": "2026-10-05",
"createdAt": "2026-10-05T18:20:00.000Z"
}staff.hired
Someone was added to this center's staff.
{
"staffId": 731045,
"role": "TEACHER",
"classroom": "Sunflowers",
"hiredAt": "2026-10-05T16:00:00.000Z"
}staff.terminated
Someone was removed from this center's staff (including a move to another center).
{
"staffId": 731045,
"role": "TEACHER",
"terminatedAt": "2026-10-05T16:00:00.000Z"
}message.sent
A family message was sent, by staff or a guardian. Never includes the text or image.
{
"messageId": "6f1d2c3b-0000-4000-8000-000000000001",
"conversationId": "fam_8a21",
"senderRole": "STAFF",
"hasImage": false,
"sentAt": "2026-10-05T19:45:00.000Z"
}Zapier
The RoundUp Zapier app uses an API key (scopes write:webhooks for triggers, plus write:inquiries for the “Create inquiry” action and the read scopes its searches need). Its triggers are REST hooks on the events above.
Schemas
Error
| Field | Type | Notes |
|---|---|---|
error required | string | |
message required | string | |
requiredScope | string |
Me
| Field | Type | Notes |
|---|---|---|
keyId | string | |
prefix | string | |
name | string | |
scopes | string[] | |
center | object |
Center
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
timezone | string | null | |
state | string | null | |
currency | string |
Child
| Field | Type | Notes |
|---|---|---|
id | integer | The child's 6-digit public id — the same id webhook payloads carry. |
firstName | string | |
lastName | string | |
dateOfBirth | string | null | |
classroom | string | null | |
enrollmentStatus | "ACTIVE" | "INACTIVE" | "WITHDRAWN" | |
enrolledAt | string | null | |
updatedAt | string | null |
Classroom
| Field | Type | Notes |
|---|---|---|
id | string | |
name | string | |
ageGroup | string | null | |
capacity | integer | null | |
status | string |
AttendanceDay
| Field | Type | Notes |
|---|---|---|
childId | integer | The child's 6-digit public id — the same id webhook payloads carry. |
date | string | |
status | "Checked In" | "Checked Out" | "Absent" | |
checkInTime | string | null | |
checkOutTime | string | null |
Invoice
| Field | Type | Notes |
|---|---|---|
id | string | |
number | string | null | |
familyId | string | null | |
status | "DRAFT" | "OPEN" | "PARTIAL" | "PAID" | "VOID" | |
amountDueCents | integer | |
amountPaidCents | integer | |
currency | string | ISO 4217, lowercase |
dueDate | string | null | |
createdAt | string | |
updatedAt | string |
Incident
| Field | Type | Notes |
|---|---|---|
id | string | |
childId | integer | null | |
type | string | null | |
severity | string | null | |
incidentDate | string | null | |
status | string | null | |
createdAt | string | null |
StaffMember
| Field | Type | Notes |
|---|---|---|
id | integer | 6-digit public id, the same as staff.* webhook payloads |
firstName | string | null | |
lastName | string | null | |
role | string | |
classroom | string | null | |
startedAt | string | null |
InquiryInput
| Field | Type | Notes |
|---|---|---|
guardianName required | string | |
guardianEmail required | string | |
guardianPhone | string | |
childName required | string | |
childDob | string | |
desiredStartDate | string | |
program | string | |
message | string |
Webhook
| Field | Type | Notes |
|---|---|---|
id | string | |
url | string | |
events | EventType[] | |
active | boolean | |
source | "app" | "api" | |
createdAt | string |
Download the OpenAPI 3.1 document · Questions: developers@roundupece.com