API
A small REST API over the same things the dashboard does: upload a manifest, list apps, read alerts, fetch a fix plan. JSON in, JSON out.
Base URL and auth
Every call goes to https://vibebeacon.app/api/v1 and carries a bearer token.
Authorization: Bearer vb_live_your_key_here- A full key (
vb_live_) belongs to a user, not an app, so one key covers everything that user owns or has been given access to. - Manifest uploads also accept a scoped token: an app-bound
vb_up_upload token, or a one-timevb_new_onboarding token. Detail on all three shapes is on the Tokens and login page. - Keys are shown once. We store a SHA-256 hash and a display prefix.
- API access is included on every account, including the free app. Creating an app beyond your free one queues it paused rather than rejecting the upload; see
POST /manifestsbelow.
Rate limit
429 with a Retry-After header in seconds, plus X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on every response. Creating new apps has its own limit: 10 in a rolling 24 hours per account.Endpoints
| Method | Path | Body | Response |
|---|---|---|---|
| GET | /apps/:id/findings | none | schemaVersion, app, scanDate, processing, processingReason, score, findings, verificationLimits (full credential required) |
| POST | /manifests | manifest v1 | appId, appSlug, componentCount, newAlerts, openAlerts, openNews, dashboardUrl, report |
| GET | /apps | none | apps: id, slug, name, lastManifestAt, openAlerts, plan, health, group, tags |
| GET | /apps/:id | none | one app: stack by kind, alerts, health, freshness per component |
| PATCH | /apps/:id | name, groupId, tags | the updated app |
| GET | /apps/:id/alerts | none | alerts: id, severity, title, advisoryId, component, fixedIn, status, publishedAt, url; plus next_cursor |
| GET | /apps/:id/alerts/:alertId | none | one alert, plus verdictReason, verdictSource, verdictAt |
| POST | /apps/:id/alerts/:alertId | status, snoozeUntil, reason, source | the updated alert |
| GET | /apps/:id/fix-plan | none | app info, bands of packages to upgrade, a review prompt |
| POST | /apps/:id/upload-token | none | token, expiresAt (session or full key of the app's owner) |
| GET | /groups | none | groups: id, name, slug, colour, appCount, severity counts, worstApp, lastUploadAt |
| POST | /device/start | hostname (optional) | deviceCode, userCode, verificationUrl, expiresIn, interval (no auth) |
| POST | /device/poll | deviceCode | status: pending, denied or expired, or approved with apiKey (no auth) |
| POST | /apps/onboarding-token | scope: one or all | token, expiresAt, maxApps (session cookie only) |
| GET | /me | none | email, plan, appCount, appLimit |
| GET | /me/export | none | every app, manifest, alert and preference this key can read |
GET /health also exists outside /v1, with no auth: it answers {"ok":true}.
Upload a manifest
The upload creates the app if it is new to you, and updates it otherwise, matched on app.appId when present, or the slug of app.name otherwise. Matching runs synchronously, so the counts in the response are real, not queued.
curl -X POST https://vibebeacon.app/api/v1/manifests \
-H "Authorization: Bearer vb_live_your_key_here" \
-H "Content-Type: application/json" \
--data @vibebeacon.json{
"appId": "0f2c9a1e-6d1b-4a5f-9c3e-2b7d8a4f1c05",
"appSlug": "my-coffee-shop-pos",
"componentCount": 214,
"newAlerts": 0,
"openAlerts": 5,
"openNews": 0,
"dashboardUrl": "https://vibebeacon.app/apps/my-coffee-shop-pos",
"report": {
"schemaVersion": 1,
"inventory": "refreshed",
"processing": "pending",
"processingReason": "Matching has not completed for this scan.",
"baseline": "unavailable",
"counts": null,
"changes": [],
"scoreBefore": null,
"scoreAfter": null,
"scopeChanged": null,
"verificationLimits": "Version matches public advisory data. Runtime use, exploit conditions and call-path safety are not verified. Development dependencies can affect builds and CI. Missing packages are not verified fixes."
}
}Past your free app with no subscription, this still returns 200 or 201: the app is stored with status: "paused", reason: "over_limit" and a billingUrl, rather than rejected outright. Activating it later runs the match against the manifest already on file.
List and read apps
curl https://vibebeacon.app/api/v1/apps \
-H "Authorization: Bearer vb_live_your_key_here"For one app's full stack, alerts and health breakdown:
curl https://vibebeacon.app/api/v1/apps/$APP_ID \
-H "Authorization: Bearer vb_live_your_key_here"To rename an app, move it into a group, or replace its tags:
curl -X PATCH https://vibebeacon.app/api/v1/apps/$APP_ID \
-H "Authorization: Bearer vb_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"Coffee Shop POS","tags":["backend","stripe"]}'Groups
Each group rolls up its apps: open alert counts by severity, the worst app in it, and the most recent upload across the group. Full behaviour is on the Groups and tags page.
curl https://vibebeacon.app/api/v1/groups \
-H "Authorization: Bearer vb_live_your_key_here"Read and change an alert
Filter with ?status=open, acknowledged, resolved, snoozed or all. Leave it off to get open alerts.
curl "https://vibebeacon.app/api/v1/apps/$APP_ID/alerts?status=open" \
-H "Authorization: Bearer vb_live_your_key_here"The list is paginated, newest first. limit takes a whole number from 1 to 200 and defaults to 50; cursor takes the next_cursor value from the previous response. Keep following next_cursor until it comes back null, which is the last page. Sending neither parameter gives you the first 50 alerts.
Set the status to acknowledged, resolved, snoozed or not_applicable. Snoozing takes a snoozeUntil timestamp; marking not applicable takes a reason of 10 to 500 characters. A resolved or not-applicable alert reopens only if a later manifest shows a different running version.
curl -X POST https://vibebeacon.app/api/v1/apps/$APP_ID/alerts/$ALERT_ID \
-H "Authorization: Bearer vb_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"status":"snoozed","snoozeUntil":"2026-10-01T00:00:00Z"}'Fetch a fix plan
The same plan the app page and the review CLI command show: open vulnerability alerts grouped by band (Fix now, Fix soon, When convenient, Dev tooling, No fix yet), each package carrying the running and target version, the upgrade command, and whether the fix is a major version bump.
curl https://vibebeacon.app/api/v1/apps/$APP_ID/fix-plan \
-H "Authorization: Bearer vb_live_your_key_here"Device login and scoped tokens
POST /device/start and POST /device/poll need no authentication at all; they are what vibebeacon login calls under the hood.
curl -X POST https://vibebeacon.app/api/v1/device/start \
-H "Content-Type: application/json" \
-d '{"hostname":"rhys-laptop"}'An app owner can mint a 7-day upload token scoped to one app, or an onboarding token scoped to one or all new apps, from a signed-in session:
curl -X POST https://vibebeacon.app/api/v1/apps/$APP_ID/upload-token \
-H "Authorization: Bearer vb_live_your_key_here"curl -X POST https://vibebeacon.app/api/v1/apps/onboarding-token \
-H "Cookie: <your browser session>" \
-H "Content-Type: application/json" \
-d '{"scope":"one"}'Who am I, and exporting your data
/me is useful for checking a key works and seeing how close you are to your app limit.
curl https://vibebeacon.app/api/v1/me \
-H "Authorization: Bearer vb_live_your_key_here"/me/export returns everything a full key can read: your apps, their latest manifests, alerts and notification preferences, including apps shared with you and the role you hold on each.
curl https://vibebeacon.app/api/v1/me/export \
-H "Authorization: Bearer vb_live_your_key_here"Member access
A full key carries its owner's access, including any app or group someone else has shared with them. An editor's key can read, upload manifests and change alert status; a viewer's key can only read. Only the owner can rename, delete, pause, archive, or change links, group, tags, members or billing. See Sharing.
Errors
Every error uses the same envelope.
{ "error": { "code": "invalid_manifest", "message": "dependencies[12].version is required" } }| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request / invalid_manifest | Bad input. The message names the field. |
| 401 | unauthorized | Missing, malformed or revoked API key. |
| 402 | plan_limit | Past the free app with no subscription. The response carries a checkout URL. |
| 404 | not_found | No app or alert with that id belongs to this key's owner. |
| 413 | payload_too_large | Over 512 KB or over 2000 dependencies. |
| 429 | rate_limited / too_many_apps | Over 60 requests a minute, or over 10 new apps in a rolling 24 hours. Honour the Retry-After header. |
| 500 | internal_error | Our fault. Safe to retry with backoff. |
Notes worth knowing
- Uploading the same manifest twice is safe. Alerts are keyed on app, advisory and component, so nothing duplicates.
- Ids are uuids. Slugs are stable for the life of the app and are what the dashboard URLs use.
- Timestamps are ISO 8601 in UTC.
- The manifest shape is documented on the manifest format page.
Scan comparison and findings contract
POST /manifests adds report with schemaVersion 1, inventory, processing, processingReason, baseline, counts, changes, scoreBefore, scoreAfter, scopeChanged and verificationLimits. counts contains new, resolved, reopened, remaining, remainingNews and remainingEol. remaining is open vulnerabilities, matching openAlerts and the UI. remainingNews is open news and is not mixed into remaining. The upload also returns openNews, so clients that previously treated openAlerts as all-open can add the two numbers. changes identifies each alert, advisory, package, change and reason. Legacy newAlerts combines created and reopened alerts; use report for separate counts.
GET /apps/:id/findings requires a full credential and never uploads, rematches or writes score snapshots or verdicts. Unknown paths remain null on the server. Processing can be pending even after inventory is accepted. See the JSON and evidence reference for field meanings, null handling and local CLI enrichment.