Authentication
Cookie session, single sign-on across surfaces

Get a session
in two steps.

Streamwake doesn't use API keys. Better-auth sets an httpOnly cookie on signup and sign-in, and every /api/v1/* route reads it on the way in. The same cookie drives /app/streams, /app/agents, and /app/integrations — sign in once, every surface knows you.

Same-origin only — no separate API host to whitelist.
Cookie is httpOnly and SameSite=Lax; JavaScript can't read it.
Server-side requireAuth() reads it on every gated route — no client-side checks to bypass.
Step 01Sign up

Create an account

The fastest path is the UI at /sign-up. The form collects name, email, and password, calls authClient.signUp.email, and on success sets the session cookie before navigating you to /app/streams.

Need to script it? The same flow is exposed as a REST endpoint. The request body is what better-auth accepts, and the response sets the cookie you'll send back on every call below.

Open /sign-upSame-origin, no CORS to configure.
POST /api/auth/sign-up/email
curl -X POST https://streamwake.polsia.io/api/auth/sign-up/email \
  -H "content-type: application/json" \
  -d '{
    "name": "Your Name",
    "email": "you@example.com",
    "password": "choose-a-strong-password"
  }'
200 Response — cookie set in headers
HTTP/2 200
Set-Cookie: better-auth.session_token=<your-session-token>; HttpOnly; SameSite=Lax; Path=/
Content-Type: application/json

{
  "token": "<your-session-token>",
  "user": {
    "id": "ckq3xuserabc123",
    "email": "you@example.com",
    "name": "Your Name"
  }
}
Step 02Cookie shape

The session cookie

On a successful signup or sign-in, better-auth's catch-all handler at /api/auth/[...all] responds with a Set-Cookie header:

  • Name: better-auth.session_token
  • Flags: HttpOnly, SameSite=Lax, Path=/
  • Read by: the server in requireAuth(req) (calls auth.api.getSession) at the top of every gated route.

The cookie is opaque to JavaScript — pull it from your browser's devtools (Application → Cookies) when you want to script curls against the API.

POST /api/auth/sign-in/email (refresh)
curl -X POST https://streamwake.polsia.io/api/auth/sign-in/email \
  -H "content-type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "you@example.com",
    "password": "your-password"
  }'

The -c flag writes the response cookies to cookies.txt; pair it with -b on subsequent calls to send them back.

Step 03Attach the cookie

Use it on every API call

Every /api/v1/* route requires the cookie. Pass it on the request line as -b "better-auth.session_token=<value>".

The same cookie also drives every authenticated dashboard page — /app/streams, /app/agents, and /app/integrations all read it via useSession() from @/lib/auth-client.

GET /api/v1/streams/<id> — cookie attached
curl https://streamwake.polsia.io/api/v1/streams/ckq3xstreamabc123 \
  -b "better-auth.session_token=<your-session-cookie>"
Step 04What you'll see

Failure modes

Three response codes you can hit during development. The first means re-auth; the second means a request-body issue; the third is a retry.

401

Unauthorized

Cookie is missing, expired, or tampered. Re-auth via /api/auth/sign-in/email and retry. Body is the verbatim { "error": "Unauthorized" } returned by require-auth.ts.

400

Validation / SSRF

POST bodies fail zod validation or the sourceUrl fails the SSRF guard. Body is { "errors": { "field": "..." } }.

500

Internal Server Error

Unexpected server failure. Body is { "error": "Internal Server Error" }. Safe to retry.

SSRF rejection messages — verbatim from src/lib/ssrf.ts
  • URL could not be parsed
  • Only http(s) URLs are allowed
  • URL has no hostname
  • Hostname did not resolve
  • Hostname resolves to a private address (<ip>)
What about the API key on the landing?

The landing CTA is a waitlist.
The developer path is a cookie.

The form on the home page — <GetApiKeyForm/> — captures an email and writes it to the waitlist. It does not issue a credential today. If product ships real API keys later, that's a separate Prisma model plus bearer-token auth wired into require-auth. The path that works right now is signup → cookie session.