# Brand voice

4 endpoints.

## Read a workspace's brand-voice profile

```http
GET https://orbitwrite.com/api/v1/brand-voice
```

Any member may read. This is the grounding an AI generation uses — brand description, audience, product context, tone, style rules, exemplars and the distilled learned memo.

**Authentication:** session cookie.

### Query parameters

- `organizationId` (string, required)

### Responses

#### 200

The workspace's profile, or null

- `ok` (boolean, required, one of `true`)
- `profile` (BrandVoiceProfile | any | null, required) — Null when the workspace has never configured a profile — a legitimate answer, not an error.

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

## Seed a workspace's default tone (onboarding)

```http
POST https://orbitwrite.com/api/v1/brand-voice
```

Creates a profile carrying only the chosen tone. Any member may seed — it is part of onboarding — but an existing profile is never overwritten.

**Authentication:** session cookie.

### Request body (`application/json`)

- `organizationId` (string, required)
- `defaultTone` (string, required, one of `professional`, `casual`, `bold`, `friendly`, `thought_leader`, `playful`, `hype`, `punchy`)

### Responses

#### 200

The profile, and whether this call created it

- `ok` (boolean, required, one of `true`)
- `seeded` (boolean, required) — False when a profile already existed and was left untouched — so a caller isn't left assuming its tone was applied.
- `profile` (BrandVoiceProfile | any | null, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

## Save a workspace's brand-voice profile

```http
PUT https://orbitwrite.com/api/v1/brand-voice
```

Requires `brandVoice:update` (owner/admin/manager). A full save of the content fields; `learnedMemo` and the AI toggles fall back to their stored values when omitted, so a partial client never wipes what the distillation job learned or silently turns learning off.

**Authentication:** session cookie.

### Request body (`application/json`)

- `brandDescription` (string, optional)
- `audience` (string, optional)
- `productContext` (string, optional)
- `defaultTone` (string | null, optional, one of `professional`, `casual`, `bold`, `friendly`, `thought_leader`, `playful`, `hype`, `punchy`, `null`)
- `styleRules` (object, optional)
  - `dos` (string[], optional)
  - `donts` (string[], optional)
  - `bannedPhrases` (string[], optional)
  - `emojiPolicy` (string, optional, one of `never`, `sparing`, `freely`)
  - `hashtagPolicy` (string, optional, one of `never`, `sparing`, `freely`)
  - `ctaPolicy` (string, optional, one of `never`, `when_relevant`, `always`)
  - `language` (string, optional)
- `exemplars` (object[], optional)
  - `id` (string, required)
  - `text` (string, required)
  - `note` (string, optional)
- `learnedMemo` (string | null, optional)
- `aiLearningEnabled` (boolean, optional)
- `useVoiceSamples` (boolean, optional)
- `organizationId` (string, required)

### Responses

#### 200

The saved profile

- `ok` (boolean, required, one of `true`)
- `profile` (BrandVoiceProfile, required)
  - `id` (string, required)
  - `organizationId` (string, required)
  - `brandDescription` (string, required)
  - `audience` (string, required)
  - `productContext` (string, required)
  - `defaultTone` (string | null, required, one of `professional`, `casual`, `bold`, `friendly`, `thought_leader`, `playful`, `hype`, `punchy`, `null`)
  - `styleRules` (object, required)
    - `dos` (string[], optional)
    - `donts` (string[], optional)
    - `bannedPhrases` (string[], optional)
    - `emojiPolicy` (string, optional, one of `never`, `sparing`, `freely`)
    - `hashtagPolicy` (string, optional, one of `never`, `sparing`, `freely`)
    - `ctaPolicy` (string, optional, one of `never`, `when_relevant`, `always`)
    - `language` (string, optional)
  - `exemplars` (object[], required)
    - `id` (string, required)
    - `text` (string, required)
    - `note` (string, optional)
  - `learnedMemo` (string | null, required)
  - `learnedMemoVersion` (integer, required) — Bumped by the distillation job when it rewrites the memo, so a caller can tell whether a cached memo is stale. Never moved by a settings save.
  - `learnedMemoUpdatedAt` (string | null, required) — ISO 8601.
  - `aiLearningEnabled` (boolean, required)
  - `useVoiceSamples` (boolean, required)
  - `createdAt` (string, required) — ISO 8601.
  - `updatedAt` (string, required) — ISO 8601.

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

## Distill learned style notes from editing history

```http
POST https://orbitwrite.com/api/v1/brand-voice/distill
```

Requires `brandVoice:update`. Rewrites the learned memo from the workspace's generation history and bumps `learnedMemoVersion`.

**Authentication:** session cookie.

### Request body (`application/json`)

- `organizationId` (string, required)

### Responses

#### 200

The refreshed memo

- `ok` (boolean, required, one of `true`)
- `memo` (string, required)
- `version` (integer, required)
- `counts` (object, required)
  - `editedPairs` (integer, required)
  - `shippedUnchanged` (integer, required)
  - `refineEvents` (integer, required)
  - `rejectionComments` (integer, required)

#### 400

Invalid request

- `error` (string, required)
- `issues` (any[], optional)

#### 401

Not signed in

- `error` (string, required)
- `issues` (any[], optional)

#### 403

Not a member of the organization

- `error` (string, required)
- `issues` (any[], optional)

#### 422

Learning disabled for the workspace, or not enough signal

- `error` (string, required)
- `issues` (any[], optional)

#### 500

Distillation failed

- `error` (string, required)
- `issues` (any[], optional)
