CalimeroClient
// A Calimero Cloud cluster by name: its nodes come from the node directory,// each attested under `policy` before anything is sent to it.await CalimeroClient.connect({ cluster: string, // e.g. 'eu-shared-1' policy: CloudPolicy, directory?: string, // default DEFAULT_DIRECTORY (Calimero Cloud's) zone?: string, // try this zone's nodes first fetch?: typeof fetch, retry?: { retries?: number; baseDelayMs?: number; maxDelayMs?: number }, random?: () => number, // for the weighted order; default Math.random}) // throws DirectoryError (reason 'unreachable' | 'not-found' | 'bad-response' | 'empty')
// A fixed list of nodes, or a cluster you run yourself.new CalimeroClient({ baseUrl: string, // one node of the cluster nodes?: string[], // other nodes, moved to on 421 WRONG_NODE or when one stays unavailable policy: CloudPolicy, // which nodes to trust; see Trust and the sealed channel fetch?: typeof fetch, // carries attestation and envelopes; default the global fetch retry?: { retries?: number; baseDelayMs?: number; maxDelayMs?: number }, // default 6, 200 ms, 2 s})client.nodeUrl is the node in use and client.nodes every node the client
may move to. Every route below is sent inside the sealed channel. passkey is a
PasskeyProvider (browserPasskey() in a page); a method that takes one
answers the node’s 412 CONFIRM_REQUIRED with it.
Credentials
Section titled “Credentials”| Method | Route |
|---|---|
signUp(passkey, name, { origin?, invite? }) → CalimeroSession (the new passkey is asked twice: to register, then to sign the account it creates, 412 CONFIRM_REQUIRED; invite joins its organization, SignUpRefused when the policy or the invite refuses) |
POST auth/passkey/register/begin, …/finish |
signUpPolicy() → { policy, orgInvites, signUpInvites, inviteRequired } (no session) |
GET sign-up |
signIn(passkey, { origin? }) → CalimeroSession |
POST auth/passkey/login/begin, …/finish |
createRecoveryCode(passkey) → the code, once (core #4844) |
POST auth/recovery |
removeRecoveryCode(passkey) |
DELETE auth/recovery |
startAccountRecovery(code, passkey) → AccountRecovery (signed out; passkey creates the new passkey; WrongRecoveryCode) |
POST auth/recovery/begin, …/start |
cancelAccountRecovery(passkey) (a passkey the account already has) |
POST auth/recovery/cancel |
finishAccountRecovery(passkey, accountId?) → CalimeroSession (at readyAt; RecoveryNotReady before) |
POST auth/recovery/finish |
refresh() → CalimeroSession (concurrent calls share one refresh; the client also refreshes by itself on a 401) |
POST auth/refresh |
signOut() |
POST auth/logout |
signOutEverywhere() |
POST auth/logout-all |
useSession(session), useApiKey(key), session, nodeUrl (the node in use) |
(local) |
me() → MeInfo (accountId, via, and the package app and origin a session is bound to) |
GET auth/me |
usage() |
GET auth/usage |
addPasskey(passkey, approver) → credential id (approver: a passkey the account already has) |
POST auth/passkeys/add/begin, …/finish |
labelPasskey(credentialId, label) |
POST auth/passkeys/{cred} |
removePasskey(credentialId, passkey) |
DELETE auth/passkeys/{cred} |
createApiKey({ label?, apps?, methods?, readOnly?, allowConfirm?, ttlSecs?, upgrades? }, passkey) → { keyId, key } (a passkey session; upgrades: { app, package? } makes an upgrade key) |
POST auth/apikeys |
listApiKeys() |
GET auth/apikeys |
revokeApiKey(keyId, passkey) (approved with the account’s passkey) |
DELETE auth/apikeys/{key} |
attestation() → AttestationDoc (unverified, for display) |
GET attestation (in the clear) |
Packages and bundles
Section titled “Packages and bundles”| Method | Route |
|---|---|
installPackage(package, version) → PackageInstalled |
POST packages/install |
uploadPackage(mpk) → PackageInstalled |
POST packages |
packages() → PackageInfo[] |
GET packages |
package(name) → PackageInfo |
GET packages/{package} |
uninstallPackage(name, version?) |
DELETE packages/{package}[/{version}] |
uploadBundle(wasm) → hash (development clusters) |
POST bundles |
Package origins
Section titled “Package origins”A package’s managers register the web origins of a frontend they host themselves, so it can sign people in with passkeys. A session opened there is bound to the package. See Sign in from your own site.
| Method | Route |
|---|---|
packageOrigins.list(package) → PackageOrigin[] ({ origin, rpId, addedBy, addedAt, served }) |
GET package-origins/{package} |
packageOrigins.add(package, origin, passkey?, { rpId?, served? }) → { package, origin, rpId, addedBy, addedAt, served } |
POST package-origins/{package} (412 over __add_origin) |
packageOrigins.remove(package, origin, passkey?) → { package, origin, removed: true } |
DELETE package-origins/{package} (412 over __remove_origin) |
add refuses with BadOrigin (BAD_ORIGIN, BAD_RP_ID), OriginTaken and
QuotaExceeded; list and remove with NotFound. Without a passkey the
412 reaches the caller as ConfirmRequired.
served: true registers a custom domain: the cluster also serves the
package’s frontend on it from the signed bundle. The origin must be
https://host with no port, and the consent the passkey signs names it
({ origin, rpId, served: true }). It further refuses with NotServing (the
cluster serves no frontends), ClusterNotUpgraded (a node is on an older
release), QuotaExceeded (4 served per package, 64 per cluster) and
OriginTaken (held with the other served; remove it first).
| Method | Route |
|---|---|
createApp(name, bundle | { package, version?, service? }, { org?, access?, confirmMethods?, initArgs? }) → app id |
POST apps |
listApps({ package?, bundle? }) → AppInfo[] |
GET apps |
getApp(app) |
GET apps/{app} |
deleteApp(app) |
DELETE apps/{app} |
setAccess(app, access) |
POST apps/{app}/access |
setAppMetadata(app, metadata) |
POST apps/{app}/metadata |
appStorage(app) |
GET apps/{app}/storage |
appAbi(app) |
GET apps/{app}/abi |
upgradeApp(app, { version?, package?, service?, bundle?, migrate? }, passkey?) |
POST apps/{app}/upgrade |
transferApp(app, to, passkey) |
POST apps/{app}/transfer |
call(app, method, args?, idempotencyKey?) → CallResult |
POST apps/{app}/intents |
callConfirmed(app, method, args, passkey, idempotencyKey?) → CallResult |
POST apps/{app}/intents |
view(app, method, args?, { stale?, minIndex? }) → ViewResult |
POST apps/{app}/views |
events(app, from?, signal?) → async iterable of CloudEvent |
GET apps/{app}/events |
uploadBlob(app, bytes) → { blobId, id, size } |
POST apps/{app}/blobs |
listBlobs(app) → BlobInfo[] |
GET apps/{app}/blobs |
blobInfo(app, id) → BlobInfo |
GET apps/{app}/blobs/{id}/info |
downloadBlob(app, id) → bytes |
GET apps/{app}/blobs/{id} |
deleteBlob(app, id) |
DELETE apps/{app}/blobs/{id} |
publishPresence(app, seq, state | null) |
POST apps/{app}/presence |
presence(app) → PresenceEntry[] |
GET apps/{app}/presence |
presenceStream(app, signal?) → async iterable of CloudEvent |
GET apps/{app}/presence/stream |
Aliases
Section titled “Aliases”| Method | Route |
|---|---|
setAlias(alias, app) |
PUT auth/aliases/{alias} |
alias(alias) → app id |
GET auth/aliases/{alias} |
aliases() → { alias: appId } |
GET auth/aliases |
deleteAlias(alias) |
DELETE auth/aliases/{alias} |
Organizations
Section titled “Organizations”| Method | Route |
|---|---|
createOrg(name, { recoveryKey? }) → org id |
POST orgs |
getOrg(org) → OrgInfo (releaseApprovers, publishes, newWorkStopped, recovery) |
GET orgs/{org} |
deleteOrg(org, passkey) |
DELETE orgs/{org} |
setOrgMetadata(org, metadata) |
POST orgs/{org}/metadata |
setMember(org, account, role, passkey) |
POST orgs/{org}/members |
removeMember(org, account, passkey) (also to leave: your own account) |
DELETE orgs/{org}/members/{account} |
setMemberMetadata(org, account, metadata) |
POST orgs/{org}/members/{account}/metadata |
setCapabilities(org, bits, passkey, { account?, team? }) |
POST orgs/{org}/capabilities |
createTeam(org, name, join, parent?) → team id |
POST orgs/{org}/teams |
getTeam(org, team) |
GET orgs/{org}/teams/{team} |
updateTeam(org, team, { name?, join?, parent?, metadata? }) |
POST orgs/{org}/teams/{team} |
deleteTeam(org, team) |
DELETE orgs/{org}/teams/{team} |
setTeamMember(org, team, account, passkey, { role?, lead?, capabilities? }) |
POST orgs/{org}/teams/{team}/members |
removeTeamMember(org, team, account, passkey) |
DELETE orgs/{org}/teams/{team}/members/{account} |
invite(org, role, passkey, teams?, { ttlSecs?, uses? }) → token |
POST orgs/{org}/invites |
redeemInvite(token) (no approval) |
POST invites/{token}/redeem |
setRecoveryKey(org, publicKey | null, passkey) (the owner) |
POST orgs/{org}/recovery |
startRecovery(org, seed) → { startedAt, readyAt } (signs locally) |
POST orgs/{org}/recovery/start |
cancelRecovery(org, passkey) (the owner) |
POST orgs/{org}/recovery/cancel |
finishRecovery(org, passkey) → { org, previousOwners } |
POST orgs/{org}/recovery/finish |
upgradeOrg(org, { package, version, team?, cascade?, migrate? }, passkey) → UpgradeRun |
POST orgs/{org}/upgrade |
upgradeRun(org, run) → UpgradeRun |
GET orgs/{org}/upgrades/{run} |
retryUpgradeRun(org, run, passkey) → UpgradeRun |
POST orgs/{org}/upgrades/{run}/retry |
approveRelease(org | null, version, passkey, approved?) |
POST orgs/{org}/releases or auth/releases |
publisherBinding(signerId) → { signerId, cluster, generation, bound } |
GET publishers/{signerId} |
bindPublisher(org, { signerId, publicKey, signature }, passkey) |
POST orgs/{org}/publishers |
upgradePublished(org, package, { version, migrate? }, passkey) → UpgradeRun |
POST orgs/{org}/packages/{package}/upgrade |
approveOutbound(org | null, package, passkey, { prefixes?, replace? }) |
POST orgs/{org}/outbound or auth/outbound |
Approvals and the consent page
Section titled “Approvals and the consent page”The approval routes above refuse a session opened on an app’s frontend
(403). An app sends its user to the node’s consent page instead
(guide).
| Method | Route |
|---|---|
appsReport(org | null, { after?, limit? }) → AppsReport |
GET orgs/{org}/apps or auth/apps |
appsReportAll(org | null) → AppsReport (every page) |
the same, following next |
releaseRules() → { governance, deadlines } |
GET releases |
pendingApprovals(org?) → PendingApprovals |
GET auth/apps (or orgs/{org}/apps), GET releases |
requestConsent(request) → { requestId, url, expiresAt, code? }; show code to the user before sending them to url, and they should see the same code on the page |
POST consent/requests |
openConsentPage(request, navigate? | { navigate?, confirm? }): checks code against confirmationCode(requestId) (else CalimeroError CONSENT_CODE_MISMATCH), runs confirm(started) so the app can show code, and navigates unless it answers false |
POST consent/requests, then navigates to url |
consentResult(requestId) → { requestId, kind, status, decidedAt } |
GET consent/{id}/result |
waitForConsent(requestId, { intervalMs?, signal?, expiresAt? }) |
polls GET consent/{id}/result |
Operator
Section titled “Operator”With the operator token (useApiKey(token)):
| Method | Route |
|---|---|
opsApps({ after?, limit? }) → OpsAppsReport |
GET ops/apps |
opsOrgs({ after?, limit? }) → OpsOrgsReport |
GET ops/orgs |
opsServedOrigins() → { fresh, origins: [{ origin, host, label, addedAt }] } |
GET ops/served-origins |
setReleaseDeadline(version, after) |
POST ops/releases/{version}/deadline |
liftReleaseDeadline(version) |
DELETE ops/releases/{version}/deadline |
Any route
Section titled “Any route”get, post, put and delete(path, body?) send any request through the
sealed channel with the client’s credential and map a refusal to a typed error.
postConfirmed(path, body, passkey) and deleteConfirmed(path, passkey, body?)
answer a 412 CONFIRM_REQUIRED.
Not covered
Section titled “Not covered”- The rest of
/ops/*, the operator’s API: use core’smeroctl. /apps/{app}/attestationand receipt verification (core’sClient::verify_app_cluster, which checks every replica’s receipt key and the cluster’s governance): not yet in this client./internal/*: node to node only.
Other exports
Section titled “Other exports”fetchAttestedKey, claimedRelease, MOCK_QUOTE_HEADER, browserPasskey,
b64url, fromB64url, errorFrom, ORG_CAPABILITIES, orgCapabilities,
DEFAULT_ORG_CAPABILITIES; for approvals releaseConsent, withdrawConsent, upgradeConsent,
consentReturn, confirmationCode, pendingApprovalsFrom, stalledBy, publisherBindingMessage,
publisherBindingBytes, releaseNotesUrl, verifyReleaseUrl; the sealed transport (createSealedFetch,
transportKeyBinding, SealedTransportError, StaleTransportKeyError); quote
and release verification (createQuoteVerifier, trustedMeasurementsFromReleases,
createSignedReleaseVerifier, trustSignedRelease, verifySignedNodeRelease,
fetchNodeRelease, fetchGithubNodeRelease, cloudNodeReleaseUrl); and the
private agent gate (attestAgent, provisionAgent, releaseAgent, …).