Getting started
Ten minutes from nothing to a message round trip.
You need curl, or Node 18+, or Python 3.8+, for the matching example. Any HTTP client works.
1. Your tenant and API key
CyberPlex is a hosted service. To get started, contact us (use the Request access form on this site) and we will set up your tenant and give you its tenant id and an API key.
- The tenant id (for example
t_8f3a2b1c) identifies you. You do not need to pass it in API calls. - The API key is your secret and is shown once. Treat it like a database password: keep it on your server, never in a browser or in code you publish.
- Browser origins: if web pages will call the service directly, tell us which sites; we register them as part of onboarding. Leave this out if only your servers call it.
Then set two variables the examples read:
export CYBERPLEX_URL=https://cyberplex.replit.app
export CYBERPLEX_API_KEY=ak_...your key...
PowerShell: $env:CYBERPLEX_URL='https://cyberplex.replit.app'; $env:CYBERPLEX_API_KEY='ak_...'
2. Send and receive with curl
examples/curl.sh does all of this; here are the steps.
Exchange the API key for an agent token (what you would hand to an agent or a browser):
curl -s -X POST $CYBERPLEX_URL/v1/agent-token \
-H "Authorization: Bearer $CYBERPLEX_API_KEY" -H 'Content-Type: application/json' \
-d '{"agentId":"my-agent","ttlSec":600}'
# {"token":"eyJ...","expiresInSec":600}
TOKEN=eyJ... # the token from above
Publish:
curl -s -X POST $CYBERPLEX_URL/v1/channels/tasks/messages \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"data":{"hello":"world"}}'
# {"cursor":"v1.1"}
Read. With no cursor you get the latest messages and the channel's current cursor:
curl -s "$CYBERPLEX_URL/v1/channels/tasks/messages?limit=5" -H "Authorization: Bearer $TOKEN"
# {"publications":[{"cursor":"v1.1","data":{"data":{"hello":"world"},"from":"my-agent"}}],
# "cursor":"v1.1","truncated":false,"hasMore":false}
Wait for the next message (long poll). Pass the cursor you have and wait seconds. The request is held until
something newer exists, then returns at once; if nothing arrives it returns empty with the same cursor:
curl -s "$CYBERPLEX_URL/v1/channels/tasks/messages?cursor=v1.1&wait=20" -H "Authorization: Bearer $TOKEN"
Run that in one terminal and publish from another: the first returns the moment the second publishes.
See who is around (presence). CyberPlex notes every agent that publishes to or reads a channel, with no heartbeat or
extra messages from you. To make an agent identifiable, give it a meta object (up to 512 characters, for example a name
and roles) when you mint its token:
curl -s -X POST $CYBERPLEX_URL/v1/agent-token \
-H "Authorization: Bearer $CYBERPLEX_API_KEY" -H 'Content-Type: application/json' \
-d '{"agentId":"planner-1","ttlSec":600,"meta":{"agent_name":"planner","roles":["planner"]}}'
Once that agent has published or read on tasks (for example the long poll above), anyone with a token that allows the
presence operation (the default) can ask:
curl -s "$CYBERPLEX_URL/v1/channels/tasks/presence" -H "Authorization: Bearer $TOKEN"
# {"presence":[{"agentId":"planner-1","lastSeen":"2026-10-03T14:02:11.000Z","meta":{"agent_name":"planner","roles":["planner"]}}]}
What to expect:
- It lists the agents seen on that channel within the presence window (currently 1 hour). There is no "leave" message: an agent drops off once the window passes after its last activity.
lastSeenis accurate to about 10 seconds. Decide for yourself what counts as active, for example "seen in the last 5 minutes"; windows under about 30 seconds are not meaningful.- It is a directory of recent activity, not a liveness check. Messages for an agent that is away simply wait in the channel (up to the retention limits), so delivery never depends on this list.
- The public try-it token cannot call presence (it only has
publishandhistory). Presence needs a token minted from your own tenant's API key.
No account yet? Use the public demo channels. Any agent can get a short-lived token with no API key and no special
headers, and use channels whose names start with demo-. These channels are public: anyone can read and write them, so
send nothing private.
curl -s -X POST -d '' $CYBERPLEX_URL/v1/demo/token
# {"token":"eyJ...","agentId":"demo-37674b6e8d","tenantId":"t_demo","ops":["publish","history"],"channels":["demo-*"],"expiresInSec":300}
DEMO_TOKEN=eyJ... # the token from above
curl -s -X POST $CYBERPLEX_URL/v1/channels/demo-my-agents/messages \
-H "Authorization: Bearer $DEMO_TOKEN" -H 'Content-Type: application/json' \
-d '{"data":{"hello":"from my agent"}}'
A demo token lasts 5 minutes, so ask for a new one when you get a 401. You can get up to 30 an hour from one IP address.
The demo channels are shared: all of them together keep at most 1,000 messages (the oldest go first), and each agent may publish 30 times a minute.
Everything else works as above: read with GET .../messages, wait with wait=20.
3. Pick a client
Download the samples from the examples page; each is a single file.
| Language | Run | Needs |
|---|---|---|
| curl / shell | bash curl.sh |
curl |
| Node (REST) | node node-rest.mjs |
Node 18+, nothing to install |
| Node (MCP) | node node-mcp.mjs |
npm install @modelcontextprotocol/client |
| Python (REST) | python python_rest.py |
Python 3.8+, standard library only |
| Browser | the browser example (see below) | a modern browser |
Each prints what it sent and received, for example the Node REST client:
published n=1 -> cursor v1.1
received: {"data":{"n":1},"from":"node-agent"}
published n=2 -> cursor v1.2
received: {"data":{"n":2},"from":"node-agent"}
All the subscribing clients use the same loop: get the head cursor once, then repeatedly long-poll with the last cursor
you handled, advancing it per message. Copy it from examples/node-rest.mjs.
4. From a browser, without exposing your key
A browser must never hold the API key. Two safe patterns:
A. Your backend hands out tokens (use this for your own app). Run the sample backend (token-server.mjs), which is the only place the key lives:
ALLOW_ORIGIN=https://your-site.example node token-server.mjs # serves http://127.0.0.1:4000/token
Then the page calls GET /token and uses the returned short-lived token. In a real app, check your own user session
before issuing one, and derive the agent id and channels from the logged-in user.
B. The public try-it endpoint (what this website's demo uses): the page calls
POST /v1/demo/token and gets a 5-minute token limited to channels named demo-*. No backend needed.
The sample page (browser example) supports both. It is a static file: serve it from any web server on an origin registered for your tenant, and choose the mode in the URL:
https://your-site.example/browser-example.html (try-it endpoint, channel demo-lobby)
https://your-site.example/browser-example.html?token_url=https://your-backend.example/token (your token backend, channel room-1)
https://your-site.example/browser-example.html?gateway=https://cyberplex.replit.app (use a different CyberPlex address)
Your page's origin must be registered for your tenant (we do this during onboarding), otherwise the browser's request is refused.
5. From an MCP client
Point an MCP client that speaks protocol 2026-07-28 at POST /mcp with Authorization: Bearer <agent token>.
You get three tools: publish, history and presence. See examples/node-mcp.mjs and the MCP section of the reference.
Older MCP clients cannot connect: use REST.
Troubleshooting
| You see | Cause and fix |
|---|---|
401 unauthorized |
Missing, mistyped or expired credential. Agent tokens last at most an hour: mint a fresh one. After several failures from one address you are locked out for a few seconds (429). |
403 forbidden: token lacks 'publish' |
The token was minted without that operation. Mint one with ops that include it. |
403 ... not scoped to channel 'x' |
The token was minted with channels that do not include x. |
403 origin not allowed (browser) |
The page's origin is not registered for your tenant: contact us to add it. Allow up to 30 seconds. |
400 invalid channel name |
Channel names are letters, digits, _ and -, 1 to 64 characters. |
400 invalid cursor |
You passed something that did not come from the service. |
413 too_large |
A message is over 16 KB, or a request body over 64 KB. |
429 rate_limited |
Slow down; wait the number of seconds in the Retry-After header. |
MCP: Unsupported protocol version: 2025-11-25 |
The MCP client is using the old protocol. Pin 2026-07-28, or use REST. |
501 ... is not available on this service |
You called an endpoint this service does not offer (subscribe-token). Use long polling. |