Skip to main content

Developer quickstart

Pulse Connect is a JSON REST API for server-side integrations, CLI tools, MCP servers and approved AI applications.

SettingValue
Contractconnect-v1 (version 1.0.0-pilot, see the changelog)
Base URLhttps://api.staging.maidcentral.ai/api/connect/v1 (staging). External pilot partners use staging only; production access isn't offered yet.
AuthenticationX-API-Key header; keys start with pc_
MethodsGET for reads; POST and PATCH for writes; DELETE only for webhook subscriptions
OpenAPIBrowse the API reference or download the OpenAPI document. Its servers list staging first (the default for generated clients), then production.

Before you start​

  • Build and run on staging only. During the external pilot you connect to staging, a copy of production that MaidCentral refreshes periodically. Production access isn't offered yet.
  • Get a staging key from MaidCentral. MaidCentral enables the API for your branch on staging and issues the key (key limits). Every staging refresh wipes keys and webhook subscriptions, so ask for a new key and re-create your subscriptions after one. The key is shown once: keep it in a secret manager, never in source control, URLs, browser code, logs, prompts or tickets.
  • Treat staging data as real. It is a copy of the branch's own data: create a test customer for your write tests, and don't book, move, skip or cancel on real customers until the business owner approves it.
export PULSE_CONNECT_KEY='pc_REPLACE_WITH_ONE_TIME_SECRET'
export PULSE_CONNECT_BASE='https://api.staging.maidcentral.ai/api/connect/v1'

Verify the connection​

curl --fail-with-body -H "X-API-Key: $PULSE_CONNECT_KEY" "$PULSE_CONNECT_BASE/me"
curl --fail-with-body -H "X-API-Key: $PULSE_CONNECT_KEY" "$PULSE_CONNECT_BASE/capabilities"

/me shows the branch, key, granted scopes and contract version. /capabilities shows which families are on for the branch. Both are free.

Read data​

curl --fail-with-body -H "X-API-Key: $PULSE_CONNECT_KEY" \
--get --data-urlencode "[email protected]" "$PULSE_CONNECT_BASE/customers/lookup"

curl --fail-with-body -H "X-API-Key: $PULSE_CONNECT_KEY" \
"$PULSE_CONNECT_BASE/customers?page_size=100"

curl --fail-with-body -H "X-API-Key: $PULSE_CONNECT_KEY" \
"$PULSE_CONNECT_BASE/jobs?start_date=2026-01-01&end_date=2026-03-31&page_size=100"

The integration recipes show full call sequences for booking, lead capture, sync and assistants.

Scopes​

Grant a key only the scopes its integration needs. The label is what the administrator ticks in Pulse.

ScopeLabel in PulseAllows
usage.readUsageGET /usage
customers.readCustomers (read)Customer list, lookup and detail; also needed with the second scope on a customer's homes, service sets and billing
tags.readTags (read)GET /tags: the branch's customer tags
homes.readHomes (read)Homes
services.readServices (read)Service sets
jobs.readJobs (read)Jobs, including /customers/{id}/jobs
teams.readTeams (read)GET /teams
technicians.readTechnicians (read)Technicians
zones.readZones (read)GET /zones
availability.readAvailability (read)GET /availability
billing.readBilling (read)Invoices, payments, balances (with customers.read for per-customer routes)
leads.readLeads (read)Leads
quotes.readQuotes (read)Quotes
pricing.readBooking configuration and pricing (read)GET /quotes/config, POST /quotes/calculate
time.readTime (read)Time summaries
reports.readReports (read)The seven reports
scorecards.readScorecards (read)Customer ratings
customers.writeCustomers (write)Create and update customers, and add or remove their tags
homes.writeHomes (write)Add and update homes
leads.writeLeads (write)Create and update leads
quotes.writeQuotes (write)Save and revise quotes
bookings.writeBookings (write)Book a saved quote
jobs.changeJobs (change)Move, skip or cancel a visit, and list the move and skip reasons
services.cancelServices (cancel)Cancel a recurring service from a date, and list its Cancel Service reasons
payments.writePayments (write)Mint a save-card link
customer_notes.writeCustomer notes (write)Customer notes
quote_notes.writeQuote notes (write)Quote notes
technician_notes.writeTechnician notes (write)Technician notes
webhooks.manageWebhooks (manage)Every /webhooks and /webhook-events route

No scope grants another: quotes.write doesn't grant quotes.read, and customers.write doesn't grant customer notes. Subscribing to a webhook event also needs that event's read scope.

Conventions​

These rules apply to every operation. The API guide covers what is specific to each one.

Ids​

Resource ids are strings of digits; send them back as strings. Webhook, event and delivery ids, and payment-link ids, are not numeric. A few query parameters take an integer or a list instead (zone_id on /availability, comma-separated team_ids); the API reference shows each. An id from another branch answers 404 resource_not_found, exactly like an unknown one.

Time zones​

