Home›Revup›API
Revup · API

Revup API, as shipped in the download

Revup REST API, Webhooks & MCP

Revup ships a JSON REST API and signed webhooks so your booking system, POS or CRM can queue review requests the moment a job finishes — and so a private message from an unhappy customer reaches your helpdesk quickly. Every customer who rates is shown your public review link, whatever the rating.

Authentication

Create a key in Settings → API & Webhooks. Send it on every call:

Authorization: Bearer apk_...

Shown once at creation, stored as a SHA-256 hash. Revoke any time — revoking the key is the off switch.

Every write checks the key holder's role, exactly as the screen does: a viewer's key cannot add a contact or create a review request (403 forbidden).

Endpoints

Method & pathWhat it does
GET /api/pingVerify a key
GET /api/contactsNewest 500 contacts
POST /api/contactsCreate a contact (name + email or phone; optional served_by, the team member their asks are credited to)
GET /api/requestsNewest 200 requests; ?status= filters
POST /api/requestsQueue (and by default send) review requests for a campaign; optional staff credits the team member who did the job (3.2.0). Each request carries staff and nps
GET /api/feedbackPrivate messages from the rating page; ?handled=false for the open ones. Each carries reply_draft, reply_by and reply_sent_at (3.2.0)
POST /api/feedback/{id}/draftSave a reply draft on a private message: {"body": "..."}. Nothing is sent; a person sends it from the inbox (3.2.0). 409 once the reply was sent
GET /api/testimonialsConsented testimonials with ready-made share links; ?status= pending, approved or hidden (3.2.0)
GET /api/openapi.jsonOpenAPI 3.0 description

Queue requests after a job completes

curl -X POST https://reviews.example.com/api/requests \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"campaign_id":1,"contact_ids":[12,13,14]}'

Add "send": false to queue without sending — useful if you want a human to review the batch first.

Duplicate-suppressed. This reuses the same core the UI uses, so a contact who already has an open request for that campaign is not queued a second time. The response reports queued, sent, pending and failed counts so you can reconcile.

pending means the channel is not configured (no SMTP / no Twilio) — the request still exists and carries a gate_url you can send yourself.

Read the private messages

curl -H "Authorization: Bearer apk_..." \
  "https://reviews.example.com/api/feedback?handled=false"
{ "feedback": [ { "id": 4, "request_id": 19, "contact_id": 7,
                  "rating": 2, "message": "Waited 40 minutes.",
                  "handled": false, "created_at": "2026-08-06 11:44:02" } ], "count": 1 }

These are the private messages. A customer who rates below your threshold is shown your public review link like everyone else, and is also offered a private message to you — it lands here. Pull them into your helpdesk and close the loop.

The payload deliberately carries no gate token: a token is a capability to submit a rating, and replaying it is not something an integration should be able to do.

MCP — the agent endpoint (new in 3.0)

Revup speaks MCP (Model Context Protocol) on one route, so an assistant can watch your review pipeline and queue follow-ups without anyone building a bridge first.

POST /mcp
Authorization: Bearer apk_...          ← the SAME revocable key the REST API uses
Content-Type: application/json

Stateless streamable-HTTP: one JSON-RPC message per request, no session, no SSE. A GET gets 405 with Allow: POST, as the transport requires — and that answer is unauthenticated, so a client probing for MCP support learns you speak it before it has a key.

Connect it to an assistant

Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.

Every client needs the same three facts, and nothing in the handshake is vendor-specific:

The addresshttps://reviews.example.com/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP over streamable HTTP, stateless

Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:

claude mcp add --transport http revup https://reviews.example.com/mcp \
  --header "Authorization: Bearer apk_xxxx"

ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):

{
  "type": "mcp",
  "server_label": "revup",
  "server_url": "https://reviews.example.com/mcp",
  "authorization": "apk_xxxx",
  "require_approval": "never"
}

**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"*. Pasted as-is, a block written for another client will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.

A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:

curl -X POST https://reviews.example.com/mcp \
  -H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Revup implements the protocol, not an integration with a particular vendor.

The tools

ToolWrites?What it does
list_requestsnoReview requests with status, rating and the customer they belong to. Filter by status, rated, limit.
request_detailnoOne request, plus any private feedback left against it.
create_requestYESQueues a review request for a customer and mints its one-time rating link.
response_statsnoResponse rate, average rating, how many raters opened your review page and how many left a private message — the dashboard's own figures — and the NPS summary (3.2.0).
list_feedbacknoThe private messages, each with the rating, the customer's first name, any reply draft and your reply templates for that rating (3.2.0).
draft_replyYES (a draft)Saves a reply draft on one private message. It is never sent from here: a person reads, edits and sends it from the inbox (3.2.0).

status is queued (scheduled inside a send window), pending (waiting to be sent or copied), sent, or failed — the same four words the Requests screen uses.

What the endpoint refuses, and why

  • It cannot change the rating page. The threshold and the review URL are not writable over MCP. An agent may chase reviews; the page every customer sees stays with the owner.
  • No figure is recomputed. response_stats returns Revup::stats() — the same call the dashboard makes — so an agent can never quote a response rate Revup disagrees with.
  • create_request queues, it does not send. Sending goes out through your own SMTP, your own Twilio, or as a link you copy; that is the owner's action and the owner's cost.
  • It writes requests through the product's own creator, so the one-open-request-per-customer rule and token minting apply exactly as they do in the browser. A second request for a customer who already has one open is refused.
  • Arguments must be single values, unknown arguments are refused by name, and out-of-enum values are refused with the valid options listed.
  • Roles apply exactly as they do in the browser. A viewer's key is refused create_request.

