SingulaComp as a Backend
Start and manage SingulaComp sessions from your backend with explicit connector, model, context, and secret scope.
Use a SingulaComp API key to start sessions from your server. Each session has one SingulaComp owner, one project, and one cost record.
Your application owns its customer identifiers and metadata. Store the
relationship between your customer and the returned session_id in your
application database.
1. Get an API key
Create a personal access token (singulacomp_pat_…) in your own settings, at
Settings → API keys (/settings/tokens), or a service-account credential
(singulacomp_sa_…) at Account → Tokens. Both authenticate a programmatic
session-create request. The API derives origin: "backend" from the credential
type.
export SINGULACOMP_API_URL="https://your-singulacomp-deployment.com/v1"
export SINGULACOMP_API_KEY="singulacomp_pat_…"
export SINGULACOMP_PROJECT_ID="…"Use a service account when you need an independently managed principal. A
service account is the service_account principal type. It has no membership,
so it holds only the roles assigned to it directly. Assign it a project role
before use — see Accounts & access.
2. Start a session
Create with HTTP
curl -X POST "$SINGULACOMP_API_URL/projects/$SINGULACOMP_PROJECT_ID/sessions" \
-H "Authorization: Bearer $SINGULACOMP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"agent_name": "support",
"opencode_model": "singulacomp/glm-5.3-flash",
"runtime_context": { "ticket_id": "ticket-123" },
"connector_bindings": {
"gmail": { "connection_id": "<connection-id>" }
},
"secrets": ["STRIPE_KEY"]
}'Create with the SDK
import { createScopedSingulaComp } from '@singulacomp/sdk/server';
const singulacomp = createScopedSingulaComp({
backendUrl: process.env.SINGULACOMP_API_URL!,
getToken: async () => process.env.SINGULACOMP_API_KEY!,
});
const session = await singulacomp.project(projectId).sessions.create({
agent_name: 'support',
opencode_model: 'singulacomp/glm-5.3-flash',
runtime_context: { ticket_id: 'ticket-123' },
connector_bindings: {
gmail: { connection_id: connectionId },
},
secrets: ['STRIPE_KEY'],
});Use createScopedSingulaComp when one server process handles concurrent requests.
Each client keeps its token and runtime state request-scoped.
Store the session_id returned by either create call:
export SESSION_ID="<session-id>"Session-create fields
| Field | Contract |
|---|---|
agent_name | Selects a declared OpenCode agent. |
opencode_model | Selects the initial OpenCode model. An unavailable model returns 400 INVALID_SESSION_MODEL. |
runtime_context | Stores non-secret scalar context. The API rejects credential-like keys, more than 64 entries, or more than 16 KiB. |
connector_bindings | Maps a connector slug to one strategy-compatible connection_id. The credential stays outside the sandbox. |
inherit_unbound | Keeps strategy-based default resolution for connectors omitted from an explicit binding map. The default is false. |
secrets | Narrows the selected agent's project-secret grant. An empty list delivers no project secrets. Only backend-origin callers can set it. |
require_connectors | Adds mandatory connectors for this create request. Missing connections return 409 CONNECTOR_CONNECTION_REQUIRED, and unconfigured slugs return 409 REQUIRED_CONNECTOR_CONNECTION_UNAVAILABLE, before the session row is inserted and before sandbox startup. |
3. Configure connectors and connections
A connector defines the tool surface. It contains a project-unique slug, display name, provider app, authorization strategy, and policies.
A connection stores one connected account or credential for that connector. Every connection inherits the connector's policies.
The authorization strategy has two values:
projectaccepts active project connections.useraccepts only an active connection owned by the acting project member.
A service account is a principal, but it is not a person, so it cannot use a
member's user connection. Use project connectors for service-account
sessions.
A personal access token can use an eligible user connection owned by the
token's member.
connectors:
- slug: gmail-read
name: Gmail read only
provider: pipedream
app: gmail
authorization_strategy: project
policies:
- match: search_email
action: always_run
agents:
support:
connectors: [gmail-read]
connectors_required: [gmail-read]The SDK exposes connections under project.connectors.connections:
const connection = await singulacomp.project(projectId).connectors.connections.reconcile({
connector_alias: 'gmail-read',
owner_type: 'project',
label: 'Support inbox',
});
await singulacomp
.project(projectId)
.connectors.connections.updateCredential(connection.connection_id, {
value: credential,
kind: 'secret',
});
await singulacomp.project(projectId).connectors.connections.activate(connection.connection_id);For a Pipedream OAuth connection, call pipedreamConnect() and
pipedreamFinalize(). Do not place its provider token in
updateCredential().
The connection object and new session binding input use connection_id.
authorization_id remains a deprecated SDK input alias.
4. Read and replace session scope
The session scope is authoritative server state. secrets_allowlist contains
the session's stored narrowing. A null value means the agent grant applies.
connector_bindings contains the materialized connection
selection.
curl -sS \
"$SINGULACOMP_API_URL/projects/$SINGULACOMP_PROJECT_ID/sessions/$SESSION_ID/scope" \
-H "Authorization: Bearer $SINGULACOMP_API_KEY"const scope = await singulacomp.session(projectId, sessionId).scope();Replace scope with PUT or rescope():
const nextScope = await singulacomp.session(projectId, sessionId).rescope({
secrets: ['STRIPE_KEY'],
connector_bindings: {
'gmail-read': { connection_id: connection.connection_id },
},
});Each supplied field uses set semantics. The new value replaces the complete previous value. Omit a field to leave it unchanged.
Connector changes apply to the next tool call. Secret removal stops future delivery. It cannot remove a value from an existing model context or process. Rotate the secret when prior disclosure matters.
5. Read session costs
The session-cost API combines finalized LLM cost and billed sandbox compute cost. Every session appears in the list, including sessions with zero cost.
curl -sS \
"$SINGULACOMP_API_URL/usage/session-costs?project_id=$SINGULACOMP_PROJECT_ID&limit=25&offset=0" \
-H "Authorization: Bearer $SINGULACOMP_API_KEY"
curl -sS \
"$SINGULACOMP_API_URL/usage/session-costs/$SESSION_ID?project_id=$SINGULACOMP_PROJECT_ID" \
-H "Authorization: Bearer $SINGULACOMP_API_KEY"The list returns session, project, owner, LLM, compute, total, request, token,
model, and compute-duration fields. It also returns reconciliation for
account usage that has no session.
The detail response adds:
model_usage, grouped by provider and modelledger_entries, with discriminatedllmandcomputerows
Use the SDK for typed reads:
const page = await singulacomp.billing.sessionCosts.list({
accountId,
projectId,
limit: 25,
offset: 0,
});
const detail = await singulacomp.billing.sessionCosts.get(sessionId, {
accountId,
projectId,
});
const sameDetail = await singulacomp.session(projectId, sessionId).cost();session.cost() does not start the session runtime.
6. Stream the answer
Await runtime readiness before using the OpenCode REST methods:
const handle = singulacomp.session(projectId, session.session_id);
await handle.ensureReady();
const stream = await handle.stream({
onEvent: (event) => {
// Render or persist the event.
},
});
await handle.send('Summarize the support queue.');Use useSession(projectId, sessionId) for React hosts. It owns startup,
readiness, the live event stream, and message synchronization.
Idempotent retries
Generate one Idempotency-Key for each logical session-create operation. Reuse
that key only when the request body is identical.
A replay with the same key and body returns the same session. A replay with a
different secret allowlist, connector binding map, or runtime context returns
409.
Security rules
- The API derives session origin from the credential. The request body cannot select it.
- Connector credentials resolve server-side for each tool call.
- A connection must match its connector's authorization strategy.
- Connector policies apply to every connection under that connector.
- Project guardrails apply above connector-connection policies.
- Secret scope can narrow an agent grant. It cannot widen one.
- A session can only do what the role verdict and the agent's manifest grant both allow. Neither one widens the other. See One vocabulary, two bindings.
- Session scope replacement cannot select a connection owned by another member.
- Store application customer metadata outside SingulaComp.
Common errors
| Status | Code | Meaning |
|---|---|---|
400 | INVALID_SESSION_MODEL | The selected model is not available to the account. |
400 | INVALID_SESSION_CONNECTOR_BINDINGS | The binding map is malformed. |
400 | INVALID_SESSION_RUNTIME_CONTEXT | Runtime context violates its shape, key, entry, or size limits. |
403 | origin_override_forbidden | A non-backend caller supplied a secret allowlist. |
403 | CONNECTOR_NOT_ASSIGNED | The selected agent is not granted the connector. |
404 create / 403 rescope | CONNECTOR_CONNECTION_NOT_FOUND | The connection does not exist in this project or violates the connector's authorization strategy. |
404 | SECRET_IDENTIFIER_NOT_FOUND | The secret allowlist contains an unknown project-secret identifier. |
409 | CONNECTOR_CONNECTION_REQUIRED | A mandatory connector has no valid active connection. Every failing connector is listed in connector_connections. |
409 | REQUIRED_CONNECTOR_CONNECTION_UNAVAILABLE | A required slug has no configured connector at all. Every failing alias is listed in connectors. |
409 | CONNECTOR_PROVIDER_UNSUPPORTED | The alias is a connector on the project but its provider has no hosted authorization page, so no connect link exists for it. |
409 | CONNECTOR_PIPEDREAM_APP_MISSING | The Pipedream connector names no app, so no connect link can be built. |
409 create / 403 rescope | CONNECTOR_CONNECTION_INACTIVE | The selected connection or connector is inactive. |
409 | IDEMPOTENCY_*_CONFLICT | The idempotency key was replayed with a different request body. |
402 | subscription_required / insufficient_credits | The account cannot start a billed session. |