# API v1

Base path: `/profiles/:slug/api/v1`

## Related agent-friendly surfaces

These routes sit alongside the tenant-scoped public API and should be kept in sync with product behavior when onboarding or readiness semantics change:

- Public agent manifest: `GET /.well-known/agent.json`
- Human-readable agent guide: `GET /agents`
- Authenticated agent context for the current signed-in account: `GET /api/agent/context`
- Versioned inspect-first agent API: `GET /api/agent/v1/*`

Unlike the tenant-scoped public API below, the authenticated agent context is session-scoped to the current owner account and is not addressed by public profile slug.

This API mirrors the multitenant public profile app. Each request is scoped to a published profile slug.

Example base path:

```text
/profiles/bravo-studio/api/v1
```

## Authentication

- The tenant-scoped public API under `/profiles/:slug/api/v1` is public and does not require `X-API-Key`.
- Invalid or missing key responses below apply to authenticated agent endpoints, not the tenant-scoped public content/chat routes.
- Authenticated agent endpoints return `401` with:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Unauthorized"
  }
}
```

## Error Contract

Error responses use:

```json
{
  "error": {
    "code": "<machine_code>",
    "message": "<human_message>",
    "details": {}
  }
}
```

Agent API v1 commonly returns these machine-readable codes:

- `api_key_missing`
- `invalid_api_key`
- `owner_context_required`
- `account_scope_required`
- `forbidden_account_scope`
- `missing_parameter`
- `account_not_found`
- `draft_not_found`
- `resource_not_found`
- `draft_archived`

Missing required agent parameters return `400` with:

```json
{
  "error": {
    "code": "missing_parameter",
    "message": "Missing required parameter",
    "details": {
      "parameter": "draft[attributes]"
    }
  }
}
```

## Endpoints

### `GET /profiles/:slug/api/v1/content/profile`

Returns the published profile summary for that account.

When an avatar is published, `avatar_url` is an Explore-served Active Storage path backed by durable storage rather than a temporary external file URL.

Response:

```json
{
  "data": {
    "name": "Bravo Studio",
    "tagline": "Design systems and shipping support",
    "bio": "Bravo Studio is another customer account fixture.",
    "location": "Remote",
    "avatar_url": "/rails/active_storage/blobs/proxy/:signed_id/bravo.png"
  }
}
```

### `GET /profiles/:slug/api/v1/content/case_studies`

Returns published case studies for that account.

Response:

```json
{
  "data": [
    {
      "title": "Platform modernization program",
      "summary": "Introduced release guardrails; improved delivery visibility; reduced ops overhead.",
      "outcome": "Reduced release risk for customer-facing changes.",
      "slug": "platform-modernization-program"
    }
  ]
}
```

### `GET /profiles/:slug/api/v1/content/posts`

Returns published writing posts for that account.

Response:

```json
{
  "data": [
    {
      "title": "Shipping practical software in small slices",
      "summary": "Notes on release discipline and pragmatic delivery.",
      "date": "2026-03-01",
      "slug": "shipping-practical-software-in-small-slices"
    }
  ]
}
```

### `GET /profiles/:slug/api/v1/content/experiences`

Returns published experience entries for that account.

Response:

```json
{
  "data": [
    {
      "company": "Bravo Studio",
      "role": "Head of Engineering",
      "location": "London",
      "start_date": "2022",
      "end_date": "",
      "current": true,
      "summary": "Scaled product delivery; led platform quality; managed release flow."
    }
  ]
}
```

### `POST /profiles/:slug/api/v1/chat/messages`

Accepts a chat question and returns a grounded answer plus sources for that profile.

Request:

```json
{
  "message": {
    "question": "What work has Bravo Studio done?"
  }
}
```

Response:

```json
{
  "data": {
    "question": "What work has Bravo Studio done?",
    "answer": "Recent work includes platform modernization and self-serve operations rollout.",
    "error_code": null,
    "sources": [
      {
        "label": "Work",
        "path": "/profiles/bravo-studio/work",
        "href": "/profiles/bravo-studio/work"
      }
    ]
  }
}
```

Unsupported questions must return:

- `answer`: `"Not in the provided material."`
- `sources`: `[]`
- optional `error_code` for provider/runtime issues such as `openai_rate_limited`, `openai_auth_failed`, `openai_timeout`, or `openai_request_failed`

## Notes

- Versioning still follows `/api/v1`.
- The API is now tenant-scoped by profile slug so it matches the public Explore experience.
- Contracts are enforced by request tests in `test/requests/api/v1`.
- Operational status remains outside this tenant surface at `GET /api/content/status`.
- `chat_status` on the ops endpoints still reports backend/readiness/key-present/model for runtime checks.
- `error_tracking_status` on the ops endpoints reports the configured provider, readiness, API-key presence, and runtime environment for internal checks.

## Explore agent API v1

Base path: `/api/agent/v1`

This is a separate inspect-first surface for humans and coding agents. It is read-heavy by default, with explicit owner-only document apply and draft operations.

### Authentication model

Public-safe endpoints:

- `GET /api/agent/v1/profile?slug=:slug`
- `GET /api/agent/v1/content?slug=:slug`

Owner-only endpoints:

- `GET /api/agent/v1/onboarding/status`
- `GET /api/agent/v1/manifest/next_actions`
- `GET /api/agent/v1/publish/preview`
- `GET /api/agent/v1/profile/document`
- `PUT /api/agent/v1/profile/document`
- `GET /api/agent/v1/drafts`
- `GET /api/agent/v1/drafts/:id`
- `POST /api/agent/v1/drafts/:id/apply`
- `GET /api/agent/v1/drafts/:id/apply_preview`
- `PATCH /api/agent/v1/drafts/:id`
- `POST /api/agent/v1/drafts/:id/archive`
- `POST /api/agent/v1/content/propose_update`

Owner-only endpoints use either:

- the current signed-in session account, or
- an account-scoped owner token passed as `X-API-Key`, optionally plus `account=<slug>` or `account_id=<id>` scope when the client wants to be explicit

When `slug` is provided, the public-safe inspect endpoints above do not require an API key. Owner-only endpoints still require the current signed-in session or an account token.

Account tokens are scoped to a single Explore account and should be created from that account's signed-in workspace. Most CLI users should use `explore login` and browser approval first; the token value is mainly for advanced, scripted, or non-interactive owner access.

### Response envelope

```json
{
  "status": "ok",
  "resource": "profile.inspect",
  "generated_at": "2026-03-26T11:00:00Z",
  "account_id": 1,
  "account_slug": "acme-careers",
  "context": {
    "mode": "owner"
  }
}
```

### Endpoints

#### `GET /api/agent/v1/profile`

Returns public profile data, completeness, weak spots, and next safe actions.

- Public mode: pass `slug=:slug` to inspect a published profile without `X-API-Key`.
- Owner mode: omit `slug` and use the current signed-in session, or pass an account token as `X-API-Key` plus optional `account` / `account_id`.

#### `PATCH /api/agent/v1/drafts/:id`

Updates an existing open draft without publishing it.

Request:

```json
{
  "draft": {
    "title": "Refined project draft",
    "summary": "Sharper framing for the same idea.",
    "attributes": {
      "summary": "Sharper framing for the same idea."
    }
  }
}
```

Archived drafts are read-only and return:

```json
{
  "error": {
    "code": "draft_archived",
    "message": "Archived drafts are read-only."
  }
}
```

#### `GET /api/agent/v1/content`

Returns a structured content inventory for projects, posts, experiences, testimonials, and public profile sections.

- Public mode: pass `slug=:slug` to inspect published content without `X-API-Key`.
- Owner mode: omit `slug` and use the current signed-in session, or pass an account token as `X-API-Key` plus optional `account` / `account_id`.

#### `GET /api/agent/v1/onboarding/status`

Returns the current onboarding/setup step, blockers, guidance, and useful internal/public URLs.

#### `GET /api/agent/v1/manifest/next_actions`

Returns the explicit next safe action and the currently allowed action set for the account.

#### `GET /api/agent/v1/profile/document`

Returns the canonical Explore profile document for the owner account as structured JSON. The CLI renders this into YAML for local editing.

#### `PUT /api/agent/v1/profile/document`

Validates and applies a profile document back into the canonical Explore account records. Invalid documents return `422` with path-specific errors under `error.details.errors`.

#### `GET /api/agent/v1/publish/preview`

Returns a safe share-readiness preview and explainable publish impact. It never publishes content.

#### `POST /api/agent/v1/content/propose_update`

Records an audit-friendly content proposal event. V1 stores the proposal for review and does not mutate or publish live content.

#### `POST /api/agent/v1/content/create_draft`

Creates a first-class draft artifact for future content. Drafts are owner-only, never published automatically, and can appear in owner-facing `content.list` / `profile.inspect` responses.

#### `GET /api/agent/v1/drafts`

Lists owner-only drafts with stable metadata and allowed next actions.

#### `GET /api/agent/v1/drafts/:id`

Returns one owner-only draft for review.

#### `POST /api/agent/v1/drafts/:id/apply`

Applies a reviewed draft into Explore data and archives the draft.

- Profile drafts update live account public profile fields.
- Project, post, and experience drafts create new account-backed records.
- This action is audited and does not perform broader publish orchestration.

#### `GET /api/agent/v1/drafts/:id/apply_preview`

Returns a read-only preview of what the draft would change if a future apply step were introduced.

- Profile drafts compare against the current live profile fields.
- Project, post, and experience drafts currently preview creation of a new item because V1 drafts do not yet carry a target record id.

#### `POST /api/agent/v1/drafts/:id/archive`

Marks one owner-only draft as archived so it no longer appears in open-draft pathways.
