Docs
Ask a person from your agent.
Your agent describes what it needs (a few fields, or a yes or no) and gets back a link. The person opens it on their phone, with no sign-up, and their answer comes back to you as validated JSON.
-
Create a request. Call
createRequestwith a JSON Schema for a form, or a reason for an approval. - Send the link. Over the channel you already have: email, chat or SMS.
-
Get the answer. It arrives at your webhook, 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). Send it however you like;
Run it by doesn't message people for you, except reminders.
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.
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
/mcpin Claude Code and choose to authenticate. -
Codex: run
codex mcp add, thencodex 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.
Tools and endpoints
Each MCP tool has a REST endpoint with the same input and output. The API reference lists every field.
| MCP tool | REST | What it does |
|---|---|---|
createRequest |
POST /requests | Create a form or approval and get the link(s) to send. |
previewRequest |
POST /requests/preview | Screenshots and lint of what the person will see. Creates nothing. |
getStatus |
GET /requests/{id} | Status, verdict and every answer so far. |
listRequests |
GET /requests | Your requests, newest first. Filter by mode or status; page with cursor. |
updateRequest |
PATCH /requests/{id} | Change content, add recipients or set a new expiry. |
remind |
POST /requests/{id}/remind | Email the link again to people who haven't answered. |
closeRequest |
DELETE /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 overallverdict.
Both take a title (up to 120 characters), an optional
requester line saying who's asking, a locale for the page
("en" or "es"), and metadata that comes back
untouched in every webhook and status.
Who answers
audience decides who gets a link.
| kind | You get | Use 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 reminders and email codes. |
"open" |
One url anyone can open |
Sign-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 want | Request |
|---|---|
| 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
| status | Means |
|---|---|
pending |
Nobody has answered yet. |
partial |
Some named people have answered, others haven't. |
complete |
Everyone answered, maxSubmissions was reached, or an approval has
its verdict.
|
expired |
The link ran out of time. |
closed |
You 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 and
setting a new expiry (counted from now) work until the request is complete, expired or
closed.
Reminders. remind emails the link again to named people who
haven't answered and have an email, at most once every 24 hours each.
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.
| Type | Keywords |
|---|---|
string |
format (email, date, time, date-time, tel, uri, duration),
minLength, maxLength, pattern
|
number, integer |
minimum, maximum |
boolean |
const: true makes a consent box that must be ticked |
enum |
A fixed list of choices |
array |
Of 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.
| Field | Schema | Answer |
|---|---|---|
| Date | string, format date | "2026-11-12" |
| Time | string, format time; step = minutes (1–60) | "14:30" |
| Date and time | string, format date-time | "2026-11-12T14:30:00-08:00" |
| Duration | string, format duration | "PT1H30M" |
| Time slots | array of date-time enum, widget slots | the picked times |
| Date range | array of 2 dates, widget daterange | ["2026-11-12", "2026-11-15"] |
| Location | object, widget location | { latitude, longitude, accuracy } |
| Money | number, currency "USD" | 1200 |
| Slider, rating | number or integer; slider needs minimum and maximum | 7 |
| Chips, radio, select | enum (chips also takes an array of enum) | "M" |
| Rank | array of enum, widget rank | every option, in the person's order |
| Yes or no | boolean, widget yesno | true |
| Colour | string, widget color | "#C2410C" |
| Country | string, widget country | "US" (ISO 3166-1) |
| Language | string, widget language | "es-AR" (BCP 47) |
| Time zone | string, 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.
| Key | Values |
|---|---|
accent, background, surface,
text
|
Hex colours |
font, headingFont |
system, Figtree, Geist, Inter, DM Sans, Manrope, Work Sans, Space Grotesk, IBM Plex Sans, Fraunces, Lora, Source Serif 4 |
radius |
sharp, soft, round |
density |
compact, comfortable |
layout |
stacked (all fields on one page) or single (one question at a time) |
logoUrl |
Your logo |
heroText, thankYou |
The 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 field | Default | Notes |
|---|---|---|
reasonrequired |
What you're asking for, up to 300 characters. | |
detail |
Context under the reason, up to 1,000 characters. | |
policy |
"all" |
"any", "all", "majority" or
{ n }: how many approvals it takes.
|
options |
Approve, Decline |
Two to four labels; the first counts as approval. Decisions come back in
snake_case: "Approve with changes" → "approve_with_changes".
|
allowComment |
true | Let people add a comment to their decision. |
showOthers |
false |
"names" or "counts" shows each person where the others
stand.
|
verify |
false |
"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,
maxSubmissionswas reached, an approval has its verdict, or the request expired.
Verify the signature. X-Runitby-Signature is the hex HMAC-SHA256
of the raw body with your account's signing secret, from the
dashboard. X-Runitby-Timestamp is the send
time in Unix seconds.
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.
| Status | When |
|---|---|
400 |
The input or schema isn't valid. |
401 |
Missing or revoked API key. |
404 |
No request with that id on your account. |
409 |
The change isn't allowed any more, e.g. editing after the first answer or reminding a closed request. |
429 |
Over 60 creations a minute, or the plan's monthly requests are used up. |
Plans and limits
| Free | Builder | Pro | |
|---|---|---|---|
| Requests a month | 100 | 1,000 | Unlimited |
| Answers per open link | 25 | 10,000 | Unlimited |
| API keys | 1 | 5 | Unlimited |
| Connected apps (OAuth) | 1 | 5 | Unlimited |
| Answers kept after delivery | 30 days | 90 days | Kept |
Past the monthly limit createRequest returns 429 until the
period resets or you upgrade; links already sent keep working. Without a webhook,
answers are kept from submission instead. See pricing.
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.