Skip to main content
At the end of this page you will have a Requests base, a page where each member of your organization sends requests to your agent and sees only their own, and a trigger that wakes the agent on every new request so it answers by writing to the row.
Bases need ravi.bot@next (3.261009.2 or later): bun add -g ravi.bot@next, or ravi update --next on an existing install. @latest has no ravi bases command, and its pages ship has no --uses.

Concepts

Who can use a data page

A data page reads the base live, as the person viewing it. It works only for signed-in members of your organization who can read the project, on a private (the default) or protected_link route. Both make the visitor sign in to the Console first.
  • Organization owner, admin and developer can open it. viewer cannot unless they are a member of the project, and billing never gets live data, so add teammates as developer or admin. A developer can read every row through the CLI: “each person sees their own” holds on the page, not between developers.
  • Someone in several organizations must switch to yours in the Console before opening the link, or sign-in fails with workspace_selection_required.
  • Anonymous visitors, people who only have a route password, and people outside the organization never get live data (connector_session_required).
ravi pages ship refuses a public route that uses live data. It exits with code 2, uploads nothing, and --json reports error.code: "LIVE_DATA_PUBLIC_ROUTE":
--members-best-effort overrides it, only for pages whose readers are all members already signed in. People outside your organization reach the agent through a channel (Slack, WhatsApp): the agent writes the row and answers in the conversation. For a public view of the numbers, publish a static snapshot (data inside the HTML, no ravi.bases.* in --uses) from a separate project.

Before you start

  • Ravi installed and the daemon running, with an agent you can talk to (main here). See Install Ravi and Make yourself the owner.
  • Logged in to the Console with a default project and an owner, admin or developer role. If ravi whoami does not list the console.bases.* and console.inbox.* scopes, log in again.
  • Run every command on the machine, and as the OS user, that runs the daemon. Row events go to the installation that subscribed.
You can also ask your agent to do each step; it follows the bases and pages skills.

Build it

1

Get the site id

The view must name the project’s Pages host (its site). Check whether the host exists:
If it exists, take the id of the site with "isDefault": true:
If it does not, ship a placeholder. The first ship creates the host, and site.id in its JSON is your site id:
If other routes on this host are data pages, add their actions with --uses on this ship too (see step 5).
2

Create the base

Save requests.json:
Dates and $today use UTC unless you pass --timezone <IANA zone> to bases create. Keep keys stable: views, filters and pages refer to them.
3

Create the view

Save view.json and replace both <SITE_ID>:
  • The filter keeps each viewer to the rows they created. The Console applies it on every call; the page cannot widen it.
  • page_viewer admits anyone who can open any route of this host, not one person. Per-person access is the filter’s job, so project only columns every member may see.
  • The page can write only request. Every new row starts as New (write.set), and reply and status stay with the agent.
4

Write the page

Copy the client. It is the only helper: there is no window.ravi and no browser SDK.
Save requests/index.html and replace <VIEW_ID>:
  • Status values arrive as option ids; the page maps them to names with describe.
  • Every value reaches the DOM through textContent (the el helper). Members type this text: never use innerHTML.
  • The messages in client.js are in Portuguese. Translate the MESSAGES table and the strings in showError in your copy if you need to.
5

Ship it

The JSON has url, site, slug, route: "/requests", visibility: "private", artifactId and uses.
  • Leave --visibility out: the route stays private.
  • --uses lists every action the page calls. If other routes on this host are data pages, list their actions too, on every ship to this host. An action left out fails on the page with connector_not_allowlisted.
  • A CONFLICT error on a re-ship usually means an unread comment from someone else on this route. See the operator bar and comments.
6

Open it as a member

Open the url. The Console asks you to sign in and brings you back to /requests/, with the form and an empty list. Members also see the operator bar at the top of the page.Send a request. It appears with status New.The page session lasts one hour. After that the banner shows a sign-in link, and reloading signs you in again.
7

Subscribe to row events

ravi inbox status must show required scopes ok : yes; if it says no, run ravi login again. Polling is on by default; ravi inbox enable turns it back on if it was disabled.The subscribe confirmation still says “ids and counts only”. The event your trigger receives does include the row values, in payload.row.
8

Create the trigger

  • The filter is the only loop protection. It matches rows created from the page. The agent’s own write arrives as bases.row.updated from the CLI and does not match.
  • The prompt drains the queue. The cooldown drops events that arrive while it runs instead of queueing them, and the inbox delivers events in batches. Handling every New row means a dropped event is picked up by the next run. The cooldown starts only when an event passes the filter.
  • The idempotency key comes from the row and its version, so a repeated event cannot write twice. @@SILENT@@ keeps the agent quiet when the queue is empty.
  • The answer lives in the row. To also get a message in a chat, add --reply-session <session name>; when you ask the agent to create the trigger from that chat, that is already the default.
Everything a member types reaches the agent’s prompt. The filter decides who wakes the agent, not what it does. If main runs with full-access, consider a dedicated agent whose explicit capabilities only cover ravi bases rows (execute:group:bases_rows). See Permissions.
9

Test end to end

Send a request from the page, then:
Within about a minute (the 15 s inbox poll, the agent’s turn, the page’s 30 s refresh) the page shows the reply and status Done. If not, see Debugging.

How the page talks to the base

Nothing is injected into your page except the operator bar. The page calls its own host with fetch:
  • POST /_ravi/connectors/exec with {"id": "<action>", "input": {...}}, same origin, using the session cookie set at sign-in. Success is HTTP 200 with {ok: true, id, revision, output}. Errors carry an HTTP status and {error}, plus fieldErrors on validation_failed and current on version_conflict, and an x-request-id header.
  • GET /_ravi/connectors/catalog?executable=true lists the actions the host allows and whether the viewer has a session.
A page cannot list views, read row history, restore rows, aggregate outside a chart, write in batches, receive pushes, upload files, call an agent or send a message. The agent does those through the CLI and writes results to rows the page reads. Each viewer gets 120 calls per minute on a site (600 per site, 1,200 per organization). Above that the page gets connector_rate_limited, and client.js waits and retries.

Limits worth knowing

  • No push. The page re-reads on a timer, and poll never goes below 30 s.
  • The cooldown drops events. When the last change matters, add a sweep with the same prompt: ravi cron add "Requests sweep" --every 1h --agent main --isolated --message "$MSG".
  • Option ids, not names. select and status values arrive as option ids in row events and on the page, so data.payload.row.values.status == "New" never matches. Read the ids with ravi bases show requests --json. Filters compare text only (==, !=, startsWith, endsWith, includes), and a missing path is false, even with !=.
  • Bulk changes. A request that changes more than 50 rows sends one bases.rows.bulk_changed event, with no rowId and no values.
  • Comments do not wake the agent. For feedback that must reach it, use a base with a form.
  • Thumbnails of data pages show an error: the capture session cannot call actions.
  • Idempotency keys are 8 to 128 characters from A-Za-z0-9._:-, start with a letter or digit, and last 24 hours. Reuse a key only to resend the same input; a changed input, including a new expectedVersion after a conflict, needs a new key. rows.archive takes none. An agent write without --idempotency-key succeeds with a warning, and a retry would write twice.
  • The CLI does not see what a viewer sees. ravi bases views query on a $viewer view returns only your own rows. Check by opening the page.
  • Only ship checks visibility. ravi pages visibility and the operator bar can still make a data route public. Keep data routes private.

Debugging

On the page, client.js adds the x-request-id to the error banner when the response has one; keep it when you ask for support. More in Triggers and Publish pages.