KindFormatExamplesZone
InstantISO 8601 with Zcreated_at, updated_at, scheduled_start_at, booked_date, paid_date, expires_at, generated_at, webhook occurred_atUTC
DateYYYY-MM-DDjob_date, first_job_date, issued_date, due_date, work_date; date filters such as start_date, created_from, updated_sinceBranch
Local timeHH:mm or HH:mm:ssAvailability windows, move scheduled_start_at (YYYY-MM-DDTHH:mm), booking arrival_window_start, report times, job.scheduled scheduled_start_timeBranch

"Branch" is the time zone GET /branches reports. Availability, job actions, booking, time summaries, invoice dates and the operational reports use the branch's scheduling day, which is the same zone except for a branch whose zone is set to UTC: that branch schedules in US Eastern while GET /branches still reports Etc/UTC.

Pagination​

  • Collections return data, has_more and next_cursor. page_size is 1–100, default 50.
  • While has_more is true, send next_cursor unchanged with the same route and filters.
  • Cursors are confidential, bound to the key's branch, the operation and the filters, and expire 24 hours after the traversal's first page. An expired or altered cursor is 400 invalid_cursor: restart from the first page. Never store a cursor as a sync checkpoint: store the branch-local date of each run and send it as updated_since on the next (Incremental sync).
  • Order is stable within one traversal; no permanent sort order is promised.
  • The per-customer billing lists page by number (page), not cursor.

Idempotency​

Every write needs an Idempotency-Key header: 1–128 letters, digits, ., _ or -, generated by you and kept with the change it belongs to. A write without one is 400 idempotency_key_required.

  • The same key, method, route and body within 24 hours returns the first response unchanged, with Pulse-Connect-Idempotent-Replay: true. Nothing runs again and nothing counts toward usage.
  • The same key on the same route with a different body is 409 idempotency_conflict. The same key on a different route is a new write.
  • A refused write stores no receipt, so retrying a refused request runs it again.
  • An API key runs one write at a time, across every write route, webhook management included. A write that waits more than two seconds is 503 service_unavailable; retry it with the same Idempotency-Key.

Concurrency​

Every customer, home, lead and quote PATCH, and booking, sends expected_updated_at, the record's updated_at from your last read (job actions call it version). A record changed since is 409 version_conflict and nothing is written: read it again and reapply.

Limits​

  • A request body is at most 64 KB (413 request_too_large).
  • A response over the size limit in /capabilities is 413 result_too_large: use a smaller page or a narrower range.

Rate limits​

ClassOperationsPer minutePer hour
Context/me, /branches, /capabilities, /usage601,000
ReadCustomers, homes, service sets, jobs, leads, quotes, per-customer billing, teams, technicians, zones, time summaries, scorecards, /quotes/config, /quotes/calculate, the reason lists1202,000
ReportThe seven reports, /invoices, /payments (one shared bucket)10100
Availability/availability30600
WriteEvery create, update, booking, note, job action, service cancel and payment link60600
Webhook admin/webhooks and /webhook-events routes30300

Each class is counted per key and per branch, per minute and per hour; whichever limit is reached first applies. All keys on a branch share the branch limits. Rate limits are separate from the monthly allowance.

Each class is a separate bucket, so reaching the limit in one class, such as a nightly invoice export, doesn't use up another class's allowance, such as availability for a booking flow.

Every request with an API key, including a refused one, also counts toward a ceiling of 120 per minute for that key, across all classes. Give bulk jobs their own key so they can't reach the ceiling your booking flow's key depends on. A separate ceiling of 600 requests per minute applies to each client IP address; an IPv6 address shares it with every address in its /64.

Errors​

Errors use application/problem+json with error_code and correlation_id. Every authenticated response carries Pulse-Connect-Operation-Id; send it and your X-Correlation-Id (optional, 1–128 letters, digits, ., _ or -) to support, never the key or response body.

Statuserror_codeAction
400invalid_requestFix the field named in detail.
400invalid_cursorRestart from the first page.
400idempotency_key_requiredSend a valid Idempotency-Key.
401invalid_credentialsCheck the key is complete, active and unexpired.
403insufficient_scope, capability_not_enabledAdd the scope, or ask MaidCentral to enable the family.
404resource_not_foundUnknown or another branch's id.
409context_not_configuredThe branch is missing a currency or time zone the operation needs. Contact MaidCentral; nothing was changed.
409version_conflict, idempotency_conflict, duplicate_customerRe-read; use a new key for a different request; look the customer up.
409Other codesOperation-specific; see the API guide.
413result_too_large, request_too_largeSmaller page, narrower range, or smaller body.
429rate_limitedWait for Retry-After, then back off with jitter.
503service_unavailableRetry after Retry-After. When it is absent, back off exponentially.

Rotate a key​

Keys can't be edited. Ask MaidCentral for the replacement (the branch needs a free key slot; it can hold up to 10 active keys), verify /me with it, switch your client over, then ask MaidCentral to revoke the old one. Revoking pauses the webhook subscriptions the old key was checked against; resume them with the new key.