RoundProbyCleanlynkAPI reference

RoundPro API

Connect a CRM like GoHighLevel, automations in Zapier, an AI assistant or your own software to a window cleaning business’s RoundPro account. Look up customers and the schedule; add and update customers, create and change jobs, and move or reassign cleans; and be told the moment something happens, with webhooks.

Base address
https://roundpro.app/api/v1
Sign in with
Authorization: Bearer rp_live_…
Format
JSON · money in pence · dates YYYY-MM-DD
Version
1

Getting started

The RoundPro API lets another system work with a window cleaning business's RoundPro account: look up customers and the schedule, add and update customers, create and change jobs, and move or reassign cleans. Everything it does is checked, logged and can be undone by the business.

  • The business owner makes a key in RoundPro: Settings → API & integrations → Make a key. They choose what it can do, and it's shown once — copy it straight into your system.
  • Every request goes to https://roundpro.app/api/v1, with the key in the Authorization header.
  • Start by calling GET /me. It tells you which business the key belongs to and what it's allowed to do.
curl https://roundpro.app/api/v1/me \
  -H "Authorization: Bearer rp_live_your_key_here"

On Windows, in PowerShell, type curl.exe instead of curl (plain curl there is a different command that doesn't understand -H).

{
  "business": { "id": "mumqddmwmmmSpnzVV2OQ", "name": "Crystal Clear Windows", "timezone": "Europe/London", "vatRegistered": false, "pricesIncludeVat": false },
  "key": { "id": "…", "name": "Zapier", "scopes": ["read", "customers:write"], "createdAt": "2026-10-07T08:07:18.803Z" },
  "today": "2026-10-07"
}

The API is part of the full RoundPro plan. A key stops working if it's revoked, if the person who made it stops being an owner or admin, or if the business's plan ends or the account is closed.

Keys and security

Send the key as a Bearer token on every request. Keys start rp_live_ followed by 40 letters and digits.

Authorization: Bearer rp_live_AbCdEf…
  • Treat a key like a password. Keep it on your server or in your integration platform's secret store — never in a web page, a mobile app, a spreadsheet or an email. The API doesn't answer requests from web browsers (there are no CORS headers) for this reason.
  • Use one key per system, so each can be switched off on its own and the change log shows which system did what.
  • RoundPro stores only a scrambled (SHA-256) copy of each key, so it can't be shown again. If a key is lost, the owner revokes it and makes a new one.
  • The owner is emailed whenever a key is made. Revoking a key in Settings stops it at once.
  • All requests must use HTTPS.

Permissions

Each key has one or more permissions, chosen by the owner when it's made. Asking for something the key isn't allowed to do gets a 403 with the permission it needs.

PermissionScope nameAllows
ReadreadSee customers, properties, jobs, rounds, the schedule, the team, leads, quotes, invoices and payments, and ask to be told about changes (webhooks). Always included.
Customerscustomers:writeAdd and update customers and their properties, add notes, archive and restore customers.
Jobsjobs:writeCreate jobs, change price, frequency, round or who does it, re-time a job's cleans, stop and restart jobs.
Scheduleschedule:writeMove a visit to another day, give it to someone on the team, and add notes to it.
Leadsleads:writeAdd enquiries (from a Facebook lead form, a website and so on). If automatic quoting is on in Settings › ALICE – Lead Assistant, RoundPro may text or email them a quote straight away.
Quotes and repliesquotes:writeMake quotes and send them to customers by email or text, reply to leads, and book a lead's first clean (telling them the date). These message your customers.
Payments and invoicesmoney:writeRecord payments (cash, bank transfer, card and so on) and send invoices to customers. Sending an invoice messages the customer.

Conventions

  • Requests and answers are JSON. Send Content-Type: application/json with a body.
  • Money is in whole pence, in fields ending Pence: £25.50 is 2550. Prices on jobs and visits are before VAT. If business.pricesIncludeVat is true, the owner thinks in prices including VAT: take the VAT out before sending one, and add it back when telling them.
  • Dates are YYYY-MM-DD in the business's own time zone (see GET /me), e.g. "2026-10-14". "Today" means today for the business.
  • Times are ISO 8601 in UTC, e.g. "2026-10-07T08:30:00.000Z".
  • IDs are strings. Treat them as opaque: don't parse them or assume a length.
  • In a PATCH, leave a field out to keep it as it is; send null to clear it.
  • Unknown fields are refused with a 400 naming the field, so a typo never silently does nothing. Answers may gain new fields over time — ignore any you don't recognise.

Lists and paging

List endpoints return a page of results and a cursor for the next page. Ask for up to 100 at a time with limit (default 50). When nextCursor is null there are no more.

GET /customers?limit=100
→ { "data": [ … ], "nextCursor": "WyIyMDI2LTEwLTA3…" }

GET /customers?limit=100&cursor=WyIyMDI2LTEwLTA3…
→ { "data": [ … ], "nextCursor": null }

Cursors are opaque — pass back exactly what you were given. Paging is stable: records added while you page through don't cause duplicates or gaps in what you've already fetched.

Errors

Anything other than a 2xx answer has an error body like this. The message is plain English and safe to show to a person; field names the input at fault when there is one.

HTTP/1.1 400 Bad Request

{ "error": { "code": "invalid", "message": "startDate can't be in the past (today is 2026-10-07 for this business)", "field": "startDate" } }
StatuscodeMeaning
400invalidSomething in the request isn't right. Fix it before trying again.
401unauthorizedNo key, or not a key that works (wrong, or revoked).
403forbiddenThe key works but can't do this: it lacks the permission, its person is no longer a manager, or the business's plan doesn't include the API.
404not_foundNo such record in this business (records belonging to other businesses are never visible).
405invalidThat method isn't used at this address.
409conflictIt can't be done in the record's current state (e.g. moving a clean that's already done), or an Idempotency-Key clash.
413too_largeThe body is over 64 KB.
429rate_limitedToo many requests. Wait for the number of seconds in Retry-After.
500server_errorOur fault. It's been reported automatically. Safe to retry with the same Idempotency-Key.

Every answer has an X-Request-Id header. Quote it if you need to ask us about a request.

Rate limits

  • 120 requests a minute per key, of which up to 60 can be changes (POST, PATCH and DELETE).
  • 300 requests a minute per business, across all its keys.
  • Over the limit you get a 429 with a Retry-After header (in seconds). Wait that long, then carry on.
  • For bulk work (hundreds of customers), pace requests to about one a second.

Retrying safely (Idempotency-Key)

Networks fail. If a POST or PATCH times out, you can't tell whether it happened. Send an Idempotency-Key header with a value unique to that change (a UUID is ideal), and if you send the same request again with the same key within 24 hours, you get the first answer back instead of the change being made twice. A replayed answer carries Idempotent-Replayed: true.

POST /customers
Idempotency-Key: 3f6c1d1e-6a0b-4d8e-9c56-1d2b8c7e9a10
Content-Type: application/json

{ "name": "Sarah Jones", "phone": "07700 900123" }
  • Use a new key for each different change. Reusing a key with a different body gets a 409.
  • Keys are 1–100 characters: letters, digits, - _ : and .
  • Zapier and GoHighLevel retries: map a unique value from the trigger (e.g. the contact or event ID) into the header so a retried step doesn't double up.

The change log and undo

Every change made through the API is written to the business's change log (Settings → API & integrations), with which key made it and what it was before. The ID of the log entry comes back in the RoundPro-Change header.

The business can undo any change from there. Undo is careful: if the record has been changed again since (in RoundPro or through the API), it's left alone rather than overwrite the newer change.

Webhooks: being told when things happen

Instead of asking RoundPro over and over whether anything has changed, a system can be told the moment something happens. The business owner adds the address to send to in Settings → API & integrations → Add a webhook, and ticks the events wanted (or everything).

EventSent whendata is a
customer.createdA new customer is added (in RoundPro, through the API, or by accepting a quote).Customer
customer.updatedA customer's details, notes or one of their properties is changed.Customer
customer.archivedA customer is archived (they've stopped). Their jobs are stopped too.Customer
customer.restoredAn archived customer is brought back.Customer
job.createdNew regular or one-off work is set up at a property.Job
job.updatedA job's price, frequency, round, worker, start date, months off or details change.Job
job.stoppedA job is stopped and its upcoming cleans removed.Job
job.restartedA stopped job is started again.Job
visit.completedA clean is marked done.Visit
visit.skippedA clean is skipped, with the reason.Visit
visit.movedA clean is moved to another day. previous.date says where it was.Visit
invoice.sentAn invoice goes to the customer for the first time.Invoice
invoice.paidAn invoice is paid in full.Invoice
payment.receivedMoney is recorded from a customer (any method).Payment
lead.createdAn enquiry comes in (through the API or Zapier), after RoundPro has tried to price it: status says whether a quote went.Lead
quote.sentA quote goes to someone for the first time (by hand, or automatically from a lead).Quote
quote.declinedSomeone says no thanks to a quote, with anything they wrote.Quote
quote.acceptedA quote is accepted (online, or converted by hand): the customer and their new job.{ quoteId, customer, job }

Each event is a POST of JSON to the address. data is the record as the API returns it (so the same fields as GET /customers/{id} and so on), as it was when the message was first sent.

POST https://your-system.example.com/roundpro
Content-Type: application/json
RoundPro-Event: visit.moved
RoundPro-Event-Id: muxw3pteaClPBAhsp2Vj
RoundPro-Delivery-Id: muxw3q8kR2mXbN0aLp4Q
RoundPro-Signature: t=1791367200,v1=5f2b…

{
  "id": "muxw3pteaClPBAhsp2Vj",
  "type": "visit.moved",
  "createdAt": "2026-10-07T09:20:00.000Z",
  "business": { "id": "mumqddmwmmmSpnzVV2OQ", "name": "Crystal Clear Windows" },
  "source": { "kind": "api", "keyId": "…", "keyName": "GoHighLevel" },
  "data": { "id": "vis…", "jobId": "job…", "customerId": "cus…", "date": "2026-10-16", "originalDate": "2026-10-14", "status": "scheduled", … },
  "previous": { "date": "2026-10-14" }
}
  • Answer with any 2xx status within 10 seconds to say it arrived. Do the slow work afterwards.
  • Anything else (an error, no answer, a redirect) is tried again after 1, 5, 15 minutes, then 1, 3, 6, 14 hours — 8 tries over about a day. An address still failing after 24 hours is switched off and the owner emailed.
  • The same event can occasionally arrive twice (for example if your answer was lost). Use id to ignore one you've already handled.
  • Events can arrive out of order. Use createdAt, or look the record up with the API if order matters.
  • source.kind says what caused it: app (someone in RoundPro), api (with keyId and keyName), customer (on their own page, or accepting a quote online) or system. If your system both changes RoundPro through the API and listens for events, ignore events whose source.keyId is your own key, so changes don't bounce back and forth.
  • Events while a webhook is switched off aren't sent later. Imports and restoring a backup don't send events.
  • Only https addresses on the public internet are used, on the normal port.

Checking a message really came from RoundPro: each webhook has a signing secret (shown once, when it's added). The RoundPro-Signature header has a time (t) and an HMAC-SHA256 (v1) of the time, a full stop, and the body exactly as sent, made with that secret. Work out the same and compare; refuse messages more than 5 minutes old. Zapier and GoHighLevel don't need this, but your own software should do it.

import crypto from "node:crypto";

// rawBody: the request body exactly as received (before JSON parsing)
function isFromRoundPro(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex"), b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Send a test from the webhook's page in Settings to check it's working: the event type is test.ping.

A system can also subscribe for itself with its API key (POST /webhooks, and DELETE /webhooks/{id} to stop) — this is how Zapier's instant triggers work. Those subscriptions show in the owner's Settings marked as added through the API, and go if the key is revoked.

Leads: enquiries quoted automatically

Send RoundPro the enquiries a business gets — from a Facebook lead form, a website form, Google or a lead site — with POST /leads (needs the Leads permission). RoundPro deals with each one as the business has set up in Settings › ALICE – Lead Assistant:

  • It's priced from the business's price list: type of property and bedrooms, extras, and how often. Send the answers as people gave them — "Semi-detached house", "3 bedrooms", "Conservatory, Extension", "Every 4 weeks" or "Monthly" are all understood. Anything it can't read with confidence is left for the business, never guessed.
  • With automatic quoting on, and everything needed in hand (a price, a street address, an email or UK mobile, inside the area the business covers), the quote is texted or emailed to them straight away, with a link to accept. Accepting adds them as a customer with the job booked in.
  • Otherwise it waits in Leads with the reason (and the price worked out, where there is one); the business can send a thank-you straight away so they know they've been heard.
  • The same email or phone number enquiring again within 7 days is kept as a duplicate, and nothing is sent twice. A business can take up to 300 leads a day.

The answer tells you what happened in outcome: quoted, quote_not_sent (a quote was made but couldn't be sent — reason says why), waiting_approval (priced, waiting for the business), needs_pricing (reason says why), outside_area, or duplicate. Put where the lead came from in source ("Facebook") and the ad or form in campaign: they're kept on the quote and the customer, and the business sees which sources win work.

POST https://roundpro.app/api/v1/leads
{ "name": "Sarah Jones", "email": "sarah@example.com", "phone": "07700 900123",
  "address1": "10 Holyhead Road", "town": "Telford", "postcode": "TF1 1AJ",
  "propertyType": "Semi-detached", "bedrooms": "3", "extras": "Conservatory", "frequency": "Every 4 weeks",
  "source": "Facebook", "campaign": "Spring offer" }
→ 201 { "id": "lead…", "status": "quoted", "outcome": "quoted", "suggestedPricePence": 2100, "quoteId": "quo…", … }

Then listen for quote.accepted (the customer and job made) and quote.declined — for example to tell Facebook which ads bring paying customers. Other answers from a form can go in answers (question → answer) and anything they wrote in message; both are shown to the business.

AI assistants (the RoundPro connector)

RoundPro has a connector for AI assistants, using the Model Context Protocol (MCP), at https://roundpro.app/mcp. Once connected, the owner can ask in plain English — "who's due on the Monday round next week?", "move Mrs Jones's clean to Friday and give it to Dan", "add Sarah Smith at 10 Holyhead Road, every 4 weeks at £18" — and the assistant does it with the same actions as this API.

  • In Claude (Pro, Max, Team or Enterprise): go to Settings → Connectors, add a custom connector, and paste https://roundpro.app/mcp. On Team and Enterprise plans an administrator may need to allow it. Claude then opens RoundPro's sign-in page. It's added on the web or desktop app, then works on the phone app too.
  • The business owner signs in, chooses what the assistant may do (it can always look things up; changing customers, jobs or the schedule are separate ticks), and confirms with their password and two-step code.
  • Every change it makes is in the change log with Undo (messages already sent can't be taken back), and fires webhooks like any other change. It can never delete anything or see card details. It only messages customers — sending a quote, a reply to a lead or an invoice — or records payments if the owner ticked those, and it reads back what it will send first.
  • It shows in Settings → API & integrations under AI assistants, where it can be disconnected at once.

For developers and other assistants:

  • Transport: Streamable HTTP, JSON answers (no server-sent events), no sessions. Protocol versions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05.
  • Sign-in: OAuth 2.1 with dynamic client registration (RFC 7591), public clients and PKCE (S256). Metadata at https://roundpro.app/.well-known/oauth-protected-resource/mcp and https://roundpro.app/.well-known/oauth-authorization-server. Access tokens last an hour; refresh tokens are replaced on every use, and reusing an old one disconnects the app.
  • An API key also works, sent as Authorization: Bearer rp_live_…, for assistants that take a fixed header.
  • Tools are this API's operations, named with underscores (customers_list, jobs_update, visits_update…), each labelled read-only or not. A connection only sees the tools its permissions allow.

What it does, and doesn't

The API works through the same rules as the RoundPro app, so the schedule always stays right:

  • Creating a regular job puts its cleans on the schedule straight away (up to 16 weeks ahead) from its start date, which must be today or later. For an existing customer, give the date of their next clean.
  • Changing a job's frequency, start date or months off re-plans its upcoming cleans; cleans already done, invoiced or moved by hand are kept.
  • Changing a job's price updates its upcoming cleans and is recorded in its price history.
  • Moving one visit leaves the rest of the job's cycle where it is (unless the job counts from completion).
  • Archiving a customer stops their jobs and removes upcoming cleans; history and invoices are kept.

To keep businesses and their customers safe, version 1 of the API won't:

  • delete anything (customers are archived instead, and can be restored);
  • send emails or texts to customers unless the key has Quotes and replies (quotes, replies to leads, a first clean's date), Payments and invoices (sending an invoice) or Leads (a lead you add may be sent its quote, or a thank-you, if the business has switched that on); or change anyone's marketing choice;
  • mark cleans as done, take card payments, or create, change or void invoices (with Payments and invoices it can record money received);
  • show card or bank details, customer page links, or anyone's email or password;
  • change the team, settings, billing or sign-ins.

Examples

Add a new customer and book them in every 4 weeks on the Monday round:

POST https://roundpro.app/api/v1/customers
{ "name": "Sarah Jones", "email": "sarah@example.com", "phone": "07700 900123",
  "property": { "address1": "10 Holyhead Road", "town": "Telford", "postcode": "TF1 1AJ", "accessNotes": "Side gate, code 1234" } }
→ 201 { "id": "cus…", "properties": [ { "id": "prop…", … } ], … }

GET https://roundpro.app/api/v1/rounds
→ { "data": [ { "id": "rnd…", "name": "Monday" }, … ] }

POST https://roundpro.app/api/v1/jobs
{ "propertyId": "prop…", "pricePence": 1800, "frequencyWeeks": 4, "startDate": "2026-10-12", "roundId": "rnd…" }
→ 201 { "id": "job…", "nextVisitDate": "2026-10-12", … }

Find a customer by phone number or postcode:

GET https://roundpro.app/api/v1/customers?search=07700900123
GET https://roundpro.app/api/v1/customers?search=TF1%201AJ

Change a job to every 6 weeks and move it to another round:

PATCH https://roundpro.app/api/v1/jobs/job…
{ "frequencyWeeks": 6, "roundId": "rnd…" }

See tomorrow's work, then move one clean to Friday and give it to Dan:

GET https://roundpro.app/api/v1/visits?from=2026-10-08&to=2026-10-08
GET https://roundpro.app/api/v1/team

PATCH https://roundpro.app/api/v1/visits/vis…
{ "date": "2026-10-10", "operativeId": "usr…" }

Add a note to a customer (it's dated and marked with your key's name):

POST https://roundpro.app/api/v1/customers/cus…/notes
{ "text": "Asked for the conservatory roof next time" }

GoHighLevel, Zapier and AI assistants

No special app is needed: any tool that can send a web request with a header can use the API.

  • GoHighLevel, hearing from RoundPro: add a workflow with the Inbound Webhook trigger, copy its address into a RoundPro webhook, press Send a test, then map the fields (data.name, data.email, data.phone…) in the workflow.
  • Zapier, hearing from RoundPro: start a Zap with Webhooks by Zapier → Catch Hook, copy its address into a RoundPro webhook, and press Send a test so Zapier sees an example.
  • GoHighLevel, changing RoundPro: in a workflow, add a Webhook (or Custom Webhook) action. Method POST or PATCH, the URL from this reference, a header Authorization: Bearer rp_live_…, and a JSON body mapping the contact's fields. Keep RoundPro as the place customers and the schedule live; send changes into it.
  • Zapier, changing RoundPro: use Webhooks by Zapier → Custom Request. Set Data Pass-Through off, put the JSON body in Data, and add the Authorization and Content-Type headers. Use a Lookup (GET /customers?search=) step first to find the right customer ID.
  • AI assistants: use the RoundPro connector (see AI assistants above) — no key to copy. For an agent you build yourself, give it the OpenAPI file and a key with only the permissions it needs.

A ready-made RoundPro app for Zapier (instant triggers, actions and searches, prices in pounds — including Create Lead for Facebook lead forms) is available by invitation — email us for the link.

Versions and changes

  • This is version 1 (/api/v1). New endpoints and new fields in answers may be added at any time without notice — build your integration to ignore fields it doesn't know.
  • Anything that would break an existing integration (removing or renaming a field, changing what one means) only happens in a new version, with at least 6 months' notice to everyone with a key.
  • Every answer carries a RoundPro-Version header with the date of the current release.

Help

Questions, or something not working? Email info@cleanlynk.co.uk with the X-Request-Id from the answer, and what you sent (but never the key).

Account

Check a key and see which business it belongs to.

GET/me

Which business and key this is

The business the key belongs to and what the key may do. A good first call to check a key works.

Permission: Read (read)

Answers with the Me.

curl "https://roundpro.app/api/v1/me" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"

Customers

The people a business cleans for. Each has one or more properties.

GET/customers

List or search customers

Customers with their properties, newest first. Use search to find someone by name, email, phone, address or postcode. Archived customers are left out unless you ask for them.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
searchMatches name, email, phone, address or postcode. Up to 100 charactersstring
archivedOne of: "false", "true", "all". Default "false"string

Answers with a page of Customer objects (data, nextCursor).

curl "https://roundpro.app/api/v1/customers?search=Jones" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/customers/{id}

Get a customer

One customer with their properties.

Permission: Read (read)

Answers with the Customer.

curl "https://roundpro.app/api/v1/customers/CUSTOMER_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
POST/customers

Add a customer

Adds a customer, and their first property if you give the address. Add a job afterwards (POST /jobs) to put them on the schedule. Nothing is sent to the customer.

Permission: Customers (customers:write) · Answers 201 Created

Body

FieldType
namerequiredUp to 120 charactersstring
emailstring or null
phoneUp to 30 charactersstring or null
notesUp to 5000 charactersstring or null
sourceWhere they came from, e.g. "Facebook – Spring offer". Up to 80 charactersstring or null
contactByHow invoices and reminders reach them. One of: "email", "text"string or null
emailOptInboolean
textOptInboolean
propertyobject
property.address1requiredUp to 120 charactersstring
property.address2Up to 120 charactersstring or null
property.townUp to 80 charactersstring or null
property.postcodeUp to 10 charactersstring or null
property.accessNotesUp to 1000 charactersstring or null
property.what3wordsThree words with dots, like filled.count.soap. Up to 80 charactersstring or null

Answers with the Customer.

curl -X POST "https://roundpro.app/api/v1/customers" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Sarah Jones","email":"sarah@example.com","phone":"07700 900123","property":{"address1":"10 Holyhead Road","town":"Telford","postcode":"TF1 1AJ","accessNotes":"Side gate, code 1234"}}'
PATCH/customers/{id}

Update a customer

Change any of the fields given; the rest stay as they are. Sending notes replaces the notes — to add a line instead, use POST /customers/{id}/notes.

Permission: Customers (customers:write)

Body

FieldType
nameUp to 120 charactersstring
emailstring or null
phoneUp to 30 charactersstring or null
notesUp to 5000 charactersstring or null
contactByHow invoices and reminders reach them. One of: "email", "text"string or null
emailOptInboolean
textOptInboolean
messagesPausedboolean
sourceWhere they came from, e.g. "Facebook – Spring offer". Up to 80 charactersstring or null

Answers with the Customer.

curl -X PATCH "https://roundpro.app/api/v1/customers/CUSTOMER_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"phone":"07700 900456","contactBy":"text"}'
POST/customers/{id}/notes

Add a note to a customer

Adds a dated line to the end of the customer's notes, marked as coming from this key.

Permission: Customers (customers:write)

Body

FieldType
textrequiredUp to 1000 charactersstring

Answers with the Customer.

curl -X POST "https://roundpro.app/api/v1/customers/CUSTOMER_ID/notes" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"text":"Asked for the conservatory roof next time"}'
POST/customers/{id}/archive

Archive a customer

For a customer who's stopped: their jobs stop and their upcoming cleans are removed. Their history and invoices are kept, and they can be restored. (Customers are never deleted through the API.)

Permission: Customers (customers:write)

Body

FieldType
reasonWhy they left. One of: "MOVED", "PRICE", "QUALITY", "OTHER_CLEANER", "NOT_NEEDED", "WE_STOPPED", "UNKNOWN"string
noteUp to 300 charactersstring or null

Answers with the Customer.

curl -X POST "https://roundpro.app/api/v1/customers/CUSTOMER_ID/archive" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"MOVED","note":"Moved to Shrewsbury"}'
POST/customers/{id}/restore

Restore an archived customer

Brings an archived customer back. Their jobs stay stopped — restart the ones you need (POST /jobs/{id}/restart).

Permission: Customers (customers:write)

Answers with the Customer.

curl -X POST "https://roundpro.app/api/v1/customers/CUSTOMER_ID/restore" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Properties

The addresses work is done at. A job belongs to a property.

POST/customers/{id}/properties

Add a property to a customer

Another address for an existing customer. It's placed on the map from its postcode.

Permission: Customers (customers:write) · Answers 201 Created

Body

FieldType
address1requiredUp to 120 charactersstring
address2Up to 120 charactersstring or null
townUp to 80 charactersstring or null
postcodeUp to 10 charactersstring or null
accessNotesUp to 1000 charactersstring or null
what3wordsThree words with dots, like filled.count.soap. Up to 80 charactersstring or null

Answers with the Property.

curl -X POST "https://roundpro.app/api/v1/customers/CUSTOMER_ID/properties" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
PATCH/properties/{id}

Update a property

Change any of the fields given. A new postcode or what3words moves its map point.

Permission: Customers (customers:write)

Body

FieldType
address1Up to 120 charactersstring
address2Up to 120 charactersstring or null
townUp to 80 charactersstring or null
postcodeUp to 10 charactersstring or null
accessNotesUp to 1000 charactersstring or null
what3wordsThree words with dots, like filled.count.soap. Up to 80 charactersstring or null

Answers with the Property.

curl -X PATCH "https://roundpro.app/api/v1/properties/PROPERTY_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"accessNotes":"Dog in the back garden — ring first"}'

Jobs

Regular or one-off work at a property: what, how often, the price, the round and who does it. A job's cleans (visits) are scheduled from it.

GET/jobs

List jobs

Jobs, filtered by customer, property or round. Active jobs only unless you ask.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
propertyIdAn ID, as returned by the API. Up to 64 charactersstring
roundIdAn ID, as returned by the API. Up to 64 charactersstring
activeOne of: "true", "false", "all". Default "true"string

Answers with a page of Job objects (data, nextCursor).

curl "https://roundpro.app/api/v1/jobs" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/jobs/{id}

Get a job

One job, with the date of its next clean.

Permission: Read (read)

Answers with the Job.

curl "https://roundpro.app/api/v1/jobs/JOB_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
POST/jobs

Create a job

Regular (every so many weeks) or one-off work at a property. Its cleans are put on the schedule straight away. Give either a price, or services from the business's list each with a price.

Permission: Jobs (jobs:write) · Answers 201 Created

Body

FieldType
propertyIdrequiredAn ID, as returned by the API. Up to 64 charactersstring
descriptionUp to 200 charactersstring or null
pricePenceThe price of a clean. Leave out when giving services (it's their total).. 0–10000000integer
servicesServices from the business's list, each with its price. Up to 20array of object
services[].serviceIdrequiredAn ID, as returned by the API. Up to 64 charactersstring
services[].pricePencerequiredWhole pence (£25.50 is 2550)integer
frequencyWeeksWeeks between cleans; null or left out for a one-off. How many weeks between cleans. 1–52integer or null
anchorModefixed: cleans stay on their cycle dates. from_completion: the next clean is counted from when the last one was done.. One of: "fixed", "from_completion"string
startDaterequiredA date, YYYY-MM-DD, in the business's time zonestring
roundIdAn ID, as returned by the API. Up to 64 charactersstring or null
operativeIdWho does it, if not the round's usual person. An ID, as returned by the API. Up to 64 charactersstring or null
durationMinutes1–600integer or null
setTimeA set time for every clean, e.g. { kind: "after", time: "14:00" }object or null
setTime.kindrequiredat: be there then; after: not before then; before: done by then. One of: "at", "after", "before"string
setTime.timerequired24-hour time, e.g. 14:00string
offMonthsUp to 11. 1–12array of integer
hazardsOne of: "OVERHEAD_LINES", "DOG", "SLIP", "LOOSE_COVER", "FRAGILE_ROOF", "UNEVEN_GROUND", "TRAFFIC", "TIGHT_ACCESS"array of string
hazardNotesUp to 500 charactersstring or null
notesA note for whoever does every clean, e.g. "Lock the back gate before leaving". Up to 500 charactersstring or null

Answers with the Job.

curl -X POST "https://roundpro.app/api/v1/jobs" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"propertyId":"prop_123","pricePence":1800,"frequencyWeeks":4,"startDate":"2026-10-14","roundId":"round_456"}'
PATCH/jobs/{id}

Update a job

Change price, frequency, round, who does it, months off or other details. Upcoming cleans are updated to match; cleans already done or invoiced aren't touched. Changing startDate re-times all its upcoming cleans from that date. Nothing is sent to the customer.

Permission: Jobs (jobs:write)

Body

FieldType
descriptionUp to 200 charactersstring
pricePenceWhole pence (£25.50 is 2550)integer
frequencyWeeksHow many weeks between cleans. 1–52integer or null
anchorModefixed: cleans stay on their cycle dates. from_completion: the next clean is counted from when the last one was done.. One of: "fixed", "from_completion"string
startDateRe-time the job: its cleans are worked out again from this date. YYYY-MM-DDstring
roundIdMove it to another round (or none). An ID, as returned by the API. Up to 64 charactersstring or null
operativeIdAn ID, as returned by the API. Up to 64 charactersstring or null
durationMinutes1–600integer or null
offMonthsUp to 11. 1–12array of integer
setTimeA set time for every clean, or null for any timeobject or null
setTime.kindrequiredat: be there then; after: not before then; before: done by then. One of: "at", "after", "before"string
setTime.timerequired24-hour time, e.g. 14:00string
hazardsOne of: "OVERHEAD_LINES", "DOG", "SLIP", "LOOSE_COVER", "FRAGILE_ROOF", "UNEVEN_GROUND", "TRAFFIC", "TIGHT_ACCESS"array of string
hazardNotesUp to 500 charactersstring or null
notesA note for whoever does every clean (null or "" removes it). Up to 500 charactersstring or null
servicesReplace the job's services (its price becomes their total). Send [] to price it as one amount with pricePence.. Up to 20array of object
services[].serviceIdrequiredAn ID, as returned by the API. Up to 64 charactersstring
services[].pricePencerequiredWhole pence (£25.50 is 2550)integer

Answers with the Job.

curl -X PATCH "https://roundpro.app/api/v1/jobs/JOB_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"frequencyWeeks":6,"roundId":"round_789"}'
POST/jobs/{id}/stop

Stop a job

No more cleans: upcoming ones are removed. History is kept and it can be restarted.

Permission: Jobs (jobs:write)

Answers with the Job.

curl -X POST "https://roundpro.app/api/v1/jobs/JOB_ID/stop" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
POST/jobs/{id}/book-first-clean

Book a lead's first clean

When someone accepts a quote from a lead, their job waits for the business to choose the first clean. This books it on the date given (the rest follow at the job's frequency) and, with tellCustomer, lets them know the date in the conversation they enquired in.

Permission: Quotes and replies (quotes:write)

Body

FieldType
daterequiredThe day of the first clean (today or later). YYYY-MM-DDstring
tellCustomerrequiredLet them know the date, in the conversation they enquired in (this messages them: true or false, it must be chosen)boolean

Answers with the Job.

curl -X POST "https://roundpro.app/api/v1/jobs/JOB_ID/book-first-clean" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-10-20","tellCustomer":true}'
POST/jobs/{id}/restart

Restart a stopped job

Puts its cleans back on the schedule from its cycle.

Permission: Jobs (jobs:write)

Answers with the Job.

curl -X POST "https://roundpro.app/api/v1/jobs/JOB_ID/restart" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Schedule

Visits: the individual cleans on the schedule, each on a date, with whoever's doing it.

GET/visits

List visits (the schedule)

Visits between two dates (up to 62 days apart), optionally for one person, round, customer or status. In date order.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
fromrequiredA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring
torequiredA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring
operativeIdAn ID, as returned by the API. Up to 64 charactersstring
roundIdAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
statusOne of: "scheduled", "completed", "skipped", "cancelled"string

Answers with a page of Visit objects (data, nextCursor).

curl "https://roundpro.app/api/v1/visits?from=2026-10-12&to=2026-10-18" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/visits/{id}

Get a visit

One visit (a single clean on the schedule), by its ID.

Permission: Read (read)

Answers with the Visit.

curl "https://roundpro.app/api/v1/visits/VISIT_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
PATCH/visits/{id}

Move, assign or add a note to a visit

Move one visit to another day (today or later), give it to someone on the team, or set its note. Only visits still scheduled can be moved. Moving one visit leaves the rest of the job's cleans where they are (unless the job counts from completion). The customer isn't told.

Permission: Schedule (schedule:write)

Body

FieldType
dateMove it to this day (today or later). YYYY-MM-DDstring
operativeIdGive it to this person on the team (null: nobody). An ID, as returned by the API. Up to 64 charactersstring or null
notesUp to 1000 charactersstring or null
setTimeThis clean only: a set time, null for any time, or "usual" to go back to the job'sobject or string or null

Answers with the Visit.

curl -X PATCH "https://roundpro.app/api/v1/visits/VISIT_ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-10-16","operativeId":"user_123"}'
POST/visits/{id}/skip

Skip a visit

Skip one clean that's still scheduled, with a reason (and a note if you like). The rest of the job's cleans stay booked. Nothing is invoiced for it and the customer isn't told. The owner can undo it.

Permission: Schedule (schedule:write)

Body

FieldType
reasonrequiredno_access: gate locked or blocked; customer_request: they asked to skip; weather; unsafe; dog: dog loose; equipment: van or equipment problem; other. One of: "no_access", "customer_request", "weather", "unsafe", "dog", "equipment", "other"string
noteAnything to add, e.g. what the customer said. Up to 500 charactersstring or null

Answers with the Visit.

curl -X POST "https://roundpro.app/api/v1/visits/VISIT_ID/skip" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason":"customer_request","note":"Away on holiday"}'

Team and rounds

Who's on the team and the business's rounds, for assigning work.

GET/team

List the team

Everyone who can sign in, for assigning work.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring

Answers with a page of TeamMember objects (data, nextCursor).

curl "https://roundpro.app/api/v1/team" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/rounds

List rounds

The business's rounds.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring

Answers with a page of Round objects (data, nextCursor).

curl "https://roundpro.app/api/v1/rounds" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"

Leads and quotes

Enquiries from people who aren't customers yet (from a Facebook lead form, a website and so on), and the quotes sent to them. A lead is priced from the business's price list; with automatic quoting on, the quote goes straight away.

POST/leads

Add a lead

Adds an enquiry, then deals with it as the business has set up in Settings › ALICE – Lead Assistant: it's priced from the price list and, with automatic quoting on, the quote is texted or emailed to them straight away. Otherwise it waits in Leads for the business (a thank-you may be sent). The answer says what happened (outcome). The same person enquiring again within 7 days is kept as a duplicate and nothing is sent twice. Give the answers as people typed them: "Semi-detached", "3 bedrooms", "Every 4 weeks" are all understood.

Permission: Leads (leads:write) · Answers 201 Created

Body

FieldType
namerequiredUp to 120 charactersstring
emailstring or null
phoneA UK mobile lets RoundPro text them. Up to 30 charactersstring or null
address1Needed before a quote can be made. Up to 120 charactersstring or null
address2Up to 120 charactersstring or null
townUp to 80 charactersstring or null
postcodeUp to 10 charactersstring or null
propertyTypeAs they answered, e.g. "Semi-detached house" (flat, terraced, semi, detached, bungalow). Up to 60 charactersstring or null
bedroomse.g. 3 or "3 bedrooms"string or integer or null
extrasExtras they want, as a list or comma separated, e.g. "Conservatory, Extension"string or array of string or null
frequencyHow often, e.g. "Every 4 weeks", "Monthly", "One-off" (or a number of weeks)string or integer or null
messageAnything else they wrote. Up to 2000 charactersstring or null
answersAny other questions and answers from the form (up to 20)object
sourceWhere it came from, e.g. "Facebook". Up to 60 charactersstring or null
campaignThe ad, campaign or form, e.g. "Spring offer". Up to 120 charactersstring or null

Answers with the Lead.

curl -X POST "https://roundpro.app/api/v1/leads" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Sarah Jones","email":"sarah@example.com","phone":"07700 900123","address1":"10 Holyhead Road","town":"Telford","postcode":"TF1 1AJ","propertyType":"Semi-detached","bedrooms":"3","extras":"Conservatory","frequency":"Every 4 weeks","source":"Facebook","campaign":"Spring offer"}'
GET/leads

List leads

Leads, newest first.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
statusOne of: "new", "quoted", "closed"string
sourceOnly leads from this source. Up to 60 charactersstring

Answers with a page of Lead objects (data, nextCursor).

curl "https://roundpro.app/api/v1/leads" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/leads/{id}

Get a lead

One lead, with what happened to it.

Permission: Read (read)

Answers with the Lead.

curl "https://roundpro.app/api/v1/leads/ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/quotes

List quotes

Quotes, newest first. Filter by status, or by the lead or customer they belong to.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
statusOne of: "draft", "sent", "viewed", "accepted", "declined", "expired", "withdrawn"string
leadIdAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring

Answers with a page of Quote objects (data, nextCursor).

curl "https://roundpro.app/api/v1/quotes" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/quotes/{id}

Get a quote

One quote: what was offered, and whether it's been sent, opened and answered.

Permission: Read (read)

Answers with the Quote.

curl "https://roundpro.app/api/v1/quotes/ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
POST/quotes

Make a quote

A quote for an existing customer (customerId, and optionally one of their properties) or someone new (name, address and an email or UK mobile). The price is the total of the services. With send: true it's emailed or texted to them straight away with a link to accept; otherwise it's kept as a draft for the owner to check and send. When they accept, they become a customer with a job.

Permission: Quotes and replies (quotes:write) · Answers 201 Created

Body

FieldType
customerIdQuote an existing customer (their details are used). Up to 64 charactersstring
propertyIdOne of that customer's properties; leave out to use their first, or give address1 for a new address. Up to 64 charactersstring
nameFor someone who isn't a customer yet. Up to 120 charactersstring
emailstring or null
phoneA UK mobile, to send it by text. Up to 30 charactersstring or null
address1Up to 120 charactersstring or null
address2Up to 120 charactersstring or null
townUp to 80 charactersstring or null
postcodeUp to 10 charactersstring or null
servicesrequiredWhat's being quoted; the price is their total. Up to 10array of object
services[].namerequiredWhat it is, e.g. "Roof clean" or "Windows front & back". Up to 120 charactersstring
services[].pricePencerequiredWhole pence (£25.50 is 2550)integer
frequencyWeeksrequiredHow often (weeks), or null for a one-off. How many weeks between cleans. 1–52integer or null
contactByHow to send it (default: email if there's an address, else text). One of: "email", "text"string
noteA note shown on the quote. Up to 1000 charactersstring or null
sendSend it straight away (true), or keep it as a draft for the owner to check (false). Default falseboolean

Answers with the Quote.

curl -X POST "https://roundpro.app/api/v1/quotes" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"cus_123","services":[{"name":"Roof clean","pricePence":100000}],"frequencyWeeks":null,"send":false}'
POST/quotes/{id}/send

Send a quote

Emails or texts the quote to them with a link to view and accept (sending again sends a fresh link). Refused if it's already been answered or withdrawn. This messages the customer and can't be undone.

Permission: Quotes and replies (quotes:write)

Body

FieldType
contactBySend by email or text (default: as set on the quote). One of: "email", "text"string

Answers with the Quote.

curl -X POST "https://roundpro.app/api/v1/quotes/ID/send" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
POST/leads/{id}/reply

Reply to a lead

Sends a message to the lead in the conversation they started (Facebook Messenger or email), as the business. The AI assistant stands back from that conversation afterwards. This messages them and can't be undone.

Permission: Quotes and replies (quotes:write)

Body

FieldType
messagerequiredWhat to say to them, in the conversation they started (Messenger or email). Up to 1000 charactersstring

Answers with the Lead.

curl -X POST "https://roundpro.app/api/v1/leads/ID/reply" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'

Money

Invoices and payments: read them, record payments, and send an invoice to the customer.

GET/invoices

List invoices

Invoices, newest first, by customer, status or issue date.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
statusOne of: "draft", "sent", "paid", "void"string
fromA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring
toA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring

Answers with a page of Invoice objects (data, nextCursor).

curl "https://roundpro.app/api/v1/invoices" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/invoices/{id}

Get an invoice

One invoice, by its ID.

Permission: Read (read)

Answers with the Invoice.

curl "https://roundpro.app/api/v1/invoices/ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
GET/payments

List payments

Payments received, newest first, by customer or date.

Permission: Read (read)

Query parameters

FieldType
limitUp to 100 (default 50). 1–100. Default 50integer
cursorUp to 200 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
fromA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring
toA date, YYYY-MM-DD, in the business's time zone. YYYY-MM-DDstring

Answers with a page of Payment objects (data, nextCursor).

curl "https://roundpro.app/api/v1/payments" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
POST/payments

Record a payment

Money a customer has paid (cash, bank transfer, card taken elsewhere and so on). It pays the invoice given first, then their oldest unpaid invoices; anything over is kept as credit. The customer isn't messaged. The owner can undo it.

Permission: Payments and invoices (money:write) · Answers 201 Created

Body

FieldType
customerIdrequiredAn ID, as returned by the API. Up to 64 charactersstring
amountPencerequiredWhole pence (£25.50 is 2550)integer
methodrequiredOne of: "cash", "card", "bank transfer", "direct debit", "cheque", "other"string
paidOnThe day it was paid (default today; not in the future). YYYY-MM-DDstring
referencee.g. the bank reference. Up to 120 charactersstring or null
invoiceIdPay this invoice first (otherwise the oldest unpaid invoices are paid, and anything over is kept as credit). Up to 64 charactersstring

Answers with the Payment.

curl -X POST "https://roundpro.app/api/v1/payments" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"cus_123","amountPence":1800,"method":"cash"}'
POST/invoices/{id}/send

Send an invoice

Emails or texts the invoice to the customer (their way), with a link to view and pay. This messages the customer and can't be undone.

Permission: Payments and invoices (money:write)

Answers with the Invoice.

curl -X POST "https://roundpro.app/api/v1/invoices/ID/send" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Webhooks

A key subscribing to events (for tools like Zapier, whose triggers add and remove their own webhooks). Each subscription is listed in the owner's Settings and is removed if the key is revoked.

GET/webhooks

List this key's webhook subscriptions

The webhooks this key has subscribed (not ones the owner added in Settings).

Permission: Read (read)

Answers with a page of object objects (data, nextCursor).

curl "https://roundpro.app/api/v1/webhooks" \
  -H "Authorization: Bearer $ROUNDPRO_KEY"
POST/webhooks

Subscribe to events

Send events to an address (https, on the public internet). Messages are signed and retried just like webhooks added in Settings. The signing secret is in the answer, once. A key can have up to 50 subscriptions.

Permission: Read (read) · Answers 201 Created

Body

FieldType
urlrequiredAn https address on the public internet. Up to 500 charactersstring
eventsrequiredEvent names (see Webhooks), or ["*"] for all. Up to 20. Up to 40 charactersarray of string
descriptionShown to the owner in Settings, e.g. the Zap's name. Up to 60 charactersstring

Answers with JSON.

curl -X POST "https://roundpro.app/api/v1/webhooks" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.zapier.com/hooks/standard/123/abc/","events":["visit.completed"],"description":"Zap: tell the office"}'
DELETE/webhooks/{id}

Unsubscribe

Stop a subscription this key made. Nothing more is sent to it.

Permission: Read (read)

Answers with JSON.

curl -X DELETE "https://roundpro.app/api/v1/webhooks/ID" \
  -H "Authorization: Bearer $ROUNDPRO_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Objects

What comes back. More fields may be added over time; ignore any you don’t know.

Customer

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
namestring
emailstring or null
phonestring or null
notesstring or null
contactByEmpty means email if there's an address, otherwise text. How invoices and reminders reach them. One of: "email", "text"string or null
emailOptInGets service emails (reminders, invoices)boolean
textOptInCan be textedboolean
marketingOptOutSaid no to marketing (read-only: changed by the customer's unsubscribe link or in RoundPro)boolean
messagesPausedRoundPro sends them nothing for nowboolean
archivedboolean
archivedAtISO 8601 date and timestring or null
creditPenceMoney paid and not yet used against an invoice. 0–10000000integer
hasSavedCardPays automatically by saved card or Direct Debitboolean
sourceWhere they came from ("Facebook – Spring offer"), if knownstring or null
propertiesarray of Property
createdAtISO 8601 date and timestring

Property

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
address1string
address2string or null
townstring or null
postcodestring or null
accessNotesstring or null
what3wordsstring or null
locationMap point, if knownobject or null
location.latnumber
location.lngnumber
createdAtISO 8601 date and timestring

Job

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
propertyIdAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
roundIdAn ID, as returned by the API. Up to 64 charactersstring or null
operativeIdAn ID, as returned by the API. Up to 64 charactersstring or null
descriptionstring
pricePenceWhole pence (£25.50 is 2550)integer
frequencyWeeksWeeks between cleans; null for a one-off. How many weeks between cleans. 1–52integer or null
anchorModefixed: cleans stay on their cycle dates. from_completion: the next clean is counted from when the last one was done.. One of: "fixed", "from_completion"string
startDateThe first clean (the cycle counts from here). YYYY-MM-DDstring
durationMinutesHow long a clean usually takes, if set. -9007199254740991–9007199254740991integer or null
setTimeA set time for every clean (route planning works around it), or nullobject or null
setTime.kindat: be there then; after: not before then; before: done by then. One of: "at", "after", "before"string
setTime.time24-hour time, e.g. 14:00string
offMonthsMonths with no cleans (seasonal), 1 = January. 1–12array of integer
hazardsOne of: "OVERHEAD_LINES", "DOG", "SLIP", "LOOSE_COVER", "FRAGILE_ROOF", "UNEVEN_GROUND", "TRAFFIC", "TIGHT_ACCESS"array of string
hazardNotesstring or null
notesA note for whoever does every clean, e.g. "Lock the back gate before leaving" (shown on the schedule card)string or null
servicesarray of JobService
services[].serviceIdAn ID, as returned by the API. Up to 64 charactersstring or null
services[].namestring
services[].pricePenceWhole pence (£25.50 is 2550)integer
activeboolean
nextVisitDateA date, YYYY-MM-DD, in the business's time zonestring or null
createdAtISO 8601 date and timestring

JobService

FieldType
serviceIdAn ID, as returned by the API. Up to 64 charactersstring or null
namestring
pricePenceWhole pence (£25.50 is 2550)integer

Visit

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
jobIdAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
propertyIdAn ID, as returned by the API. Up to 64 charactersstring
dateA date, YYYY-MM-DD, in the business's time zonestring
originalDateIts date on the job's cycle (differs from date when it's been moved). YYYY-MM-DDstring
statusOne of: "scheduled", "completed", "skipped", "cancelled"string
pricePenceWhole pence (£25.50 is 2550)integer
operativeIdAn ID, as returned by the API. Up to 64 charactersstring or null
notesstring or null
completedAtISO 8601 date and timestring or null
skipReasonstring or null
invoiceIdAn ID, as returned by the API. Up to 64 charactersstring or null
setTimeThis clean's set time: its own if it has one, otherwise the job'sobject or null
setTime.kindat: be there then; after: not before then; before: done by then. One of: "at", "after", "before"string
setTime.time24-hour time, e.g. 14:00string

TeamMember

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
namestring
roleOne of: "owner", "admin", "operative"string
activeboolean

Round

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
namestring
colourstring
notesstring or null
defaultOperativeIdAn ID, as returned by the API. Up to 64 charactersstring or null

Invoice

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
numberstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
statusOne of: "draft", "sent", "paid", "void"string
issueDateA date, YYYY-MM-DD, in the business's time zonestring
dueDateA date, YYYY-MM-DD, in the business's time zonestring
subtotalPenceWhole pence (£25.50 is 2550)integer
vatPenceWhole pence (£25.50 is 2550)integer
totalPenceWhole pence (£25.50 is 2550)integer
paidPenceWhole pence (£25.50 is 2550)integer
paidAtISO 8601 date and timestring or null

Payment

FieldType
idAn ID, as returned by the API. Up to 64 charactersstring
customerIdAn ID, as returned by the API. Up to 64 charactersstring
invoiceIdAn ID, as returned by the API. Up to 64 charactersstring or null
amountPenceWhole pence (£25.50 is 2550)integer
methodstring
paidOnA date, YYYY-MM-DD, in the business's time zonestring
referencestring or null

Me

FieldType
businessobject
business.idAn ID, as returned by the API. Up to 64 charactersstring
business.namestring
business.timezonestring
business.vatRegisteredboolean
business.pricesIncludeVatThe business types and talks about prices including VAT. Prices here are still before VAT: take the VAT out of a price they give you before sending it.boolean
keyobject
key.idAn ID, as returned by the API. Up to 64 charactersstring
key.namestring
key.scopesOne of: "read", "customers:write", "jobs:write", "schedule:write", "leads:write", "quotes:write", "money:write"array of string
key.createdAtISO 8601 date and timestring
todayToday's date where the business is. YYYY-MM-DDstring

Error

FieldType
errorobject
error.codeOne of: "unauthorized", "forbidden", "not_found", "invalid", "conflict", "rate_limited", "too_large", "server_error"string
error.messagestring
error.fieldThe field at fault, when there is onestring