Connect an agent
An agent opens its own window in a signed-in identity and works there the way a person would: it reads the page and presses buttons. It never gets the cookies or the password, and it asks a person before anything that can't be undone.
Before you start
Agents come with Starter, Pro and Business. The Free plan has none. Only admins add agents and change what they may do.
- Sign in to the website once, yourself. Add the identity and sign in to the website in your own window. Agents work in identities that are already signed in; they never sign in themselves.
- Add the agent. In Agents, choose New agent, name it, tick the identities it can drive and choose Add agent.
- Copy its key. It's shown once. Put it in your agent's settings or wherever it keeps secrets.
- Set its limits. On the agent's page: Can drive or Read only in each identity, what it asks a person about first and who it asks, its browser hours a month and the Drive files it may send.
Keys
A key is jp_live_ followed by 28 letters and numbers. It's shown once, when it's made. After that Joinpane shows only its start, like jp_live_7Hq2…, so you can tell keys apart. Anyone with the key can act as the agent in the identities it may use.
- Send it as a bearer token on every request, over MCP and the HTTP API alike:
Authorization: Bearer jp_live_… - It expires in 90 days, unless the admin who made it chose Never. An expired key gets
401 unauthenticated. - Replace a key without stopping a task. On the agent's page, choose New key. The old key turns off 24 hours later, which gives you a day to switch it over; clear Turn off the old key in 24 hours to keep both.
- Revoke a key to turn it off at once. A task using it stops at its next step.
- Deleting an agent turns off all its keys, closes its windows and cancels the questions it was waiting on.
Connect over MCP
Joinpane is an MCP server at https://mcp.joinpane.com/<workspace>, where <workspace> is your workspace's address, the part after joinpane.com/. Agents in Joinpane shows it with yours filled in. The server speaks Streamable HTTP and keeps no session: every request carries the key.
{
"mcpServers": {
"joinpane": {
"url": "https://mcp.joinpane.com/northlight",
"headers": { "Authorization": "Bearer YOUR_AGENT_KEY" }
}
}
}Clients that ask for a transport want Streamable HTTP, sometimes written "type": "http". In Claude Code, one command adds the server:
claude mcp add --transport http joinpane https://mcp.joinpane.com/northlight \
--header "Authorization: Bearer YOUR_AGENT_KEY"- A key works only at its own workspace's address. At another, the server answers
403and names the right one. - It supports MCP 2025-11-25, 2025-06-18 and 2025-03-26. Answers are JSON. The server has nothing to send unasked, so a GET to its address answers
405. - When it connects, your agent gets instructions on how to work: list identities, open a window, read the page, act by ref, and wait for a person when a press asks first.
- A tool that fails says why in its result, marked as an error, in a sentence your agent can act on: “All 5 windows in Studio uploads are open. Try again when one closes.”
MCP tools
The page's buttons, links and fields each carry a ref, like e12, that the tools act on. Use refs from the latest answer: every tool that reads the page hands out new ones.
- list_identities
- The identities this agent may use. Returns
identities: each one'sname,website,accessandstatus.No arguments. - open_window
- Opens the agent's own window in an identity, already signed in, or gives back the one it has there. Returns
window: itsid,identity,url,titleandaccess.- identitystring, requiredThe identity's name.
- anotherbooleanOpen one more window there, up to its Windows at once. Default
false.
- list_windows
- The agent's open windows. Returns
windows, asopen_windowdescribes each.No arguments. - read_page
- Reads the page in the window's current tab. Returns text: the page's title and address, its tabs, its elements by ref, then its text. And the screenshot, as an image.
- windowstring, requiredThe window id from
open_window. - screenshotbooleanInclude a screenshot. Default
true.
- windowstring, requiredThe window id from
- click
- Presses an element. Presses that need a person's yes ask first and wait up to 20 seconds. Returns the page, as for
read_page. OrStatus: waitingwith the question and its id, orStatus: declined.- windowstring, requiredThe window id from
open_window. - refstring, requiredAn element's ref, like
e12. - approvalstringAn approved question's id.
- windowstring, requiredThe window id from
- type
- Types into a field. Returns the page, without a screenshot.
- windowstring, requiredThe window id from
open_window. - refstring, requiredThe field's ref.
- textstring, requiredWhat to type.
- clearbooleanReplace the field's text. Default
true. - submitbooleanPress Enter afterwards. Default
false. Asks a person first when the form's button would. - approvalstringAn approved question's id.
- windowstring, requiredThe window id from
- press_key
- Presses one key. Returns the page, without a screenshot.
- windowstring, requiredThe window id from
open_window. - keystring, requiredA key name, like
EnterorEscape.EnterandSpaceask a person first when a click on what's focused would. - approvalstringAn approved question's id.
- windowstring, requiredThe window id from
- scroll
- Scrolls by most of a screen. Returns the page, as for
read_page.- windowstring, requiredThe window id from
open_window. - directionstring
downorup. Defaultdown.
- windowstring, requiredThe window id from
- go_back
- Goes back a page in the current tab. Returns the page, as for
read_page.- windowstring, requiredThe window id from
open_window.
- windowstring, requiredThe window id from
- go_forward
- Goes forward a page in the current tab, after
go_back. Returns the page, as forread_page.- windowstring, requiredThe window id from
open_window.
- windowstring, requiredThe window id from
- switch_tab
- Makes another tab the current one. Returns the page, as for
read_page.- windowstring, requiredThe window id from
open_window. - indexinteger, requiredThe tab's index from
read_page.
- windowstring, requiredThe window id from
- list_drive_files
- The Drive files the agent may send to websites. Returns
files: each one'sname,sizein bytes andmimetype.- identitystringOnly files from this identity, by name or id.
- upload_file
- Sends Drive files to the website's file picker. Returns the page, without a screenshot.
- windowstring, requiredThe window id from
open_window. - from_drivestring or string[], requiredFile names from
list_drive_files. - refstringThe file field or upload button. Default: the page's first file field.
- windowstring, requiredThe window id from
- ask_person
- Asks the people this agent asks, with a screenshot of the window. The first answer counts. Waits up to 20 seconds. Returns
question: itsid,status,answerand who answered.- windowstring, requiredThe window id from
open_window. - questionstring, requiredWhat to ask.
- optionsstring[]The answers to offer, 2 to 6. Default
["Yes", "No"]. - detailstringWhat you'll do with the answer.
- windowstring, requiredThe window id from
- wait_for_answer
- Waits up to 20 seconds for an answer. Returns
question, asask_persondescribes it.- question_idstring, requiredThe question's id.
- start_task
- Names what the agent is doing, so people watching can follow its steps and see the ones to come. Returns
task: itsid,titleandplan.- titlestring, requiredWhat you're doing.
- identitystringThe identity it's for.
- planstring[]The steps you mean to take, up to 20 of 120 characters.
- update_plan
- Replaces the plan of the current task. Returns
task: itsid,titleandplan.- stepsstring[], requiredWhat's left to do, not what's done. An empty list clears the plan.
- finish_task
- Ends the current task. Returns
task: itsid,titleandstatus.- summarystringHow it went.
- stoppedbooleanEnd it as stopped rather than done.
- close_window
- Closes the window, freeing one of the identity's 5. Returns
Closed.- windowstring, requiredThe window id from
open_window.
- windowstring, requiredThe window id from
How a page reads
read_page and every tool that acts answer with the page as text, ready for a model: its title and address, its tabs when there's more than one, its elements by ref and then its words. A screenshot comes as an image where the tool takes one.
Page: Episode 13 details https://studio.example/content/ep13/details Elements: e1 link "Content" e2 link "Uploads" e3 textbox "Title" type=text value="Episode 13" e4 textbox "Description" e5 button "Upload thumbnail" e6 button "Save draft" e7 button "Publish" Text: Content Uploads Episode 13 details Title Description Thumbnail Visibility Draft Save draft Publish
When a press is waiting for a person, the answer says so, with the question's id:
Status: waiting Question apr_01k7f4a6s2d8g3h5j9k1m7p0qr (pending): Uploader wants to press "Publish" on studio.example Asked: Mei Lin, Rin Kato Call wait_for_answer with question_id apr_01k7f4a6s2d8g3h5j9k1m7p0qr.
Use the HTTP API
For your own code, the same abilities are endpoints at https://api.joinpane.com/v1, with the same key. Send JSON with Content-Type: application/json; answers are JSON.
# Open a window in Studio uploads curl https://api.joinpane.com/v1/windows \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"identity":"Studio uploads"}' # Read the page in it, with the window's id from the answer curl https://api.joinpane.com/v1/windows/$WINDOW/page \ -H "Authorization: Bearer $JOINPANE_KEY"
Every endpoint, with its request, its response and the errors to expect, is in the HTTP API reference.
What agents can and can't do
An agent can
- Open its own window in the identities an admin picked, already signed in.
- Read a page as text, with the elements it can press or fill in and a screenshot.
- Press, type, scroll, go back and forward and switch tabs, with real clicks and keys.
- Send files from Drive to a website, from the files its Drive setting allows.
- Ask people a question and wait for their answer.
An agent can't
- Get cookies, session tokens, stored data or saved passwords. No tool runs a script on a page.
- Read or type into password fields. A password's value never appears in a read, and typing into one stops with
password, unless an admin allows agents to in team policies. - Open or act in anyone else's window.
- Open pages an admin keeps for admins only, or addresses the identity's rules don't allow.
- Get around a website's checks. When a page asks to prove it's a person, Joinpane tells the people in the identity, and the agent should stop and ask a person. Joinpane never solves these.
Read only
With Read only in an identity, an agent can read pages, go to an address, scroll, go back and forward and switch tabs. Pressing, typing, keys and sending files answer 403 forbidden.
When a person takes over
Anyone with access can watch an agent's window and take over. While a person drives it, the agent's steps answer 409 conflict with who's driving, and it carries on once they hand over. Everything an agent does goes in the audit log under its own name and key, and its steps show in Agents as it works.
Asking a person
Some presses can't be undone. Before them, an agent stops and asks the people its settings name, all at once. They see what the agent sees, and the first answer counts.
- Always
- Publishing and paying: publish, post, go live, schedule, pay, buy, check out, subscribe and the like. Locked on for every agent by workspace policy.
- If an admin turns it on
- Changing settings on the website's account, privacy and billing pages, deleting, and sending messages: replies, comments and direct messages.
Joinpane tells from the button's name, and for settings from the page it's on. When in doubt, it asks. By default the person who added the agent is asked; an admin can choose others in Agents.
- The press stops and asks. It waits up to 20 seconds for an answer. Approved in time, the press goes ahead and you get the page.
- Still waiting, it says so. You get
"status": "waiting"and the question with its id. - Wait for the answer. Call
wait_for_answer, orGET /v1/questions/{question}?wait=25, until it's answered. - Press again with the approval. Pass the question's id as
approval. If the answer was Decline, leave it and move on: a press nobody approved answers403.
If nobody answers in 30 minutes, the agent keeps waiting or skips that task, as its settings say. Skipping leaves the website as it is. To ask something of its own, an agent uses ask_person, or POST /v1/questions, with the answers it offers.
Tasks and plans
A task names what an agent is doing, like “Publish episode 13”. Its steps show in Agents and beside its window as it works, so the people watching can follow along. Opening a window starts one, like “Working in Studio uploads”, when none is running there.
- Start one with its plan:
start_task, or POST /v1/tasks, with a title and, if you like, the steps you mean to take. People watching see the plan as the steps to come, after the steps taken. - Keep the plan current:
update_plan, or PUT /v1/tasks/current/plan, replaces it. Send what's left, not what's done; an empty list clears it. - A plan is up to 20 steps of 120 characters each. Longer steps are shortened and empty ones dropped.
- Finish the task with
finish_task, or POST /v1/tasks/current/finish, as done or stopped, with a summary. A finished task shows no plan. Starting a new task in the same identity finishes the one before as done.
Windows, hours and pauses
- Windows count toward the identity's 5, people and agents together. When all are open, opening one answers
409 seats_full. An agent has one window in each identity: opening another where it has one gives that one back. - Close the window when the task is done. It frees a place for someone else, and an agent's browser hours count the time its windows are open.
- An admin can set a monthly budget of browser hours for each agent, counted in the workspace's time zone. At the budget the agent pauses: opening a window and every step answer
409 conflict, and the people it asks are told. A person can raise the budget or resume it in Agents. - Hours come from your plan: 100 a month on Starter and 200 on Pro, shared by people and agents, then $0.05 an hour. See Pricing.
- A paused agent waits. Paused by a person or at its budget, it can't open windows or take steps, which answer
409 conflict, until someone resumes it. - There's no rate limit per request. Send one step at a time to a window and wait for its answer: a step waits for the page to settle and gives up after 45 seconds with
503 node_unavailable. - A window that's closed answers
404. Open one again and carry on.
A worked example
Uploader publishes episode 13 on studio.example. It opens a window in Studio uploads, reads the page, clicks into the episode, types its description, asks a person before publishing, and finishes. First with curl, then over MCP.
With curl
- Keep the key in your shell.The commands below read it from
JOINPANE_KEY.export JOINPANE_KEY=jp_live_… - Name the task, with its plan.People watching see its steps so far and the ones to come.
curl https://api.joinpane.com/v1/tasks \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Publish episode 13","identity":"Studio uploads","plan":["Open episode 13","Add its description","Ask before publishing","Publish it"]}' - Open a window in the identity.The answer's
window.idis the window,win_01k7f3q2m9x4c8v6t1b5n0r2hd. Keep it.curl https://api.joinpane.com/v1/windows \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"identity":"Studio uploads"}' export WINDOW=win_01k7f3q2m9x4c8v6t1b5n0r2hd - Read the page.Among its elements is
{"ref": "e5", "role": "link", "name": "Episode 13"}.curl "https://api.joinpane.com/v1/windows/$WINDOW/page?screenshot=false" \ -H "Authorization: Bearer $JOINPANE_KEY"
- Click the episode.The answer is its details page, where the description field is
e4and Publish ise7.curl https://api.joinpane.com/v1/windows/$WINDOW/click \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"ref":"e5"}' - Type the description, and say what's left.Typing replaces what's in the field unless you send
clear: false. The new plan has only the steps still to come.curl https://api.joinpane.com/v1/windows/$WINDOW/type \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"ref":"e4","text":"Our spring live special, in full."}' curl -X PUT https://api.joinpane.com/v1/tasks/current/plan \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"steps":["Ask before publishing","Publish it"]}' - Press Publish. It asks a person first.Publishing always asks. Nobody answered within 20 seconds, so the answer is
"status": "waiting"with the question. Keep its id.curl https://api.joinpane.com/v1/windows/$WINDOW/click \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"ref":"e7"}' export QUESTION=apr_01k7f4a6s2d8g3h5j9k1m7p0qr - Wait for the answer.Ask again until
statusisanswered. Here Mei chose Approve.curl "https://api.joinpane.com/v1/questions/$QUESTION?wait=25" \ -H "Authorization: Bearer $JOINPANE_KEY"
- Press Publish again, with the approval.The press goes through and the answer is the published page.
curl https://api.joinpane.com/v1/windows/$WINDOW/click \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d "{\"ref\":\"e7\",\"approval\":\"$QUESTION\"}" - Finish the task and close the window.Closing frees one of the identity's windows and stops the agent's hours.
curl https://api.joinpane.com/v1/tasks/current/finish \ -H "Authorization: Bearer $JOINPANE_KEY" \ -H "Content-Type: application/json" \ -d '{"summary":"Published episode 13."}' curl -X DELETE https://api.joinpane.com/v1/windows/$WINDOW \ -H "Authorization: Bearer $JOINPANE_KEY"
Over MCP
Add Joinpane to your agent's MCP settings, as in Connect over MCP, and give it the task in your own words: “In Studio uploads, add the description ‘Our spring live special, in full.’ to episode 13 and publish it.” These are the calls it makes:
start_task(title: "Publish episode 13", identity: "Studio uploads", plan: ["Open episode 13", "Add its description", "Ask before publishing", "Publish it"]) open_window(identity: "Studio uploads") # The window: win_01k7f3q2m9x4c8v6t1b5n0r2hd read_page(window: "win_01k7…r2hd") click(window: "win_01k7…r2hd", ref: "e5") type(window: "win_01k7…r2hd", ref: "e4", text: "Our spring live special, in full.") update_plan(steps: ["Ask before publishing", "Publish it"]) click(window: "win_01k7…r2hd", ref: "e7") # Status: waiting. Question apr_01k7f4a6s2d8g3h5j9k1m7p0qr went to Mei and Rin. wait_for_answer(question_id: "apr_01k7…p0qr") # Answer: Approve (Mei Lin) click(window: "win_01k7…r2hd", ref: "e7", approval: "apr_01k7…p0qr") finish_task(summary: "Published episode 13.") close_window(window: "win_01k7…r2hd")
The tools return what the endpoints above do, as text and an image. Its steps show live in Agents, where Mei and Rin can watch and take over at any point.