{"openapi":"3.1.0","info":{"title":"RoughFigure API","version":"1.0.0","description":"Read your leads, update their status and list your estimators from your own systems. Webhooks tell you the moment something happens."},"servers":[{"url":"https://api.roughfigure.com"}],"security":[{"apiKey":[]}],"tags":[{"name":"Account","description":"Check a key works."},{"name":"Leads","description":"People who sent their details through your estimator."},{"name":"Estimators"},{"name":"Site visits","description":"Visits customers or your team booked for a lead."},{"name":"Webhook subscriptions","description":"Add and remove webhooks from code (this is how Zapier connects). Webhooks added here can also be seen in the dashboard."}],"paths":{"/v1/me":{"get":{"tags":["Account"],"operationId":"getMe","summary":"The business and permissions for this key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"business":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"apiKey":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/leads":{"get":{"tags":["Leads"],"operationId":"listLeads","summary":"List leads, newest first","description":"Needs the `leads:read` permission.","parameters":[{"name":"status","in":"query","description":"One or more statuses, comma separated.","schema":{"type":"string","example":"new,contacted"}},{"name":"estimatorId","in":"query","schema":{"type":"string"}},{"name":"email","in":"query","description":"Only leads with this email address (any capitals).","schema":{"type":"string","format":"email"}},{"name":"createdAfter","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"updatedAfter","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"The `nextCursor` from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of leads","content":{"application/json":{"schema":{"type":"object","required":["data","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page. Null on the last page."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/bookings":{"get":{"tags":["Site visits"],"operationId":"listBookings","summary":"List site visits, newest booking first","description":"Each item has the same shape as the `booking.created` webhook. Needs the `leads:read` permission.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Recent site visits","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"},"lead":{"$ref":"#/components/schemas/Lead"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/leads/{leadId}":{"parameters":[{"name":"leadId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Leads"],"operationId":"getLead","summary":"Get one lead","description":"Needs the `leads:read` permission.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Leads"],"operationId":"updateLead","summary":"Update a lead’s status","description":"Needs the `leads:write` permission. The change shows on the lead’s timeline as made with your key, and sends a `lead.updated` webhook. Marking a lead won can start a Google review request.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"status":{"type":"string","enum":["new","contacted","quoted","won","lost"]},"lostReason":{"type":["string","null"],"maxLength":200,"description":"Kept when the status is (or becomes) lost."},"finalJobValue":{"type":["integer","null"],"minimum":0,"description":"In pence. Kept when the status is (or becomes) won."},"tags":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":30},"description":"Replaces the lead’s tags."}}},"example":{"status":"won","finalJobValue":42000}}}},"responses":{"200":{"description":"The updated lead","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/estimators":{"get":{"tags":["Estimators"],"operationId":"listEstimators","summary":"List estimators","description":"Needs the `estimators:read` permission. Archived estimators are left out.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Estimator"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/v1/webhooks":{"post":{"tags":["Webhook subscriptions"],"operationId":"subscribe","summary":"Add a webhook","description":"Needs the `leads:read` permission. The response includes the signing secret; it isn’t shown again here (admins can reveal it in the dashboard).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"Must use https."},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEvent"}},"description":{"type":"string","maxLength":100}}}}}},"responses":{"201":{"description":"Added","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"secret":{"type":"string","example":"whsec_…"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/webhooks/{webhookId}":{"delete":{"tags":["Webhook subscriptions"],"operationId":"unsubscribe","summary":"Remove a webhook","description":"A key can only remove webhooks it added.","parameters":[{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"lead.created":{"post":{"summary":"New lead","description":"Sent to your URL as a signed POST. Reply with any 2xx status within 10 seconds.","parameters":[{"name":"RoughFigure-Event","in":"header","schema":{"type":"string","const":"lead.created"}},{"name":"RoughFigure-Delivery","in":"header","schema":{"type":"string"}},{"name":"RoughFigure-Signature","in":"header","schema":{"type":"string","example":"t=1767225600,v1=5f2b…"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event ID, also sent as the RoughFigure-Delivery header. The same on every retry: use it to ignore repeats."},"type":{"type":"string","const":"lead.created"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"lead":{"$ref":"#/components/schemas/Lead"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"lead.updated":{"post":{"summary":"Lead changed (status, value, tags or who it’s assigned to)","description":"Sent to your URL as a signed POST. Reply with any 2xx status within 10 seconds.","parameters":[{"name":"RoughFigure-Event","in":"header","schema":{"type":"string","const":"lead.updated"}},{"name":"RoughFigure-Delivery","in":"header","schema":{"type":"string"}},{"name":"RoughFigure-Signature","in":"header","schema":{"type":"string","example":"t=1767225600,v1=5f2b…"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event ID, also sent as the RoughFigure-Delivery header. The same on every retry: use it to ignore repeats."},"type":{"type":"string","const":"lead.updated"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"lead":{"$ref":"#/components/schemas/Lead"},"changes":{"type":"array","items":{"type":"string","enum":["status","finalJobValue","lostReason","assignedUserId","tags"]}}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"booking.created":{"post":{"summary":"Site visit booked","description":"Sent to your URL as a signed POST. Reply with any 2xx status within 10 seconds.","parameters":[{"name":"RoughFigure-Event","in":"header","schema":{"type":"string","const":"booking.created"}},{"name":"RoughFigure-Delivery","in":"header","schema":{"type":"string"}},{"name":"RoughFigure-Signature","in":"header","schema":{"type":"string","example":"t=1767225600,v1=5f2b…"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type","createdAt","data"],"properties":{"id":{"type":"string","description":"The event ID, also sent as the RoughFigure-Delivery header. The same on every retry: use it to ignore repeats."},"type":{"type":"string","const":"booking.created"},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"},"lead":{"$ref":"#/components/schemas/Lead"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Create a key in the dashboard under Integrations, then send it as `Authorization: Bearer tbp_live_…`."}},"schemas":{"Lead":{"type":"object","required":["id","createdAt","updatedAt","status","name","email"],"properties":{"id":{"type":"string","example":"cmh1x2y3z0000abcd"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["new","contacted","quoted","won","lost"]},"name":{"type":"string","example":"James Carter"},"email":{"type":"string","format":"email","example":"james.carter@oakline-tree-care.test"},"phone":{"type":["string","null"],"example":"+447700900123"},"postcode":{"type":["string","null"],"example":"SW1A 1AA"},"service":{"type":["string","null"],"description":"The first job type they chose, e.g. \"Tree felling\".","example":"Tree felling"},"estimateLow":{"type":["integer","null"],"description":"In pence.","example":30000},"estimateHigh":{"type":["integer","null"],"description":"In pence.","example":45000},"estimateText":{"type":["string","null"],"example":"£300 – £450"},"currency":{"type":"string","example":"GBP"},"vatIncluded":{"type":"boolean"},"requiresSiteVisit":{"type":"boolean","description":"True when the estimator showed \"site visit needed\" instead of a price."},"answers":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"answer":{"type":"string"}}},"example":[{"question":"How tall is the tree?","answer":"Small (under 5m)"}]},"message":{"type":["string","null"],"description":"Anything else they told you."},"photoCount":{"type":"integer","description":"Photos are in the dashboard."},"tags":{"type":"array","items":{"type":"string"}},"assignedTo":{"type":["string","null"],"description":"Name of the team member."},"finalJobValue":{"type":["integer","null"],"description":"In pence, once won."},"lostReason":{"type":["string","null"]},"wonAt":{"type":["string","null"],"format":"date-time"},"marketingConsent":{"type":"boolean"},"source":{"type":"string","enum":["widget","hosted_page","import","api"]},"sourceUrl":{"type":["string","null"],"description":"The page the estimator was on."},"utm":{"type":["object","null"],"additionalProperties":{"type":"string"}},"estimatorId":{"type":["string","null"]},"estimatorName":{"type":["string","null"]},"dashboardUrl":{"type":"string","format":"uri"}}},"Estimator":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","published"]},"language":{"type":"string","enum":["en-GB"]},"publicKey":{"type":"string","description":"Goes in the embed code (`data-key`)."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Booking":{"type":"object","properties":{"id":{"type":"string"},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["confirmed","cancelled","completed","no_show"]},"bookedBy":{"type":"string","enum":["homeowner","staff"]},"timeZone":{"type":"string","example":"Europe/London"}}},"WebhookEvent":{"type":"string","enum":["lead.created","lead.updated","booking.created"],"description":"`lead.created`: New lead. `lead.updated`: Lead changed (status, value, tags or who it’s assigned to). `booking.created`: Site visit booked"},"Scope":{"type":"string","enum":["leads:read","leads:write","estimators:read"],"description":"`leads:read`: Read leads. `leads:write`: Change lead status. `estimators:read`: List estimators"},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string","example":"Lead not found"},"details":{"description":"For validation errors: which fields need fixing."},"requestId":{"type":"string","description":"Quote this if you contact us."}}}}}},"responses":{"BadRequest":{"description":"Something in the request needs fixing (`validation_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"No key, or not a valid one (`invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the permission (`insufficient_scope`), or the plan doesn’t include the API (`upgrade_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"More than 120 requests a minute (`rate_limited`). Wait for the Retry-After seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}