Skip to content
Launch special: 50% off your first 2 months· 50% off for 2 months

Developers

Build on RepeatCrew

A REST API, signed webhooks, and a Zapier app, so RepeatCrew fits into the rest of your business.

Scoped API keys

Create keys in Settings → API with only the permissions an integration needs.

Predictable REST

JSON everywhere, cursor pagination, newest first, one error format.

Signed webhooks

HMAC-SHA256 signatures, automatic retries with backoff, and a delivery log.

Zapier

Instant triggers via REST hooks, plus actions to create leads, contacts, bookings, and messages.

Quickstart

  1. An owner or admin opens Settings → API & webhooks in RepeatCrew and creates a key with the permissions you need.
  2. Copy the key (it starts with rc_live_). It's shown only once.
  3. Call the API with it:
curl https://repeatcrew.com/api/v1/me \
  -H "Authorization: Bearer $REPEATCREW_API_KEY"

Authentication

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:contactsRead contacts
write:contactsCreate and update contacts
read:leadsRead leads
write:leadsCreate leads
read:appointmentsRead appointments
write:appointmentsBook appointments
read:invoicesRead invoices
read:paymentsRead payments
write:messagesSend 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)

curl https://repeatcrew.com/api/v1/me \
  -H "Authorization: Bearer $REPEATCREW_API_KEY"

Contacts

get/api/v1/contactsScope: read:contacts

List contacts

Parameters

  • limit query · integer
  • cursor query · string — next_cursor from the previous page
  • sort query · "created_at" | "updated_at" — Newest first by this timestamp
  • email query · string
  • phone query · string
  • q query · 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)

curl "https://repeatcrew.com/api/v1/contacts?limit=50" \
  -H "Authorization: Bearer $REPEATCREW_API_KEY"
post/api/v1/contactsScope: write:contacts

Create a contact

Creates a contact. If one already has the same email or phone, it's returned instead with `created: false` (200).

Body (JSON)

  • first_name string (nullable)
  • last_name string (nullable)
  • company string (nullable)
  • email string (email) (nullable)
  • phone string (nullable)
  • notes string (nullable)
  • tags string[]
  • source string — Where the contact came from (default: api)
  • sms_opt_in boolean
  • email_opt_in boolean

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

  • id path · 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

  • id path · string (uuid) · required

Body (JSON)

  • first_name string (nullable)
  • last_name string (nullable)
  • company string (nullable)
  • email string (email) (nullable)
  • phone string (nullable)
  • notes string (nullable)
  • tags string[]
  • sms_opt_in boolean
  • email_opt_in boolean
  • do_not_contact boolean

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

  • limit query · integer
  • cursor query · string — next_cursor from the previous page
  • sort query · "created_at" | "updated_at" — Newest first by this timestamp
  • status query · "new" | "contacted" | "qualified" | "quoted" | "booked" | "won" | "lost"
  • contact_id query · string (uuid)
  • created_after query · string (date-time)

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_name string (nullable)
  • last_name string (nullable)
  • email string (email) (nullable)
  • phone string (nullable)
  • message string (nullable) — The customer's inquiry; recorded as the first message
  • service_id string (uuid) (nullable)
  • source string — Lead source label (default: api)
  • metadata object — 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

  • limit query · integer
  • cursor query · string — next_cursor from the previous page
  • sort query · "created_at" | "updated_at" — Newest first by this timestamp
  • from query · string (date-time) — Starts at or after
  • to query · string (date-time) — Starts before
  • status query · "scheduled" | "confirmed" | "in_progress" | "completed" | "cancelled" | "no_show"
  • contact_id query · string (uuid)

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)

curl "https://repeatcrew.com/api/v1/appointments?from=2026-10-01&to=2026-10-08" \
  -H "Authorization: Bearer $REPEATCREW_API_KEY"
post/api/v1/appointmentsScope: write:appointments

Book an appointment

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_id string (uuid) · required
  • starts_at string (date-time) · required
  • service_id string (uuid) (nullable)
  • lead_id string (uuid) (nullable)
  • title string (nullable)
  • duration_minutes integer
  • ends_at string (date-time)
  • location string (nullable)
  • notes string (nullable)
  • price_cents integer
  • require_availability boolean — 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)

curl https://repeatcrew.com/api/v1/appointments \
  -H "Authorization: Bearer $REPEATCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact_id":"<contact id>","service_id":"<service id>","starts_at":"2026-10-03T15:00:00-05:00"}'

Invoices

get/api/v1/invoicesScope: read:invoices

List invoices

Parameters

  • limit query · integer
  • cursor query · string — next_cursor from the previous page
  • sort query · "created_at" | "updated_at" — Newest first by this timestamp
  • status query · "draft" | "sent" | "viewed" | "partially_paid" | "paid" | "overdue" | "void"
  • contact_id query · string (uuid)

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

  • limit query · integer
  • cursor query · string — next_cursor from the previous page
  • sort query · "created_at" | "updated_at" — Newest first by this timestamp
  • status query · "pending" | "succeeded" | "failed" | "refunded"
  • kind query · "deposit" | "payment" | "refund"
  • invoice_id query · string (uuid)
  • contact_id query · string (uuid)

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_id string (uuid) · required
  • body string · required
  • channel "sms" | "email" — Defaults to the contact's best reachable channel
  • subject string (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)

  • url string (uri)
  • target_url string (uri) — Alias of url (Zapier REST hooks)
  • event "contact.created" | "lead.created" | "lead.status_changed" | "appointment.created" | "appointment.updated" | "appointment.cancelled" | "appointment.completed" | "invoice.sent" | "invoice.paid" | "payment.received" | "message.received" | "review.received"
  • events "contact.created" | "lead.created" | "lead.status_changed" | "appointment.created" | "appointment.updated" | "appointment.cancelled" | "appointment.completed" | "invoice.sent" | "invoice.paid" | "payment.received" | "message.received" | "review.received"[]
  • description string (nullable)

Responses: 201 Created · 400 Invalid parameter or JSON · 401 Missing, invalid, revoked, or expired API key · 403 The key lacks a required scope · 409 Conflict · 422 Validation failed · 429 Rate limited (see Retry-After)

curl https://repeatcrew.com/api/v1/hooks \
  -H "Authorization: Bearer $REPEATCREW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/repeatcrew","event":"lead.created"}'
delete/api/v1/hooks/{id}Any valid key

Unsubscribe (REST hooks)

Parameters

  • id path · string (uuid) · required

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.

contact.createdA contact is added
lead.createdA new lead arrives
lead.status_changedA lead's status changes
appointment.createdAn appointment is booked
appointment.updatedAn appointment is rescheduled or changed
appointment.cancelledAn appointment is cancelled
appointment.completedA job is completed
invoice.sentAn invoice is sent
invoice.paidAn invoice is paid in full
payment.receivedA payment is received
message.receivedA customer sends a text or email
review.receivedA customer leaves a review or rating
{
  "id": "evt_4f0c8a1e9b2d4c6f8a0e1b3c5d7f9a2b",
  "object": "event",
  "type": "lead.created",
  "created_at": "2026-09-28T17:04:11.482Z",
  "organization_id": "…",
  "data": {
    "object": "lead",
    "id": "…",
    "status": "new",
    "source": "website_form",
    "title": "Quote for a full detail?",
    "contact": { "object": "contact", "id": "…", "first_name": "Dana", "phone": "+15125550142" }
  }
}

Verifying signatures

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}.

Questions? Contact us.