# VibeCodingList Agent API auth.md

You are an agent. This document tells you how to register for, use, and
revoke a VibeCodingList Agent API credential. Registration is user-session
provisioning: a signed-in user, or you holding their authenticated session,
creates a scoped API key, and that credential then authenticates you against
`/api/v1` and scoped AI Website Audit reads on `/mcp`.

Base URL: `/api/v1`. Machine-readable contract: `/openapi.json`.
Public MCP needs no API key or CLI installation. Its published Research report
is public; the rolling Data Room has a separate availability gate and is
currently unavailable. [Public setup](/developers#connect).

## Tooling

You can call these endpoints directly, but two things exist to save you the
trouble:

- **CLI** — `npm install -g @vcl-labs/agent-cli`, binary `vcl`.
  Optional for supported account actions; version 0.1.3 has no Research or AI Website Audit commands.
  Source: https://github.com/VCL-Labs/agent-cli
- **Canonical agent skill** — [hosted SKILL.md](/.well-known/agent-skills/vibecodinglist-agent-api/SKILL.md).
  This maintained Markdown guide covers public MCP, scoped review reads and
  account workflows. The external GitHub skill may lag behind it.

## Step 1 - Register (provision a credential)

Both registration paths require the user's authenticated session. An API key
can never mint another API key.

### Dashboard (user-driven)

Sign in at VibeCodingList and open **/me/developer?tab=api**. The page shows
whether account API access is available. Choose
[Manage my projects](/me/developer?tab=api&preset=projects),
[Give feedback](/me/developer?tab=api&preset=feedback),
[AI Website Audit results](/me/developer?tab=api&preset=reviews), or **Read only**,
then review the selected permissions. Use
**Customize permissions** if needed. Name and create the key. The secret is
shown once — keep it private. Keys look like `vcl_sk_<8 hex>_<48 hex>`.

An account may hold ten active keys. Revoked keys do not count against that.

### Programmatic (session-authenticated)

With the user's session, provision a credential directly:

```http
POST /api/me/api-keys HTTP/1.1
Host: vibecodinglist.com
Content-Type: application/json
Authorization: Bearer <the user's session token>
```

```json
{ "name": "my-agent", "scopes": ["projects:read", "feedback:read"] }
```

A `201` response returns `{ "key": { ... }, "plainKey": "vcl_sk_...",
"warning": "..." }`. `plainKey` is the credential and is shown exactly once;
store it immediately. This endpoint accepts only a user session, never an
API key.

## Step 2 - Use the credential

```http
GET /api/v1/me HTTP/1.1
Authorization: Bearer vcl_sk_...
```

Either header works, and both are checked on every request:

```
Authorization: Bearer vcl_sk_...
x-vcl-api-key: vcl_sk_...
```

`x-vcl-api-key` is read first; the bearer header is the fallback. A credential
that does not begin with `vcl_sk_` is ignored by both paths.

## Scopes

Scopes are exact — none implies another, and none is hierarchical. A request to
an endpoint whose scope the key lacks returns `403`.

| Scope | Permits |
| --- | --- |
| `projects:read` | Search listed projects, read one project, list the operator's own projects including pending and rejected ones. |
| `projects:write` | Submit a new project, partially update a project the operator owns, and upload an image. |
| `feedback:read` | Read the feedback on a project. |
| `feedback:write` | Leave feedback on a project the operator does **not** own. |
| `feedback:reply` | Reply to feedback on a project the operator **does** own. |
| `research:read` | Read full permitted Data Room aggregates with an active pass; uses the shared pass allowance on HTTP and MCP. |
| `ai_reviews:read` | Read existing builder-visible AI Website Audit results for projects the operator currently owns; no review creation or writes. |

`GET /api/v1/me` requires no scope and works with any valid key. Use it to
check which operator and scopes a key carries.

## Data Room access over HTTP or MCP

Published Research reports and the limited Data Room preview are free. Once the
separate Data Room publication gate opens, a $29 one-time pass gives 30 days
and 1,000 successful full-data reads. Create a `research:read` key at
[Data Room access](/research/data-room#access). This key works on
`/api/research/explorer` and `POST /mcp` independently of the account API beta;
it does not grant private audit access or writes. See [Research API docs](/research-api.md)
for preview routes, paid CSV/SVG exports, quota and expiry.

## AI Website Audit results over REST or MCP

Create a key with only `ai_reviews:read`. Account API access and AI Website Audit
access must both be enabled. `GET /api/v1/ai-reviews?limit=20&offset=0`
returns `{ reviews, limit, offset, hasMore }`. Limit is an integer from 1 to
50 (default 20), offset is a nonnegative integer (default 0). Increase offset
by limit while hasMore is true. `GET /api/v1/ai-reviews/{id}` accepts a
positive numeric review id and returns `{ review }` with status, scores,
summary, prioritized fixes, findings, timestamps and the existing review link.

For MCP, explicitly configure the same key on `POST /mcp` with
`x-vcl-api-key` or `Authorization: Bearer`. Use a manually configured
client supporting custom HTTP headers. VCL does not provide an MCP OAuth flow.
Browser sessions alone do not unlock private tools. Authorized keys expose
`list_ai_reviews` (the same limit/offset) and `get_ai_review` (numeric id).
Keep the key in private client configuration, never in an agent prompt.

Only builder-visible results for projects you currently own are returned; there
is no team or administrator bypass. Missing and unauthorized review IDs both
return 404. Credentials, exploration/session data, internal metadata, tokens
and admin notes are excluded. No MCP tool starts a paid review or performs
writes. [Run a new review](/me/ai-review) using the existing credit confirmation.

## A key acts as its operator

There is no separate agent identity. A key resolves to the user who created it,
and everything done with it is attributed to that user: submitted projects are
owned by them, and feedback is authored by them, with the key's name recorded
alongside for traceability. A key never confers admin rights, whoever owns it.

Because the operator is the actor, the ordinary rules apply: feedback on your
own project is refused, and so is feedback on a project you are a team member
of.

## `feedback:write` and `feedback:reply` are opposites

They are separate scopes on purpose, and neither implies the other:

- `feedback:write` posts feedback on **someone else's** project and can create
  a pending cash reward.
- `feedback:reply` replies to feedback on **your own** project and never
  creates a reward.

A key that only needs to answer your reviewers should carry `feedback:reply`
alone, so it can never spend from a reward pool. Keys created before
`feedback:reply` existed do not have it; create a new key to use it.

Before submitting feedback, actually test the project with specialist prompts
and browser/testing tools. Include specific observations, reproduction steps
and actionable recommendations backed by evidence. Generic AI-generated reviews
may be flagged. Rewards depend on eligibility, quality and a funded boost;
they are not guaranteed.

## Rate limiting

The daily request budget is per **operator**, not per key. Every key an account
holds shares one bucket, so minting extra keys grants no extra allowance.
Exceeding it returns `429`.

`POST /api/v1/uploads` carries a second, tighter per-operator limit on top of
that budget — image uploads are far more expensive than a JSON call.

## Retrying a write safely

Optional. The JSON write endpoints — `POST /api/v1/projects`,
`PATCH /api/v1/projects/:id`, `POST /api/v1/projects/:id/feedback` and
`POST /api/v1/feedback/:id/replies` — accept an `Idempotency-Key` header.
Send nothing and they behave exactly as before. `POST /api/v1/uploads` does
not take the header: it is a multipart request, and a repeated upload creates a
new object rather than a duplicate row.

Generate one key per logical operation (a UUID is ideal; 1–255 characters), and
reuse it **only** to retry that same operation after a lost or uncertain
response:

```bash
curl -s -X POST https://vibecodinglist.com/api/v1/projects/30/feedback \
  -H "x-vcl-api-key: $VCL_API_KEY" \
  -H "Idempotency-Key: 7f1c2a64-example-4d3b-9e77-000000000001" \
  -H "Content-Type: application/json" \
  -d '{"content": "..."}'
```

If the first attempt already succeeded, the retry writes nothing: it returns
the original status and body, plus an `Idempotent-Replay: true` response
header so you can tell. Keys are remembered for 24 hours, per operator.

- a different request under a key you already used is
  `422 IDEMPOTENCY_KEY_REUSE` — never recycle a key across operations
- retrying while the first attempt is still running is
  `409 IDEMPOTENCY_KEY_IN_FLIGHT`; wait, then retry
- a blank key, or one longer than 255 characters, is
  `400 INVALID_IDEMPOTENCY_KEY`
- an attempt that failed releases the key, so that same key is the right one to
  retry with

## Step 3 - Revoke

Revoke from **/me/developer**, or with the user's session:

```http
DELETE /api/me/api-keys/:id HTTP/1.1
Authorization: Bearer <the user's session token>
```

Revocation is immediate — the next request with
that key returns `401`. Revoked keys stay listed so past attribution remains
readable. There is no way to un-revoke; issue a new key instead.

## These keys are not project-intelligence keys

VibeCodingList has a separate, project-scoped API at
`/api/project-intelligence/v1` which uses `vcl_pi_` keys sent as
`x-project-api-key`. The two systems share no credentials:

- a `vcl_sk_` key sent to a project-intelligence endpoint is rejected `401`
- a `vcl_pi_` key sent to `/api/v1` is rejected `401`

Each namespace matches its own key prefix and hashes with its own salt. Pick the
API first, then use its key.
See the [project integration reference](/project-intelligence-api.md).

## Worked example

```bash
curl -s https://vibecodinglist.com/api/v1/me \
  -H "Authorization: Bearer $VCL_API_KEY"
```

```json
{
  "operator": { "userId": 1234, "username": "ada" },
  "key": { "id": 7, "scopes": ["projects:read", "feedback:read"] }
}
```

Reading a page of listed projects, which needs `projects:read`:

```bash
curl -s "https://vibecodinglist.com/api/v1/projects?q=notion&limit=5" \
  -H "x-vcl-api-key: $VCL_API_KEY"
```

## Errors

Failures return `{ "error": "..." }`. Some handlers add a stable `"code"`;
review handlers return only `error`, as do the authentication and scope
layers. Do not require `code` for review failures, `401`, or
`403`-from-scope responses. Disabled access gates return 404 with
`FEATURE_DISABLED`.
