Sign in with co/core
Let your users connect their own atmosphere account to your app in one click. Jobs run on their credits, with a signed receipt for every one.
Some apps want inference on their user's atmosphere account, not their own — a feed reader that summarizes each user's feed, an assistant that bills each user's balance. co/core's device pairing is the primitive for that: your app starts a pairing, sends the user to co/core to approve it, and receives a scoped API key bound to the user's DID. You never see their password, and the key only ever spends their credits.
The whole flow is four HTTP calls against https://cocore.dev/api/xrpc/dev.cocore.devicePair.*. No SDK required.
1. Start a pairing
From your server, begin a pairing. appName and appDid name your app on the approval screen; keyName labels the key in the user's account; returnUrl is where co/core sends the browser back after approval (its host must be allowlisted — see step 4).
curl -X POST https://cocore.dev/api/xrpc/dev.cocore.devicePair.start \
-H 'content-type: application/json' \
-d '{
"appName": "Your App",
"appDid": "did:plc:your-app-did",
"keyName": "Your App",
"returnUrl": "https://yourapp.example/connected"
}'
# → {
# "deviceId": "…", # secret; keep it on your server
# "userCode": "HB8G7HX7", # show this to the user
# "verificationUri": "https://cocore.dev/devices/new?code=HB8G7HX7",
# "pollIntervalSecs": 3,
# "expiresInSecs": 600
# }Keep deviceId on your server — it is the secret that later reads the key. Send the user to verificationUri.
2. The user approves
At verificationUri the user signs in to co/core (once) and sees a consent screen naming your app. On approve, co/core mints an API key scoped to their DID and sends the browser back to your returnUrl. If you never showed the user the code, devicePair.describe?userCode=… echoes back your app name so a second screen can confirm who is asking.
3. Poll for the key
While the user is approving, poll from your server with the deviceId every pollIntervalSecs. Until they act it returns pending; on approve it returns session carrying the scoped key.
curl "https://cocore.dev/api/xrpc/dev.cocore.devicePair.poll?deviceId=DEVICE_ID"
# pending: { "status": "pending" }
# denied: { "status": "denied" } (410 expired / consumed are terminal too)
# ready: {
# "status": "session",
# "session": {
# "did": "did:plc:the-user",
# "handle": "user.example.com",
# "apiKey": "cocore-…", # store encrypted; spends the user's credits
# "apiBase": "https://cocore.dev/api/v1"
# }
# }Store apiKey encrypted, keyed to the user. Then it is an ordinary co/core key: call https://cocore.dev/api/v1/chat/completions with it (see the quickstart). Each response carries an x_cocore block with the provider and a receipt URI, and the job is billed to the user, not to you.
4. Register your app (recommended)
Two things make the flow feel first-class instead of anonymous:
- An app registration record. Publish a
dev.cocore.app.registrationrecord on your app's own repo with yourname,website,iconUrl, andreturnUrls. The consent screen reads it to show your name and icon rather than a bare DID. - A verified return host. co/core only sends the browser back to a
returnUrlwhose host you control. Prove it by serving/.well-known/cocore-app.jsonlisting your app DID(s), so an attacker can't point the redirect at their own site.
// dev.cocore.app.registration (rkey "self") on your app's repo
{
"$type": "dev.cocore.app.registration",
"name": "Your App",
"website": "https://yourapp.example",
"iconUrl": "https://yourapp.example/icon.svg",
"description": "What your app does with the user's atmosphere account.",
"returnUrls": ["https://yourapp.example/connected"]
}
// https://yourapp.example/.well-known/cocore-app.json
{ "did": "did:plc:your-app-did" }Both are optional — pairing works without them — but together they turn “an unknown DID wants access” into “Your App wants access,” with the redirect locked to a host you own.
Notes
- The
deviceIdis a bearer secret for the pending pairing; keep it server-side and never put it in the browser. - A pairing expires after
expiresInSecs. If the user never approves, start a new one. - The key stops working if the user revokes it in their atmosphere account or their underlying session lapses — handle a
401by asking them to reconnect.