Run it by

Ask people outside the chat for answers or approvals by link: forms, surveys and sign-offs they answer on their phone, returned to your agent as JSON.

Hosted MCP Server

npx add-mcp 'https://runitby.app/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

Ask anyone from your agent.

Your agent describes what it needs (a form, a survey, or a yes or no) and gets back a link for one person, each person in a group, or anyone you share it with. People open it on their phone, with no sign-up, and their answers come back to you as validated JSON.

  1. Create a request. Call createRequest with a JSON Schema for a form, or a reason for an approval.
  2. Send the link. Pass sendEmail: true and we email it to each named person, send it over a channel you already have (email, chat or SMS), or post an open link anywhere.
  3. Get the answer. On Builder and Pro, your MCP client is pushed every answer the moment it lands. It also arrives at your webhook, by email with notifyEmail, or read it with getStatus.

The same tools are available two ways: an MCP server for agents that speak MCP, and a REST API for everything else. Agents can read this guide in llms.txt.

Quickstart

From an API key to a person's answer in four steps.

1Get an API key

Sign in with your email and copy the key from the dashboard. It starts with sk_live_; keep it on your server. Using Claude Code, Codex or another MCP client? Skip this: add the server by URL and sign in from the client.

2Create a request

This one asks Jordan for a shipping address and a delivery date. The schema is plain JSON Schema; see Form schema for what's supported.

3Send the link

