{"openapi":"3.1.0","info":{"title":"VibeAdmin API","version":"1.0.0","description":"Sync users, send events and read stats. Authenticate with a project API key: `Authorization: Bearer adm_live_…` (production) or `adm_test_…` (development/preview). A key only ever sees its own environment. Rate limit: 600 requests/minute per key."},"servers":[{"url":"https://www.easy-admin.solutions"}],"security":[{"bearer":[]}],"paths":{"/v1/users":{"get":{"summary":"List users","operationId":"listUsers","description":"Scope: `users.read`.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"search","in":"query","schema":{"type":"string"},"description":"Matches name, email, ids and custom fields."},{"name":"sort","in":"query","schema":{"type":"string"},"example":"createdAt:desc","description":"`field:asc|desc`; fields: joinedAt, email, plan, … or `meta.<field>`."},{"name":"verified","in":"query","schema":{"type":"boolean"}},{"name":"plan","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string","enum":["active","suspended","deleted"]}},{"name":"role","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"inactive_days","in":"query","schema":{"type":"number"},"description":"Last active more than N days ago (or never)."},{"name":"filter","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true,"description":"Repeatable `field:op:value`, e.g. `meta.company:eq:Acme` or `metric.workflows:gt:10`. Ops: eq, neq, gt, gte, lt, lte, contains, is_null, not_null, older_than_days, within_days."}],"responses":{"200":{"description":"Users","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create or update a user","operationId":"upsertUser","description":"Upsert keyed by your user id: fields you omit are left unchanged and `metadata` keys are merged (`null` removes a key). Scope: `users.write`. Fails with 409 for users managed by an integration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/bulk":{"post":{"summary":"Upsert up to 500 users","operationId":"upsertUsers","description":"Same merge semantics as the single upsert. Valid users are saved; invalid ones are reported by index. Scope: `users.write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["users"],"properties":{"users":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/UserInput"}}}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"type":"object","properties":{"upserted":{"type":"integer"},"created":{"type":"integer"},"updated":{"type":"integer"},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/Rejected"}}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{id}":{"get":{"summary":"Get a user","operationId":"getUser","description":"Scope: `users.read`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Your own user id (preferred) or the VibeAdmin user id."}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a user","operationId":"updateUser","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Your own user id (preferred) or the VibeAdmin user id."}],"description":"Scope: `users.write`. For users synced from an integration the change is written back to your app (name, email, metadata) and requires write access to have been granted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserInput"}}}},"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a user","operationId":"deleteUser","description":"Scope: `users.delete`. Removes the user in your source app when it comes from an integration.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Your own user id (preferred) or the VibeAdmin user id."}],"responses":{"200":{"description":"Deleted"},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{id}/suspend":{"post":{"summary":"Suspend a user","operationId":"suspendUser","description":"Scope: `users.write`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Your own user id (preferred) or the VibeAdmin user id."}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/users/{id}/restore":{"post":{"summary":"Restore a suspended user","operationId":"restoreUser","description":"Scope: `users.write`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Your own user id (preferred) or the VibeAdmin user id."}],"responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/broadcasts":{"get":{"summary":"List broadcasts","operationId":"listBroadcasts","description":"Newest first, with delivery stats. Scope: `broadcast.read`.","responses":{"200":{"description":"Broadcasts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Broadcast"}}}}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a draft broadcast","operationId":"createBroadcast","description":"Creates a draft; nothing is sent until you call the send endpoint. Merge tags: `{{first_name|there}}`, `{{name}}`, `{{email}}`, `{{plan}}`, `{{meta.<field>}}`. Marketing and product-update emails get an unsubscribe footer automatically. Scope: `broadcast.create`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/broadcasts/{id}":{"get":{"summary":"Get a broadcast","operationId":"getBroadcast","description":"Includes the email body and stats. Scope: `broadcast.read`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Broadcast","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Broadcast"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/broadcasts/{id}/send":{"post":{"summary":"Send a draft broadcast","operationId":"sendBroadcast","description":"Irreversible. Pass the recipient count you expect (shown in the dashboard's pre-send summary); if the audience has changed the call fails with 409 so nothing is sent by surprise. Sending is asynchronous: poll the broadcast for status and stats. Scope: `broadcast.send`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirmRecipients"],"properties":{"confirmRecipients":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"Sending started","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["sending"]},"recipients":{"type":"integer"}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events":{"get":{"summary":"List events","operationId":"listEvents","description":"Newest first. Scope: `analytics.read`.","parameters":[{"name":"name","in":"query","schema":{"type":"string"}},{"name":"userId","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"integer"},"description":"`nextCursor` from the previous page."}],"responses":{"200":{"description":"Events","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"nextCursor":{"type":["integer","null"]}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Send events","operationId":"trackEvents","description":"Send one event, or `{ \"events\": [...] }` (up to 500). Use `idempotencyKey` to make retries safe. Scope: `events.write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EventInput"},{"type":"object","required":["events"],"properties":{"events":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/EventInput"}}}}]}}}},"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResult"}}}},"207":{"description":"Partially accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResult"}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/event-definitions":{"get":{"summary":"List discovered event names","operationId":"listEventDefinitions","description":"Scope: `analytics.read`.","responses":{"200":{"description":"Definitions","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stats":{"get":{"summary":"Headline statistics","operationId":"getStats","description":"Scope: `analytics.read`.","responses":{"200":{"description":"Stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"post":{"summary":"Inbound webhook","operationId":"receiveWebhook","security":[],"description":"Authenticated by signature, not an API key. Send header `x-vibeadmin-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" using the webhook secret>`; requests older than 5 minutes are rejected. Body: `{ \"type\": \"user.created|user.updated|user.deleted|payment.succeeded|<custom>\", \"userId\": \"…\", \"data\": {…}, \"id\": \"optional idempotency id\" }`. Typed paths (`/user-created`, `/user-updated`, `/user-deleted`, `/payment`, `/event`) are also accepted and then `type` may be omitted.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"x-vibeadmin-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Processed"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Project API key (adm_live_… / adm_test_…)"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"BroadcastInput":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"subject":{"type":"string","maxLength":200},"previewText":{"type":"string","maxLength":200},"html":{"type":"string","description":"Email body (sanitised: no scripts, forms or remote CSS)."},"text":{"type":"string","description":"Plain-text alternative; generated from html when empty."},"category":{"type":"string","enum":["marketing","product_updates","security","transactional"],"default":"marketing","description":"Marketing and product updates honour unsubscribes; security and transactional do not."},"audience":{"oneOf":[{"type":"object","properties":{"type":{"const":"all"}}},{"type":"object","properties":{"type":{"const":"filter"},"rules":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","example":"plan"},"op":{"type":"string","example":"eq"},"value":{}}}}}}]},"onlyVerified":{"type":"boolean","default":false}}},"Broadcast":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"subject":{"type":"string"},"previewText":{"type":"string"},"category":{"type":"string"},"status":{"type":"string","enum":["draft","sending","sent","failed","canceled"]},"audience":{"type":"object"},"onlyVerified":{"type":"boolean"},"totalRecipients":{"type":"integer"},"error":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"html":{"type":"string"},"text":{"type":"string"},"stats":{"type":"object","properties":{"recipients":{"type":"integer"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"opened":{"type":"integer"},"clicked":{"type":"integer"},"bounced":{"type":"integer"},"complained":{"type":"integer"},"unsubscribed":{"type":"integer"},"failed":{"type":"integer"},"rates":{"type":"object","description":"Percentages."}}}}},"Rejected":{"type":"object","properties":{"index":{"type":"integer"},"error":{"type":"string"}}},"UserInput":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Your own id for the user."},"email":{"type":"string","format":"email"},"name":{"type":"string"},"avatar":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"verified":{"type":"boolean"},"role":{"type":"string"},"plan":{"type":"string"},"country":{"type":"string"},"status":{"type":"string","enum":["active","suspended","deleted"]},"signupSource":{"type":"string"},"lastLoginAt":{"type":"string","format":"date-time"},"lastActiveAt":{"type":"string","format":"date-time"},"metadata":{"type":"object","additionalProperties":{"type":["string","number","boolean","null"]},"description":"Custom fields; max 50 keys. Become columns and filters automatically. null removes a key (PATCH)."}}},"User":{"type":"object","properties":{"id":{"type":"string"},"externalId":{"type":"string"},"provider":{"type":"string"},"email":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"createdAt":{"type":["string","null"],"format":"date-time"},"verified":{"type":"boolean"},"role":{"type":["string","null"]},"status":{"type":"string"},"plan":{"type":["string","null"]},"country":{"type":["string","null"]},"signupSource":{"type":["string","null"]},"lastLoginAt":{"type":["string","null"],"format":"date-time"},"lastActiveAt":{"type":["string","null"],"format":"date-time"},"metadata":{"type":"object"},"metrics":{"type":"object","additionalProperties":{"type":"number"}},"syncedAt":{"type":"string","format":"date-time"}}},"EventInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,100}$","example":"workflow.created"},"userId":{"type":"string","description":"Your user id."},"properties":{"type":"object","description":"Up to 16KB. `value` (number) feeds sum/max/min/latest statistics; `amount`/`currency` feed revenue on payment.* events."},"occurredAt":{"type":"string","format":"date-time","description":"Defaults to now. Max 1 day in the future, 2 years in the past."},"idempotencyKey":{"type":"string","maxLength":200}}},"Event":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"userId":{"type":["string","null"]},"properties":{"type":"object"},"occurredAt":{"type":"string","format":"date-time"},"source":{"type":"string"}}},"IngestResult":{"type":"object","properties":{"accepted":{"type":"integer"},"duplicates":{"type":"integer"},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/Rejected"}}}},"Stats":{"type":"object","properties":{"totalUsers":{"type":"integer"},"newUsers7d":{"type":"integer"},"newUsersPrev7d":{"type":"integer"},"growthPct30d":{"type":["number","null"]},"activeUsers30d":{"type":"integer"},"verifiedPct":{"type":"number"},"paidUsers":{"type":"integer"},"revenue30d":{"type":"number"},"revenueTotal":{"type":"number"},"currency":{"type":["string","null"]}}}}}}