Manifest format
vibebeacon.json version 1. This is the only thing we accept, and the only thing we hold. If a field is not on this page, the validator rejects it.
A complete example
{
"schemaVersion": 1,
"generator": { "name": "vibebeacon-cli", "version": "0.1.0" },
"generatedAt": "2026-09-03T10:00:00Z",
"app": {
"name": "My Coffee Shop POS",
"description": "Orders and payments for one cafe",
"kind": "web",
"hosting": ["vercel", "supabase"],
"aiTools": ["cursor", "claude-code"]
},
"runtimes": [
{ "name": "node", "version": "22.11.0" }
],
"frameworks": [
{ "name": "next", "version": "15.1.0", "ecosystem": "npm" }
],
"dependencies": [
{ "name": "react", "version": "18.3.1", "ecosystem": "npm", "direct": true, "dev": false },
{ "name": "stripe", "version": "17.4.0", "ecosystem": "npm", "direct": true, "dev": false }
],
"services": [
{ "name": "stripe" },
{ "name": "supabase" }
],
"infrastructure": [
{ "name": "postgres", "version": "16" }
]
}Top level
| Field | Type | Required | Notes |
|---|---|---|---|
| schemaVersion | number | Yes | Always 1 today. We reject anything we do not understand rather than guessing. |
| generator | object | Yes | name and version of whatever produced the file. Use "manual" if a person or an AI tool wrote it by hand. |
| generatedAt | string | Yes | ISO 8601 in UTC. Used to work out manifest age and to nudge you when it goes stale. |
| app | object | Yes | Describes the app itself. See below. |
| packageManager | enum | No | npm, pnpm, yarn, bun, pip, poetry, uv, cargo, go, bundler or composer. Absent means unknown; it only changes which install command an alert suggests. |
| workspaces | object | No | count and ecosystems only, for a project spread over several folders. Absent when one lockfile was read. It never holds a folder name or a path. |
| runtimes | array | No | Language and platform runtimes, such as node or python. |
| frameworks | array | No | Frameworks worth calling out separately, such as next or django. |
| dependencies | array | No | Packages. Up to 2000 entries. |
| services | array | No | Third-party services you call, such as stripe or supabase. Name only. |
| infrastructure | array | No | Databases, proxies, container bases, such as postgres 16 or docker. |
The app object
| Field | Type | Required | Notes |
|---|---|---|---|
| app.appId | string (uuid) | No | The app's id, printed back into the manifest file after your first upload. When present and you still own it, this identifies the app instead of the name; a mismatched owner is rejected rather than silently reassigned. |
| app.name | string | Yes | 1 to 80 characters. Slugged to identify the app when appId is absent. Uploading the same slug updates that app. |
| app.description | string | No | Up to 300 characters. Only for your own benefit in the dashboard. |
| app.kind | enum | Yes | web, api, mobile, desktop, cli or other. |
| app.hosting | string[] | No | Provider names such as vercel or fly. Free text up to 40 characters each. |
| app.aiTools | string[] | No | Which tools built it, such as cursor or lovable. Used for aggregate stats only. |
| app.group | string | No | 1 to 50 characters. Assigns the app to a group, creating it if it does not exist yet. |
| app.tags | string[] | No | Up to 20 tags, 1 to 40 characters each. Replaces the app's tags outright, so send the full list, not just new ones. |
Component entries
Entries in runtimes, frameworks, dependencies, services and infrastructure share one shape. Services often carry a name and nothing else, which is fine.
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | Yes | Package or runtime name, lower-cased on our side before matching. |
| version | string | Yes | Exact resolved version where possible, such as 18.3.1. |
| ecosystem | enum | Yes | Required on dependencies and frameworks. Runtimes, services and infrastructure do not carry one. |
| direct | boolean | No | True if it is in your package file rather than pulled in by something else. Defaults to true. |
| dev | boolean | No | True for dev-only dependencies. Scanned by default. Instant email and health scoring skip these until you opt in; the app page groups them into their own "Dev tooling" band instead of hiding them. |
| resolved | boolean | No | Set false when the version came from a range rather than a lockfile. We then match conservatively. |
Identity, groups, tags and package manager
A manifest can carry more than the stack. Once an app has an id, the CLI writes it back into the file on disk so later uploads land on the same app when you rename it. group and tags can also be set from the manifest, or later from the dashboard; see Groups and tags. packageManager only changes which install command an alert suggests, for example pnpm add instead of npm install.
A project with a frontend folder and a backend folder is one app, and the scan reads the lockfile in each of them. When it read more than one, the manifest carries a workspaces object with two fields: how many lockfiles were read and which ecosystems they covered. That is all it can hold. Folder names and paths stay on your machine.
{
"schemaVersion": 1,
"generator": { "name": "vibebeacon-cli", "version": "0.1.0" },
"generatedAt": "2026-09-03T10:00:00Z",
"app": {
"appId": "0f2c9a1e-6d1b-4a5f-9c3e-2b7d8a4f1c05",
"name": "My Coffee Shop POS",
"kind": "web",
"hosting": ["vercel", "supabase"],
"aiTools": ["cursor", "claude-code"],
"group": "Client work",
"tags": ["backend", "stripe"]
},
"packageManager": "pnpm",
"runtimes": [
{ "name": "node", "version": "22.11.0" }
],
"dependencies": [
{ "name": "react", "version": "18.3.1", "ecosystem": "npm", "direct": true, "dev": false }
]
}Links are never in the manifest
Ecosystems we accept
npm, PyPI, Go, crates.io, RubyGems, Packagist, Maven, NuGet, Hex, Pub. These are spelled the way OSV spells them, which is why the capitals look inconsistent.
Versions
- Give us the exact resolved version from the lockfile wherever you can.
- A range like
^18.2.0is stripped to18.2.0and the entry is marked"resolved": false. - Unresolved entries only produce alerts when every version in the range is affected, so you get fewer false alarms and occasionally a missed match.
- npm, Go and crates versions are compared with semver. PyPI uses PEP 440 comparison.
Limits
- 2000 dependencies, 50 runtimes, 100 frameworks, 100 services, 100 infrastructure entries.
- 512 KB for the whole file.
- 80 characters for the app name, 300 for the description, 40 per hosting entry.
- 50 characters for a group name, 20 tags of up to 40 characters each.
- Sixty uploads a minute per API key, and 10 new apps a day per account.
What gets rejected
Secret-shaped strings
sk_live_ and ghp_ prefixes, JWT shapes, runs of 32 or more hex or base64 characters, password=, and credentials inside URLs such as ://user:pass@. A match means the whole upload is refused with an error naming the field. Nothing is stored, and the value is not written to our logs.- Unknown top-level fields. We do not silently keep extras.
- A
schemaVersionwe do not support. - Absolute or relative file paths anywhere in a value.
- Repository URLs and database names.
The reasoning behind all of this is on the privacy page.
Versions of the format itself
Version 1 is what exists today. Version 2 will add an optional components array so you can import a CycloneDX SBOM if you already produce one. Version 1 files will keep working.
Understand the next scan
Inventory refresh is separate from verified remediation. Keep app identity and scan scope consistent, inspect changed alert IDs and reasons, and use the real score explanation. Read scan results and agent access for pending processing, read-only findings JSON and authentication errors. Choose explicit handoff and upload permissions in Fixing an alert.