# Pass the Pad API, for AI agents Base URL: https://passthepad.com/api/v1 Auth: send the club's key on every request: `Authorization: Bearer ptp_live_...` (or `ptp_test_...`). Test keys (ptp_test_) run writes in a dry run: the request is checked and answered, then rolled back, and no email or webhook goes out. Use a test key first whenever you change data. Permissions a key can have: members:read, members:write, memberships:read, memberships:write, forms:read, forms:write, events:read, events:write, signins:read, payments:read, payments:write, members:login, webhooks:manage. Requests and answers are JSON. Lists return `{"data": [...], "has_more": true|false, "next_cursor": "..."}`; get the next page with `starting_after=` (and `limit`, 1-100). Errors return `{"error": {"type", "code", "message", "param"}}` with a 4xx status; read `message`, it says what to fix. Writes accept an `Idempotency-Key` header: reuse the same key when retrying the same change so it never happens twice. Keep under 600 requests a minute. Endpoints: - GET /members ?q= &status=(any|none|current|active|grace|pending|lapsed|cancelled|expiring) &level_id= &tag= &attended_event_id= &email= &updated_since= &mailing_list= - POST /members {email, first_name, last_name} (finds the person if the email is already there) - GET /members/{id}, PATCH /members/{id} {first_name?, last_name?, email?} - POST /members/{id}/login-link {to: site|portal, return_url?, send_email?} (a one-time sign-in link) - POST /sessions/verify {token, audience?}, POST /sessions/refresh {token, audience?} (member tokens from sign-in) - DELETE /members/{id}/sessions (sign a member out everywhere) - GET /levels, GET /levels/{id} - GET /memberships ?member_id= &level_id= &status=, GET /memberships/{id} - POST /memberships {member_id, level_id, ...} (gives someone a membership) - POST /memberships/{id}/renew, POST /memberships/{id}/cancel - GET /events, POST /events {name, date, ...}, GET /events/{id}/registrations - GET /signins ?event_id= &member_id= &series_id= - GET /payments ?member_id= &purpose=, GET /payments/{id} - GET /forms, GET /forms/{id} (each form's questions, and its levels (join) or amounts (donate)) - POST /forms/{id}/submissions {email, first_name, last_name, answers: {question id: answer}, mailing_list, join: level_id, auto_renew, confirm_by_email; donate: amount_cents, monthly; renew: send_email; contact: message; success_url, cancel_url} (sends the form as a person filling it in would; the answer's `next` says what to do: action pay|done|check_email|already_member|renew|renews_automatically|nothing_to_renew|not_found, url, message) - GET /forms/{id}/submissions ?status=, GET /form-submissions/{id} - POST /checkouts {purpose: membership|donation, member_id or email+first_name+last_name, level_id, auto_renew, action (join|renew|upgrade, worked out when left out) | amount_cents, frequency (once|month|year), fund, note, anonymous; processor, send_email, success_url, cancel_url} (a payment page; send the payer to its `url`; success_url and cancel_url are https addresses where they come back, "{CHECKOUT_ID}" in them becomes the id) - GET /checkouts/{id} (status open|paid|expired|cancelled) - GET /webhooks, POST /webhooks {url, events, description}, DELETE /webhooks/{id} Webhooks: POSTed JSON with headers PTP-Event, PTP-Event-Id, PTP-Delivery and PTP-Signature: t=,v1=." with the endpoint's signing secret>. Check the signature and that t is recent, answer 2xx within 10 seconds, and treat PTP-Event-Id as the key for not handling the same event twice (deliveries are retried for 3 days). Events: member.created, member.updated, membership.started, membership.renewed, membership.lapsed, membership.cancelled, payment.succeeded, payment.failed, payment.refunded, registration.created, signin.created, form.submitted. Full reference: https://passthepad.com/developers