Developers
Feedback Guru API
Ask customers for a review the moment a booking ends or an order ships. Your system calls one endpoint with the customer's name and email; we send the request from your business name, follow up on your schedule, and stop when they respond.
The API is included with Pro ($19/mo per location). You can sign up, set up a location and read everything here for free.
What you can do
- ·Queue a review request with the customer's name, your own message, 1 to 3 emails, and when each one goes out.
- ·Check each request: sent, opened, rated, clicked through to Google or Trustpilot.
- ·Cancel a request before the next email.
- ·Pull the star ratings and private notes customers leave, filtered by rating or date.
- ·List your locations, and update names, review links, alerts, timezone, default schedule and logo.
Safe to retry: a repeat call for the same customer within 6 months returns 409 duplicate instead of a second email.
Documentation
REST over HTTPS, JSON in and out.
Getting started
Base URL: https://www.feedback-guru.com/api/v1. Send and receive JSON. Authenticate every request with your key:
curl https://www.feedback-guru.com/api/v1/locations \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY"
The API works once at least one location in your account is on Pro; until then calls return 403 plan_required. Review requests can be sent for Pro locations. Keys are for servers only. Don't put one in a web page or mobile app, where anyone could read it. You can make up to 120 requests per minute per key; past that you get a 429 with a Retry-After header.
Queue a review request
POST /api/v1/locations/{id}/review-requests (Pro locations)
curl -X POST https://www.feedback-guru.com/api/v1/locations/123/review-requests \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"email_attempts": 2,
"days_until_first_attempt": 1,
"days_before_second_attempt": 4
}'
| Field | Type | Details |
|---|---|---|
| string, required | The customer's email address. | |
| first_name | string, recommended | Used in the greeting ("Hi Jane,"). Up to 100 characters. Without a plain first name the email says "Hi there". |
| last_name | string, optional | Up to 100 characters. Shown in your dashboard, not in the email. |
| message | string, optional | Replaces our default text in the email body. Plain text, up to 1000 characters, line breaks kept, no links. The greeting, the "Rate your experience" button, your business name and address, and the unsubscribe link are always included. Without it we use our standard message. |
| email_attempts | integer 1-3, default 1 | How many emails this customer can get: the request plus up to 2 reminders. Reminders stop as soon as they open their link. |
| days_until_first_attempt | integer 0-90, default 0 | Wait this many days before the first email. 0 means the next sending window. |
| days_before_second_attempt | integer 1-30 | Days between the first email and the first reminder. Defaults to the location's setting. Ignored when email_attempts is 1. |
| days_before_third_attempt | integer 1-30 | Days between the first and second reminder. Defaults to the location's setting. Ignored unless email_attempts is 3. |
Returns 201 with the request (see the request object below).
- One request per customer per location every 6 months. If this email address was added to this location in the last 6 months (from the API or the dashboard, any status), you get 409 duplicate with existing_request_id, and nothing is sent. Addresses are compared without case or surrounding spaces.
- Unsubscribes are respected. Addresses that unsubscribed from this business, bounced, or marked an email as spam get 422 suppressed.
- Consent. By calling this endpoint you confirm the person is your customer and agreed to hear from your business, the same confirmation as the dashboard's checkbox. Don't send purchased or rented lists.
- Sending pace. Requests are queued, not sent instantly. We send at most 10 emails per location per day, between 9am and 6pm in the location's timezone, reminders first. A request whose day has come goes out in the next window with room.
The request object
{
"data": {
"id": 812,
"location_id": 123,
"status": "queued",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Smith",
"name": "Jane Smith",
"message": null,
"source": "api",
"email_attempts": 2,
"emails_sent": 0,
"schedule": {
"days_until_first_attempt": 1,
"days_before_second_attempt": 4,
"days_before_third_attempt": null
},
"first_attempt_not_before": "2026-10-23T14:05:11Z",
"created_at": "2026-10-22T14:05:11Z",
"last_sent_at": null,
"opened_at": null,
"rated_at": null,
"review_link_clicked_at": null,
"ended_at": null
}
}
| Field | Type | Details |
|---|---|---|
| status | string | queued (not emailed yet), in_progress (emailed, more reminders possible), responded (opened their link), finished (all emails sent, no response), unsubscribed, bounced, complained (marked as spam), canceled. |
| opened_at | timestamp | When they opened their rating link. Reminders stop at this point. |
| rated_at | timestamp | When they left a rating on your rating page. |
| review_link_clicked_at | timestamp | When they clicked through to your Google or Trustpilot review page. The review site doesn't tell us whether they posted. |
All timestamps are UTC, ISO 8601.
List, look up and cancel requests
GET /api/v1/locations/{id}/review-requests, newest first. Optional query parameters: email, status, limit (1-100, default 50) and cursor (the next_cursor from the previous page; null on the last page).
curl "https://www.feedback-guru.com/api/v1/locations/123/review-requests?email=jane@example.com" \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY"
GET /api/v1/review-requests/{request_id} returns one request.
DELETE /api/v1/review-requests/{request_id} cancels a queued or in-progress request: no more emails go to that customer. The address still counts as asked for the next 6 months.
Ratings and notes
The star ratings and private notes customers leave on your rating page (from your QR code, link or review request emails). Reviews posted on Google or Trustpilot live on those sites and aren't included.
GET /api/v1/locations/{id}/reviews, newest first. Optional query parameters: min_rating and max_rating (1-5), since (a UTC date like 2026-10-01 or time like 2026-10-01T14:00:00Z), limit (1-100, default 50) and cursor.
curl "https://www.feedback-guru.com/api/v1/locations/123/reviews?max_rating=3&since=2026-10-01" \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY"
{
"data": [
{
"id": 5120,
"location_id": 123,
"rating": 3,
"note": "Great coffee, but we waited 20 minutes.",
"device": "mobile",
"created_at": "2026-10-20T16:42:09Z"
}
],
"next_cursor": null
}
| Field | Type | Details |
|---|---|---|
| rating | integer 1-5 | The star rating. |
| note | string or null | The private note, if they wrote one. |
| device | mobile, desktop or null | What they rated from. |
| created_at | timestamp | When they rated, in UTC. |
GET /api/v1/reviews/{review_id} returns one.
Free locations show their latest 5 ratings, as in the dashboard; the list then includes a limited object saying so. Pro locations show all of them.
Locations
GET /api/v1/locations lists your locations; GET /api/v1/locations/{id} returns one.
{
"data": {
"id": 123,
"name": "Ember Cafe",
"display_name": "Ember Cafe",
"name_identifier": "ember-cafe-123",
"feedback_url": "https://www.feedback-guru.com/feedback/ember-cafe-123",
"plan": "pro",
"review_platform": "Google",
"review_url": "https://search.google.com/local/writereview?placeid=...",
"google_place_id": "ChIJ...",
"logo_url": "https://storage.googleapis.com/...",
"email_alerts": { "enabled": true, "recipients": ["manager@example.com"] },
"timezone": "America/New_York",
"review_request_defaults": {
"email_attempts": 3,
"days_before_second_attempt": 3,
"days_before_third_attempt": 4
},
"sending": { "paused": false, "paused_reason": null, "ready": true },
"created_at": "2026-01-14T18:20:00Z"
}
}
PATCH /api/v1/locations/{id} changes only the fields you send:
curl -X PATCH https://www.feedback-guru.com/api/v1/locations/123 \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"display_name": "Ember Cafe Downtown", "timezone": "America/Chicago"}'
| Field | Type | Details |
|---|---|---|
| display_name | string | The name on your rating page, 1-200 characters. Your feedback URL stays the same. |
| review_url | string | A Google review link (search.google.com, g.page) or a Trustpilot review page (trustpilot.com/evaluate/your-domain). Locations linked to a Google listing always use that listing's link. |
| email_alerts | object | {"enabled": true|false, "recipients": ["a@example.com"]}: email alerts for new ratings, to up to 10 extra addresses. |
| timezone | string | Sending happens 9am-6pm here. One of: America/New_York, America/Chicago, America/Denver, America/Phoenix, America/Los_Angeles, America/Anchorage, Pacific/Honolulu, America/Toronto, America/Vancouver, Europe/London, Europe/Berlin, Asia/Jerusalem, Australia/Sydney. |
| review_request_defaults | object | The schedule for requests added in the dashboard, and the default waits for API requests: email_attempts (1-3), days_before_second_attempt and days_before_third_attempt (1, 2, 3, 4, 5, 6, 7, 10, 14 days). API requests still default to 1 email unless you send email_attempts. |
Creating and deleting locations is only available in the dashboard.
PUT /api/v1/locations/{id}/logo sets the logo on your rating page. Send a file (PNG, JPEG, WebP or GIF, up to 5 MB) or the public URL of one. We resize it to at most 512px and remove any metadata.
curl -X PUT https://www.feedback-guru.com/api/v1/locations/123/logo \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY" \
-F "file=@logo.png"
curl -X PUT https://www.feedback-guru.com/api/v1/locations/123/logo \
-H "Authorization: Bearer $FEEDBACK_GURU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/logo.png"}'
DELETE /api/v1/locations/{id}/logo removes it; your rating page then shows your business's first letter.
Errors
Errors share one shape, with a stable code to check in your code and a message for people. Validation errors also name the field.
{
"error": {
"code": "duplicate",
"message": "This customer was already asked by this location in the last 6 months, so we won't ask again yet.",
"existing_request_id": 640
}
}
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json, missing_logo | The body isn't a JSON object, or the logo is missing. |
| 401 | missing_api_key, invalid_api_key | No key, a wrong key, or a revoked key. |
| 403 | plan_required, pro_required | The account has no Pro location yet, or this location isn't on Pro (needed for review requests). |
| 404 | not_found | No such location or request in your account. |
| 405 | not_supported, method_not_allowed | For example, creating or deleting a location. |
| 409 | duplicate | This address was added to the location in the last 6 months. |
| 409 | location_paused, location_not_ready | Sending is paused, or we still need the business name and postal address. Fix it in the dashboard. |
| 409 | not_cancelable | The request already ended. |
| 413 | too_large | The body is over 8 MB. |
| 422 | invalid_*, unknown_field, suppressed, message_has_links, message_too_long, logo_url_unusable, has_google_listing | A field didn't pass validation; see message and field. |
| 429 | rate_limited | Over 120 requests per minute. Wait for Retry-After seconds. |
| 5xx | server_error, storage_error | Something failed on our side. Retrying is safe: a repeated request for the same customer returns 409 duplicate instead of a second email. |