Phuze

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 code

Prefer 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

KeyHeaderUse
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

MethodPathAuthDescription
POST/clientsRegister a new client
POST/clients/activateActivate with email code
GET/clients/meCLI or SecretGet client info & status
PATCH/clients/meCLIUpdate name, domains, or sender name (from_name, Builder+)
POST/clients/me/keys/rotateCLIRotate secret key
GET/clients/me/quotaCLI or SecretGet quota usage — soft/hard caps, overage count, test accounts
GET/clients/me/usersCLI or SecretList your users — tags, activity (last login), MAU status; paginated
POST/clients/me/test-accountsCLIDeclare/refresh a test address — 10/min · 60/hr sends for 14 days (dev & testing)
DELETE/clients/me/test-accounts/:emailCLIRemove a test address
POST/feedbackCLI or SecretFeedback / feature interest (e.g. custom sender domains)
POST/clients/me/delete-requestSend deletion code to registered email
POST/clients/me/delete-confirmConfirm 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

MethodPathAuthDescription
POST/auth/magic-linkPublishable + OriginSend 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

MethodPathAuthDescription
POST/sessions/verifySecretVerify session (server-to-server)
POST/sessions/revokeSecretRevoke a session
GET/sessions/pickupPublishable + OriginPoll for the session after a magic-link send (?pending_id=) — the custom-flow handoff
GET/sessions/currentPublishable + OriginCheck active session (X-Session-Token)
POST/sessions/logoutPublishable + OriginLog out (X-Session-Token)

Building a custom login UI without the widget? The flow is: generate a crypto.randomUUID() pending_idPOST /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

MethodPathAuthDescription
GET/users/:uidSecretLook up user by UID
GET/users/by-email/:emailSecretLook up user by email
POST/users/me/delete-requestPublishable + SessionSend account deletion confirmation email (trusted clients only)

Tags

MethodPathAuthDescription
POST/users/:uid/tagsSecretAssign a tag (body: { tag }). Idempotent. 404 if the uid isn't yours.
GET/users/:uid/tagsSecretList a user's tags you've assigned, sorted alphabetically
DELETE/users/:uid/tags/:tagSecretRemove 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