Phuze Developer Docs
← Introduction · Core Concepts · API Reference · Widget · Integrations · Security · FAQ · CLI · Agent setup: phuze-setup.md · Agent skill: phuze-skill.md
Quick Start
1. Register
curl -X POST https://phuze.edato.me/api/v1/clients \
-H "Content-Type: application/json" \
-d '{"name":"My App","email":"you@example.com","domains":["myapp.com"]}'
# With an invite code (required if your email is not whitelisted):
curl -X POST https://phuze.edato.me/api/v1/clients \
-H "Content-Type: application/json" \
-d '{"name":"My App","email":"you@example.com","domains":["myapp.com"],"invite_code":"XXXX-XXXX-XXXX"}'
# → { "client_id": "cl_...", "status": "pending", "message": "Check your email..." }
# 403 = email not approved and no valid invite codePrefer a form? Register at phuze.edato.me/signup — same result, and it shows your keys once.
2. Activate
curl -X POST https://phuze.edato.me/api/v1/clients/activate \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","code":"123456"}'
# → { "publishable_key": "pk_live_...", "secret_key": "sk_live_...", "cli_token": "cli_..." }Keys are shown once — save secret_key and cli_token now, then add them to your environment:
# Frontend (safe to expose in HTML)
NEXT_PUBLIC_PHUZE_PUBLISHABLE_KEY=pk_live_...
# Backend only — never put this in frontend code
PHUZE_SECRET_KEY=sk_live_...3. Add the widget
<script src="https://phuze.edato.me/phuze.js"></script>
<script>
Phuze.init({ publishableKey: 'pk_live_...' })
Phuze.mount('#phuze-widget')
</script>
<div id="phuze-widget"></div>4. Verify sessions server-side
The widget stores the session token in localStorage under the key phuze_session. To verify it on your server, read it on the client and send it with your request:
// Client — read the token and send it to your server
const token = localStorage.getItem('phuze_session')
const res = await fetch('/api/your-protected-endpoint', {
headers: { 'Authorization': 'Bearer ' + token }
})// Server — verify the token with Phuze
const token = req.headers.authorization?.replace('Bearer ', '')
const res = await fetch('https://phuze.edato.me/api/v1/sessions/verify', {
method: 'POST',
headers: {
'Authorization': 'Secret sk_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
})
const { valid, uid, email } = await res.json()
if (!valid) { /* reject the request */ }The full valid response is { valid, uid, email, session_id, client_id, created_at, expires_at, type } — email may be absent in rare cases. Destructure the fields you need; never validate with a closed/strict schema (additionalProperties: false, zod .strict()) — new fields may be added.
Auth Schemes
| Key | Header | Use |
|---|---|---|
pk_live_… | Authorization: Publishable pk_live_… | Browser / widget calls |
sk_live_… | Authorization: Secret sk_live_… | Server-to-server |
cli_… | Authorization: CLI cli_… | CLI / management |
API Reference
Quick index below. Full request/response contracts, errors, and the custom login flow are on the API Reference page.
Client Management
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /clients | — | Register a new client |
| POST | /clients/activate | — | Activate with email code |
| GET | /clients/me | CLI or Secret | Get client info & status |
| PATCH | /clients/me | CLI | Update name, domains, or sender name (from_name, Builder+) |
| POST | /clients/me/keys/rotate | CLI | Rotate secret key |
| GET | /clients/me/quota | CLI or Secret | Get quota usage — soft/hard caps, overage count, test accounts |
| GET | /clients/me/users | CLI or Secret | List your users — tags, activity (last login), MAU status; paginated |
| POST | /clients/me/test-accounts | CLI | Declare/refresh a test address — 10/min · 60/hr sends for 14 days (dev & testing) |
| DELETE | /clients/me/test-accounts/:email | CLI | Remove a test address |
| POST | /feedback | CLI or Secret | Feedback / feature interest (e.g. custom sender domains) |
| POST | /clients/me/delete-request | — | Send deletion code to registered email |
| POST | /clients/me/delete-confirm | — | Confirm deletion with email + code — permanently deletes client |
⚠ Dangerous — Client Account Deletion
Deleting a client is permanent and irreversible. All sessions, CLI tokens, quota records, and the client itself are deleted. Your end-users will not be able to sign in via this client after deletion.
Step 1 — Request a deletion code (sent to your registered email):
curl -X POST https://phuze.edato.me/api/v1/clients/me/delete-request \
-H "Content-Type: application/json" \
-d '{"email":"owner@example.com"}'
# → { "message": "Deletion code sent to owner@example.com..." }Step 2 — Confirm with your email and the 6-digit code from your inbox:
curl -X POST https://phuze.edato.me/api/v1/clients/me/delete-confirm \
-H "Content-Type: application/json" \
-d '{"email":"owner@example.com","code":"123456"}'
# → { "deleted": true, "client_id": "cl_..." }Auth Flow
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/magic-link | Publishable + Origin | Send sign-in email to user. Optional body fields: locale, redirect_url. |
| GET | /auth/verify?token=&client_id= | — | Verify link → creates session → shows a success page with a "Return to app" button linking to redirect_url (no auto-redirect) |
Sessions
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /sessions/verify | Secret | Verify session (server-to-server) |
| POST | /sessions/revoke | Secret | Revoke a session |
| GET | /sessions/pickup | Publishable + Origin | Poll for the session after a magic-link send (?pending_id=) — the custom-flow handoff |
| GET | /sessions/current | Publishable + Origin | Check active session (X-Session-Token) |
| POST | /sessions/logout | Publishable + Origin | Log out (X-Session-Token) |
Building a custom login UI without the widget? The flow is: generate a crypto.randomUUID() pending_id → POST /auth/magic-link with it → poll GET /sessions/pickup?pending_id= (about once every 2 seconds, up to ~10 minutes — faster polling from one IP gets rate-limited) until { ready: true, session_token } → verify server-side. Use crypto.randomUUID(), never Math.random(), for thepending_id. Full recipe + security notes in the developer docs.
Users
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /users/:uid | Secret | Look up user by UID |
| GET | /users/by-email/:email | Secret | Look up user by email |
| POST | /users/me/delete-request | Publishable + Session | Send account deletion confirmation email (trusted clients only) |
Tags
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /users/:uid/tags | Secret | Assign a tag (body: { tag }). Idempotent. 404 if the uid isn't yours. |
| GET | /users/:uid/tags | Secret | List a user's tags you've assigned, sorted alphabetically |
| DELETE | /users/:uid/tags/:tag | Secret | Remove a tag. Idempotent — always 204 |
Tags are string labels you attach to your own users — membership tiers, roles, feature flags, cohorts. Phuze only stores and serves them; your app owns the gating logic. Names match [a-z0-9-]{1,64}, scoped to your client (up to 64 per user), and are not visible to other clients.
Widget (phuze.js)
Add passwordless login with one script tag — the widget is self-contained (renders the sign-in form when logged out, the email + "Sign out" button when logged in).
<script src="https://phuze.edato.me/phuze.js"></script>
<script>
Phuze.init({ publishableKey: 'pk_live_...' })
Phuze.mount('#phuze-widget')
</script>
<div id="phuze-widget"></div>Full API, session type, DOM binding, customization, and styling are on the Widget page.
Local Development & CORS
localhost and 127.0.0.1 (any port) are always allowed as origins — no configuration needed while developing locally. Every other domain must be in your client's authorized list, or requests get a 403 forbidden. Add one with phuze domains add staging.myapp.com (WIP). Full details: Security → Domain allowlisting.
Error Format
{ "error": { "code": "unauthorized", "message": "Missing Authorization header" } }Errors always take this shape. Full code list (including rate_limited with retry_after_seconds) and per-endpoint details: API Reference → Errors.
Quotas & Plans
A "login" is one accepted magic-link send, counted against your plan's daily quota. Current tiers, prices, and the overage rate live at phuze.edato.me/pricing — rendered from the plan config and always authoritative. Paid plans keep sending past their quota (billed as overage) up to a hard cap where sends stop (429 quota_exceeded); Free stops at its quota. MAU is not a plan limit — a high flat guardrail, never billed for registered or returning users. Full model: Core Concepts → Quotas. Your client's live effective values: GET /clients/me/quota.
Healthcheck
GET https://phuze.edato.me/api/v1/healthcheck
→ { "status": "ok", "timestamp": "2026-06-22T…", "service": "phuze-auth" }Phuze · phuze.edato.me · Introduction · Agent setup: phuze-setup.md · Agent skill: phuze-skill.md