RoundUp · openapi.json

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

ScopeGrants
read:centerRead the center's name, time zone and settings summary
read:childrenRead the child roster
read:classroomsRead classrooms
read:attendanceRead daily check-in and check-out times
read:billingRead invoices
read:incidentsRead incident type, severity and dates
read:staffRead staff names, roles and classrooms
write:inquiriesCreate enrollment inquiries
write:webhooksCreate 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

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

curl https://api.roundupece.com/api/v1/center \
  -H "Authorization: Bearer $ROUNDUP_API_KEY"

GET /v1/children

List children

Scope: read:children

Parameters

Responses

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

Responses

curl https://api.roundupece.com/api/v1/children/ID \
  -H "Authorization: Bearer $ROUNDUP_API_KEY"

GET /v1/classrooms

List classrooms

Scope: read:classrooms

Responses

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

Responses

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

Responses

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

Responses

curl https://api.roundupece.com/api/v1/invoices/ID \
  -H "Authorization: Bearer $ROUNDUP_API_KEY"

GET /v1/incidents

List incidents

Scope: read:incidents

Parameters

Responses

curl https://api.roundupece.com/api/v1/incidents \
  -H "Authorization: Bearer $ROUNDUP_API_KEY"

GET /v1/staff

List staff

Scope: read:staff

Responses

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

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

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

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

Responses

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

Responses

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

FieldTypeNotes
error requiredstring
message requiredstring
requiredScopestring

Me

FieldTypeNotes
keyIdstring
prefixstring
namestring
scopesstring[]
centerobject

Center

FieldTypeNotes
idstring
namestring
timezonestring | null
statestring | null
currencystring

Child

FieldTypeNotes
idintegerThe child's 6-digit public id — the same id webhook payloads carry.
firstNamestring
lastNamestring
dateOfBirthstring | null
classroomstring | null
enrollmentStatus"ACTIVE" | "INACTIVE" | "WITHDRAWN"
enrolledAtstring | null
updatedAtstring | null

Classroom

FieldTypeNotes
idstring
namestring
ageGroupstring | null
capacityinteger | null
statusstring

AttendanceDay

FieldTypeNotes
childIdintegerThe child's 6-digit public id — the same id webhook payloads carry.
datestring
status"Checked In" | "Checked Out" | "Absent"
checkInTimestring | null
checkOutTimestring | null

Invoice

FieldTypeNotes
idstring
numberstring | null
familyIdstring | null
status"DRAFT" | "OPEN" | "PARTIAL" | "PAID" | "VOID"
amountDueCentsinteger
amountPaidCentsinteger
currencystringISO 4217, lowercase
dueDatestring | null
createdAtstring
updatedAtstring

Incident

FieldTypeNotes
idstring
childIdinteger | null
typestring | null
severitystring | null
incidentDatestring | null
statusstring | null
createdAtstring | null

StaffMember

FieldTypeNotes
idinteger6-digit public id, the same as staff.* webhook payloads
firstNamestring | null
lastNamestring | null
rolestring
classroomstring | null
startedAtstring | null

InquiryInput

FieldTypeNotes
guardianName requiredstring
guardianEmail requiredstring
guardianPhonestring
childName requiredstring
childDobstring
desiredStartDatestring
programstring
messagestring

Webhook

FieldTypeNotes
idstring
urlstring
eventsEventType[]
activeboolean
source"app" | "api"
createdAtstring

Download the OpenAPI 3.1 document · Questions: developers@roundupece.com