Skip to content

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.

header
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-time vb_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 /manifests below.

Rate limit

Sixty requests a minute per key. Going over returns 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

MethodPathBodyResponse
GET/apps/:id/findingsnoneschemaVersion, app, scanDate, processing, processingReason, score, findings, verificationLimits (full credential required)
POST/manifestsmanifest v1appId, appSlug, componentCount, newAlerts, openAlerts, openNews, dashboardUrl, report
GET/appsnoneapps: id, slug, name, lastManifestAt, openAlerts, plan, health, group, tags
GET/apps/:idnoneone app: stack by kind, alerts, health, freshness per component
PATCH/apps/:idname, groupId, tagsthe updated app
GET/apps/:id/alertsnonealerts: id, severity, title, advisoryId, component, fixedIn, status, publishedAt, url; plus next_cursor
GET/apps/:id/alerts/:alertIdnoneone alert, plus verdictReason, verdictSource, verdictAt
POST/apps/:id/alerts/:alertIdstatus, snoozeUntil, reason, sourcethe updated alert
GET/apps/:id/fix-plannoneapp info, bands of packages to upgrade, a review prompt
POST/apps/:id/upload-tokennonetoken, expiresAt (session or full key of the app's owner)
GET/groupsnonegroups: id, name, slug, colour, appCount, severity counts, worstApp, lastUploadAt
POST/device/starthostname (optional)deviceCode, userCode, verificationUrl, expiresIn, interval (no auth)
POST/device/polldeviceCodestatus: pending, denied or expired, or approved with apiKey (no auth)
POST/apps/onboarding-tokenscope: one or alltoken, expiresAt, maxApps (session cookie only)
GET/menoneemail, plan, appCount, appLimit
GET/me/exportnoneevery 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.

bash
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
200 response
{
  "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

bash
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:

bash
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:

bash
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.

bash
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.

bash
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.

bash
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.

bash
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.

bash
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:

bash
curl -X POST https://vibebeacon.app/api/v1/apps/$APP_ID/upload-token \
  -H "Authorization: Bearer vb_live_your_key_here"
bash
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.

bash
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.

bash
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
{ "error": { "code": "invalid_manifest", "message": "dependencies[12].version is required" } }
StatusCodeMeaning
400invalid_request / invalid_manifestBad input. The message names the field.
401unauthorizedMissing, malformed or revoked API key.
402plan_limitPast the free app with no subscription. The response carries a checkout URL.
404not_foundNo app or alert with that id belongs to this key's owner.
413payload_too_largeOver 512 KB or over 2000 dependencies.
429rate_limited / too_many_appsOver 60 requests a minute, or over 10 new apps in a rolling 24 hours. Honour the Retry-After header.
500internal_errorOur 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.