earful

quickstart

From a key to a posted reply in five calls

1. create a key

In Earful, open Settings, API, name the key, pick write and create it. It’s shown once. Every plan includes the API.

export EARFUL_KEY=ef_…

Read keys can only GET. Rotate a key from the same place (or POST /api/v1/keys/{id}/rotate) and the old one keeps working for 24 hours while you swap it.

2. search for the business

curl -X POST https://earful.onibaku.co.uk/api/v1/place-searches \
  -H "Authorization: Bearer $EARFUL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Harbour & Vine", "near": "Falmouth"}'

That answers 202 with the search’s id. Each site is searched in the background; ask until status isn’t running:

curl https://earful.onibaku.co.uk/api/v1/place-searches/55 \
  -H "Authorization: Bearer $EARFUL_KEY"

Google, App Store and Google Play answer in seconds; Tripadvisor and Trustpilot in a minute or so.

3. add it

Pick a candidate and add it to a location (GET /api/v1/locations lists them; Free has one):

curl -X POST https://earful.onibaku.co.uk/api/v1/locations/loc_3/profiles \
  -H "Authorization: Bearer $EARFUL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"candidateId": 302}'

Sites search doesn’t cover take the review page link instead: {"url": "https://www.feefo.com/en-GB/reviews/…"}. The first reviews arrive within a minute or two, then older ones load in the background (Plus: up to 5,000); import.status on the profile says how it’s going, and a profile.imported webhook says when it’s over.

A profile’s externalId is the listing’s own id on its site (a Tripadvisor d7106969, a Trustpilot domain, a Google cid). Match on it, not the URL: GET /api/v1/profiles?site=tripadvisor&externalId=d7106969. Adding a second link to the same listing answers 409 with existingId. Tag locations with your own externalRef (say your store id) and filter on it.

4. list reviews

curl "https://earful.onibaku.co.uk/api/v1/reviews?status=unreplied&rating=2&limit=20" \
  -H "Authorization: Bearer $EARFUL_KEY"

Filter by site, location, rating, status, from/to, q. Page by passing each answer’s nextCursor back as cursor until it’s null (offset still works but is deprecated). Ids are opaque strings like rev_981.

5. reply

curl -X POST https://earful.onibaku.co.uk/api/v1/reviews/rev_981/draft \
  -H "Authorization: Bearer $EARFUL_KEY"
curl -X POST https://earful.onibaku.co.uk/api/v1/reviews/rev_981/reply \
  -H "Authorization: Bearer $EARFUL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi Ann, thank you, and sorry about the wait. Sam"}'

Where the review’s page is connected to the site’s owner API (App Store Connect, Google Play) the reply is posted there and postedToSite is true. Otherwise it’s recorded as replied and replyUrl is where to post it.

6. stay in sync

Keep a copy of your reviews without re-reading them all: walk GET /api/v1/reviews/changes once, keep its nextCursor, and call again with it whenever you like. You get every review that changed since (new, edited, replied, tagged, removed from the site) and, in removed, the ids that are gone for good.

curl "https://earful.onibaku.co.uk/api/v1/reviews/changes?cursor=eyJzZXEiOjQwMjF9" \
  -H "Authorization: Bearer $EARFUL_KEY"

7. webhooks

POST /api/v1/alerts {"channel": "webhook", "target": "https://…"} sends reviews.new and profile.imported events, signed with Earful-Signature: t=…,v1=… (HMAC-SHA256 of t.body with the secret in that answer). Anything but a 2xx is retried after 1, 5, 30, 120, 360 and 720 minutes. GET /api/v1/webhook-deliveries is the log, and POST /api/v1/webhook-deliveries/{id}/replay sends one again with the same event id, so dedupe on id.

8. retries and test mode

Send an Idempotency-Key header (any unique string) with a POST and a retry within 24 hours gets the first answer back instead of doing it twice.

Pick Test mode when you make a key (or POST /api/v1/keys {"mode": "test"}) for an ef_test_… key. It reaches a sandbox copy of your workspace full of made-up reviews, where nothing leaves Earful: no collection, AI, emails or replies to the sites. Its webhooks do go out, to the endpoints you set up with the test key, with livemode: false and the same signing.

9. sdks

TypeScript and Python clients generated from openapi.json, with no dependencies, are in the repository’s sdk/ folder. Both have paginate and a webhook signature check.

const earful = new Earful(process.env.EARFUL_KEY);
for await (const r of earful.paginate((q) => earful.listReviews(q), { status: 'unreplied' })) console.log(r.id);
earful = Earful(os.environ["EARFUL_KEY"])
for r in earful.paginate(earful.list_reviews, {"status": "unreplied"}): print(r["id"])

Errors are application/problem+json with a detail that says what to change. Every answer carries RateLimit-Remaining; at zero you get 429 with Retry-After.