Every MCP write lands in the audit trail beside the browser and API writes, naming the key's user as the actor and recording via: mcp.

Example

curl -X POST https://your-install/mcp \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"response_stats","arguments":{}}}'

Webhooks

Add a receiver in Settings → API & Webhooks and choose events:

EventFires when
request.sentA review request is dispatched
feedback.receivedA customer sends you a private message from the rating page — wire this to your helpdesk
rating.publicA customer opens your public review page from the rating page (any rating; once per request)
testimonial.approvedYou approve a testimonial a customer agreed to. Carries the quote, the name and share links for X, Facebook and LinkedIn, so your own automation can post it (3.2.0)

Every delivery is signed:

X-Revup-Event: feedback.received
X-Revup-Signature: sha256=<hmac_sha256(raw_body, your_webhook_secret)>

Compare with hash_hmac('sha256', $rawBody, $secret) and reject on mismatch.

Inbound: automatic asks (3.2.0)

Settings → Automatic asks gives each trigger a private address, POST /hooks/in/{key}. Another app posts to it when a job, booking or sale ends; Revup finds or adds the customer and asks them through the trigger's campaign — at once, or in the trigger's own send window.

  • Signed. With a signing secret set, a delivery carries `X-<App>-Signature: sha256=<HMAC-SHA256 of the raw body> (any header ending in -Signature); a wrong one answers 401` and creates nothing. Without a secret, the address itself is the key.
  • Generic JSON. email or phone (and name, staff, id) at the top level or inside customer, contact or data. Form-encoded bodies work too.
  • Jobora. job.completed (and job.signed_off). The customer's email is read from Jobora's own GET /api/customers with the key you give the trigger; the assigned technician is credited.
  • Slotly. appointment.created and appointment.rescheduled queue the ask for the morning after the appointment ends, appointment.cancelled withdraws it, payment.recorded asks if nothing was queued. The customer is read from Slotly's GET /api/v1/appointments/{id}.
  • Once per customer. A customer asked in the last N days (30 by default), by any app or person, is not asked again; the delivery answers 200 with "outcome":"duplicate" so the sender does not retry. Every delivery is logged on the Automatic asks page.
curl -X POST https://reviews.example.com/hooks/in/<key> \
  -H 'Content-Type: application/json' \
  -H "X-Signature: sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)" \
  -d "$BODY"

Public widget (3.2.0)

When you switch the widget on (Testimonials), three public addresses answer; each sets no cookie and is cacheable for five minutes. Switched off, they answer 404.

AddressWhat it is
GET /widget.jsThe two-line embed's script: frames /widget and sizes it
GET /widgetThe widget page: approved comments and, if chosen, the average of every rating
GET /widget.jsonThe same data as JSON, open to any site (CORS), for drawing it yourself

Email notifications

Revup already had its own SMTP settings and mailer, so 2.0 adds no new mail configuration — just a notify address and one toggle in Settings: email me the moment a customer sends a private message. That is the one alert where minutes matter.

Errors

CodeMeaning
401Missing, unknown or revoked key
422Validation — detail names the offending field

Connect it to Zapier, Make or n8n

RevUp talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.

Before you start

  • In RevUp, open Settings → API & Webhooks. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
  • Use your install's full address, for example https://revup.yourcompany.com.

When something happens in RevUp: send it to another tool

  1. In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
  2. Copy that URL into RevUp under Settings → API & Webhooks, and choose the event or events below.
  3. Make the event happen once in RevUp, so the other tool receives a sample and you can map its fields.
EventFires when
request.sentA review request is dispatched
feedback.receivedA customer sends you a private message from the rating page — wire this to your helpdesk
rating.publicA customer opens your public review page from the rating page (any rating; once per request)
testimonial.approvedYou approve a testimonial a customer agreed to. Carries the quote, the name and share links for X, Facebook and LinkedIn, so your own automation can post it (3.2.0)

Every delivery carries X-Revup-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.

When something happens elsewhere: act in RevUp

  • Zapier: "Webhooks by Zapier", action POST (or Custom Request).
  • Make: module HTTP → Make a request.
  • n8n: the HTTP Request node.

Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.

The endpoints that change something:

  • POST /api/contacts: Create a contact (name + email or phone; optional served_by, the team member their asks are credited to)
  • POST /api/requests: Queue (and by default send) review requests for a campaign; optional staff credits the team member who did the job (3.2.0). Each request carries staff and nps
  • POST /api/feedback/{id}/draft: Save a reply draft on a private message: {"body": "..."}. Nothing is sent; a person sends it from the inbox (3.2.0). 409 once the reply was sent

Example recipes

  • request.sent → a row in Google Sheets: webhook trigger on request.sent (A review request is dispatched), then the Google Sheets "Create row" action with the fields you want.
  • feedback.received → a message in Slack or Teams: webhook trigger on feedback.received (A customer sends you a private message from the rating page — wire this to your helpdesk), then the Slack or Microsoft Teams "Send message" action.
  • A new form entry or row elsewhere → RevUp: the other tool's trigger (for example a new Google Sheets row or a form submission), then an HTTP action calling POST /api/contacts (Create a contact (name + email or phone; optional served_by, the team member their asks are credited to)).

These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; RevUp's own automated tests check its API.

← Back to Revup · Manual · Quickstart · Test run