# Kepeink Management API Full Agent Guide Base URL: `https://manage.kepeink.hu` The management console and automation clients use the same API. Browser sessions use secure cookies; non-interactive clients should use machine API keys with `Authorization: Bearer kpm_...`. ## LLM Quickstart — minimum viable tunnel Prereqs: `KEPEINK_API_KEY=kpm_…` (human creates it in the dashboard Account & activity → Access tokens). All management calls carry `Authorization: Bearer $KEPEINK_API_KEY` and `User-Agent: my-automation/1.0`. Never invent key prefixes: `kpm_` = management API key, `kpt_` = agent tunnel token (shown once on tunnel create — save it immediately, keep it out of prompts). ```sh # 0. Where to learn: always probe these first curl -fsS https://kepeink.hu/llms.txt # marketing entrypoint curl -fsS https://manage.kepeink.hu/llms.txt # this guide's 1-page version curl -fsS https://manage.kepeink.hu/openapi.json | jq .paths # authoritative shapes # Human tutorials with compose snippets: # https://kepeink.hu/docs/install-the-agent # https://kepeink.hu/docs/run-the-agent-with-docker # https://kepeink.hu/docs/expose-immich-with-docker # https://kepeink.hu/docs/end-to-end-tls # 1. Verify the key + inspect workspace/role/billing/legal curl -fsS https://manage.kepeink.hu/api/v1/me \ -H "Authorization: Bearer $KEPEINK_API_KEY" # 2. Create a proxy tunnel (target must be reachable FROM the agent host) curl -fsS -X POST https://manage.kepeink.hu/api/v1/tunnels \ -H "Authorization: Bearer $KEPEINK_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target":"http://localhost:3000","backend_mode":"proxy"}' # → { tunnel_id, sni, public_url, agent_token:"kpt_…", tls_mode, cert_source, enabled } # Other targets: proxy "http://192.168.1.5:8080" (LAN host), static "/var/www/dist" (absolute path on agent host), ssh "host:22" # 3a. Install the agent as a host service (auto-starts on boot, hourly self-update) curl -fsSL https://cdn.kepeink.hu/install.sh | sudo sh -s -- --token=kpt_xxxxxxxx # Windows (elevated PowerShell): # Invoke-WebRequest -UseBasicParsing https://cdn.kepeink.hu/install.ps1 -OutFile $env:TEMP\kepeink-install.ps1; & $env:TEMP\kepeink-install.ps1 -Token kpt_xxxxxxxx # Foreground (no install): curl -fsSL https://cdn.kepeink.hu/run.sh | sh -s -- --token=kpt_xxxxxxxx # 3b. Or run as Docker (same compose project as the app): # image: registry.kepeink.hu/agent:2 (anonymous pull, multi-arch, major-pinned :2; update: docker compose pull && docker compose up -d kepeink-agent) # env: KEPEINK_AGENT_TOKEN=kpt_… ← the ONLY required env (baked: KEPEINK_AGENT_NO_SUPERVISOR=1, KEPEINK_AGENT_AUTO_UPDATE=0, KEPEINK_AGENT_DIR=/tmp) # target must resolve INSIDE the agent container: http://: (e.g. http://immich-server:2283), NOT http://localhost:… (that is the agent itself) # host-side service: http://host.docker.internal: + extra_hosts: ["host.docker.internal:host-gateway"] on the agent service # 4. Verify curl -fsS https:/// # tunnel's public_url curl -fsS https://manage.kepeink.hu/api/v1/tunnels | jq .tunnels # 5. Optional — end-to-end TLS (agent holds the key, edge only relays ciphertext) # Whole-tunnel rproxy (needs e2e_tls feature grant — 403 otherwise): curl -fsS -X POST https://manage.kepeink.hu/api/v1/tunnels/$TUNNEL_ID/e2e -H "Authorization: Bearer $KEPEINK_API_KEY" -H "Content-Type: application/json" -d '{"enabled": true}' # Custom domain (free, no grant): after attaching the domain (POST /api/v1/custom-domains), then curl -fsS -X POST https://manage.kepeink.hu/api/v1/custom-domains/$DOMAIN_ID/e2e -H "Authorization: Bearer $KEPEINK_API_KEY" -H "Content-Type: application/json" -d '{"enabled": true}' # Docker e2e note: KEPEINK_AGENT_DIR=/tmp is ephemeral — the Let's Encrypt cert cache vanishes on container recreate; first hit then re-issues (~15 s). Mount a volume at /tmp (or set KEPEINK_AGENT_DIR to a mounted path) to persist. # Scanner noise note: in e2e mode internet-wide scanners log as "e2e-tls handshake failed; closing stream err=EOF" — expected, rate-limited 1/30s; terminated mode hides this at the edge. # 6. Common mutations curl -fsS -X POST https://manage.kepeink.hu/api/v1/tunnels/$ID/enabled -H "Authorization: Bearer $KEPEINK_API_KEY" -H "Content-Type: application/json" -d '{"enabled": false}' # toggle without deleting curl -fsS -X PATCH https://manage.kepeink.hu/api/v1/tunnels/$ID -H "Authorization: Bearer $KEPEINK_API_KEY" -H "Content-Type: application/json" -d '{"target":"http://localhost:4000"}' # update target ``` Exact JSON shapes and status codes (403/409/429 on e2e, 400 on bad target, etc.) are authoritative in `/openapi.json`. The sections below enumerate every endpoint. ## Authentication Model Browser sign-in starts with either `GET /api/auth/google/start`, when Google OAuth is configured, or `POST /api/auth/start` for email login. Email login completes through a magic link or `POST /api/auth/verify-code`. A successful browser login sets `__Host-kepeink_session`, an HttpOnly, Secure, SameSite=Lax cookie. Browser session tokens are sliding 14-day credentials with a 90-day hard cap. Browser automation can also exchange an active `kpm_...` machine API token at `POST /api/auth/token-login`; this creates a browser session for the same user, workspace, and role as the machine token. Machine API keys are `kpm_...` bearer tokens stored only as hashes server-side. They do not expire automatically. Self-service keys inherit the workspace role of the browser session that created them. Platform admins on `admin.kepeink.hu` can mint audited API tokens for active users/workspaces when an operator reason is provided. Machine tokens cannot create more machine tokens. Revoke API keys with `DELETE /api/v1/sessions/{token_id}`. ### First API call A human creates the key in the dashboard under **Account & activity** and **Access tokens**. Automated clients should keep it in a secret store and call: ```sh curl -fsS https://manage.kepeink.hu/api/v1/me \ -H "Authorization: Bearer $KEPEINK_API_KEY" \ -H "User-Agent: my-automation/1.0" ``` The response identifies the active workspace and role. Switch workspace with `POST /api/v1/workspaces/switch` only when the key has another active membership. Prefer the API over UI automation for every non-human-gated task. ## Capability Model Every authenticated request resolves to exactly one workspace and one role: `owner`, `admin`, `member`, or `viewer`. - `owner`: full workspace control, billing checkout, free-trial activation, account-level destructive actions. - `admin`: tunnel management and token management where server routes allow it. - `member`: tunnel create/update and tunnel token issuance where server routes allow it. - `viewer`: read-only access and workspace switching/creation where server routes allow it. Use `GET /api/v1/me` to inspect the current token, workspace, role, billing state, legal acceptance state, and service configuration. ## Human-Only Action `POST /api/v1/free-trial/activate` is intentionally human-gated with reCAPTCHA Enterprise action `free_trial_activate` and a `0.9` server threshold. Agents should not attempt this action. `POST /api/auth/start` is also reCAPTCHA-scored with action `auth_start` and a low `0.1` threshold. The returned assessment is stored on the email challenge so SES hard-bounce feedback can annotate it as fraudulent. `POST /api/public/takedown` uses action `takedown_submit` and requires a `1.0` score. ## Core Endpoints ### Public Auth - `POST /api/auth/start` Body: `{ "email": "user@example.com", "recaptcha_token": "...", "code": "..." }` Starts email-link and email-code login. Always returns accepted for valid email shape. The optional `code` is a capability code: one carrying the `register` capability lets a brand-new email through while `registration_mode` is `invite_only`; any `credit`/`feature` capabilities it holds are applied on first sign-in. When registration is invite-only and a brand-new email is neither allow-listed nor accompanied by a valid `register` code, the endpoint returns `403 { "error": "access_required" }` and the UI offers the waitlist. Existing users sign in without a code. - `POST /api/public/waitlist` Body: `{ "email": "user@example.com", "source": "", "recaptcha_token": "..." }` Records an email that asked to be notified when signup opens, shown after auth/start returns `access_required`. `source` carries the `?src=` attribution slug when present. Requires the same reCAPTCHA token as `auth_start`. Idempotent per email. - `GET /api/auth/config` Returns public auth feature flags: `google_oauth_configured`, `email_auth_configured`, and `registration_mode` (`invite_only` | `open`). Production currently deploy-disables Google OAuth. - `GET /api/auth/google/start` Redirects the browser to Google OAuth using server-side authorization-code flow. - `GET /api/auth/google/callback` Handles Google's OAuth callback, requires a verified Google email, creates a Kepeink browser session, and redirects back to the management UI. - `POST /api/auth/verify-code` Body: `{ "challenge_id": "...", "code": "123456" }` Completes email-code login or returns an MFA challenge when the account requires two-step verification. - `GET /api/auth/verify-link` Browser-navigated redirect target from the sign-in email link; sets the session cookie and redirects into the UI. Agents should use `POST /api/auth/verify-code` instead. - `POST /api/auth/mfa/verify` Body: `{ "challenge_id": "...", "method": "totp", "code": "123456" }` or `{ "challenge_id": "...", "method": "recovery_code", "recovery_code": "..." }` Completes the MFA challenge and returns a browser session token while also setting the cookie. - `POST /api/auth/token-login` Body: `{ "token": "kpm_..." }` Exchanges an active machine API token for a browser session while also setting the cookie. - `GET /api/auth/session` Returns whether the browser session is currently authenticated. - `POST /api/auth/logout` Revokes the current browser session or bearer token. ### Workspace And Identity - `GET /api/v1/me` Read active user, workspace, role, billing state, and feature configuration. - `GET /api/v1/workspaces` List workspaces available to the current user. - `POST /api/v1/workspaces` Body: `{ "name": "Production" }` Creates a workspace and returns a new browser session pinned to it. - `DELETE /api/v1/workspaces/{id}` Owner/admin only. Disables the workspace and removes live serving state for its tunnels, agent tokens, routes, and custom domains. A browser session deleting its active workspace is moved to another accessible active workspace when one exists; otherwise it is signed out. - `POST /api/v1/workspaces/switch` Body: `{ "workspace_id": "..." }` Returns a new browser session pinned to the selected workspace. ### API Keys And Sessions - `GET /api/v1/sessions` Lists active browser sessions and machine API keys for the current user. - `POST /api/v1/api-keys` Body: `{ "label": "Claude Desktop" }` Browser-session only. Creates a non-expiring `kpm_...` API key with the same role as the current session. The plaintext key is returned once as `api_key`. - `DELETE /api/v1/sessions/{token_id}` Revokes a browser session or machine API key created by the current user. ### Platform Admin Tokens - `POST /api/v1/admin/workspaces/{workspace_id}/tokens` Body: `{ "user_id": "...", "role": "owner", "label": "e2e", "reason": "..." }` Platform-admin session only. Creates or updates the user's active workspace membership for the requested role, mints a `kpm_...` machine API token, and returns the plaintext once as `api_token`. ### Tunnels - `GET /api/v1/tunnels` Lists tunnels in the current workspace. - `POST /api/v1/tunnels` Creates a tunnel. Body: `{ "target": "http://127.0.0.1:8080" }` plus optional `backend_mode` (`proxy` default | `static` | `ssh`). **The server allocates the public hostname** — do not send `sni`, which is an operator-only override; `tls_mode` must be omitted or `"terminated"` (end-to-end TLS is a separate flow). The response carries the first `agent_token` in plaintext, returned once and never again — store it before doing anything else. - `GET /api/v1/tunnels/{id}` Reads one tunnel in the current workspace. - `PATCH /api/v1/tunnels/{id}` Updates tunnel configuration. - `DELETE /api/v1/tunnels/{id}` Retires a tunnel. Treat as destructive. - `POST /api/v1/tunnels/{id}/enabled` Body: `{ "enabled": true | false }` Fast on/off toggle for a tunnel without config churn. - `POST /api/v1/tunnels/{id}/e2e` Body: `{ "enabled": true | false }` Owner-only. Enabling switches the tunnel to end-to-end TLS (passthrough + agent-managed ACME): the agent terminates public TLS with its own Let's Encrypt certificate and the private key never leaves the customer machine. Requires the workspace's e2e_tls feature grant (403), no attached cert_worker custom domains (409 — agent-managed custom domains are compatible), per-registered-domain issuance headroom (429), and a connected agent new enough to serve it (409). Disabling reverts to the shared wildcard. - `GET /api/v1/tunnels/{id}/tokens` Lists active/revoked agent tokens for a tunnel. - `POST /api/v1/tunnels/{id}/tokens` Issues a new agent token for a tunnel. Plaintext is returned immediately and stored for email-code re-display. - `POST /api/v1/tunnels/{id}/tokens/{token_id}/reveal/request` Sends an 8-digit email code for viewing a stored active agent token. - `POST /api/v1/tunnels/{id}/tokens/{token_id}/reveal/verify` Body: `{ "challenge_id": "...", "code": "12345678" }`. Returns the stored agent token plaintext. - `DELETE /api/v1/tunnels/{id}/tokens/{token_id}` Revokes a tunnel agent token. ### Tunnel Access Gate - `GET /api/v1/tunnels/{id}/access` Reads a tunnel's basic-auth/MFA access-gate policy. - `PUT /api/v1/tunnels/{id}/access` Body: `{ "mfa_enabled": true | false }` Replaces the access-gate policy. Enabling MFA requires at least one existing access user with a TOTP secret configured. - `POST /api/v1/tunnels/{id}/access/users` Body: `{ "username": "...", "password": "...", "totp_secret_b32": "..." }` Adds a basic-auth access user to the tunnel's access gate. `totp_secret_b32` is required if the gate has MFA enabled. - `PATCH /api/v1/tunnels/{id}/access/users/{access_user_id}` Body: `{ "username": "...", "password": "..." }` (either field optional) Updates a tunnel access user. - `DELETE /api/v1/tunnels/{id}/access/users/{access_user_id}` Disables a tunnel access user. ### Custom Domains - `GET /api/v1/custom-domains` Lists custom domains in the current workspace. - `POST /api/v1/custom-domains` Creates a custom domain and starts DNS verification. - `GET /api/v1/custom-domains/{id}` Reads one custom domain, including verification and certificate status. - `POST /api/v1/custom-domains/{id}/verify` Re-checks DNS verification for a pending custom domain. - `POST /api/v1/custom-domains/{id}/e2e` Body: `{ "enabled": true | false }` Owner-only and FREE. Enabling sets cert_source='agent_managed_acme': the customer's agent obtains and holds a Let's Encrypt certificate for the customer-owned hostname, edged forwards the raw encrypted bytes, and the private key never leaves the customer machine. It is free because the cert counts against the customer's own registered domain (not our shared pool), so there is no feature-grant check. Disabling reverts to the Kepeink-issued cert-worker flow (brief downtime while the cert is obtained) and is refused with 409 while the domain still points at an end-to-end (passthrough) tunnel. - `PATCH /api/v1/custom-domains/{id}` Updates custom domain configuration. - `POST /api/v1/custom-domains/{id}/retry-certificate` Retries certificate issuance after a prior failure. - `DELETE /api/v1/custom-domains/{id}` Removes a custom domain. Treat as destructive. ### Account MFA - `GET /api/v1/account/mfa` Reads the current user's MFA enrollment status. - `POST /api/v1/account/mfa/email/request` Human-only. Sends an 8-digit code to the account's current email address; required before TOTP enrollment can start. - `POST /api/v1/account/mfa/email/verify` Human-only. Body: `{ challenge_id, code }`. On success returns an `enroll_challenge_id` ticket that `totp/start` consumes. - `POST /api/v1/account/mfa/totp/start` Human-only. Body: `{ enroll_challenge_id }` from the email-verify step above — this proves live mailbox control before a secret is generated. Starts TOTP enrollment; the returned secret must be scanned into an authenticator app. - `POST /api/v1/account/mfa/totp/confirm` Human-only. Confirms TOTP enrollment with a code from the authenticator app. - `POST /api/v1/account/mfa/recovery-codes/regenerate` Human-only. Invalidates and reissues MFA recovery codes. - `POST /api/v1/account/mfa/disable` Human-only. Disables MFA for the current user. ### Billing - `GET /api/v1/billing/products` Lists what the operator sells, split into `{ one_time: [], subscriptions: [] }`. Each entry is `{ product_id, name, description?, price_amount_cents, price_currency, recurring_interval?, credit_bytes, entitlement_days, grants_feature?, feature_granted? }`. Two kinds share the list: **credit tiers** (`credit_bytes` and/or `entitlement_days` above zero) top up transferable bytes and validity, while **feature add-ons** carry `grants_feature` (e.g. `e2e_tls`) with zero credit and zero entitlement — they unlock a workspace capability and move no traffic. Do not present an add-on as a credit tier; `feature_granted: true` means this workspace already holds it and buying again grants nothing new. - `POST /api/v1/billing/checkouts` Body: `{ "product_id": "..." }` Creates a hosted checkout URL. Requires owner role. - `POST /api/v1/billing/codes/redeem` Body: `{ "code": "..." }` Owner-only. Redeems a capability code against the current workspace: `credit` tops up `credit_bytes`/`entitlement_days`, `feature` grants the named workspace feature. Returns `{ applied: { credit_bytes?, entitlement_days?, features? }, new_balance_bytes? }`. Invalid/expired/exhausted/revoked/wrong-email codes return a specific 4xx. - `GET /api/v1/invites` Any workspace member (owner/admin/member). Lists the caller's own single-use invite codes newest-first, each with `{ code_id, code, share_url, status, bound_email?, note?, max_uses, used_count, valid_until?, created_at, redemptions: [{ email, redeemed_at }] }`, plus `{ quota, used_slots, enabled }`. - `POST /api/v1/invites` Body: `{ "bound_email"?: "...", "expires_in_days"?: , "note"?: "..." }` Any workspace member. Mints a single-use code carrying the `register` capability, owned by the caller, that admits one new person during invite-only registration. Returns the created invite (same shape as list, plus `share_url`). `403 invites_disabled` when the operator has turned invites off; `429 invite_quota_reached` when at the pending-invite limit. - `POST /api/v1/invites/{id}/revoke` Any workspace member. Revokes one of the caller's own invites (scoped to the minting user). Returns `{ code_id, status: "revoked" }`; unknown/already-revoked/other-user codes return `404 not_found`. - `POST /api/v1/free-trial/activate` Human-only. Requires reCAPTCHA token and owner role. ### Account - `POST /api/v1/account/delete/request` Browser-session only; `kpm_...` machine tokens receive 403 Forbidden. Sends an 8-digit deletion code to the account email and returns `{ "challenge_id": "..." }` with HTTP 202. - `POST /api/v1/account/delete` Body: `{ "challenge_id": "...", "code": "12345678" }` Browser-session only; `kpm_...` machine tokens receive 403 Forbidden. Confirms deletion and returns 204 No Content. The account is soft-deleted, sessions and API keys are revoked, solely-owned workspaces are disabled, and remaining account residue is purged after a 30-day grace period. - `GET /api/v1/account/export` Browser-session only; `kpm_...` machine tokens receive 403 Forbidden. Downloads an `application/json` attachment with `Content-Disposition` and the fields `generated_at`, `user`, `workspaces`, `tunnels`, `credit_grants`, `usage_summary`, `audit_events`, and `audit_truncated`. Export is rate-limited to 2/hour per user; rate-limited calls return 429. This endpoint returns 200 with JSON, not 204. - `GET /api/v1/agent-release` Current agent release manifest and machine download URLs. ### Live updates - `GET /api/v1/management/events` SSE stream for UI state sync updates. ### Legal And Audit - `GET /api/v1/legal/current` Reads current ToS/Privacy dates, exact rendered-document SHA-256 hashes, and acceptance state. - `POST /api/v1/legal/accept` Interactive-session only. Body includes the current dates and hashes plus `terms_agreed`, `endpoint_authorization_attested`, and `privacy_notice_acknowledged`, all `true`. Records append-only clickwrap evidence for the user and workspace. Other customer-service APIs return HTTP 428 until complete. - `GET /api/v1/legal/text/{kind}/{releaseDate}` Public HTML for a legal document. - `GET /api/v1/legal/text/{kind}` Public HTML for the latest version of a legal document. - `GET /api/v1/audit?limit=25&cursor=...&action=...` Lists audit events visible to the current user. ### Public Takedown - `POST /api/public/takedown` Body: `{ "url": "...", "reporter_email": "...", "reason_text": "...", "recaptcha_token": "..." }` Public, email-verified, rate-limited, and reCAPTCHA-gated. - `GET /takedown/verify?t=...` Confirms a takedown report from email. ### Telemetry - `POST /api/public/page-event` Closed-set UI telemetry labels. Agents should not call this endpoint. ### Platform admin (operator-only) - `/api/v1/admin/*` endpoints exist for platform-admin operators, return `403` for non-admin accounts, and are documented in `openapi.json` under tag `platform-admin`. This includes an authenticated admin status/event surface (`GET /api/v1/admin/status`, `GET /api/v1/admin/events`, `GET /api/v1/admin/events/stream`) distinct from the customer-scoped `GET /api/v1/management/events` stream. - Capability codes (launch-phase registration gate + credit/feature grants): `GET /api/v1/admin/codes` (paginated list, `?q=` search over code/label/note/bound email), `POST /api/v1/admin/codes` (mint a code; body `{ "capabilities": [{ "capability": "register"|"credit"|"feature", "credit_bytes"?, "entitlement_days"?, "feature_name"? }], "bound_email"?, "valid_from"?, "valid_until"?, "max_uses"?, "label"?, "note"? }` — at least one capability required — returns the generated `code`), and `POST /api/v1/admin/codes/{code_id}/revoke`. - Waitlist: `GET /api/v1/admin/waitlist` (paginated list, `?q=` search over email/source; `?format=csv` for a CSV export) — the launch-waitlist signups captured from `POST /api/public/waitlist`. - `GET /api/v1/admin/stats` includes an `e2e_tls` block for end-to-end TLS observability: `activations_7d` (true paid-enable event count from `mgmt.tunnels_archive`, `archived_reason='e2e_enabled'`, trailing 7 days — counts even later-disabled tunnels), `active_tunnels` (tunnels currently on `tls_mode='passthrough'`), `custom_domains_free` (custom domains on the free per-domain path, `cert_source='agent_managed_acme'`), `issuance_headroom_limit` (per-registered-domain enable-guard cap), and `pool_domains[]` — one row per `unpaid_pool` registered domain with `base_domain`, `used` (live agent-managed tunnels inside the issuance window, the exact count the enable guard enforces), and `limit`. ### External Webhooks - `POST /api/v1/email/ses-sns-webhook` Amazon SES feedback receiver delivered through SNS. The server verifies the SNS signature and configured topic ARN before writing local bounce/complaint suppressions. - `POST /api/v1/billing/creem-webhook` Hosted-checkout webhook receiver. Verifies the Creem HMAC signature and records the event; credit is applied asynchronously by the reconciler. Creem is the sole billing provider. Not a customer-facing endpoint. ## MCP/WebMCP Tool Annotations Fetch `/.well-known/mcp-tools.json` for machine-readable tool descriptions. It uses MCP-style `annotations`: - `readOnlyHint`: true for tools that only read state. - `destructiveHint`: true for operations that revoke, delete, suspend, or spend. - `idempotentHint`: true when repeated calls with the same arguments should not create additional state. - `openWorldHint`: true when the operation reaches external systems or accepts public internet input. Annotations are UX hints for agents and clients. Server-side authorization, role checks, token hashing, and audit logging remain the security controls.