{"openapi":"3.1.0","info":{"title":"Earful API","version":"1.0.0","summary":"Reviews from Google, Tripadvisor, Trustpilot, the App Store, Google Play and more, in one API.","description":"Everything the Earful app does: find a business on review sites, add its review pages, read reviews as they arrive, draft replies with AI, post them, and get webhooks for new reviews.\n\nAuthenticate with `Authorization: Bearer ef_…`. Every plan includes the API; plan limits apply as in the app. Errors are RFC 9457 `application/problem+json`. Every keyed answer carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` for its key (120 requests a minute). Ids are opaque strings like `loc_3` (bare numbers still work in paths). Lists page with `cursor`: pass `nextCursor` back until it’s null (`offset` still works but is deprecated). `GET /reviews/changes` keeps a copy in sync. Every POST takes an `Idempotency-Key`. Webhooks are signed, retried with backoff, logged and replayable. An `ef_test_` key reaches a sandbox with made-up data that makes no outside calls except its own webhooks, sent with `livemode: false`. TypeScript and Python clients generated from this spec are in the repo’s sdk/ folder.\n\nThe MCP server at `/mcp` also takes OAuth 2.1 access tokens (see `mcpOAuth`), which is how claude.ai connectors sign in. Those tokens only work at `/mcp`; the REST API takes keys.","contact":{"name":"Earful support","email":"admin@onibaku.co.uk","url":"https://earful.onibaku.co.uk/api/docs"}},"servers":[{"url":"https://earful.onibaku.co.uk","description":"This server"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Place search","description":"Find a business on review sites by name."},{"name":"Locations","description":"Businesses (or apps), each across its review sites."},{"name":"Profiles","description":"Review pages: one site of one location."},{"name":"Reviews","description":"Collected reviews."},{"name":"Replies","description":"AI drafts and replies."},{"name":"Auto-replies","description":"Rules, the approval queue and the log."},{"name":"Alerts","description":"Email, Slack and webhooks for new reviews."},{"name":"Review links","description":"Short links and QR codes to get reviews."},{"name":"Review requests","description":"Ask customers for reviews by email or text, with a reminder and opt-outs."},{"name":"Apps","description":"App Store and Google Play rating trends and AI topics."},{"name":"Map rankings","description":"Google Maps rank on a grid of points around a location."},{"name":"Workspace","description":"Reply voice, plan and usage."},{"name":"API keys","description":"Create, rotate and revoke keys."},{"name":"Connected apps","description":"MCP clients signed in with OAuth, and disconnecting them."},{"name":"Free tools","description":"The no-sign-up tools on the Earful site. No key, a small daily allowance per IP address."},{"name":"Meta","description":"This spec."}],"paths":{"/api/v1/openapi.json":{"get":{"operationId":"getOpenApi","summary":"This specification","description":"The OpenAPI 3.1 document for this API, with this server as its server URL. No key needed.","tags":["Meta"],"security":[],"x-earful-scope":"public","responses":{"200":{"description":"The spec.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example":{"value":{"openapi":"3.1.0"}}}}}}}}},"/api/v1/workspace":{"get":{"operationId":"getWorkspace","summary":"Workspace, plan and usage","description":"The key’s workspace: its reply voice, plan limits and what’s used of them this month.\n\nAny key (read or write).","tags":["Workspace"],"x-earful-scope":"read","responses":{"200":{"description":"The workspace.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"},"examples":{"example":{"value":{"id":1,"name":"Harbour & Vine reviews","businessName":"Harbour & Vine","tone":"friendly","voiceNotes":"Family-run. Use first names.","signoff":"Sam and the team","plan":{"name":"pro","locations":3,"checkEveryMinutes":60,"aiDraftsPerMonth":null,"alertChannels":["digest","email","slack","webhook"],"directReplies":true,"addons":{"local":0},"requestEmailsPerMonth":3000,"requestTextsPerMonth":0,"rankPointsPerMonth":450,"appTopics":true},"usage":{"locations":2,"profiles":5,"aiDraftsThisMonth":41,"aiDraftsLeft":null,"requestEmailsThisMonth":212,"requestTextsThisMonth":0,"rankPointsThisMonth":98},"autoReply":{"enabled":false,"dailyCap":20}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"patch":{"operationId":"updateWorkspace","summary":"Update the reply voice","description":"Changes the workspace name and what AI drafts are written from. Fields left out keep their value; send null to clear one.\n\nNeeds a **write** key.","tags":["Workspace"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80},"businessName":{"type":["string","null"],"maxLength":100},"tone":{"type":"string","enum":["friendly","professional","playful","concise"]},"voiceNotes":{"type":["string","null"],"maxLength":1000},"signoff":{"type":["string","null"],"maxLength":100},"autoReply":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Off until you turn it on."},"dailyCap":{"type":"integer","minimum":1,"maximum":500,"description":"Most replies posted automatically in 24 hours."}}}}},"examples":{"voice":{"value":{"tone":"playful","signoff":"Sam and the team"}},"autoReply":{"value":{"autoReply":{"enabled":true,"dailyCap":30}}}}}}},"responses":{"200":{"description":"The updated workspace.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"},"examples":{"example":{"value":{"id":1,"name":"Harbour & Vine reviews","businessName":"Harbour & Vine","tone":"friendly","voiceNotes":"Family-run. Use first names.","signoff":"Sam and the team","plan":{"name":"pro","locations":3,"checkEveryMinutes":60,"aiDraftsPerMonth":null,"alertChannels":["digest","email","slack","webhook"],"directReplies":true,"addons":{"local":0},"requestEmailsPerMonth":3000,"requestTextsPerMonth":0,"rankPointsPerMonth":450,"appTopics":true},"usage":{"locations":2,"profiles":5,"aiDraftsThisMonth":41,"aiDraftsLeft":null,"requestEmailsThisMonth":212,"requestTextsThisMonth":0,"rankPointsThisMonth":98},"autoReply":{"enabled":false,"dailyCap":20}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations":{"get":{"operationId":"listLocations","summary":"List locations","description":"Every location in the workspace, oldest first. Locations past the plan’s limit stay listed but aren’t collected.\n\nAny key (read or write).","tags":["Locations"],"x-earful-scope":"read","responses":{"200":{"description":"The locations.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":"loc_3","name":"Harbour & Vine, Falmouth","profileCount":2,"createdAt":"2026-10-28T09:30:00.000Z","externalRef":null}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"externalRef","in":"query","schema":{"type":"string"},"description":"Only the location with your reference."}]},"post":{"operationId":"createLocation","summary":"Add a location","description":"Adds a business or branch. Refused with 402 once the plan’s locations are used up.\n\nNeeds a **write** key.","tags":["Locations"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"externalRef":{"type":["string","null"],"maxLength":200,"description":"Your own reference for it, like your store id. Filter with GET /locations?externalRef=."}},"required":["name"]},"examples":{"branch":{"value":{"name":"Harbour & Vine, Falmouth","externalRef":"store-0042"}}}}}},"responses":{"201":{"description":"The new location.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"},"examples":{"example":{"value":{"id":"loc_3","name":"Harbour & Vine, Falmouth","profileCount":0,"createdAt":"2026-10-28T09:30:00.000Z","externalRef":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/locations/{id}":{"get":{"operationId":"getLocation","summary":"Get a location","description":"One location.\n\nAny key (read or write).","tags":["Locations"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The location.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"},"examples":{"example":{"value":{"id":"loc_3","name":"Harbour & Vine, Falmouth","profileCount":2,"createdAt":"2026-10-28T09:30:00.000Z","externalRef":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"patch":{"operationId":"updateLocation","summary":"Rename a location","description":"Renames a location.\n\nNeeds a **write** key.","tags":["Locations"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"externalRef":{"type":["string","null"],"maxLength":200,"description":"Your own reference for it, like your store id. Filter with GET /locations?externalRef=."}},"required":[]},"examples":{"rename":{"value":{"name":"Harbour & Vine, Falmouth harbour","externalRef":"store-0042"}}}}}},"responses":{"200":{"description":"The renamed location.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Location"},"examples":{"example":{"value":{"id":"loc_3","name":"Harbour & Vine, Falmouth","profileCount":2,"createdAt":"2026-10-28T09:30:00.000Z","externalRef":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteLocation","summary":"Delete a location","description":"Deletes a location with its review pages and their reviews. A workspace keeps at least one location (409).\n\nNeeds a **write** key.","tags":["Locations"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations/{id}/profiles":{"post":{"operationId":"addProfile","summary":"Add a review page to a location","description":"Adds a review page by its link (url) or by a place search result (candidateId), and queues its first collection. One page per site per location (409).\n\nNeeds a **write** key.\n\nOne profile per listing: a link to one that’s already here (another domain, tracking parameters, http) answers 409 with existingId.","tags":["Profiles"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The review page link."},"candidateId":{"type":"integer","description":"A candidate from GET /place-searches/{id}."},"name":{"type":"string","maxLength":100,"description":"Defaults to the site and business name."}},"oneOf":[{"required":["url"]},{"required":["candidateId"]}]},"examples":{"candidate":{"value":{"candidateId":302}},"url":{"value":{"url":"https://uk.trustpilot.com/review/harbourandvine.co.uk"}}}}}},"responses":{"201":{"description":"The new profile.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"},"examples":{"example":{"value":{"id":"prf_12","locationId":"loc_3","locationName":"Harbour & Vine, Falmouth","site":"tripadvisor","name":"Tripadvisor: Harbour & Vine","url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","reviewCount":0,"averageRating":null,"lastCheckedAt":null,"nextCheckAt":"2026-10-28T10:30:00.000Z","lastError":null,"createdAt":"2026-10-01T12:00:00.000Z","externalId":null,"import":{"status":"done","reviews":0,"limit":5000,"finishedAt":null,"error":null}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/profiles":{"get":{"operationId":"listProfiles","summary":"List profiles","description":"Every review page, or those of one location. Find a listing with site and externalId rather than its URL: links change, ids don’t.\n\nAny key (read or write).","tags":["Profiles"],"x-earful-scope":"read","parameters":[{"name":"location","in":"query","schema":{"type":"string"},"description":"Only this location’s."},{"name":"site","in":"query","schema":{"type":"string"},"description":"With externalId: find a listing."},{"name":"externalId","in":"query","schema":{"type":"string"},"description":"The listing’s id on its site (see Profile.externalId), like d7106969 on Tripadvisor."}],"responses":{"200":{"description":"The profiles.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Profile"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":"prf_12","locationId":"loc_3","locationName":"Harbour & Vine, Falmouth","site":"tripadvisor","name":"Tripadvisor: Harbour & Vine","url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","reviewCount":214,"averageRating":4.6,"lastCheckedAt":"2026-10-28T09:30:00.000Z","nextCheckAt":"2026-10-28T10:30:00.000Z","lastError":null,"createdAt":"2026-10-01T12:00:00.000Z","externalId":null,"import":{"status":"done","reviews":214,"limit":5000,"finishedAt":"2026-10-28T09:30:00.000Z","error":null}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/profiles/{id}":{"get":{"operationId":"getProfile","summary":"Get a profile","description":"One review page.\n\nAny key (read or write).","tags":["Profiles"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The profile.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"},"examples":{"example":{"value":{"id":"prf_12","locationId":"loc_3","locationName":"Harbour & Vine, Falmouth","site":"tripadvisor","name":"Tripadvisor: Harbour & Vine","url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","reviewCount":214,"averageRating":4.6,"lastCheckedAt":"2026-10-28T09:30:00.000Z","nextCheckAt":"2026-10-28T10:30:00.000Z","lastError":null,"createdAt":"2026-10-01T12:00:00.000Z","externalId":null,"import":{"status":"done","reviews":214,"limit":5000,"finishedAt":"2026-10-28T09:30:00.000Z","error":null}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteProfile","summary":"Delete a profile","description":"Deletes a review page and its reviews.\n\nNeeds a **write** key.","tags":["Profiles"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/profiles/{id}/check":{"post":{"operationId":"checkProfile","summary":"Collect now","description":"Queues a collection straight away instead of at the plan’s next check. At most 10 an hour per workspace (429). New reviews appear in a minute or two.\n\nNeeds a **write** key.","tags":["Profiles"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"Queued; nextCheckAt is now.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"},"examples":{"example":{"value":{"id":"prf_12","locationId":"loc_3","locationName":"Harbour & Vine, Falmouth","site":"tripadvisor","name":"Tripadvisor: Harbour & Vine","url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","reviewCount":214,"averageRating":4.6,"lastCheckedAt":"2026-10-28T09:30:00.000Z","nextCheckAt":"2026-10-28T09:30:00.000Z","lastError":null,"createdAt":"2026-10-01T12:00:00.000Z","externalId":null,"import":{"status":"done","reviews":214,"limit":5000,"finishedAt":"2026-10-28T09:30:00.000Z","error":null}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews":{"get":{"operationId":"listReviews","summary":"List reviews","description":"Reviews newest first, filtered like the inbox. Removed reviews are left out unless status is deleted or any.\n\nAny key (read or write).\n\nPage with cursor: pass each answer’s nextCursor back until it’s null. limit and offset still work but offset is deprecated. To keep a copy in sync, use GET /reviews/changes instead.","tags":["Reviews"],"x-earful-scope":"read","parameters":[{"name":"site","in":"query","schema":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]}},{"name":"location","in":"query","schema":{"type":"string"}},{"name":"profile","in":"query","schema":{"type":"string"}},{"name":"rating","in":"query","schema":{"type":"integer","minimum":1,"maximum":5}},{"name":"status","in":"query","schema":{"type":"string","enum":["unreplied","replied","open","handled","edited","deleted","any"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Published on or after."},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"Published on or before."},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"First seen by Earful at or after; for polling."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Text, title or reviewer contains."},{"name":"tag","in":"query","schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Cursor"},{"name":"sentiment","in":"query","schema":{"type":"string","enum":["positive","neutral","negative","mixed"]},"description":"Only reviews the AI read this way."},{"name":"version","in":"query","schema":{"type":"string"},"description":"App stores: reviews left on this app version."},{"name":"country","in":"query","schema":{"type":"string"},"description":"App stores: reviews from this store country."}],"responses":{"200":{"description":"A page of reviews.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"total":{"type":"integer","description":"Matching reviews."},"limit":{"type":"integer"},"offset":{"type":"integer"},"nextCursor":{"type":["string","null"],"description":"The next page’s cursor; null after the last review."}},"required":["data","total","limit","offset","nextCursor"]},"examples":{"example":{"value":{"data":[{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":null,"firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service"],"draft":null,"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}],"total":1,"limit":50,"offset":0,"nextCursor":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews/{id}":{"get":{"operationId":"getReview","summary":"Get a review","description":"One review, with how a reply to it would reach the site.\n\nAny key (read or write).","tags":["Reviews"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The review.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewDetail"},"examples":{"example":{"value":{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":null,"firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service"],"draft":null,"replyRoute":{"mode":"link","via":null,"url":"https://www.tripadvisor.co.uk/ManagementCenter"},"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"patch":{"operationId":"updateReview","summary":"Mark handled, set tags","description":"Marks a review handled or not, and replaces its tags.\n\nNeeds a **write** key.","tags":["Reviews"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"handled":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string","pattern":"^[A-Za-z0-9 _-]{1,30}$"}}}},"examples":{"handled":{"value":{"handled":true,"tags":["service","weekend"]}}}}}},"responses":{"200":{"description":"The review.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewDetail"},"examples":{"example":{"value":{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":"2026-10-28T09:30:00.000Z","firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service","weekend"],"draft":null,"replyRoute":{"mode":"link","via":null,"url":"https://www.tripadvisor.co.uk/ManagementCenter"},"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews/{id}/draft":{"post":{"operationId":"generateDraft","summary":"Draft a reply with AI","description":"Writes a reply in the workspace’s voice and keeps it as the review’s draft. Free includes 10 a month (402 after).\n\nNeeds a **write** key.","tags":["Replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"The draft.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Draft"},"examples":{"example":{"value":{"text":"Hi Ann, thank you, and sorry about the wait…","aiDraftsLeft":7}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}},"put":{"operationId":"saveDraft","summary":"Edit the draft","description":"Replaces the review’s draft reply.\n\nNeeds a **write** key.","tags":["Replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":5000}},"required":["text"]},"examples":{"edit":{"value":{"text":"Hi Ann, thank you, and sorry about the wait on Saturday. Sam"}}}}}},"responses":{"200":{"description":"The saved draft.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Draft"},"examples":{"example":{"value":{"text":"Hi Ann, thank you, and sorry about the wait on Saturday. Sam","aiDraftsLeft":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews/{id}/reply":{"post":{"operationId":"replyToReview","summary":"Reply","description":"Posts the reply through the site’s owner API where the profile is connected to one (App Store Connect, Google Play, Google Business Profile) and the plan allows direct replies. Otherwise Earful records it as replied by hand: postedToSite is false and replyUrl is where to post it.\n\nNeeds a **write** key.","tags":["Replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":5000}},"required":["text"]},"examples":{"reply":{"value":{"text":"Hi Ann, thank you, and sorry about the wait on Saturday. Sam"}}}}}},"responses":{"201":{"description":"Posted or recorded.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyResult"},"examples":{"example":{"value":{"postedToSite":false,"via":"manual","replyUrl":"https://www.tripadvisor.co.uk/ManagementCenter","review":{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":null,"firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service"],"draft":null,"replyRoute":{"mode":"link","via":null,"url":"https://www.tripadvisor.co.uk/ManagementCenter"},"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/alerts":{"get":{"operationId":"listAlerts","summary":"List alerts","description":"Every alert: email digests, instant email, Slack and signed webhooks.\n\nAny key (read or write).","tags":["Alerts"],"x-earful-scope":"read","responses":{"200":{"description":"The alerts.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Alert"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":"alr_7","channel":"webhook","frequency":null,"target":"hooks.acme.example","maxRating":3,"sites":[],"lastSentAt":"2026-10-28T09:30:00.000Z","lastError":null,"createdAt":"2026-10-28T09:30:00.000Z","locationId":null,"sentiments":[],"tags":[],"keywords":[]}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"createAlert","summary":"Add an alert or webhook","description":"Adds an alert. A webhook gets a POST of {event: \"reviews.new\", workspace, reviews: [Review]} for each batch of new reviews, signed with an Earful-Signature: t=…,v1=… header: an HMAC-SHA256 of \"t.body\" with the secret in this answer. Filters (rating, sites, location, sentiment, tags, keywords) all have to match. Email and Slack alerts carry a signed link to approve or edit the reply without signing in. Slack and webhooks are on Plus (402).\n\nNeeds a **write** key.","tags":["Alerts"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","enum":["digest","email","slack","webhook"]},"target":{"type":"string","description":"An email address, a Slack incoming webhook URL or your https webhook URL."},"frequency":{"type":"string","enum":["daily","weekly"],"description":"Digests only."},"maxRating":{"type":"integer","enum":[1,2,3,4]},"sites":{"type":"array","items":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]}},"locationId":{"type":["string","integer"],"description":"Only this location. Its id (loc_3), or the bare number."},"sentiments":{"type":"array","items":{"type":"string","enum":["positive","neutral","negative","mixed"]}},"tags":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"},"description":"Match any one, case-insensitive."}},"required":["channel","target"]},"examples":{"webhook":{"value":{"channel":"webhook","target":"https://hooks.acme.example/earful","maxRating":3}},"digest":{"value":{"channel":"digest","target":"owner@acme.example","frequency":"weekly"}},"keywords":{"value":{"channel":"email","target":"owner@acme.example","keywords":["refund","food poisoning"],"sentiments":["negative"]}}}}}},"responses":{"201":{"description":"The alert, and a webhook’s secret.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewAlert"},"examples":{"example":{"value":{"id":"alr_7","channel":"webhook","frequency":null,"target":"hooks.acme.example","maxRating":3,"sites":[],"lastSentAt":"2026-10-28T09:30:00.000Z","lastError":null,"createdAt":"2026-10-28T09:30:00.000Z","secret":"whsec_9fQ2…","locationId":null,"sentiments":[],"tags":[],"keywords":[]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/alerts/{id}":{"get":{"operationId":"getAlert","summary":"Get an alert","description":"One alert.\n\nAny key (read or write).","tags":["Alerts"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The alert.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Alert"},"examples":{"example":{"value":{"id":"alr_7","channel":"webhook","frequency":null,"target":"hooks.acme.example","maxRating":3,"sites":[],"lastSentAt":"2026-10-28T09:30:00.000Z","lastError":null,"createdAt":"2026-10-28T09:30:00.000Z","locationId":null,"sentiments":[],"tags":[],"keywords":[]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteAlert","summary":"Delete an alert","description":"Stops an alert.\n\nNeeds a **write** key.","tags":["Alerts"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/alerts/{id}/test":{"post":{"operationId":"testAlert","summary":"Send a test","description":"Sends the newest review (or a made-up one) through the alert now. 400 says why delivery failed.\n\nNeeds a **write** key.","tags":["Alerts"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Sent.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Alert"},"examples":{"example":{"value":{"id":"alr_7","channel":"webhook","frequency":null,"target":"hooks.acme.example","maxRating":3,"sites":[],"lastSentAt":"2026-10-28T09:30:00.000Z","lastError":null,"createdAt":"2026-10-28T09:30:00.000Z","locationId":null,"sentiments":[],"tags":[],"keywords":[]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/keys":{"get":{"operationId":"listKeys","summary":"List API keys","description":"Every REST API key in the workspace, with when, where from and how often each was used. Keys themselves are never shown again.\n\nAny key (read or write).","tags":["API keys"],"x-earful-scope":"read","responses":{"200":{"description":"The keys.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":4,"name":"BI export","scope":"read","prefix":"ef_3k9Qw1x","createdAt":"2026-10-28T09:30:00.000Z","expiresAt":null,"lastUsedAt":"2026-10-28T09:30:00.000Z","lastUsedIp":"203.0.113.7","requestCount":1288,"mode":"live"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"createKey","summary":"Create a key","description":"Makes a key. The answer holds the key itself, once; Earful keeps only its hash.\n\nNeeds a **write** key.","tags":["API keys"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"scope":{"type":"string","enum":["read","write"],"default":"read"},"expiresInDays":{"type":"integer","minimum":1,"maximum":730,"description":"Leave out for a key that doesn’t expire."},"mode":{"type":"string","enum":["live","test"],"default":"live","description":"test makes an ef_test_ key for the workspace’s sandbox: made-up data, no outside calls."}}},"examples":{"bi":{"value":{"name":"BI export","scope":"read","expiresInDays":90}},"test":{"value":{"name":"CI","scope":"write","mode":"test"}}}}}},"responses":{"201":{"description":"The key, shown once.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewApiKey"},"examples":{"example":{"value":{"id":4,"name":"BI export","scope":"read","prefix":"ef_3k9Qw1x","createdAt":"2026-10-28T09:30:00.000Z","expiresAt":null,"lastUsedAt":null,"lastUsedIp":null,"requestCount":0,"key":"ef_3k9Qw1x…","mode":"live"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/keys/{id}":{"patch":{"operationId":"renameKey","summary":"Rename a key","description":"Changes a key’s name.\n\nNeeds a **write** key.","tags":["API keys"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60}},"required":["name"]},"examples":{"rename":{"value":{"name":"Looker"}}}}}},"responses":{"200":{"description":"The key.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"},"examples":{"example":{"value":{"id":4,"name":"Looker","scope":"read","prefix":"ef_3k9Qw1x","createdAt":"2026-10-28T09:30:00.000Z","expiresAt":null,"lastUsedAt":"2026-10-28T09:30:00.000Z","lastUsedIp":"203.0.113.7","requestCount":1288,"mode":"live"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"revokeKey","summary":"Revoke a key","description":"Stops a key working, straight away.\n\nNeeds a **write** key.","tags":["API keys"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/keys/{id}/rotate":{"post":{"operationId":"rotateKey","summary":"Rotate a key","description":"Makes a new key with the same name, scope and expiry, and ends the old one after a grace period (24 hours unless you say; 0 ends it now) so callers can switch without downtime.\n\nNeeds a **write** key.","tags":["API keys"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"graceHours":{"type":"integer","minimum":0,"maximum":168,"default":24}}},"examples":{"week":{"value":{"graceHours":168}}}}}},"responses":{"201":{"description":"The new key, shown once, and the old one’s new expiry.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotatedApiKey"},"examples":{"example":{"value":{"id":5,"name":"BI export","scope":"read","prefix":"ef_3k9Qw1x","createdAt":"2026-10-28T09:30:00.000Z","expiresAt":null,"lastUsedAt":"2026-10-28T09:30:00.000Z","lastUsedIp":"203.0.113.7","requestCount":0,"key":"ef_Zp0…","previous":{"id":4,"name":"BI export","scope":"read","prefix":"ef_3k9Qw1x","createdAt":"2026-10-28T09:30:00.000Z","expiresAt":"2026-10-29T09:30:00.000Z","lastUsedAt":"2026-10-28T09:30:00.000Z","lastUsedIp":"203.0.113.7","requestCount":1288,"mode":"live"},"mode":"live"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/connected-apps":{"get":{"operationId":"listConnectedApps","summary":"List connected apps","description":"Apps connected to the workspace’s MCP server with OAuth (claude.ai connectors and the like), with who allowed them and when they were last used.\n\nAny key (read or write).","tags":["Connected apps"],"x-earful-scope":"read","responses":{"200":{"description":"The connected apps.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConnectedApp"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":2,"name":"Claude","clientId":"efc_Zk3PqA9wX1bT0cV7nL2mRg","redirectHosts":["claude.ai"],"scope":"write","connectedBy":"owner@harbour.example","createdAt":"2026-10-28T09:30:00.000Z","lastUsedAt":"2026-10-28T10:02:00.000Z","lastUsedIp":"203.0.113.7","requestCount":42}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/connected-apps/{id}":{"delete":{"operationId":"revokeConnectedApp","summary":"Disconnect an app","description":"Revokes the app’s access and refresh tokens straight away. It has to be connected again to come back.\n\nNeeds a **write** key.","tags":["Connected apps"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/place-searches":{"post":{"operationId":"createPlaceSearch","summary":"Search for a business","description":"Starts a search for a business or app on each site that has a search source: Google, Tripadvisor and Trustpilot (through DataForSEO, when the server has it), the App Store and Google Play. Answers 202 at once; poll GET /place-searches/{id}. Each site retries with backoff until it’s done or has failed 5 times. Other sites are added by link. 30 searches an hour per workspace.\n\nNeeds a **write** key.","tags":["Place search"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200,"description":"The business or app name."},"near":{"type":"string","maxLength":100,"description":"A town or area, for Google and Tripadvisor."},"sites":{"type":"array","items":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"description":"Defaults to every searchable site."}},"required":["query"]},"examples":{"restaurant":{"value":{"query":"Harbour & Vine","near":"Falmouth"}},"app":{"value":{"query":"Tallyho","sites":["appstore","googleplay"]}}}}}},"responses":{"202":{"description":"Started. Location points at the search.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Location":{"description":"The search’s URL.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceSearch"},"examples":{"example":{"value":{"id":55,"query":"Harbour & Vine","near":"Falmouth","status":"running","createdAt":"2026-10-28T09:30:00.000Z","sites":[{"site":"appstore","status":"pending","attempts":0,"error":null,"finishedAt":null},{"site":"google","status":"pending","attempts":0,"error":null,"finishedAt":null},{"site":"tripadvisor","status":"pending","attempts":1,"error":null,"finishedAt":null}],"candidates":[]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/place-searches/{id}":{"get":{"operationId":"getPlaceSearch","summary":"Search status and results","description":"Where each site’s search is, and the businesses found so far. Add one with POST /locations/{id}/profiles {candidateId}.\n\nAny key (read or write).","tags":["Place search"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The search.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceSearch"},"examples":{"example":{"value":{"id":55,"query":"Harbour & Vine","near":"Falmouth","status":"done","createdAt":"2026-10-28T09:30:00.000Z","sites":[{"site":"appstore","status":"done","attempts":0,"error":null,"finishedAt":"2026-10-28T09:30:00.000Z"},{"site":"google","status":"done","attempts":0,"error":null,"finishedAt":"2026-10-28T09:30:00.000Z"},{"site":"tripadvisor","status":"done","attempts":1,"error":null,"finishedAt":"2026-10-28T09:30:00.000Z"}],"candidates":[{"candidateId":301,"site":"google","name":"Harbour & Vine","address":"12 Arwenack St, Falmouth TR11 3JA","url":"https://www.google.com/maps/place/Harbour+%26+Vine/@50.153,-5.067,17z?cid=1234567890123456789","rating":4.7,"reviewCount":532},{"candidateId":302,"site":"tripadvisor","name":"Harbour & Vine","address":null,"url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","rating":4.5,"reviewCount":214}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews/{id}/analyse":{"post":{"operationId":"analyseReview","summary":"Read a review with AI","description":"Reads the review again: sentiment, an English translation if it’s in another language, and the workspace’s AI tags that fit. New reviews get this automatically; use it for older ones.\n\nNeeds a **write** key.","tags":["Reviews"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The review, read.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewDetail"},"examples":{"example":{"value":{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":null,"firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service"],"draft":null,"replyRoute":{"mode":"link","via":null,"url":"https://www.tripadvisor.co.uk/ManagementCenter"},"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/tag-rules":{"get":{"operationId":"listTagRules","summary":"List AI tags","description":"Tags the AI applies to new reviews, each with when it applies.\n\nAny key (read or write).","tags":["Reviews"],"x-earful-scope":"read","responses":{"200":{"description":"The AI tags.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TagRule"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":3,"tag":"service","description":"Mentions waiting, slow service or staff attitude","createdAt":"2026-10-28T09:30:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"createTagRule","summary":"Add an AI tag","description":"Adds a tag the AI applies to new reviews when its description fits; saving an existing tag changes its description. Up to 50.\n\nNeeds a **write** key.","tags":["Reviews"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string","maxLength":30},"description":{"type":"string","maxLength":300}},"required":["tag","description"]},"examples":{"service":{"value":{"tag":"service","description":"Mentions waiting, slow service or staff attitude"}}}}}},"responses":{"201":{"description":"The AI tag.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagRule"},"examples":{"example":{"value":{"id":3,"tag":"service","description":"Mentions waiting, slow service or staff attitude","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/tag-rules/{id}":{"delete":{"operationId":"deleteTagRule","summary":"Remove an AI tag","description":"Stops the AI applying it. Reviews keep tags they already have.\n\nNeeds a **write** key.","tags":["Reviews"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reply-rules":{"get":{"operationId":"listReplyRules","summary":"List reply rules","description":"Auto-reply rules, in the order they’re tried: a location’s own first, then the ones for every location. The first that fits a new review wins.\n\nAny key (read or write).","tags":["Auto-replies"],"x-earful-scope":"read","responses":{"200":{"description":"The rules.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReplyRule"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":2,"locationId":null,"minRating":4,"maxRating":5,"sites":["appstore","googleplay"],"languages":[],"keywords":[],"action":"post","createdAt":"2026-10-28T09:30:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"createReplyRule","summary":"Add a reply rule","description":"New reviews that fit get an AI reply that is posted (action post, on sites with a reply API, up to the daily cap) or held in the approval queue. Defaults: 4 and 5 stars, held for approval. Auto-replies also need turning on: PATCH /api/v1/workspace with autoReply.\n\nNeeds a **write** key.","tags":["Auto-replies"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["post","approve"],"default":"approve"},"locationId":{"type":["string","integer"],"description":"Only this location. Its id (loc_3), or the bare number."},"minRating":{"type":"integer","minimum":1,"maximum":5,"default":4,"description":"1 to 3 stars only when you set it lower."},"maxRating":{"type":"integer","minimum":1,"maximum":5,"default":5},"sites":{"type":"array","items":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]}},"languages":{"type":"array","items":{"type":"string"}},"keywords":{"type":"array","items":{"type":"string"}}}},"examples":{"post":{"value":{"action":"post","minRating":4,"sites":["appstore","googleplay"]}},"hold":{"value":{"action":"approve","minRating":1,"maxRating":5,"keywords":["refund"]}}}}}},"responses":{"201":{"description":"The rule.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRule"},"examples":{"example":{"value":{"id":2,"locationId":null,"minRating":4,"maxRating":5,"sites":["appstore","googleplay"],"languages":[],"keywords":[],"action":"post","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/reply-rules/{id}":{"get":{"operationId":"getReplyRule","summary":"Get a reply rule","description":"One auto-reply rule.\n\nAny key (read or write).","tags":["Auto-replies"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The rule.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRule"},"examples":{"example":{"value":{"id":2,"locationId":null,"minRating":4,"maxRating":5,"sites":["appstore","googleplay"],"languages":[],"keywords":[],"action":"post","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteReplyRule","summary":"Remove a reply rule","description":"Removes the rule. Replies it already held stay in the queue.\n\nNeeds a **write** key.","tags":["Auto-replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auto-replies":{"get":{"operationId":"listAutoReplies","summary":"List auto-replies","description":"What auto-replies did, newest first: the approval queue (status pending) and the audit log of what was posted, approved, rejected or skipped.\n\nAny key (read or write).","tags":["Auto-replies"],"x-earful-scope":"read","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","posted","approved","rejected","skipped"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Auto-replies.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AutoReply"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"nextCursor":{"type":["string","null"],"description":"The next page’s cursor; null after the last item."}},"required":["data","limit","offset","nextCursor"]},"examples":{"example":{"value":{"data":[{"id":"arp_14","reviewId":"rev_981","ruleId":2,"status":"pending","text":"Thanks so much, Ann! We’re glad the crab linguine hit the spot.","reason":"The rule holds replies for approval.","decidedBy":null,"createdAt":"2026-10-28T09:30:00.000Z","decidedAt":null,"review":{"site":"appstore","rating":5,"author":"Ann P","text":"Lovely app, booking took seconds.","profileName":"App Store: Harbour & Vine"}}],"limit":50,"offset":0,"nextCursor":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auto-replies/{id}":{"get":{"operationId":"getAutoReply","summary":"Get an auto-reply","description":"One auto-reply.\n\nAny key (read or write).","tags":["Auto-replies"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The auto-reply.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoReply"},"examples":{"example":{"value":{"id":"arp_14","reviewId":"rev_981","ruleId":2,"status":"pending","text":"Thanks so much, Ann! We’re glad the crab linguine hit the spot.","reason":"The rule holds replies for approval.","decidedBy":null,"createdAt":"2026-10-28T09:30:00.000Z","decidedAt":null,"review":{"site":"appstore","rating":5,"author":"Ann P","text":"Lovely app, booking took seconds.","profileName":"App Store: Harbour & Vine"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auto-replies/{id}/approve":{"post":{"operationId":"approveAutoReply","summary":"Approve and post","description":"Posts a held reply to the site, as drafted or with your text. 409 if it was already decided, the review has a reply, or the site has no reply API (copy those across).\n\nNeeds a **write** key.","tags":["Auto-replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":5000,"description":"Leave out to post the AI draft."}}},"examples":{"asDrafted":{"value":{}},"edited":{"value":{"text":"Thank you, Ann. See you again soon!"}}}}}},"responses":{"200":{"description":"The auto-reply, approved.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoReply"},"examples":{"example":{"value":{"id":"arp_14","reviewId":"rev_981","ruleId":2,"status":"approved","text":"Thanks so much, Ann! We’re glad the crab linguine hit the spot.","reason":null,"decidedBy":"api:Zapier","createdAt":"2026-10-28T09:30:00.000Z","decidedAt":"2026-10-28T09:30:00.000Z","review":{"site":"appstore","rating":5,"author":"Ann P","text":"Lovely app, booking took seconds.","profileName":"App Store: Harbour & Vine"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/auto-replies/{id}/reject":{"post":{"operationId":"rejectAutoReply","summary":"Reject","description":"Takes a held reply out of the queue without posting it. The draft stays on the review.\n\nNeeds a **write** key.","tags":["Auto-replies"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The auto-reply, rejected.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoReply"},"examples":{"example":{"value":{"id":"arp_14","reviewId":"rev_981","ruleId":2,"status":"rejected","text":"Thanks so much, Ann! We’re glad the crab linguine hit the spot.","reason":"The rule holds replies for approval.","decidedBy":"api:Zapier","createdAt":"2026-10-28T09:30:00.000Z","decidedAt":"2026-10-28T09:30:00.000Z","review":{"site":"appstore","rating":5,"author":"Ann P","text":"Lovely app, booking took seconds.","profileName":"App Store: Harbour & Vine"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations/{id}/review-link":{"get":{"operationId":"getReviewLink","summary":"Get a review link","description":"The location’s short review link, its QR code and click count.\n\nAny key (read or write).","tags":["Review links"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The review link.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewLink"},"examples":{"example":{"value":{"locationId":"loc_4","code":"q7Rk2mWa","url":"https://earful.onibaku.co.uk/r/q7Rk2mWa","site":"google","targetUrl":"https://search.google.com/local/writereview?placeid=ChIJN1t_tDeuEmsRUsoyG83frY4","clicks":31,"lastClickAt":"2026-10-28T09:30:00.000Z","qrSvg":"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 264 264\">…</svg>","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"operationId":"setReviewLink","summary":"Set a review link","description":"Makes the location’s review link, or changes where it goes (the code, QR code and click count stay). Send site and, for Google, placeId; for other sites the location needs a review page there. Or send any https url.\n\nNeeds a **write** key.","tags":["Review links"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"string","enum":["google","tripadvisor","trustpilot","appstore","googleplay","booking","feefo","reviewsio"]},"placeId":{"type":"string","description":"Google’s place ID, for site google."},"url":{"type":"string","format":"uri","description":"Any https page instead of a site."}}},"examples":{"google":{"value":{"site":"google","placeId":"ChIJN1t_tDeuEmsRUsoyG83frY4"}},"trustpilot":{"value":{"site":"trustpilot"}}}}}},"responses":{"200":{"description":"The review link.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewLink"},"examples":{"example":{"value":{"locationId":"loc_4","code":"q7Rk2mWa","url":"https://earful.onibaku.co.uk/r/q7Rk2mWa","site":"google","targetUrl":"https://search.google.com/local/writereview?placeid=ChIJN1t_tDeuEmsRUsoyG83frY4","clicks":31,"lastClickAt":"2026-10-28T09:30:00.000Z","qrSvg":"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 264 264\">…</svg>","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteReviewLink","summary":"Remove a review link","description":"Removes the link; printed QR codes stop working.\n\nNeeds a **write** key.","tags":["Review links"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations/{id}/review-requests":{"post":{"operationId":"createReviewRequest","summary":"Ask a customer for a review","description":"Queues an email (or text) to one customer with the location’s review link, sent within a minute, and one reminder later to people who haven’t clicked (see the settings). Needs the location’s review link and the sending rules agreed (409 otherwise). A customer asked at this location in the last 30 days, or who opted out, is refused with 409. Over the month’s allowance is 402, and so is any text: texts aren’t on a plan yet. Only ask real, recent customers who were told they’d be asked: transactedAt older than 180 days is refused.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":100},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"A mobile, +44 7… or 07…."},"channel":{"type":"string","enum":["email","sms"],"description":"Defaults to email when there’s one."},"orderRef":{"type":"string","maxLength":100},"transactedAt":{"type":"string","description":"When they bought or visited."},"sendAt":{"type":"string","format":"date-time","description":"Send later, up to 30 days ahead."}}},"examples":{"email":{"value":{"name":"Ann Price","email":"ann@example.com","orderRef":"A1043","transactedAt":"2026-10-27"}},"sms":{"value":{"name":"Sam Ellis","phone":"07700 900123","channel":"sms"}}}}}},"responses":{"201":{"description":"The request, queued.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewRequest"},"examples":{"example":{"value":{"id":301,"locationId":"loc_4","locationName":"Harbour & Vine, Falmouth","channel":"email","contact":"ann@example.com","name":"Ann Price","orderRef":"A1043","source":"api","status":"queued","sendAt":"2026-10-28T09:30:00.000Z","sentAt":null,"remindedAt":null,"clickedAt":null,"reviewId":null,"unsubscribedAt":null,"error":null,"transactedAt":"2026-10-27T19:00:00.000Z","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/locations/{id}/review-requests/import":{"post":{"operationId":"importReviewRequests","summary":"Ask customers from a CSV","description":"A CSV file’s text: a header row (name, email, phone, order_ref, transacted_at; common alternatives like first_name, last_name and mobile work too), then one customer a row, up to 2,000. Good rows are queued; the rest are listed with the reason.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv":{"type":"string"}},"required":["csv"]},"examples":{"csv":{"value":{"csv":"name,email,order_ref\nAnn Price,ann@example.com,A1043\nSam Ellis,sam@example.com,A1044\n"}}}}}},"responses":{"200":{"description":"What was queued and skipped.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"integer"},"ids":{"type":"array","items":{"type":"integer"}},"skipped":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"reason":{"type":"string"}},"required":["row","reason"]}}},"required":["created","ids","skipped"]},"examples":{"example":{"value":{"created":2,"ids":[302,303],"skipped":[{"row":4,"reason":"That email address doesn’t look right."}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/review-requests":{"get":{"operationId":"listReviewRequests","summary":"List review requests","description":"Newest first.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","parameters":[{"name":"location","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["queued","sent","failed","suppressed"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of requests.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReviewRequest"}},"limit":{"type":"integer"},"offset":{"type":"integer"},"nextCursor":{"type":["string","null"],"description":"The next page’s cursor; null after the last request."}},"required":["data","limit","offset","nextCursor"]},"examples":{"example":{"value":{"data":[{"id":301,"locationId":"loc_4","locationName":"Harbour & Vine, Falmouth","channel":"email","contact":"ann@example.com","name":"Ann Price","orderRef":"A1043","source":"api","status":"sent","sendAt":"2026-10-28T09:30:00.000Z","sentAt":"2026-10-28T09:30:00.000Z","remindedAt":null,"clickedAt":"2026-10-28T09:30:00.000Z","reviewId":"rev_5512","unsubscribedAt":null,"error":null,"transactedAt":"2026-10-27T19:00:00.000Z","createdAt":"2026-10-28T09:30:00.000Z"}],"limit":50,"offset":0,"nextCursor":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/review-requests/{id}":{"get":{"operationId":"getReviewRequest","summary":"Get a review request","description":"One request and how far it got.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The request.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewRequest"},"examples":{"example":{"value":{"id":301,"locationId":"loc_4","locationName":"Harbour & Vine, Falmouth","channel":"email","contact":"ann@example.com","name":"Ann Price","orderRef":"A1043","source":"api","status":"queued","sendAt":"2026-10-28T09:30:00.000Z","sentAt":null,"remindedAt":null,"clickedAt":null,"reviewId":null,"unsubscribedAt":null,"error":null,"transactedAt":"2026-10-27T19:00:00.000Z","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/review-requests/stats":{"get":{"operationId":"getReviewRequestStats","summary":"Review request funnel","description":"For requests made in the last `days` (7, 30, 90 or 365; 30 by default): how many were sent, clicked, matched to a review (by the reviewer’s name, at the same location, within 30 days), reminded and opted out. Plus this month’s sends against the plan.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","parameters":[{"name":"location","in":"query","schema":{"type":"integer"}},{"name":"days","in":"query","schema":{"type":"integer","enum":[7,30,90,365]}}],"responses":{"200":{"description":"The funnel.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer"},"requested":{"type":"integer"},"queued":{"type":"integer"},"sent":{"type":"integer"},"failed":{"type":"integer"},"suppressed":{"type":"integer"},"reminded":{"type":"integer"},"clicked":{"type":"integer"},"reviewed":{"type":"integer"},"unsubscribed":{"type":"integer"},"clickRate":{"type":["number","null"],"description":"clicked / sent."},"reviewRate":{"type":["number","null"],"description":"reviewed / sent."},"usage":{"type":"object","properties":{"emails":{"type":"integer"},"emailLimit":{"type":"integer"},"texts":{"type":"integer"},"textLimit":{"type":"integer"}},"required":["emails","emailLimit","texts","textLimit"]}},"required":["days","requested","queued","sent","failed","suppressed","reminded","clicked","reviewed","unsubscribed","clickRate","reviewRate","usage"]},"examples":{"example":{"value":{"days":30,"requested":240,"queued":4,"sent":230,"failed":2,"suppressed":4,"reminded":120,"clicked":88,"reviewed":41,"unsubscribed":3,"clickRate":0.383,"reviewRate":0.178,"usage":{"emails":212,"emailLimit":1100,"texts":31,"textLimit":250}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/review-requests/settings":{"get":{"operationId":"getReviewRequestSettings","summary":"Get message settings","description":"The email, reminder and text templates, the reminder delay, where replies go, whether the sending rules are agreed, and suggested consent wording.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","responses":{"200":{"description":"The settings.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewRequestSettings"},"examples":{"example":{"value":{"emailSubject":"How was your visit to {business}?","emailBody":"Hi {name},\n\nWould you take a minute to leave us a review?\n\n{link}\n\nThank you,\n{business}","reminderSubject":"A quick reminder from {business}","reminderBody":"Hi {name}, here’s the link again: {link}","smsBody":"Hi {name}, thanks for choosing {business}. Would you leave us a quick review? {link}","reminderDays":3,"replyTo":"hello@harbourandvine.co.uk","acknowledged":true,"acknowledgedAt":"2026-10-28T09:30:00.000Z","consentWording":"We’ll send you one email (or text) after your visit asking for a review, and one reminder. You can opt out now, or with the link in the message."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"operationId":"setReviewRequestSettings","summary":"Change message settings","description":"Fields left out keep their value. Templates must contain {link}; Earful adds the opt-out line. Send acknowledged: true once to agree the sending rules: only real customers who bought or booked recently, who were told they’d be asked and could say no.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emailSubject":{"type":"string"},"emailBody":{"type":"string"},"reminderSubject":{"type":"string"},"reminderBody":{"type":"string"},"smsBody":{"type":"string","maxLength":100},"reminderDays":{"type":"integer","enum":[0,1,2,3,4,5,6,7,10,14]},"replyTo":{"type":["string","null"]},"acknowledged":{"type":"boolean"}}},"examples":{"agree":{"value":{"acknowledged":true}},"reminder":{"value":{"reminderDays":5,"replyTo":"hello@harbourandvine.co.uk"}}}}}},"responses":{"200":{"description":"The settings.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewRequestSettings"},"examples":{"example":{"value":{"emailSubject":"How was your visit to {business}?","emailBody":"Hi {name},\n\nWould you take a minute to leave us a review?\n\n{link}\n\nThank you,\n{business}","reminderSubject":"A quick reminder from {business}","reminderBody":"Hi {name}, here’s the link again: {link}","smsBody":"Hi {name}, thanks for choosing {business}. Would you leave us a quick review? {link}","reminderDays":3,"replyTo":"hello@harbourandvine.co.uk","acknowledged":true,"acknowledgedAt":"2026-10-28T09:30:00.000Z","consentWording":"We’ll send you one email (or text) after your visit asking for a review, and one reminder. You can opt out now, or with the link in the message."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/suppressions":{"get":{"operationId":"listSuppressions","summary":"List opt-outs","description":"Customers who opted out of review requests, or were added by hand. They’re never asked again.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","responses":{"200":{"description":"Opt-outs, newest first.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Suppression"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":7,"contact":"sam@example.com","reason":"unsubscribed","createdAt":"2026-10-28T09:30:00.000Z"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"addSuppression","summary":"Add an opt-out","description":"An email address or mobile number never to ask.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"type":"string"}},"required":["contact"]},"examples":{"email":{"value":{"contact":"sam@example.com"}}}}}},"responses":{"201":{"description":"The opt-out.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"},"examples":{"example":{"value":{"id":8,"contact":"+447700900123","reason":"manual","createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}]}},"/api/v1/suppressions/{id}":{"delete":{"operationId":"deleteSuppression","summary":"Remove an opt-out","description":"Only when the customer asks to hear from you again.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations/{id}/request-hook":{"get":{"operationId":"getRequestHook","summary":"Get a location’s webhook URL","description":"The secret URL a till, booking system or Zapier posts customers to. 404 until one is made.\n\nAny key (read or write).","tags":["Review requests"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The URL.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"url":{"type":"string","format":"uri"}},"required":["locationId","url"]},"examples":{"example":{"value":{"locationId":"loc_4","url":"https://earful.onibaku.co.uk/api/v1/hooks/review-requests/k3P9xQ2mRt7wZb4nVh8c"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"rotateRequestHook","summary":"Make a new webhook URL","description":"Makes the location’s webhook URL, or replaces it (the old one stops working at once).\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"The URL.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"url":{"type":"string","format":"uri"}},"required":["locationId","url"]},"examples":{"example":{"value":{"locationId":"loc_4","url":"https://earful.onibaku.co.uk/api/v1/hooks/review-requests/k3P9xQ2mRt7wZb4nVh8c"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteRequestHook","summary":"Remove the webhook URL","description":"The URL stops working.\n\nNeeds a **write** key.","tags":["Review requests"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/hooks/review-requests/{code}":{"post":{"operationId":"postRequestHook","summary":"Webhook: ask a customer for a review","description":"No key: the secret URL from the location’s webhook is the credential. Send one customer as JSON or a form, with field names as your system has them (name or first_name and last_name; email; phone or mobile; order_ref, order_id or booking_id; transacted_at). In Zapier, use Webhooks by Zapier, POST. A repeat or opted-out customer answers 202 with status skipped, so a till can post every sale.","tags":["Review requests"],"x-earful-scope":"public","security":[],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"description":"From the webhook URL."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"examples":{"pos":{"value":{"first_name":"Ann","last_name":"Price","email":"ann@example.com","order_id":"A1043"}}}}}},"responses":{"202":{"description":"Queued, or skipped with the reason.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":["integer","null"]},"status":{"type":"string","enum":["queued","skipped"]},"reason":{"type":["string","null"]}},"required":["id","status","reason"]},"examples":{"queued":{"value":{"id":304,"status":"queued","reason":null}},"skipped":{"value":{"id":null,"status":"skipped","reason":"That customer was asked in the last 30 days."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"402":{"$ref":"#/components/responses/PaymentRequired"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/apps":{"get":{"operationId":"listApps","summary":"List apps","description":"App Store and Google Play profiles, with their rating and the newest version a review mentions. Their reviews are GET /api/v1/reviews?profile={id}, filterable by version and country.\n\nAny key (read or write).","tags":["Apps"],"x-earful-scope":"read","responses":{"200":{"description":"Apps.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/App"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":"prf_12","locationId":"loc_6","site":"appstore","name":"App Store: Harbour & Vine","url":"https://apps.apple.com/gb/app/harbour-vine/id1234567890","reviewCount":412,"averageRating":4.3,"latestVersion":"5.2.0"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/apps/{id}/trends":{"get":{"operationId":"getAppTrends","summary":"App rating trends","description":"Average rating by week, by app version (with when each version’s first review arrived, which marks the release on a chart) and by store country, over the last 30, 90 (default) or 365 days.\n\nAny key (read or write).","tags":["Apps"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"},{"name":"days","in":"query","schema":{"type":"integer","enum":[30,90,365]}}],"responses":{"200":{"description":"Trends.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"app":{"$ref":"#/components/schemas/App"},"days":{"type":"integer"},"weeks":{"type":"array","items":{"type":"object","properties":{"week":{"type":"string","format":"date"},"reviews":{"type":"integer"},"averageRating":{"type":["number","null"]}},"required":["week","reviews","averageRating"]}},"versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"string"},"firstSeenAt":{"type":"string","format":"date-time"},"reviews":{"type":"integer"},"averageRating":{"type":["number","null"]}},"required":["version","firstSeenAt","reviews","averageRating"]}},"countries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string"},"reviews":{"type":"integer"},"averageRating":{"type":["number","null"]}},"required":["country","reviews","averageRating"]}}},"required":["app","days","weeks","versions","countries"]},"examples":{"example":{"value":{"app":{"id":"prf_12","locationId":"loc_6","site":"appstore","name":"App Store: Harbour & Vine","url":"https://apps.apple.com/gb/app/harbour-vine/id1234567890","reviewCount":412,"averageRating":4.3,"latestVersion":"5.2.0"},"days":90,"weeks":[{"week":"2026-10-19","reviews":31,"averageRating":3.9}],"versions":[{"version":"5.2.0","firstSeenAt":"2026-10-20T08:12:00.000Z","reviews":28,"averageRating":3.7}],"countries":[{"country":"GB","reviews":25,"averageRating":4.1}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/apps/{id}/topics":{"get":{"operationId":"getAppTopics","summary":"Get app topics","description":"The latest AI grouping of the app’s reviews into topics. 404 until it’s been run.\n\nAny key (read or write).","tags":["Apps"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Topics.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTopics"},"examples":{"example":{"value":{"appId":"prf_12","reviewCount":200,"topics":[{"title":"Crashes when uploading a photo","kind":"bug","summary":"Since 5.2.0 the app closes when attaching a photo.","reviewIds":[5512,5520]},{"title":"Wants a dark mode","kind":"feature_request","summary":"People reading at night ask for a dark theme.","reviewIds":[5490]}],"createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"post":{"operationId":"findAppTopics","summary":"Find app topics","description":"Groups the app’s 200 newest written reviews into up to 12 topics (bugs, feature requests, complaints, praise, questions) with AI. Plus plan; 10 runs an hour per workspace; 503 when AI is off on the server.\n\nNeeds a **write** key.","tags":["Apps"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"Topics.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppTopics"},"examples":{"example":{"value":{"appId":"prf_12","reviewCount":200,"topics":[{"title":"Crashes when uploading a photo","kind":"bug","summary":"Since 5.2.0 the app closes when attaching a photo.","reviewIds":[5512,5520]},{"title":"Wants a dark mode","kind":"feature_request","summary":"People reading at night ask for a dark theme.","reviewIds":[5490]}],"createdAt":"2026-10-28T09:30:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/rank-trackers":{"get":{"operationId":"listRankTrackers","summary":"List rank trackers","description":"Each tracker with its latest scan.\n\nAny key (read or write).","tags":["Map rankings"],"x-earful-scope":"read","responses":{"200":{"description":"Trackers.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RankTracker"}}},"required":["data"]},"examples":{"example":{"value":{"data":[{"id":9,"locationId":"loc_4","keyword":"seafood restaurant","gridSize":7,"radiusKm":2,"center":{"lat":50.153,"lng":-5.067},"business":{"cid":"1234567890123456789","name":"Harbour & Vine"},"every":"week","nextScanAt":"2026-11-04T09:30:00.000Z","createdAt":"2026-10-28T09:30:00.000Z","scans":[{"id":88,"trackerId":9,"status":"done","points":49,"averageRank":3.4,"top3Share":0.592,"foundShare":0.918,"costUsd":0.0294,"createdAt":"2026-10-28T09:30:00.000Z","finishedAt":"2026-10-28T09:30:00.000Z"}]}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/locations/{id}/rank-trackers":{"post":{"operationId":"createRankTracker","summary":"Track a Google Maps search","description":"Where the location ranks on Google Maps for keyword, at each point of a gridSize × gridSize grid (3 to 13, odd; 7 by default) radiusKm (0.5 to 25; 2 by default) from the centre to each edge. Scans straight away; every rescans on a schedule. The centre and the business (its cid, else its name) come from the location’s Google Maps page, or send lat, lng and name. Each point uses one of the plan’s monthly grid points (402 when they run out); 503 when DataForSEO isn’t set up on the server.\n\nNeeds a **write** key.","tags":["Map rankings"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"keyword":{"type":"string","maxLength":100},"gridSize":{"type":"integer","enum":[3,5,7,9,11,13]},"radiusKm":{"type":"number","minimum":0.5,"maximum":25},"every":{"type":"string","enum":["week","fortnight","month"]},"lat":{"type":"number"},"lng":{"type":"number"},"name":{"type":"string","description":"The business name exactly as Google Maps shows it."},"cid":{"type":"string","description":"Google’s customer id for the business."}},"required":["keyword"]},"examples":{"weekly":{"value":{"keyword":"seafood restaurant","gridSize":7,"radiusKm":2,"every":"week"}},"pinned":{"value":{"keyword":"emergency plumber","lat":51.5072,"lng":-0.1276,"name":"Fix-It Plumbing"}}}}}},"responses":{"201":{"description":"The tracker, with its first scan running.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankTracker"},"examples":{"example":{"value":{"id":9,"locationId":"loc_4","keyword":"seafood restaurant","gridSize":7,"radiusKm":2,"center":{"lat":50.153,"lng":-5.067},"business":{"cid":"1234567890123456789","name":"Harbour & Vine"},"every":"week","nextScanAt":"2026-11-04T09:30:00.000Z","createdAt":"2026-10-28T09:30:00.000Z","scans":[{"id":88,"trackerId":9,"status":"running","points":49,"averageRank":null,"top3Share":null,"foundShare":null,"costUsd":null,"createdAt":"2026-10-28T09:30:00.000Z","finishedAt":null}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/rank-trackers/{id}":{"get":{"operationId":"getRankTracker","summary":"Get a rank tracker","description":"The tracker and its scan history, newest first (up to a year of weekly scans).\n\nAny key (read or write).","tags":["Map rankings"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The tracker.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankTracker"},"examples":{"example":{"value":{"id":9,"locationId":"loc_4","keyword":"seafood restaurant","gridSize":7,"radiusKm":2,"center":{"lat":50.153,"lng":-5.067},"business":{"cid":"1234567890123456789","name":"Harbour & Vine"},"every":"week","nextScanAt":"2026-11-04T09:30:00.000Z","createdAt":"2026-10-28T09:30:00.000Z","scans":[{"id":88,"trackerId":9,"status":"done","points":49,"averageRank":3.4,"top3Share":0.592,"foundShare":0.918,"costUsd":0.0294,"createdAt":"2026-10-28T09:30:00.000Z","finishedAt":"2026-10-28T09:30:00.000Z"}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"delete":{"operationId":"deleteRankTracker","summary":"Delete a rank tracker","description":"Deletes it with its scans.\n\nNeeds a **write** key.","tags":["Map rankings"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"204":{"description":"Done; no body.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/rank-trackers/{id}/scans":{"post":{"operationId":"scanRankTracker","summary":"Scan again","description":"Starts a scan now (409 while one is running). Points finish within minutes; poll GET /api/v1/rank-scans/{id}.\n\nNeeds a **write** key.","tags":["Map rankings"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"The scan, running.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Location":{"description":"The scan’s URL.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankScan"},"examples":{"example":{"value":{"id":88,"trackerId":9,"status":"running","points":49,"averageRank":null,"top3Share":null,"foundShare":null,"costUsd":null,"createdAt":"2026-10-28T09:30:00.000Z","finishedAt":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/rank-scans/{id}":{"get":{"operationId":"getRankScan","summary":"Get a rank scan","description":"The rank at every grid point (row by row from the north-west), the top 3 there, and the businesses that most often beat this one.\n\nAny key (read or write).","tags":["Map rankings"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The scan.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RankScanDetail"},"examples":{"example":{"value":{"id":88,"trackerId":9,"status":"done","points":49,"averageRank":3.4,"top3Share":0.592,"foundShare":0.918,"costUsd":0.0294,"createdAt":"2026-10-28T09:30:00.000Z","finishedAt":"2026-10-28T09:30:00.000Z","competitors":[{"name":"The Shack","cid":"987654321","appearances":31,"averageRank":1.6}],"grid":[{"index":0,"lat":50.17099,"lng":-5.09509,"status":"done","rank":4,"top":[{"rank":1,"title":"The Shack","cid":"987654321"}]}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/reviews/changes":{"get":{"operationId":"listReviewChanges","summary":"Sync reviews","description":"Every review that changed after the cursor (new, edited, replied, tagged, handled, removed from the site), oldest change first, and the ids of reviews that are gone for good (their profile was deleted). Start without a cursor to walk the whole workspace, then keep nextCursor and call again later: you get only what changed. Apply removed, then data.\n\nAny key (read or write).","tags":["Reviews"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Cursor"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Changes per page."}],"responses":{"200":{"description":"Changes after the cursor.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"},"description":"Reviews as they are now."},"removed":{"type":"array","items":{"type":"string"},"description":"Ids of reviews deleted from Earful."},"nextCursor":{"type":"string","description":"Where to carry on from; always set."},"hasMore":{"type":"boolean","description":"More changes are waiting: call again now."}},"required":["data","removed","nextCursor","hasMore"]},"examples":{"example":{"value":{"data":[{"id":"rev_981","site":"tripadvisor","profileId":"prf_12","profileName":"Tripadvisor: Harbour & Vine","reviewId":"1011625097","author":"Ann P","rating":2,"title":"Slow on a Saturday","text":"Lovely crab linguine but we waited 50 minutes for mains.","language":"en","publishedAt":"2026-10-27T19:04:00.000Z","updatedAt":null,"ownerReply":null,"reviewUrl":"https://www.tripadvisor.co.uk/ShowUserReviews-g186233-d1234567-r1011625097-Harbour_Vine-Falmouth.html","editedAt":null,"deletedAt":null,"handledAt":null,"firstSeenAt":"2026-10-28T09:30:00.000Z","tags":["service"],"draft":null,"sentiment":"negative","translation":null,"appVersion":null,"country":null,"history":false}],"removed":["rev_77"],"nextCursor":"eyJzZXEiOjQwMjF9","hasMore":false}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/webhook-deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List webhook deliveries","description":"Every webhook send, newest first: its status, tries and your endpoint’s answer. A failed send is retried after 1, 5, 30, 120, 360 and 720 minutes, then given up. Kept 30 days.\n\nAny key (read or write).","tags":["Alerts"],"x-earful-scope":"read","parameters":[{"name":"alert","in":"query","schema":{"type":"string"},"description":"Only this webhook alert."},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","failed"]}},{"$ref":"#/components/parameters/Cursor"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size."}],"responses":{"200":{"description":"A page of deliveries.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"nextCursor":{"type":["string","null"]}},"required":["data","nextCursor"]},"examples":{"example":{"value":{"data":[{"id":"whd_88","alertId":"alr_7","event":"reviews.new","status":"pending","test":false,"attempts":2,"responseStatus":503,"error":"Webhook answered 503","replayOf":null,"createdAt":"2026-10-28T09:30:00.000Z","lastAttemptAt":"2026-10-28T09:31:00.000Z","nextAttemptAt":"2026-10-28T10:01:00.000Z","deliveredAt":null}],"nextCursor":null}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/webhook-deliveries/{id}":{"get":{"operationId":"getWebhookDelivery","summary":"Get a webhook delivery","description":"One delivery, with the exact body sent as payload.\n\nAny key (read or write).","tags":["Alerts"],"x-earful-scope":"read","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"The delivery.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookDelivery"},{"type":"object","properties":{"payload":{"type":"object"}},"required":["payload"]}]},"examples":{"example":{"value":{"id":"whd_88","alertId":"alr_7","event":"reviews.new","status":"pending","test":false,"attempts":2,"responseStatus":503,"error":"Webhook answered 503","replayOf":null,"createdAt":"2026-10-28T09:30:00.000Z","lastAttemptAt":"2026-10-28T09:31:00.000Z","nextAttemptAt":"2026-10-28T10:01:00.000Z","deliveredAt":null,"payload":{"id":"evt_9QwLx2","event":"profile.imported","livemode":true,"createdAt":"2026-10-28T09:30:00.000Z","workspace":{"id":1,"name":"Harbour & Vine"},"profile":{"id":12,"locationId":3,"locationName":"Harbour & Vine, Falmouth","site":"tripadvisor","name":"Tripadvisor: Harbour & Vine","url":"https://www.tripadvisor.co.uk/Restaurant_Review-g186233-d1234567-Reviews-Harbour_Vine-Falmouth.html","reviewCount":214,"averageRating":4.6,"lastCheckedAt":"2026-10-28T09:30:00.000Z","nextCheckAt":"2026-10-28T10:30:00.000Z","lastError":null,"createdAt":"2026-10-01T12:00:00.000Z"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/webhook-deliveries/{id}/replay":{"post":{"operationId":"replayWebhookDelivery","summary":"Replay a webhook delivery","description":"Sends the same body (same event id) to the same alert again, as a new delivery, signed afresh. It retries like any other.\n\nNeeds a **write** key.","tags":["Alerts"],"x-earful-scope":"write","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"The new delivery.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"},"examples":{"example":{"value":{"id":"whd_91","alertId":"alr_7","event":"reviews.new","status":"delivered","test":false,"attempts":1,"responseStatus":200,"error":null,"replayOf":"whd_88","createdAt":"2026-10-28T09:30:00.000Z","lastAttemptAt":"2026-10-28T09:31:00.000Z","nextAttemptAt":null,"deliveredAt":"2026-10-28T10:05:00.000Z"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api/v1/tools/google-places":{"get":{"operationId":"findGooglePlace","summary":"Free tool: find a Google place ID and review link","description":"No key. Looks a business up on Google by name and answers each match with its place ID and write-a-review link. 10 new lookups a day per IP address; repeat lookups come from a cache and don’t count. With a key, use place searches instead.","tags":["Free tools"],"x-earful-scope":"public","security":[],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":100},"description":"The business name."},{"name":"near","in":"query","required":false,"schema":{"type":"string","maxLength":60},"description":"A town or postcode to narrow it down."}],"responses":{"200":{"description":"Matches, best first. Empty when nothing matched.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":["string","null"]},"placeId":{"type":"string"},"reviewUrl":{"type":"string","format":"uri"},"rating":{"type":["number","null"]},"reviewCount":{"type":["integer","null"]}},"required":["name","address","placeId","reviewUrl","rating","reviewCount"]}}},"required":["data"]},"examples":{"found":{"value":{"data":[{"name":"Salami Social Club","address":"12 High Street, Leeds LS1 4AB","placeId":"ChIJN1t_tDeuEmsRUsoyG83frY4","reviewUrl":"https://search.google.com/local/writereview?placeid=ChIJN1t_tDeuEmsRUsoyG83frY4","rating":4.6,"reviewCount":412}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/v1/tools/reply-drafts":{"post":{"operationId":"draftFreeReply","summary":"Free tool: draft a reply to a pasted review","description":"No key. Drafts a reply to one review in the tone you pick. Nothing is stored. 5 drafts a day per IP address, reviews up to 2,000 characters. With a key, draft replies to collected reviews instead, in your workspace’s voice.","tags":["Free tools"],"x-earful-scope":"public","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"review":{"type":"string","minLength":1,"maxLength":2000},"rating":{"type":["integer","null"],"minimum":1,"maximum":5},"tone":{"type":"string","enum":["friendly","professional","playful","concise"],"default":"friendly"},"businessName":{"type":["string","null"],"maxLength":100}},"required":["review"],"additionalProperties":false},"examples":{"complaint":{"value":{"review":"Waited 40 minutes for our mains and nobody said why.","rating":2,"tone":"professional","businessName":"Salami Social Club"}}}}}},"responses":{"200":{"description":"The draft.","content":{"application/json":{"schema":{"type":"object","properties":{"reply":{"type":"string"}},"required":["reply"]},"examples":{"draft":{"value":{"reply":"Thank you for telling us, and we’re sorry about the wait. That isn’t the evening we want anyone to have. Please get in touch so we can look into what happened."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/UnprocessableContent"},"429":{"$ref":"#/components/responses/TooManyRequests"},"502":{"$ref":"#/components/responses/Unavailable"},"503":{"$ref":"#/components/responses/Unavailable"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key from Settings, API (or POST /api/v1/keys): Authorization: Bearer ef_…. Read keys can only GET; write keys can do everything."},"mcpOAuth":{"type":"oauth2","description":"For the MCP server at /mcp only, not the REST API. Discovery: /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server. Clients register dynamically at /oauth/register or use a Client ID Metadata Document; PKCE (S256) and the resource parameter are required. Access tokens last an hour; refresh tokens rotate on use. Revoke at /oauth/revoke.","flows":{"authorizationCode":{"authorizationUrl":"/oauth/authorize","tokenUrl":"/oauth/token","refreshUrl":"/oauth/token","scopes":{"read":"Search and read reviews, locations, review pages and held replies.","write":"Also draft and post replies, approve auto-replies and add review pages. Includes read."}}}}},"headers":{"RateLimit-Limit":{"description":"Requests this key may make per minute.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests left in this minute.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the minute resets.","schema":{"type":"integer"}}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The resource’s id, like loc_3. The bare number still works."},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Page size."},"Offset":{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Deprecated: page with cursor. Rows to skip.","deprecated":true},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"nextCursor from the previous page. Keep the same filters."},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","schema":{"type":"string","minLength":1,"maxLength":255},"description":"Any unique string, like a UUID. Retrying with the same key within 24 hours gets the first answer back (with Idempotent-Replayed: true) instead of doing it twice. The same key on a different request is a 422."}},"responses":{"BadRequest":{"description":"Bad Request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Bad Request","status":400,"detail":"Give the location a name, like the business or branch."}}}}}},"Unauthorized":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Send a valid, unexpired key as Authorization: Bearer <key>."}}}}}},"PaymentRequired":{"description":"Payment Required","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Payment Required","status":402,"detail":"Your plan covers 1 location. Add a location to your plan first."}}}}}},"Forbidden":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Forbidden","status":403,"detail":"This key is read-only. Create a key with write scope to make changes."}}}}}},"NotFound":{"description":"Not Found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Not Found","status":404,"detail":"That review isn’t in this workspace."}}}}}},"Conflict":{"description":"Conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Conflict","status":409,"detail":"Harbour & Vine already has a Tripadvisor profile."}}}}}},"TooManyRequests":{"description":"Too Many Requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Too Many Requests","status":429,"detail":"Too many requests for this key. Wait for the window to reset."}}}}},"headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"Unavailable":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Service Unavailable","status":503,"detail":"AI drafts are switched off on this server (no ANTHROPIC_API_KEY)."}}}}}},"UnprocessableContent":{"description":"Unprocessable Content","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"examples":{"problem":{"value":{"type":"about:blank","title":"Unprocessable Entity","status":422,"detail":"That Idempotency-Key was used for a different request. Send a new key."}}}}}}},"schemas":{"Problem":{"type":"object","properties":{"type":{"type":"string","description":"A URI for the problem type; always about:blank, so title is the HTTP status text."},"title":{"type":"string","description":"The HTTP status text."},"status":{"type":"integer","description":"The HTTP status code."},"detail":{"type":"string","description":"What went wrong, in plain English, and usually how to fix it."},"existingId":{"type":"string","description":"On a 409 for a profile that’s already there: its id."}},"required":["type","title","status","detail"],"description":"An RFC 9457 problem details object, sent as application/problem+json."},"Location":{"type":"object","properties":{"id":{"type":"string","pattern":"^loc_\\d+$","description":"Location id."},"name":{"type":"string","description":"The business or branch name."},"profileCount":{"type":"integer","description":"How many review pages this location has."},"createdAt":{"type":"string","format":"date-time","description":"When it was added."},"externalRef":{"type":["string","null"],"maxLength":200,"description":"Your own reference for it, like your store id. Filter with GET /locations?externalRef=."}},"required":["id","name","profileCount","createdAt","externalRef"],"description":"One business (or app) across all its review sites. Plans are priced per location."},"Profile":{"type":"object","properties":{"id":{"type":"string","pattern":"^prf_\\d+$","description":"Profile id."},"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location it belongs to."},"locationName":{"type":["string","null"],"description":"That location’s name."},"site":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"],"description":"Which review site."},"name":{"type":"string","description":"A name for the review page."},"url":{"type":"string","description":"The review page.","format":"uri"},"reviewCount":{"type":"integer","description":"Reviews held (not counting removed ones)."},"averageRating":{"type":["number","null"],"description":"Average star rating of those reviews."},"lastCheckedAt":{"type":["string","null"],"format":"date-time","description":"When reviews were last collected."},"nextCheckAt":{"type":"string","format":"date-time","description":"When the next collection is due."},"lastError":{"type":["string","null"],"description":"Why the last collection failed, if it did."},"createdAt":{"type":"string","format":"date-time","description":"When it was added."},"externalId":{"type":["string","null"],"description":"The listing’s own id on its site: a Google cid or place id, a Tripadvisor location id (d7106969), a Trustpilot domain, an App Store id, a Play package, a Booking.com hotel slug. Two links to one listing share it, so it’s what to match on, not url. null where the link doesn’t carry one."},"import":{"type":"object","description":"Its history import. The first reviews come within a minute or two; then older ones load in the background, up to the plan’s cap (Free 40, Plus 5,000). A profile.imported webhook says when it’s over.","properties":{"status":{"type":"string","enum":["queued","importing","done","failed"],"description":"queued: first import not in yet. importing: loading older reviews. done or failed: over."},"reviews":{"type":"integer","description":"Reviews held so far."},"limit":{"type":["integer","null"],"description":"The history cap it ran to, once over."},"finishedAt":{"type":["string","null"],"format":"date-time","description":"When it was over."},"error":{"type":["string","null"],"description":"Why it failed, if it did."}},"required":["status","reviews","limit","finishedAt","error"]}},"required":["id","locationId","locationName","site","name","url","reviewCount","averageRating","lastCheckedAt","nextCheckAt","lastError","createdAt","externalId","import"],"description":"One review page of one location on one site."},"Review":{"type":"object","properties":{"id":{"type":"string","pattern":"^rev_\\d+$","description":"Earful’s id for the review."},"site":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"profileId":{"type":"string","pattern":"^prf_\\d+$","description":"The profile it came from."},"profileName":{"type":["string","null"]},"reviewId":{"type":"string","description":"The site’s own id for the review."},"author":{"type":["string","null"]},"rating":{"type":["integer","null"],"minimum":1,"maximum":5,"description":"Whole stars; out-of-10 sites are halved."},"title":{"type":["string","null"]},"text":{"type":["string","null"]},"language":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When the reviewer posted it."},"updatedAt":{"type":["string","null"],"format":"date-time","description":"When the site says it was last changed."},"ownerReply":{"oneOf":[{"type":"null"},{"type":"object","properties":{"text":{"type":"string","description":"The reply."},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When it was posted."}},"required":["text","publishedAt"]}],"description":"The business’s reply, from the site or recorded in Earful."},"reviewUrl":{"type":["string","null"],"format":"uri"},"editedAt":{"type":["string","null"],"format":"date-time","description":"When Earful noticed the reviewer edit it."},"deletedAt":{"type":["string","null"],"format":"date-time","description":"When Earful noticed it removed from the site."},"handledAt":{"type":["string","null"],"format":"date-time","description":"When it was marked handled."},"firstSeenAt":{"type":"string","format":"date-time","description":"When Earful first collected it."},"tags":{"type":"array","items":{"type":"string"}},"draft":{"type":["string","null"],"description":"The reply draft, if there is one."},"sentiment":{"type":["string","null"],"enum":["positive","neutral","negative","mixed",null],"description":"Read by AI when the review arrived; null until then or with AI off."},"translation":{"type":["string","null"],"description":"An English translation by AI, for reviews in another language."},"appVersion":{"type":["string","null"],"description":"App stores: the app version the review was left on."},"country":{"type":["string","null"],"description":"App stores: the store country, ISO 3166 (alpha-2 from public feeds, alpha-3 from App Store Connect)."},"history":{"type":"boolean","description":"Came in with the profile’s first import or history backfill, so no alert went out for it."}},"required":["id","site","profileId","profileName","reviewId","author","rating","title","text","language","publishedAt","updatedAt","ownerReply","reviewUrl","editedAt","deletedAt","handledAt","firstSeenAt","tags","draft","sentiment","translation","appVersion","country","history"]},"ReviewDetail":{"allOf":[{"$ref":"#/components/schemas/Review"},{"type":"object","properties":{"replyRoute":{"type":"object","properties":{"mode":{"type":"string","enum":["api","link","none"],"description":"api: POST /reply posts to the site. link: post it on the site at url. none: no way to reply."},"via":{"type":["string","null"],"description":"Which owner API posts it (appstore, googleplay, gbp) when mode is api."},"url":{"type":["string","null"],"format":"uri","description":"The site’s reply page when mode is link."}},"required":["mode","via","url"]}},"required":["replyRoute"]}]},"Draft":{"type":"object","properties":{"text":{"type":"string","description":"The draft reply."},"aiDraftsLeft":{"type":["integer","null"],"description":"AI drafts left this month; null when unlimited or not an AI draft."}},"required":["text","aiDraftsLeft"]},"ReplyResult":{"type":"object","properties":{"postedToSite":{"type":"boolean","description":"true when Earful posted it through the site’s owner API."},"via":{"type":"string","enum":["appstore","googleplay","gbp","facebook","manual"],"description":"manual means recorded only: post it on the site yourself."},"replyUrl":{"type":["string","null"],"format":"uri","description":"Where to post it when via is manual."},"review":{"$ref":"#/components/schemas/ReviewDetail"}},"required":["postedToSite","via","replyUrl","review"]},"Alert":{"type":"object","properties":{"id":{"type":"string","pattern":"^alr_\\d+$","description":"Alert id."},"channel":{"type":"string","enum":["digest","email","slack","webhook"]},"frequency":{"type":["string","null"],"enum":["daily","weekly",null],"description":"For digests."},"target":{"type":"string","description":"Where it goes: the email address, “Slack channel”, or the webhook’s host. Full URLs stay encrypted."},"maxRating":{"type":["integer","null"],"description":"Only reviews at or below this rating; null for all."},"sites":{"type":"array","items":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"description":"Only these sites; empty for all."},"lastSentAt":{"type":["string","null"],"format":"date-time","description":"When it last sent."},"lastError":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","description":"When it was added."},"locationId":{"type":["string","null"],"pattern":"^loc_\\d+$","description":"Only this location; null for all."},"sentiments":{"type":"array","items":{"type":"string","enum":["positive","neutral","negative","mixed"]},"description":"Only reviews the AI read as one of these; empty for any."},"tags":{"type":"array","items":{"type":"string"},"description":"Only reviews with one of these tags; empty for any."},"keywords":{"type":"array","items":{"type":"string"},"description":"Only reviews that mention one of these (in the text or its translation); empty for any."}},"required":["id","channel","frequency","target","maxRating","sites","lastSentAt","lastError","createdAt","locationId","sentiments","tags","keywords"]},"NewAlert":{"allOf":[{"$ref":"#/components/schemas/Alert"},{"type":"object","properties":{"secret":{"type":["string","null"],"description":"A webhook’s signing secret. Shown only in this answer."}},"required":["secret"]}]},"ApiKey":{"type":"object","properties":{"id":{"type":"integer","description":"Key id."},"name":{"type":"string","description":"What it’s for."},"scope":{"type":"string","enum":["read","write"],"description":"read: GET only. write: everything."},"prefix":{"type":"string","description":"The start of the key, to tell keys apart."},"createdAt":{"type":"string","format":"date-time","description":"When it was made."},"expiresAt":{"type":["string","null"],"format":"date-time","description":"When it stops working; null for never. A rotated key’s grace period ends here."},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"When it was last used."},"lastUsedIp":{"type":["string","null"],"description":"The address it was last used from."},"requestCount":{"type":"integer","description":"Requests made with it."},"mode":{"type":"string","enum":["live","test"],"description":"test: an ef_test_ key, which reaches the workspace’s sandbox (made-up data, no outside calls)."}},"required":["id","name","scope","prefix","createdAt","expiresAt","lastUsedAt","lastUsedIp","requestCount","mode"]},"NewApiKey":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"key":{"type":"string","description":"The key itself. Shown only in this answer; Earful keeps a hash."}},"required":["key"]}]},"RotatedApiKey":{"allOf":[{"$ref":"#/components/schemas/NewApiKey"},{"type":"object","properties":{"previous":{"$ref":"#/components/schemas/ApiKey"}},"required":["previous"]}]},"Workspace":{"type":"object","properties":{"id":{"type":"integer","description":"Workspace id."},"name":{"type":"string","description":"Workspace name."},"businessName":{"type":["string","null"],"description":"The name AI drafts sign as."},"tone":{"type":"string","enum":["friendly","professional","playful","concise"]},"voiceNotes":{"type":["string","null"]},"signoff":{"type":["string","null"]},"plan":{"type":"object","properties":{"name":{"type":"string","enum":["free","pro"]},"locations":{"type":"integer","description":"Locations the plan covers."},"checkEveryMinutes":{"type":"integer","description":"How often reviews are collected."},"aiDraftsPerMonth":{"type":["integer","null"],"description":"null for unlimited."},"alertChannels":{"type":"array","items":{"type":"string"}},"directReplies":{"type":"boolean","description":"Whether replies post straight to sites with an owner API."},"addons":{"type":"object","properties":{"local":{"type":"integer"}},"required":["local"],"description":"How many of the plan’s locations the Local add-on (map rank points) covers."},"requestEmailsPerMonth":{"type":"integer"},"requestTextsPerMonth":{"type":"integer"},"rankPointsPerMonth":{"type":"integer","description":"Map rank grid points a month."},"appTopics":{"type":"boolean","description":"Whether AI topics for apps are included."}},"required":["name","locations","checkEveryMinutes","aiDraftsPerMonth","alertChannels","directReplies","addons","requestEmailsPerMonth","requestTextsPerMonth","rankPointsPerMonth","appTopics"]},"usage":{"type":"object","properties":{"locations":{"type":"integer","description":"Locations in use."},"profiles":{"type":"integer","description":"Review pages in use."},"aiDraftsThisMonth":{"type":"integer","description":"AI drafts made this calendar month."},"aiDraftsLeft":{"type":["integer","null"],"description":"null for unlimited."},"requestEmailsThisMonth":{"type":"integer","description":"Sent and queued this calendar month."},"requestTextsThisMonth":{"type":"integer"},"rankPointsThisMonth":{"type":"integer"}},"required":["locations","profiles","aiDraftsThisMonth","aiDraftsLeft","requestEmailsThisMonth","requestTextsThisMonth","rankPointsThisMonth"]},"autoReply":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Off until you turn it on."},"dailyCap":{"type":"integer","minimum":1,"maximum":500,"description":"Most replies posted automatically in 24 hours."}},"required":["enabled","dailyCap"],"description":"Auto-replies: see reply rules."}},"required":["id","name","businessName","tone","voiceNotes","signoff","plan","usage","autoReply"]},"Candidate":{"type":"object","properties":{"candidateId":{"type":"integer","description":"Pass this to POST /locations/{id}/profiles to add it."},"site":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"name":{"type":"string","description":"The business or app."},"address":{"type":["string","null"],"description":"Street address; for apps, the developer."},"url":{"type":"string","description":"Its review page.","format":"uri"},"rating":{"type":["number","null"]},"reviewCount":{"type":["integer","null"]}},"required":["candidateId","site","name","address","url","rating","reviewCount"]},"PlaceSearch":{"type":"object","properties":{"id":{"type":"integer","description":"Search id."},"query":{"type":"string","description":"What was searched for."},"near":{"type":["string","null"]},"status":{"type":"string","enum":["running","done","failed"],"description":"done once every site has finished (some may have failed); failed when all did."},"createdAt":{"type":"string","format":"date-time","description":"When it started."},"sites":{"type":"array","items":{"type":"object","properties":{"site":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"status":{"type":"string","enum":["pending","waiting","done","failed"]},"attempts":{"type":"integer","description":"Failed tries so far; each retry waits twice as long."},"error":{"type":["string","null"],"description":"The last failure."},"finishedAt":{"type":["string","null"],"format":"date-time","description":"When it finished."}},"required":["site","status","attempts","error","finishedAt"]}},"candidates":{"type":"array","items":{"$ref":"#/components/schemas/Candidate"}}},"required":["id","query","near","status","createdAt","sites","candidates"]},"TagRule":{"type":"object","properties":{"id":{"type":"integer"},"tag":{"type":"string","description":"Lower case letters, digits, - and _."},"description":{"type":"string","description":"When the AI should apply it."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","tag","description","createdAt"]},"ReplyRule":{"type":"object","properties":{"id":{"type":"integer"},"locationId":{"type":["string","null"],"pattern":"^loc_\\d+$","description":"The location it covers; null for every location."},"minRating":{"type":"integer","minimum":1,"maximum":5},"maxRating":{"type":"integer","minimum":1,"maximum":5},"sites":{"type":"array","items":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"description":"Empty for every site."},"languages":{"type":"array","items":{"type":"string"},"description":"Two-letter codes; empty for any."},"keywords":{"type":"array","items":{"type":"string"},"description":"The review must mention one; empty for any."},"action":{"type":"string","enum":["post","approve"],"description":"post: post the AI reply where the site has a reply API, otherwise hold it. approve: always hold it."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","locationId","minRating","maxRating","sites","languages","keywords","action","createdAt"]},"AutoReply":{"type":"object","properties":{"id":{"type":"string","pattern":"^arp_\\d+$","description":"Auto-reply id."},"reviewId":{"type":"string","pattern":"^rev_\\d+$","description":"The review."},"ruleId":{"type":["integer","null"]},"status":{"type":"string","enum":["pending","posted","approved","rejected","skipped"],"description":"pending is the approval queue; posted means posted automatically."},"text":{"type":["string","null"],"description":"The AI reply (as approved, if edited)."},"reason":{"type":["string","null"],"description":"Why it’s held or skipped."},"decidedBy":{"type":["string","null"],"description":"auto, an email, an API key or a Slack user."},"createdAt":{"type":"string","format":"date-time"},"decidedAt":{"type":["string","null"],"format":"date-time"},"review":{"type":"object","properties":{"site":{"type":"string","enum":["google","tripadvisor","trustpilot","booking","appstore","googleplay","feefo","reviewsio","yelp","deliveroo","justeat","ubereats","opentable","doordash","grubhub","facebook","g2","capterra"]},"rating":{"type":["integer","null"]},"author":{"type":["string","null"]},"text":{"type":["string","null"]},"profileName":{"type":"string"}},"required":["site","rating","author","text","profileName"]}},"required":["id","reviewId","ruleId","status","text","reason","decidedBy","createdAt","decidedAt","review"]},"ReviewLink":{"type":"object","properties":{"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"code":{"type":"string"},"url":{"type":"string","format":"uri","description":"The short link to print or share; it redirects and counts the click."},"site":{"type":["string","null"],"description":"The site it sends people to, if it’s one Earful knows."},"targetUrl":{"type":"string","format":"uri","description":"Where it sends people: the site’s write-a-review page."},"clicks":{"type":"integer"},"lastClickAt":{"type":["string","null"],"format":"date-time"},"qrSvg":{"type":"string","description":"A QR code for url, as an SVG document."},"createdAt":{"type":"string","format":"date-time"}},"required":["locationId","code","url","site","targetUrl","clicks","lastClickAt","qrSvg","createdAt"]},"ConnectedApp":{"type":"object","description":"An app someone connected to the workspace with OAuth, like a claude.ai connector to the MCP server.","properties":{"id":{"type":"integer","description":"Connection id."},"name":{"type":"string","description":"The app’s name, as it registered."},"clientId":{"type":"string","description":"Its OAuth client id: efc_… when it registered itself, or the https address of its Client ID Metadata Document."},"redirectHosts":{"type":"array","items":{"type":"string"},"description":"Where it sends people back to after sign-in."},"scope":{"type":"string","enum":["read","write"],"description":"read: search and read. write: also draft, post and add."},"connectedBy":{"type":"string","format":"email","description":"Who allowed it."},"createdAt":{"type":"string","format":"date-time","description":"When it was connected."},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"When it last called the MCP server."},"lastUsedIp":{"type":["string","null"],"description":"The address it last called from."},"requestCount":{"type":"integer","description":"MCP requests it has made."}},"required":["id","name","clientId","redirectHosts","scope","connectedBy","createdAt","lastUsedAt","lastUsedIp","requestCount"]},"ReviewRequest":{"type":"object","properties":{"id":{"type":"integer"},"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"locationName":{"type":["string","null"]},"channel":{"type":"string","enum":["email","sms"]},"contact":{"type":"string","description":"Email address, or mobile number in E.164."},"name":{"type":["string","null"]},"orderRef":{"type":["string","null"]},"source":{"type":"string","enum":["api","csv","webhook","app"]},"status":{"type":"string","enum":["queued","sent","failed","suppressed"],"description":"suppressed: the customer opted out before it went."},"sendAt":{"type":"string","format":"date-time"},"sentAt":{"type":["string","null"],"format":"date-time"},"remindedAt":{"type":["string","null"],"format":"date-time","description":"When the one reminder went, to people who hadn’t clicked."},"clickedAt":{"type":["string","null"],"format":"date-time","description":"First click on the review link."},"reviewId":{"type":["string","null"],"pattern":"^rev_\\d+$","description":"The review Earful matched to this request by the reviewer’s name."},"unsubscribedAt":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]},"transactedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","locationId","locationName","channel","contact","name","orderRef","source","status","sendAt","sentAt","remindedAt","clickedAt","reviewId","unsubscribedAt","error","transactedAt","createdAt"]},"ReviewRequestSettings":{"type":"object","properties":{"emailSubject":{"type":"string"},"emailBody":{"type":"string","description":"Must contain {link}. {name} and {business} are filled in."},"reminderSubject":{"type":"string"},"reminderBody":{"type":"string"},"smsBody":{"type":"string","maxLength":100},"reminderDays":{"type":"integer","description":"0 for no reminder."},"replyTo":{"type":["string","null"],"format":"email"},"acknowledged":{"type":"boolean","description":"Whether the owner agreed the sending rules; requests need it."},"acknowledgedAt":{"type":["string","null"],"format":"date-time"},"consentWording":{"type":"string","description":"Suggested wording for receipts and booking forms."}},"required":["emailSubject","emailBody","reminderSubject","reminderBody","smsBody","reminderDays","replyTo","acknowledged","acknowledgedAt","consentWording"]},"Suppression":{"type":"object","properties":{"id":{"type":"integer"},"contact":{"type":"string"},"reason":{"type":"string","enum":["unsubscribed","manual"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","contact","reason","createdAt"]},"App":{"type":"object","properties":{"id":{"type":"string","pattern":"^prf_\\d+$","description":"The App Store or Google Play profile’s id."},"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"site":{"type":"string","enum":["appstore","googleplay"]},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"reviewCount":{"type":"integer"},"averageRating":{"type":["number","null"]},"latestVersion":{"type":["string","null"]}},"required":["id","locationId","site","name","url","reviewCount","averageRating","latestVersion"]},"AppTopics":{"type":"object","properties":{"appId":{"type":"string","pattern":"^prf_\\d+$","description":"The app’s profile id."},"reviewCount":{"type":"integer","description":"Reviews the topics were drawn from (the newest written ones)."},"topics":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"kind":{"type":"string","enum":["bug","feature_request","complaint","praise","question"]},"summary":{"type":"string"},"reviewIds":{"type":"array","items":{"type":"integer"}}},"required":["title","kind","summary","reviewIds"]}},"createdAt":{"type":"string","format":"date-time"}},"required":["appId","reviewCount","topics","createdAt"]},"RankScan":{"type":"object","properties":{"id":{"type":"integer"},"trackerId":{"type":"integer"},"status":{"type":"string","enum":["running","done","failed"]},"points":{"type":"integer","description":"Grid points, which count against the plan’s monthly allowance."},"averageRank":{"type":["number","null"],"description":"Average Maps rank over the points where the business shows."},"top3Share":{"type":["number","null"],"description":"Share of points (0–1) where it’s in the top 3."},"foundShare":{"type":["number","null"],"description":"Share of points (0–1) where it’s in the top 20."},"costUsd":{"type":["number","null"]},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"}},"required":["id","trackerId","status","points","averageRank","top3Share","foundShare","costUsd","createdAt","finishedAt"]},"RankTracker":{"type":"object","properties":{"id":{"type":"integer"},"locationId":{"type":"string","pattern":"^loc_\\d+$","description":"The location."},"keyword":{"type":"string"},"gridSize":{"type":"integer","enum":[3,5,7,9,11,13]},"radiusKm":{"type":"number"},"center":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}},"required":["lat","lng"]},"business":{"type":"object","properties":{"cid":{"type":["string","null"]},"name":{"type":"string"}},"required":["cid","name"]},"every":{"type":["string","null"],"enum":["week","fortnight","month",null]},"nextScanAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"scans":{"type":"array","items":{"$ref":"#/components/schemas/RankScan"},"description":"Newest first."}},"required":["id","locationId","keyword","gridSize","radiusKm","center","business","every","nextScanAt","createdAt","scans"]},"RankScanDetail":{"allOf":[{"$ref":"#/components/schemas/RankScan"},{"type":"object","properties":{"competitors":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"cid":{"type":["string","null"]},"appearances":{"type":"integer","description":"Points where it’s in the top 3."},"averageRank":{"type":"number"}},"required":["name","cid","appearances","averageRank"]}},"grid":{"type":"array","description":"Row by row from the north-west corner.","items":{"type":"object","properties":{"index":{"type":"integer"},"lat":{"type":"number"},"lng":{"type":"number"},"status":{"type":"string","enum":["pending","waiting","done","failed"]},"rank":{"type":["integer","null"],"description":"null: not in the top 20 there (or not scanned yet)."},"top":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"integer"},"title":{"type":"string"},"cid":{"type":["string","null"]}},"required":["rank","title","cid"]}}},"required":["index","lat","lng","status","rank","top"]}}},"required":["competitors","grid"]}]},"WebhookDelivery":{"type":"object","description":"One webhook send: what went, to which alert, and how it went.","properties":{"id":{"type":"string","pattern":"^whd_\\d+$","description":"Delivery id."},"alertId":{"type":"string","pattern":"^alr_\\d+$","description":"The webhook alert it went to."},"event":{"type":"string","enum":["reviews.new","profile.imported"],"description":"The event."},"status":{"type":"string","enum":["pending","delivered","failed"],"description":"pending: will retry at nextAttemptAt."},"test":{"type":"boolean","description":"A test send (POST /alerts/{id}/test); never retried."},"attempts":{"type":"integer","description":"Tries so far."},"responseStatus":{"type":["integer","null"],"description":"Your endpoint’s last HTTP status."},"error":{"type":["string","null"],"description":"Why the last try failed."},"replayOf":{"type":["string","null"],"pattern":"^whd_\\d+$","description":"The delivery this replays."},"createdAt":{"type":"string","format":"date-time"},"lastAttemptAt":{"type":["string","null"],"format":"date-time"},"nextAttemptAt":{"type":["string","null"],"format":"date-time","description":"The next retry, while pending."},"deliveredAt":{"type":["string","null"],"format":"date-time"}},"required":["id","alertId","event","status","test","attempts","responseStatus","error","replayOf","createdAt","lastAttemptAt","nextAttemptAt","deliveredAt"]},"ReviewsNewEvent":{"type":"object","properties":{"id":{"type":"string","description":"The event’s id (evt_…); a replay sends the same one, so dedupe on it."},"event":{"const":"reviews.new"},"livemode":{"type":"boolean","description":"false when it comes from the test-mode sandbox (an ef_test_ key’s workspace)."},"createdAt":{"type":"string","format":"date-time"},"workspace":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"required":["id","name"]},"reviews":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}},"required":["id","event","livemode","createdAt","workspace","reviews"],"description":"New reviews that match the alert’s filters."},"ProfileImportedEvent":{"type":"object","properties":{"id":{"type":"string","description":"The event’s id (evt_…); a replay sends the same one, so dedupe on it."},"event":{"const":"profile.imported"},"livemode":{"type":"boolean","description":"false when it comes from the test-mode sandbox (an ef_test_ key’s workspace)."},"createdAt":{"type":"string","format":"date-time"},"workspace":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"required":["id","name"]},"profile":{"$ref":"#/components/schemas/Profile"}},"required":["id","event","livemode","createdAt","workspace","profile"],"description":"A profile’s history import is over (import.status done or failed)."}}},"webhooks":{"reviews.new":{"post":{"operationId":"reviewsNew","summary":"New reviews","description":"New reviews that match a webhook alert’s filters.","tags":["Alerts"],"parameters":[{"name":"Earful-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the alert’s secret>. Refuse it if t is over 5 minutes off."},{"name":"Earful-Event","in":"header","schema":{"type":"string"}},{"name":"Earful-Delivery","in":"header","schema":{"type":"string"},"description":"The delivery’s number."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewsNewEvent"}}}},"responses":{"200":{"description":"Any 2xx counts as delivered; anything else is retried."}}}},"profile.imported":{"post":{"operationId":"profileImported","summary":"Profile imported","description":"A profile’s history import is over. Sent to webhook alerts whose site and location filters fit the profile.","tags":["Alerts"],"parameters":[{"name":"Earful-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\" with the alert’s secret>. Refuse it if t is over 5 minutes off."},{"name":"Earful-Event","in":"header","schema":{"type":"string"}},{"name":"Earful-Delivery","in":"header","schema":{"type":"string"},"description":"The delivery’s number."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileImportedEvent"}}}},"responses":{"200":{"description":"Any 2xx counts as delivered; anything else is retried."}}}}}}