Send the key in the Authorization header as a bearer token. Every request acts on the key's workspace only; there's no way to reach another workspace's data. Keys can expire and can be revoked at any time. Each endpoint needs a scope:
read:contacts
Read contacts
write:contacts
Create and update contacts
read:leads
Read leads
write:leads
Create leads
read:appointments
Read appointments
write:appointments
Book appointments
read:invoices
Read invoices
read:payments
Read payments
write:messages
Send texts and emails
Errors & limits
Errors use HTTP status codes and one JSON shape. Validation errors (422) include details with each field's problem.
{ "error": { "code": "insufficient_scope", "message": "This API key needs the read:leads scope." } }
Each key can make 120 requests per minute. Responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; over the limit you get 429 with Retry-After. Sending messages also counts toward your plan's text limit, and opt-outs are always respected.
Pagination
Lists return newest first: { "object": "list", "data": [...], "has_more": true, "next_cursor": "…" }. Pass cursor=<next_cursor> for the next page and limit (1–100, default 25). Use sort=updated_at to see recently changed records first.
Endpoint reference
Account
get/api/v1/meAny valid key
Get the key's workspace
Returns the workspace and the key's scopes. Use it to test a key.
Responses: 200 OK · 401 Missing, invalid, revoked, or expired API key · 429 Rate limited (see Retry-After)
cursorquery · string — next_cursor from the previous page
sortquery · "created_at" | "updated_at" — Newest first by this timestamp
emailquery · string
phonequery · string
qquery · string — Search name, email, or company
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 429 Rate limited (see Retry-After)
Creates a contact. If one already has the same email or phone, it's returned instead with `created: false` (200).
Body (JSON)
first_namestring (nullable)
last_namestring (nullable)
companystring (nullable)
emailstring (email) (nullable)
phonestring (nullable)
notesstring (nullable)
tagsstring[]
sourcestring — Where the contact came from (default: api)
sms_opt_inboolean
email_opt_inboolean
Responses: 200 Existing contact matched on email/phone · 201 Created · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 422 Validation failed · 429 Rate limited (see Retry-After)
get/api/v1/contacts/{id}Scope: read:contacts
Get a contact
Parameters
idpath · string (uuid) · required
Responses: 200 OK · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 404 Not found (or belongs to another workspace) · 429 Rate limited (see Retry-After)
patch/api/v1/contacts/{id}Scope: write:contacts
Update a contact
Parameters
idpath · string (uuid) · required
Body (JSON)
first_namestring (nullable)
last_namestring (nullable)
companystring (nullable)
emailstring (email) (nullable)
phonestring (nullable)
notesstring (nullable)
tagsstring[]
sms_opt_inboolean
email_opt_inboolean
do_not_contactboolean
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 404 Not found (or belongs to another workspace) · 409 Conflict · 422 Validation failed · 429 Rate limited (see Retry-After)
Leads
get/api/v1/leadsScope: read:leads
List leads
Parameters
limitquery · integer
cursorquery · string — next_cursor from the previous page
sortquery · "created_at" | "updated_at" — Newest first by this timestamp
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 429 Rate limited (see Retry-After)
post/api/v1/leadsScope: write:leads
Create a lead
Captured like a website form lead: matches or creates the contact, opens a lead and conversation, and runs your "New lead arrives" automations. An open lead for the same contact is reused (200, `created: false`).
Body (JSON)
first_namestring (nullable)
last_namestring (nullable)
emailstring (email) (nullable)
phonestring (nullable)
messagestring (nullable) — The customer's inquiry; recorded as the first message
service_idstring (uuid) (nullable)
sourcestring — Lead source label (default: api)
metadataobject — Extra form answers (string values)
Responses: 200 Existing open lead reused · 201 Created · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 422 Validation failed · 429 Rate limited (see Retry-After)
curl https://repeatcrew.com/api/v1/leads \
-H "Authorization: Bearer $REPEATCREW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"first_name":"Dana","phone":"+15125550142","message":"Quote for a full detail?","source":"website"}'
Appointments
get/api/v1/appointmentsScope: read:appointments
List appointments
Parameters
limitquery · integer
cursorquery · string — next_cursor from the previous page
sortquery · "created_at" | "updated_at" — Newest first by this timestamp
fromquery · string (date-time) — Starts at or after
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 429 Rate limited (see Retry-After)
Books through the same service as the calendar. With `require_availability` (default true) the start time must be an open slot in your booking hours; otherwise 409 `slot_unavailable`.
Body (JSON)
contact_idstring (uuid) · required
starts_atstring (date-time) · required
service_idstring (uuid) (nullable)
lead_idstring (uuid) (nullable)
titlestring (nullable)
duration_minutesinteger
ends_atstring (date-time)
locationstring (nullable)
notesstring (nullable)
price_centsinteger
require_availabilityboolean — Reject the booking unless the slot is open in your booking hours (default: true)
Responses: 201 Created · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 404 Not found (or belongs to another workspace) · 409 Conflict · 422 Validation failed · 429 Rate limited (see Retry-After)
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 429 Rate limited (see Retry-After)
Payments
get/api/v1/paymentsScope: read:payments
List payments
Parameters
limitquery · integer
cursorquery · string — next_cursor from the previous page
sortquery · "created_at" | "updated_at" — Newest first by this timestamp
Responses: 200 OK · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 429 Rate limited (see Retry-After)
Messages
post/api/v1/messagesScope: write:messages
Send a text or email
Sends to a contact on SMS or email (default: their best reachable channel). Opt-outs, Do Not Contact, and your plan's text limit are enforced; a refused send returns 422.
Body (JSON)
contact_idstring (uuid) · required
bodystring · required
channel"sms" | "email" — Defaults to the contact's best reachable channel
subjectstring (nullable) — Email subject
Responses: 201 Sent · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 404 Not found (or belongs to another workspace) · 422 Validation failed · 429 Rate limited (see Retry-After)
curl https://repeatcrew.com/api/v1/messages \
-H "Authorization: Bearer $REPEATCREW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact_id":"<contact id>","body":"Your detailer is on the way!"}'
Webhooks
get/api/v1/hooksAny valid key
List REST hook subscriptions
Responses: 200 OK · 401 Missing, invalid, revoked, or expired API key · 429 Rate limited (see Retry-After)
post/api/v1/hooksAny valid key
Subscribe a URL to events (REST hooks)
Creates a webhook endpoint tied to this key (used by Zapier). The key needs the read scope for each event's data, e.g. `read:leads` for `lead.created`. The signing secret is returned once.
Body (JSON)
urlstring (uri)
target_urlstring (uri) — Alias of url (Zapier REST hooks)
Responses: 204 Deleted · 401 Missing, invalid, revoked, or expired API key · 404 Not found (or belongs to another workspace) · 429 Rate limited (see Retry-After)
Webhooks
Add an endpoint in Settings → API & webhooks (or subscribe with POST /api/v1/hooks). We send a POST with a JSON event when something happens. Respond with any 2xx within 10 seconds. Failed deliveries are retried with exponential backoff (1 minute, doubling, 8 attempts over about 4 hours); an endpoint that keeps failing, or returns 410 Gone, is turned off. Every delivery shows in the log with a Resend button. URLs must be public https addresses.
Each request carries RepeatCrew-Signature: t=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> keyed with your endpoint's signing secret. Also sent: RepeatCrew-Event, RepeatCrew-Event-Id (use it to ignore duplicates), and RepeatCrew-Delivery.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the exact request body string; header: the RepeatCrew-Signature header
export function verifyRepeatCrewSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // 5-minute tolerance
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
Zapier
Connect the RepeatCrew app in Zapier with an API key. Triggers: new lead, new contact, appointment booked, invoice paid, payment received (instant, via REST hooks). Actions: create lead, create contact, send message, book appointment. Building your own integration? The same REST hooks work anywhere: subscribe with POST /api/v1/hooks and unsubscribe with DELETE /api/v1/hooks/{id}.