# One For All Auth: Developer Guide Let your Even Hub app use a user's **Groq** AI or **Spotify** without asking them to paste API keys into your app. The user sets up each service once on One For All Auth (https://14a.dev). Your app shows a short code. The user approves it, and your app is connected. **Your app never sees their keys.** The user can disconnect your app at any time. Plain language, written so a person or an AI coding assistant can follow it top to bottom. --- ## How it works (30 seconds) 1. Your app calls `startConnect()` and gets a code like `BCDF-GHJK`. 2. You show the code on the phone screen and the glasses, plus an "Open approval page" button. 3. The user approves on https://14a.dev/link (the button opens it with the code filled in). 4. `login.done` resolves. You're connected, and this survives app restarts. 5. You call `ofa.chat("groq", …)` or `ofa.spotify("/me/player/…")`. There's no server to run, no client secret, and no redirect URLs. --- ## Quick start ### 1. Get an app ID Sign in at **https://14a.dev/developer** → **Register a new app**. - Pick the services your app uses (Groq, Spotify). - Copy the **app ID** it gives you, e.g. `lyrics-on-glasses-k3x9q2`. It isn't secret; it goes in your code. ### 2. Add the SDK (one file) Download these two files into your project's `src/` folder: - https://14a.dev/sdk/oneforall.js - https://14a.dev/sdk/oneforall.d.ts (types, for TypeScript) ```ts import { OneForAllAuth, OneForAllAuthError, SCOPES, evenHubStorage, createConnectButton } from "./oneforall.js"; ``` It has no dependencies and works with plain Vite + TypeScript Even Hub projects. ### 3. Allow the network in `app.json` ```json "permissions": [ { "name": "network", "desc": "Connects to One For All Auth to use your AI and music services", "whitelist": [ "https://oneforall-api.brandon-813.workers.dev", "https://api.spotify.com" ] } ] ``` Include `https://api.spotify.com` only if you use Spotify. ### 4. Connect and use it ```ts import { waitForEvenAppBridge } from "@evenrealities/even_hub_sdk"; import { OneForAllAuth, OneForAllAuthError, SCOPES, evenHubStorage } from "./oneforall.js"; const bridge = await waitForEvenAppBridge(); const ofa = new OneForAllAuth({ clientId: "YOUR-APP-ID", // from step 1 storage: evenHubStorage(bridge), // keeps the login across restarts optionalScopes: [SCOPES.GROQ, SCOPES.SPOTIFY], // or `scopes: [...]` if your app can't work without them }); async function connect() { const login = await ofa.startConnect(); showCode(login.userCode); // your UI: phone screen AND glasses copyButton.onclick = () => navigator.clipboard.writeText(login.verificationUriComplete); // user pastes it into their phone's browser await login.done; // resolves once the user approves } if (!(await ofa.isConnected())) await connect(); // Groq: OpenAI-style chat completion if (await ofa.hasScope(SCOPES.GROQ)) { const res = await ofa.chat("groq", { model: "llama-3.1-8b-instant", messages: [{ role: "user", content: "One fun fact in under 30 words." }], }); console.log(res.choices[0].message.content); } // Spotify: any Web API call, path after https://api.spotify.com/v1 if (await ofa.hasScope(SCOPES.SPOTIFY)) { const r = await ofa.spotify("/me/player/currently-playing"); if (r.status === 204) console.log("Nothing playing"); else console.log((await r.json()).item?.name); } ``` That's the whole integration. A full working example (phone UI plus glasses display) is in `examples/even-groq/src/main.ts` in the One For All Auth repo. --- ## The Connect button Use the official button, so users recognize it in every app. **Easiest: let the SDK make it.** Its styles are built in, so no CSS is needed: ```ts import { createConnectButton } from "./oneforall.js"; const button = createConnectButton({ onClick: connect }); document.querySelector("#connect-area")!.append(button); ``` **Building your own?** Match this exactly: | | | |---|---| | Label | **Connect with One For All Auth** (use "Reconnect" wording only when reconnecting) | | Background | `#71e7eb` (hover `#8eedf0`) | | Text and icon | `#131b24` (blue-gray off-black, not pure black) | | Shape | 44px tall minimum, 10px rounded corners, 18px side padding | | Font | 16px, bold (700), your app's system font | | Icon | The One For All Auth dot mark, 18px tall, left of the label (`brandMarkSvg()` returns it as SVG) | ```html ``` Please don't recolor it, stretch the icon, or change the wording. --- ## What to show the user while connecting Show the code **on the glasses and the phone**. Users can approve by reading the code off the glasses. ``` Connect One For All Auth Code: BCDF-GHJK Approve it on your phone. ``` On the phone, show: - the code in large text - a **Copy link** button that copies `login.verificationUriComplete`, with "Paste this into your phone's browser" - a small line: "Or go to 14a.dev/link and enter the code", using `login.verificationUri` > **Inside the Even Hub app, don't rely on `login.open()`.** `window.open` doesn't open a separate browser there. It loads the approval page inside Even Hub and replaces your app's page, which is the one waiting for approval, so the login is lost. Copy the link and have the user open it in their phone's browser. `open()` is fine in the simulator and in normal browsers. - a **Cancel** button → `login.cancel()` Codes expire after 10 minutes. If `login.done` rejects with `expired_token`, show a "Try again" button that calls `connect()` again. --- ## Required vs optional services | Option | Behavior | |---|---| | `scopes: [...]` | **Required.** The user can't approve until every one is set up on their account. | | `optionalScopes: [...]` | **Optional.** If the user hasn't set one up, it's skipped. Your app connects with the rest. | After connecting, check with `await ofa.hasScope(SCOPES.GROQ)`. If an optional service is missing, tell the user: > "Groq isn't connected. Set it up at 14a.dev, then tap Reconnect." and make **Reconnect** call `connect()` again. Reconnecting replaces the old connection automatically. --- ## SDK reference ### `new OneForAllAuth(options)` | Option | Type | Notes | |---|---|---| | `clientId` | string | Your app ID. Required. | | `storage` | `TokenStorage` | Use `evenHubStorage(bridge)` in Even Hub apps. Required. | | `scopes` | string[] | Required services. | | `optionalScopes` | string[] | Optional services. | | `apiUrl` | string | Leave unset. | ### Methods | Method | Returns | What it does | |---|---|---| | `isConnected()` | `Promise` | True if this app has a saved connection. | | `startConnect()` | `Promise` | Starts the code login. Also used to reconnect. | | `hasScope(scope)` | `Promise` | Whether the user granted that service. | | `chat(provider, body)` | `Promise` | OpenAI-style chat completion. `provider`: `"groq"`. | | `spotify(path, init?)` | `Promise` | Spotify Web API call; `path` is after `/v1`. | | `getProviderToken("spotify")` | `Promise` | Raw 1-hour Spotify token, if you'd rather call Spotify yourself. | | `fetch(path, init?)` | `Promise` | Any One For All Auth API path with auth handled (advanced). | | `proxyBaseUrl("groq")` | string | OpenAI-compatible base URL, for OpenAI client libraries (advanced; send requests with `ofa.fetch`). | | `disconnect()` | `Promise` | Disconnects this app and forgets its login. | ### Helpers | Export | What it is | |---|---| | `createConnectButton({ onClick, label? })` | The official Connect button, styled. | | `brandMarkSvg(color?, size?)` | The One For All Auth mark as an SVG string. | | `BRAND_COLOR`, `BRAND_INK` | `#71e7eb` and `#131b24`. | ### `DeviceLogin` (from `startConnect()`) | Field | Notes | |---|---| | `userCode` | Show it on the phone and glasses. | | `verificationUri` | `https://14a.dev/link`, for typing the code on another device. | | `verificationUriComplete` | Same page with the code filled in. | | `open()` | Opens `verificationUriComplete`. Returns `false` if blocked. | | `done` | Resolves on approval; rejects with `OneForAllAuthError` otherwise. | | `cancel()` | Stops waiting (`done` rejects with `cancelled`). | ### Scopes | Constant | Value | Service | Notes | |---|---|---|---| | `SCOPES.GROQ` | `llm:groq` | Groq AI | Chat (`chat("groq", …)`). Speech-to-text via `ofa.fetch("/v1/proxy/groq/openai/v1/audio/transcriptions", …)`. | | `SCOPES.SPOTIFY` | `spotify:playback` | Spotify | Now playing and play/pause/skip. Playback control needs Spotify Premium. | Your app can only request scopes it registered in step 1. --- ## Errors Every SDK error is a `OneForAllAuthError` with a `code`: | `code` | Meaning | What your app should do | |---|---|---| | `not_connected` | No login, or the user disconnected your app. | Show **Connect**. | | `insufficient_scope` | This connection doesn't include that service. | "Set it up at 14a.dev, then tap Reconnect." | | `credential_missing` | The user removed that service from their account. | Same as above. | | `credential_expired` | Spotify needs reconnecting (every 180 days). | "Reconnect Spotify at 14a.dev." | | `access_denied` | The user tapped Deny. | Show **Connect** again. | | `expired_token` | The code expired (10 minutes). | Show **Try again**. | | `cancelled` | You called `login.cancel()`. | Nothing. | | `rate_limited` | Too many requests: about 60 a minute per user, or 600 a minute for your whole app. | Wait and retry. Don't poll faster than every few seconds. | | `app_in_review` | Your app is still in review and already has 25 testers. | Ask for review (see below). | | `app_suspended` | Your app has been suspended. | Show `err.message`. Email us to find out why. | | `provider_error` | Groq or Spotify returned an error. | Show `err.message`. | ```ts try { await ofa.chat("groq", body); } catch (err) { if (err instanceof OneForAllAuthError && err.code === "not_connected") showConnectButton(); else showMessage(err instanceof Error ? err.message : "Something went wrong"); } ``` --- ## Review and limits - **New apps start in review.** While in review, your app works for you and up to 25 other people, which is enough for testing. Your app's status is on the [Developer page](https://14a.dev/developer). - **To get approved,** email **brandon@scarletsocial.com** with your app ID and what it does. Approved apps work for everyone. - **Verified** apps don't show the "unverified" warning on the approval page. Ask in the same email. - **Rate limits:** about 60 requests a minute per user, and 600 a minute for your app across all its users. If you need more, ask. - **Suspension:** apps that break the rules below, misuse people's data, or cause trouble for Groq or Spotify can be suspended. A suspended app stops working for everyone right away (`app_suspended`). If it's lifted, existing connections work again without users reconnecting. --- ## Rules (please follow) - **Never ask users for API keys.** That's what One For All Auth replaces. - **Don't log or display tokens.** The SDK handles them; you never need to touch them. - **Ask only for the services you use**, and prefer `optionalScopes` unless your app truly can't work without one. - **Poll gently.** "Now playing" every 3–5 seconds is fine; every second is not. - Users see your app's name and description when approving, and can disconnect it any time. --- ## Testing - Use the Even Hub simulator. The approval page opens in your normal browser there. On a real phone, links open inside Even Hub, so test the Copy link flow on the phone too. - New apps show an **"unverified"** warning on the approval page. That's expected. - See your connection on https://14a.dev under **Connected apps**, and disconnect it there to test `not_connected`. --- ## Without the SDK (raw HTTP) Base URL: `https://oneforall-api.brandon-813.workers.dev`. Form-encoded or JSON bodies. 1. `POST /oauth/device/code` with `client_id`, `scope` (space-separated, required), `optional_scope` (space-separated). Returns `device_code`, `user_code`, `verification_uri`, `verification_uri_complete`, `interval`, `expires_in`. 2. Every `interval` seconds: `POST /oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code`, `device_code`, `client_id`. - `400 {"error":"authorization_pending"}`: keep waiting. - `slow_down`: add 5 s to the interval. - Success returns `access_token` (15 min), `refresh_token`, `expires_in`, `scope`. 3. Renew: `POST /oauth/token` with `grant_type=refresh_token`, `refresh_token`, `client_id`. **Refresh tokens are single use.** Save the new one every time; reusing an old one disconnects the app. 4. Call services with `Authorization: Bearer `: - Groq: `POST /v1/proxy/groq/openai/v1/chat/completions` (also `GET …/models`, `POST …/audio/transcriptions`) - Spotify: `POST /v1/tokens/spotify` → `{ access_token, expires_at }`, then call `https://api.spotify.com/v1/...` with it. 5. Disconnect: `POST /oauth/revoke` with `token=`. --- ## For AI coding assistants When integrating One For All Auth into an Even Hub app: 1. Copy `oneforall.js` (and `oneforall.d.ts`) from https://14a.dev/sdk/ into `src/`. 2. Add the API host (and `https://api.spotify.com` if using Spotify) to `app.json` → `permissions` → `network` → `whitelist`. 3. Create `new OneForAllAuth({ clientId, storage: evenHubStorage(bridge), optionalScopes: [...] })` after `waitForEvenAppBridge()`. 4. On start: if `!(await ofa.isConnected())`, show the official Connect button (`createConnectButton({ onClick })`). On tap: `startConnect()`, show `userCode` on the phone and glasses, add a Copy link button for `login.verificationUriComplete` (don't call `login.open()` inside Even Hub: it replaces the app's page), `await login.done`. 5. Before using a service, check `hasScope()`. If it's missing, show "set it up at 14a.dev, then Reconnect", with Reconnect calling `startConnect()`. 6. Use `ofa.chat("groq", {...})` for AI and `ofa.spotify("/me/player/...")` for Spotify. 7. Catch `OneForAllAuthError` and handle `not_connected` by showing Connect again. 8. Never store, log, or display tokens or keys. --- ## Help Questions, bugs, or want your app verified? Email **brandon@scarletsocial.com**.