Pair, share, invite (devices + holders)
How to add a phone or a colleague to a Syncropel instance — issue a one-time invite, scan or click the link, and your device or your colleague's home instance walks away holding its own credential.
What this is
spl serve plus the Studio at /settings/devices give you a built-in flow for adding either of two things to a running instance:
- A new device of yours — a phone, a tablet, a fresh laptop. It joins as a guest holder — Syncropel mints a fresh service account + bearer for the device, the device stores the credential in browser IDB, and you can sign in from it anywhere.
- A federated colleague — someone who's already paired with their own instance and wants access to yours. They join as a federated holder — Syncropel verifies a cryptographic attestation signed by their home instance, then mints an instance-bound bearer scoped to whatever you granted.
Both flows share one primitive: core.invite.v1. A single body kind, one record on the instance's th_invites thread, one signed URL, two redeem paths. This guide is operator-facing.
Who this is for
- Operators who want to add their phone or another device to their instance.
- Operators inviting a colleague to a specific thread or namespace.
- Anyone who needs to revoke access after the fact (lost device, team change, lost laptop).
If you're integrating the invite flow into your own UI or CLI, read the API reference for the /v1/invites/* routes and the body-kinds reference for the on-the-wire envelope shapes.
How it works in three minutes
- From
/settings/devicesyou fill in:- Optional device label ("iPhone 15", "Alice's laptop", "dev-team-onboard")
- Access level —
reader(records:read),contributor(records:read+write, default), oradmin(full instance ops). The default is contributor, not admin — you have to opt into handing out admin keys. - Expires in — 15 minutes / 1 hour / 24 hours / 7 days
- Max uses — 1 / 5 / 10 (one-shot pair is the norm; multi-use is for "hand the QR around the room")
- Notes — free-text up to 280 chars; surfaces in the operator list and audit feed
- Click Generate pair link → Syncropel emits a
core.invite.v1record, returns a signed redirect URL plus a QR payload, and Studio displays both side-by-side. - The recipient opens the link (or scans the QR):
- For a guest device: the
/i/<invite-id>preview page generates a fresh keypair in the browser viaSubtleCrypto, POSTs to/v1/invites/<id>/redeem, gets back a bearer, and hands the holder off tosyncropel.com/pair/completewhich writes the credential into IDB and lands them on the workspace. - For a federated holder: the recipient clicks "Sign in using which identity?", picks a paired profile, their home instance signs an attestation, the issuer verifies the signature against the holder's DID document, and the same handoff completes.
- For a guest device: the
- From
/settings/devices, every issued invite shows up in the Issued invites table with its current state —active,redeemed,revoked,expired, orexhausted. Revoke is one click; bulk-revoke wipes every expired or exhausted invite in one call.
Step-by-step: pair your phone
- Sign in to your instance Studio.
- Click Settings → Devices.
- Under Pair a device, fill in:
- Label:
My phone - Access level:
contributor(default) - Expires in:
15 minutes - Max uses:
1(default)
- Label:
- Click Generate pair link — Studio shows the QR.
- On the phone, open the camera app, point at the QR, tap the resulting link. Safari / Chrome opens the preview page → "Pair with this instance" button.
- Tap it. The browser generates a keypair, calls your instance, gets back a bearer, redirects to
syncropel.com/pair/complete?..., writes the credential to IDB, lands on your workspace. - Back in Studio on desktop, the table row for the invite flips from Active to Redeemed within a second (SSE-driven; no manual refresh needed).
Step-by-step: invite a federated colleague
Premise: Bob has his own instance at bob.syncropel.app and is already paired to it. Alice wants Bob to see a specific thread on alice.syncropel.app.
- Alice's Studio at
alice.syncropel.app/settings/devices:- Label:
Bob (federated) - Access level: pick
readerif Bob only needs to read, or build a custom thread-scoped scope via the API (scope_target: {kind: "thread", thread_id: "..."}). - Accept holder types: under the form's "Advanced" expander, set to
federated(or leave asguest, federatedto accept either). - Generate.
- Label:
- Alice sends Bob the pair link (Slack, email, signed URL — Syncropel signs the URL so it's tamper-evident).
- Bob clicks the link. The URL routes him to
syncropel.com/pair?invite=<url>; the Studio detectsaccept_holder_types: federatedand shows the Federated holder flow at/pair/federated. - Bob picks his paired profile (
bob.syncropel.app). Studio callsPOST bob.syncropel.app/v1/invites/sign-attestation— Bob's home instance signs the holder_proof. - Studio then calls
POST alice.syncropel.app/v1/invites/<id>/redeemwithholder_type: federated, Bob's DID, and the holder_proof. - Alice's instance:
- Looks up Bob's DID document
- Verifies the cryptographic signature against the signer key
- Verifies the issued-at-ms is within the freshness window (±60s)
- Mints an instance-bound bearer scoped to whatever Alice granted
- Bob's browser stores the new credential, adds
alice.syncropel.appto his profile list, lands on Alice's instance.
The audit row on Alice's side shows holder_did = did:sync:user:bob, holder_pubkey = <Bob's key>, and the granted scopes.
Revoke + audit
- Revoke one: click the shield-with-slash icon next to any active or redeemed row. Idempotent — clicking again returns "already revoked" without erroring.
- Bulk revoke: "Clean up expired" / "Clean up used-up" buttons appear in the panel header when there are rows in those states.
- See the trail: the Recent invite activity panel below the table folds
core.invite.event.v1records fromth_audit_invites— every redeem (with holder DID), every revoke (with revoker actor + reason), every refused redeem (with the refusal reason:expired,revoked,holder_type_mismatch,bad_proof,freshness_violation, etc.).
Renew your bearer
Long-running devices can rotate their bearer ahead of expiry to slide the window forward. The Renew device strip appears at the top of /settings/devices whenever the active profile holds a bearer. Click it → the browser hashes the current bearer (sha256) to derive the token_id, POSTs /v1/tokens/<token_id>/rotate, gets a fresh bearer with a new expiry, writes the new credential to IDB. Self-only — Syncropel rejects rotation attempts from a bearer that doesn't match the path.
Templates
If you find yourself issuing the same shape repeatedly ("dev team onboarding: contributor, 7 days, 5 uses, labeled with date"), save it as a template:
- Fill out the form once with your desired shape.
- Click Save as template — the label you typed becomes the template label.
- Next time, pick from the Templates dropdown at the top of the form — the fields auto-fill, you tweak the label, and generate.
Templates are stored as core.invite_template.v1 records on th_invite_templates (admin-scoped to your operator); they survive across sessions.
What Syncropel guarantees
The threat model spells out the guarantees in full. The load-bearing ones:
- Defense-in-depth: every operator endpoint checks both the bearer scope and the consent grant. A bearer with
adminscope but noth_invites:writegrant cannot issue invites. - Federated holder proofs are mandatory, never optional: redeem requests with
holder_type: federatedMUST carry a valid Ed25519 attestation signed by the holder's home instance, with a fresh issued-at-ms. Syncropel refuses (and audits) onbad_proof,key_unknown, orfreshness_violation. - Audit thread parity: every redeem / revoke / refusal on
th_invitesmirrors toth_audit_invites. The mirror runs in the same transaction so partial state ("redeem succeeded but audit didn't") cannot happen. - Federation pair revoke cascades: when you delete a federation pair, every federated-holder bearer issued through that pair is revoked in the same transaction. Stops "I removed the pair but their bearer still works" silently broken state.
- Always-admin auth on operator routes: even on an instance configured
auth.required = false, the operator endpoints (/v1/invites,/v1/invites/audit,/v1/invites/bulk-revoke,/v1/invite-templates,/v1/tokens/*/rotate) refuse anonymous callers. The auth-off master switch covers data-plane endpoints; the admin gate is independent.
Reference
- API reference — every
/v1/invites/*route, request/response shape, error codes. - Body-kinds reference —
core.invite.v1,core.invite.event.v1,core.invite_template.v1,core.api_token.activity.v1. @syncropel/sdkclient.invites.*— typed SDK surface.- API reference — pair, share, invite — design rationale, threat model, identity model, scope grammar, and the full set of routes.
Async federation — pairing across offline peers
Use a store-and-forward relay so your laptop and phone can participate in a federation without staying online together. Covers what the relay protects, what it sees, and how to fall back to synchronous federation when both peers are reachable.
Universal traversal — your reach
See everything you reach across all your instances as one continuous view, ask graph questions about the people and threads connected to you, and understand how shared threads appear with exactly the access that was granted.