The response has one link per person in links[] (or a single url for an open link). With "sendEmail": true we email each named person who has an email their own link, in your account's name. Each link's email says what happened: "sent", "failed" (the request still exists; call remind later), "limit" (the plan's email limits are reached), "no address" or "off". Send the rest however you like: email, chat or SMS.

4Get the answer

When Jordan submits, your webhookUrl receives a response.created event with the data, already checked against your schema. No webhook? Call getStatus with the requestId, set notifyEmail and we email you when it's done, or on Builder and Pro subscribe over MCP and your agent is told as soon as Jordan answers. See Hearing back.

Authentication

Every MCP and REST call sends an API key as a bearer token. The same key works for both. A missing or revoked key gets a 401. There are two ways to get one.

Sign in from your MCP client

Add https://runitby.app/mcp with no key and the client signs you in over OAuth: a browser opens, you enter your email, and the sign-in link brings you back to a page that asks you to allow the app. First time? The same link creates your account. The app then holds its own key, named after it, which you can revoke under Keys.

  • Claude Code: run the command on the right, then /mcp in Claude Code and choose to authenticate.
  • Codex: run codex mcp add, then codex mcp login.
  • Claude (web and desktop): Settings, Connectors, Add custom connector, and paste the URL.
  • Other clients that support MCP authorization: add the URL with no headers.

Create a key in the dashboard

For the REST API, scripts, and clients that only take a header: create and revoke keys in the dashboard and send the key in the Authorization header.

Plans count the two apart. Free includes one key and one connected app, Builder five of each, Pro as many as you need.

Framework examples

Building your own agent? Start from one file. Each example connects to the MCP server with an API key and has the agent ask Jordan for a shipping address, email her the link and wait for her answer. Set RUNITBY_API_KEY and ANTHROPIC_API_KEY, install the packages on its first lines, and run it.

Tools and endpoints

Each MCP tool has a REST endpoint with the same input and output. The API reference lists every field.

MCP toolRESTWhat it does
createRequestPOST /requestsCreate a form or approval and get the link(s), emailed for you if you ask.
previewRequestPOST /requests/previewScreenshots and lint of what the person will see. Creates nothing.
getStatusGET /requests/{id}Status, verdict and every answer so far.
listRequestsGET /requestsYour requests, newest first. Filter by mode or status; page with cursor.
updateRequestPATCH /requests/{id}Change content, add recipients or set a new expiry.
remindPOST /requests/{id}/remindEmail the link to people who haven't answered.
closeRequestDELETE /requests/{id}Stop accepting answers.

Forms and approvals

Every request has a mode:

"form"

Collect data. You send a schema; the person gets a form built from it, and you get back an object that matches it.

"approval"

Get a decision. You send approval.reason; each person gets a card with Approve and Decline (or your own labels) and an optional comment. You get each decision and an overall verdict.

Both take a title (up to 120 characters), an optional requester line saying who's asking, a locale for the page and its emails ("en", "es", "pt", "fr", "de", "it", "ja" or "zh", or with a region like "es-AR" for dates), and metadata that comes back untouched in every webhook and status. Write the title and questions in the same language.

Who answers

audience decides who gets a link.

kindYou getUse it for
"named"links[], one per person in recipients (1 to 50, each { name, email?, phone? })Answers you need to attribute to someone. Approvals must be named. An email enables sendEmail, reminders and email codes.
"open"One url anyone can openSign-ups and surveys. maxSubmissions caps the answers (default 1).

What to use it for

A mode and an audience cover most things you'd otherwise do with a form builder, a poll, a scheduling app or an email thread.

You wantRequest
One person's details: a shipping address, client intake, a booking slot"form", named, one recipient
A survey, poll, feedback or sign-ups from anyone"form", open with maxSubmissions. Post the one url anywhere; getStatus returns every answer for your agent to summarise.
RSVPs, availability, shift cover or check-ins from a known group"form", named (up to 50), so every answer is attributed and remind chases the rest
Approvals, sign-offs and votes"approval" with a policy; add verify: "email" for money, access or deletion

Status and expiry

statusMeans
pendingNobody has answered yet.
partialSome named people have answered, others haven't.
completeEveryone answered, maxSubmissions was reached, or an approval has its verdict.
expiredThe link ran out of time.
closedYou called closeRequest.

Expiry. expiresIn takes "30m", "48h", "7d" or seconds. The default is 7 days and the maximum 90.

Changing a request. updateRequest changes the title, schema, sections, reason or theme until the first answer arrives. Adding recipients, setting a new expiry (counted from now) and changing notifyEmail work until the request is complete, expired or closed. Added recipients aren't emailed; send their links or call remind with their recipientIds.

Reminders. remind emails the link to named people who haven't answered and have an email, at most once every 24 hours each. The sendEmail email counts as the first.

Hearing back. Use one or combine several:

webhookUrl

Your server gets every answer as it happens. See Webhooks.

notifyEmail

We email your account a summary and a link to the request in the dashboard: "each" on every answer (until the plan's email limits are reached, then only when it ends), or "complete" once when it completes or expires. Approval emails include the decision and comment; form answers stay in the dashboard. Off by default; updateRequest can change it.

getStatus with wait

Holds the call for up to 25 seconds (REST: ?wait=25) and returns as soon as someone answers or the status changes. Call it again to keep waiting.

MCP push (Builder and Pro)

Each request is the MCP resource runitby://requests/{requestId}, linked from createRequest; reading it returns the same as getStatus. On Builder and Pro, clients on the 2026-07-28 protocol can subscribe with subscriptions/listen and get notifications/resources/updated on every answer or status change, with one subscription open at a time on Builder and up to three on Pro. On Free subscriptions/listen answers with an error naming the other ways to hear back.

Form schema

Plain JSON Schema with type: "object" at the top and up to 40 fields. required[] is enforced, and title and description become the label and help text.

TypeKeywords
stringformat (email, date, time, date-time, tel, uri, duration), minLength, maxLength, pattern
number, integerminimum, maximum
booleanconst: true makes a consent box that must be ticked
enumA fixed list of choices
arrayOf primitives or of an enum; minItems, maxItems

Hints. Put per-field options in "x-runitby": widget, placeholder, help, columns (1 or 2), step and currency.

Sections. Group fields with sections: [{ title, fields: [...] }] next to the schema.

Nested objects (except the location field), oneOf, anyOf, allOf and $ref are rejected with a message saying what to change. Fields asking for passwords, PINs, card numbers, CVVs, security codes, SSNs or one-time codes are refused.

Field types

Ready-made fields for things people find awkward to type. Each one is ordinary JSON Schema plus a widget, so the answer validates like any other field.

FieldSchemaAnswer
Datestring, format date"2026-11-12"
Timestring, format time; step = minutes (1–60)"14:30"
Date and timestring, format date-time"2026-11-12T14:30:00-08:00"
Durationstring, format duration"PT1H30M"
Time slotsarray of date-time enum, widget slotsthe picked times
Date rangearray of 2 dates, widget daterange["2026-11-12", "2026-11-15"]
Locationobject, widget location{ latitude, longitude, accuracy }
Moneynumber, currency "USD"1200
Slider, ratingnumber or integer; slider needs minimum and maximum7
Chips, radio, selectenum (chips also takes an array of enum)"M"
Rankarray of enum, widget rankevery option, in the person's order
Yes or noboolean, widget yesnotrue
Colourstring, widget color"#C2410C"
Countrystring, widget country"US" (ISO 3166-1)
Languagestring, widget language"es-AR" (BCP 47)
Time zonestring, widget timezone"America/New_York"

Add an enum to a country or language field to limit the choices.

Preview

previewRequest takes the same form or approval fields as createRequest (no audience, expiry, webhook or metadata) and returns screenshots at 390 px and 1280 px wide, plus lint[] with anything that will read badly. Nothing is created.

Pass sampleData to see the form filled in, and viewports to get only one size. Preview first when the same form will be sent many times.

Themes

theme makes the page look like your product. Without it, pages are ink on warm white, Figtree with Fraunces headings, round corners.

KeyValues
accent, background, surface, textHex colours
font, headingFontsystem, Figtree, Geist, Inter, DM Sans, Manrope, Work Sans, Space Grotesk, IBM Plex Sans, Fraunces, Lora, Source Serif 4
radiussharp, soft, round
densitycompact, comfortable
layoutstacked (all fields on one page) or single (one question at a time)
logoUrlYour logo
heroText, thankYouThe greeting above the form and the message after sending

Approval options

Approvals need a named audience: each person gets their own link, so every decision is attributed to them. The verdict is "approved", "declined", or "expired" when nobody decided in time; never a silent yes.

approval fieldDefaultNotes
reason requiredWhat you're asking for, up to 300 characters.
detailContext under the reason, up to 1,000 characters.
policy"all""any", "all", "majority" or { n }: how many approvals it takes.
optionsApprove, DeclineTwo to four labels; the first counts as approval. Decisions come back in snake_case: "Approve with changes" → "approve_with_changes".
allowCommenttrueLet people add a comment to their decision.
showOthersfalse"names" or "counts" shows each person where the others stand.
verifyfalse"email": each person confirms a 6-digit code sent to their own address before the buttons work, so a forwarded link can't decide for them. Every recipient needs an email. Use it for money, access or deletion.

Webhooks

Set webhookUrl (https) on a request and we POST JSON to it for two events:

response.created

Each submission or decision, with the answer in response.

request.completed

Every named person answered, maxSubmissions was reached, an approval has its verdict, or the request expired.

Verify the signature. X-Runitby-Timestamp is the send time in Unix seconds. X-Runitby-Signature is the hex HMAC-SHA256 of the timestamp, a dot and the raw body, with your account's signing secret from the dashboard. Reject timestamps more than 5 minutes old, so a captured delivery can't be replayed later.

Retries. Any 2xx within 10 seconds counts as delivered. Otherwise we retry 4 times, 1 min, 5 min, 30 min and 2 h after each failure. After that the answer is still there in getStatus.

Errors

Errors are JSON with a readable message and, for invalid input, problems[] pointing at each field to change. Agents can usually fix the request from the message alone.

StatusWhen
400The input or schema isn't valid.
401Missing or revoked API key.
404No request with that id on your account.
409The change isn't allowed any more, e.g. editing after the first answer or reminding a closed request.
429Over 60 creations a minute, or the plan's monthly requests are used up.

Plans and limits

LimitFreeBuilderPro
Requests a month2502,00010,000
Answers per open link2510,000Unlimited
API keys15Unlimited
Connected apps (OAuth)15Unlimited
Answers kept after delivery30 days90 daysKept
Emails a month5004,00020,000
Emails per request101010
MCP push (open subscriptions)—13
Price a month$0$9$29

Past the monthly limit createRequest returns 429 until the period resets or you upgrade (on Pro, the fair-use limit, write to us for more); links already sent keep working. Without a webhook, answers are kept from submission instead. See pricing.

The emails we send for you (links, reminders and notifyEmail updates) count against both email limits. Past either one, links come back as "limit" for you to send yourself and "each" updates stop. The email saying a request ended always goes out, and approval codes don't count.

On every plan: 60 creations a minute per API key, title up to 120 characters, approval.reason 300, approval.detail 1,000 and metadata under 4 KB.

You are the data controller for what you collect. Ask only for what the task needs.