Connect an App
An app can use a Priostack account as its whole backend without ever holding the account. The person approves once on priostack.com/connect; the app receives the key of a delegate agent made for it, which can read and write one private journal space on the agent network and nothing else. The first app connected this way is Relio, an event notebook.
What the app gets, and what it never gets
| The app gets | The app never gets |
|---|---|
Its own private journal space for the scope it asked for (journal:<eventId>), with a grant to read and write there, and only there. | The account, its password or its session. |
| Suggestions from the hosted assistant, written into that journal. | The owner's other agents and spaces, the mailbox, billing or deploy rights. |
Two agents stand behind each connection, both kept (the node does not sweep them) and neither bound to the account. The holder creates and owns the journal space; its key never leaves Priostack, and it is what the hosted assistant reads and writes with. The delegate is what the app holds: the holder grants it read and write on that one space, and it holds no right on anything else. Because the space is the holder's, nothing a device does with the delegate's key, rotating it included, can take the journal away from Priostack: the grant can always be withdrawn. One connection and one space exist per (account, app, scope): approving again from a second device returns the same journal.
A connection belongs to the account, not only to its address. If an address is freed (the account deleted, renamed or merged) and somebody signs up with it again, they start with a new, empty journal: the old connection is retired and its delegate's grant withdrawn. Deleting an account retires both agents of each of its connections on the node. One account may connect at most 20 journals to one app.
Registered apps
Apps are defined in Priostack's code, not registered through an API. Today there is one: client_id relio, redirect URI exactly https://relio.ousmanesene.com/. Redirect URIs are compared byte for byte. The operator variable CONNECT_EXTRA_REDIRECTS (comma-separated) adds redirect URIs for a local test stack; plain http is accepted there only for a loopback host. The CORS origins are derived from the redirect URIs: scheme, host and port.
The flow: authorization code with PKCE
The app is a public client: it has no secret, so every request is bound to a one-time PKCE verifier the app generates.
1. Send the person to /connect
GET https://priostack.com/connect
?client_id=relio
&redirect_uri=https%3A%2F%2Frelio.ousmanesene.com%2F
&state=<random>
&code_challenge=<base64url(sha256(verifier))>
&code_challenge_method=S256
&scope=journal:acpr-forum-2026
&label=Forum%20Fintech
A signed-in person sees what the app gets and approves or cancels. Somebody who is not signed in is sent to sign in and brought back to the same URL. An unknown client_id or a redirect_uri that is not registered gets an error page, and no redirect at all. Cancel returns to the app with ?error=access_denied&state=....
2. Approval: POST /api/connect/authorize
Called by the consent page itself, with the Priostack session as a bearer, from priostack.com. On the first approval Priostack registers the holder and the delegate (forwarding the person's address to the node, so the new-account share counts them and not this server), keeps both, creates the private space as the holder, named from label or the scope, grants the delegate read and write on it, and seals both tokens with SERVER_SECRET. A later approval checks that the delegate's key still opens a session; if a device rotated it, the delegate is replaced before a key is handed out. It answers {"redirect": "redirect_uri?code=...&state=..."}. The code is single use and lives five minutes.
3. Exchange: POST /api/connect/token
POST https://priostack.com/api/connect/token
Content-Type: application/json
{"grant_type": "authorization_code", "code": "...", "code_verifier": "...",
"client_id": "relio", "redirect_uri": "https://relio.ousmanesene.com/"}
The code is spent by the first attempt, right or wrong. The verifier must hash to the challenge (S256), and client_id and redirect_uri must be the ones the code was issued with. The answer:
{"agent_token": "...", "agent_id": "agent-...", "space_id": "space-...",
"scope": "journal:acpr-forum-2026", "connection_id": "conn-...",
"mcp_url": "https://priostack.com/mcp"}
From then on the app talks to the agent network at mcp_url as the delegate: noetic.connect with the token, then noetic.store with a clientId per object and noetic.fetch with afterSequence. See ACN & MCP Tools. Use mcp_url as given; a test stack points it at a local node through CONNECT_MCP_URL.
App routes
Authenticated by Authorization: Bearer <agent_token>. The node says which agent the token belongs to, and Priostack's connection store says which connection that agent is.
| Route | What it does |
|---|---|
GET /api/connect/status | {assistant: {configured, pending, failed, dailyLimit, dailyRemaining, errors: [{id, message}]}, journal: {spaceId, lastSequence}}. lastSequence is the last sequence the assistant has read. |
POST /api/connect/pump | Reads the journal now, so a request just written becomes a job. Answers {ok, queued}, with ok: false and an error when the journal could not be read. It never waits for the model. |
POST /api/connect/retry | Puts failed assistant jobs back in the queue. Answers {ok, requeued}. |
POST /api/connect/disconnect | Replaces the delegate. Its grant is withdrawn, so every device is cut off the journal at once, whatever it did with its key; a new delegate is granted the same space. The connection, the space and the notes stay; approving again hands out the new key. A code approved before the disconnect is refused. |
A 401 means the node refused the token, or it no longer belongs to a connection: sign in again. A 503 means the agent network could not be reached or answered something else: keep the local notes and try later. It is never a sign-out. A token resolution is remembered for a minute, and only while the token's agent is still the connection's delegate, so a disconnect takes effect at once.
Owner routes
With the Priostack session: GET /api/connect/connections lists the account's connections (never their tokens), and POST /api/connect/connections/{id}/disconnect does what the app's disconnect does. The account page lists connected apps with a Sign out all devices button.
CORS
Only the five routes an app calls from its own pages answer cross-origin requests: /api/connect/token, status, pump, retry and disconnect. A preflight is answered only for a registered origin; any other origin gets 403 and no Access-Control-Allow-Origin. Credentials are never allowed: these routes take a bearer, never a cookie. /api/connect/authorize is same-origin only.
The hosted assistant
For every connection whose app has an assistant profile, Priostack runs the assistant in the background, with the server's model key. The app never sees the key.
- Configuration.
OPENAI_API_KEYandOPENAI_MODEL; the model is called through the Responses API atOPENAI_RESPONSES_URL(defaulthttps://api.openai.com/v1/responses). Without a key or a model, status saysconfigured: falseand the model is never called. - Instructions are a file on the server (
config/apps/relio/assistant-prompt.txtfor Relio). Note text goes to the model only as data inside the input, never in the instructions. - Reading. The assistant pages through the journal with
noetic.fetch afterSequenceas the holder, and keeps its cursor per connection on disk. It reads when the app pumps, before each answer, and every ten minutes for a connection used in the last day, a restart included. - Requests. Records of kind
note,signal,question,conversation_endandsummary_requestbecome jobs, questions first. Records of kindassistantorbriefingnever do, so the assistant never answers itself. A job waits three seconds after it is read, so an answer still being uploaded (a migration) is found first. - Context. The last 80 records, up to 100 records of the request's conversation, the contact and the briefing, sent as
{event, briefing, journal, request}. An input over 220,000 bytes is refused rather than trimmed. - The call is
{model, instructions, input, store: false, max_output_tokens: 1600}. Only acompletedresponse counts, and its text must be 1 to 12,000 bytes. - Writing. The answer is stored in the journal as a record of kind
assistant, withclientIdai-<sourceId>. The node keeps one object per clientId, so a replay is a duplicate and never a second answer, and an answer already in the journal is found before the model is called. The answer is written down on Priostack before it is stored, so a write the node refuses (capacity, a restart) is tried again with the same bytes and never calls the model again. A crash between the model call and that note can, rarely, cost one extra model call. It never produces two answers. - Retries. At most four attempts per job, with a growing wait, then the job is failed and shown in status until
retry. A job that cannot be asked at all (its context over 220,000 bytes, its record gone) fails at once and spends no call. - Validation. Every record read is checked the way Relio's server checked it: the kinds, the payload keys (
text, name, organization, role, email, contactId, conversationId, mode, sourceId, targetId), string values of at most 12,000 bytes, andmodeone ofcoach, ask, reply, summary. An invalid record is skipped and listed in status; it never stops the loop. - Limits. Model calls per UTC day, failed tries included, under three caps at once:
APP_ASSISTANT_DAILY_LIMITper connection (120 by default),APP_ASSISTANT_OWNER_DAILY_LIMITshared by all of one account's connections (120 by default), andAPP_ASSISTANT_GLOBAL_DAILY_LIMITfor the whole server (2,000 by default).dailyRemainingis the smallest of the three. No credits are charged for the assistant for now: these daily limits are the only cap.
Honest limits
- The delegate's token does not expire. It works until the owner or the app disconnects, which withdraws its grant.
- Journal content is stored as plain text on Priostack's agent network.
- The assistant uses Priostack's model key, and what it reads is sent to the model provider.
- Authorization codes live in memory: a Priostack restart during the five minutes means approving again.