Profile
| Method | Endpoint | Description |
|---|---|---|
| GET | /me | Current identity and role |
| PATCH | /me | Update name, avatar, and the panel's persisted UI state (ui_state JSON — per-user view prefs) |
Team members
Invite teammates with roles. Roles gate panel sections; API keys map onto the same roles (read ≈ viewer, write ≈ editor for content). Team management itself requires a logged-in user session, not an API key.
| Method | Endpoint | Description |
|---|---|---|
| GET | /users | List team members |
| POST | /users/invite | Invite by email — they set their own password |
| PATCH | /users/:uid/role | Change role (owner only) |
| DELETE | /users/:uid | Remove from team (owner only) |
| Role | Can do |
|---|---|
owner | Everything, including team and API key management. |
admin | Content and settings, plus support tickets. |
editor | Content management (sites, offers, articles…). |
viewer | Read-only. |
API keys
The keys documented on the overview page are manageable from the panel (API Keys page) or by an owner session:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api-keys | List keys (prefix, name, scope, last used — never the secret) |
| POST | /api-keys | Create — full key returned once |
| DELETE | /api-keys/:kid | Revoke instantly (204) |
curl -X POST $API/api-keys \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"name":"stats dashboard","scope":"read"}'
# → {"id":"…","key":"vd_live_…","prefix":"vd_live_7f2a…"} — store "key" NOW, it won't be shown again
We store only a SHA-256 hash of the key. Sessions authenticated with a key cannot manage keys or team members — a leaked key can't escalate.
Support tickets
| Method | Endpoint | Description |
|---|---|---|
| GET | /tickets | Your tickets (subject, status, dates) |
| POST | /tickets | Open one — {"subject", "body"} |
| GET | /tickets/:tid | Ticket detail with the full message thread |
| POST | /tickets/:tid/messages | Reply — {"body"}; notifies our team |
| PATCH | /tickets/:tid | Set status — {"status": "closed"} to resolve |
Plan
GET /plan returns your current plan and its feature flags — the same flags that gate link health, cloaking, postbacks, comparison tables, media library, lead generation and the audit log. Check it before scripting against gated endpoints; a missing feature returns 403 with the flag name.
Subscription expiry enforcement. The live subscription is the effective source of truth: while it is in period its plan wins, and once current_period_end passes the account falls back to Free limits. The response exposes this via subscription_expired (bool) and expired_plan_name. Existing content keeps serving; creates and gated features return 403 until the subscription is renewed.
Limits. max_sites and max_offers_per_site are hard caps enforced on create (403 with a plan-limit error). The Enterprise plan reports -1 for both, meaning unlimited — treat any negative limit as "no cap".
Audit log
GET /audit-log?limit=200 — who changed what, when: every create/update/delete with the actor (user or API key), the action, and the diff summary. Plan-gated: audit_log. Use it to answer "which integration renamed this offer at 3 AM".