---
title: "Authors"
description: "The byline roster for a Site."
canonical: "https://writavo.com/docs/api/authors"
last-updated: "2026-09-30"
---

# Authors

The byline roster for a Site.

Base URL: `https://api.writavo.com/v1`

### GET /authors

List authors

- **Operation id**: `listAuthors`
- **Scope**: `authors:read`
- **Permission**: `taxonomy.read`
- **Rate limit class**: read
- **Plan feature**: `none`
- **Spends credits**: no
- **Publishable key may call it**: yes

The byline roster for the Site.

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `cursor` | query | string | no | The opaque cursor from `data.next_cursor` on the previous page. Do not parse or build one; its encoding will change. |
| `limit` | query | integer | no | Page size. Values above the maximum are clamped, not rejected: ask for a thousand and you get a hundred and a `next_cursor`. |
| `fields` | query | string | no | Comma separated field allow list. Default projection: `id, name, slug, bio, avatar_url, job_title, socials, author_type, is_ai_generated, is_default, created_at`. |

**Responses**

| Status | Data | Description |
|---|---|---|
| 200 | Page | A page of authors. |
| 400 | - | `INVALID_REQUEST`. Bad JSON, an unusable query parameter value, or a missing required header. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl https://api.writavo.com/v1/authors \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"
```

### POST /authors

Create an author

- **Operation id**: `createAuthor`
- **Scope**: `authors:write`
- **Permission**: `taxonomy.manage`
- **Rate limit class**: write
- **Plan feature**: `none`
- **Spends credits**: no
- **Publishable key may call it**: no

`is_ai_generated` marks a persona rather than a real person. It defaults to `true`
because that is what the generation pipeline creates. Set it to `false` for a human
byline, and be accurate about it: it is what your disclosure copy keys off.

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string | yes | A unique key per logical operation, 1 to 255 printable ASCII characters (a UUID works), scoped to the Site and endpoint and kept for 24 hours. - Same key and body: the original response is replayed, so retrying a timeout is safe. - Same key, different body: `409 IDEMPOTENCY_KEY_CONFLICT`. - Same key while the first is running: `409 IDEMPOTENCY_KEY_IN_FLIGHT`; wait and retry. |

**Request body** (required)

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes |  |
| `slug` | string | no | The author's stable URL key, unique on the Site. Derived from `name` when omitted, and kept when the author is renamed. |
| `bio` | string | null | no |  |
| `avatar_url` | uri | null | no |  |
| `job_title` | string | null | no | Emitted as the schema.org `jobTitle` of the byline. |
| `socials` | AuthorSocials | no | The author's profiles elsewhere, as network to absolute http(s) URL. Emitted as the schema.org `sameAs` of the byline. A write REPLACES the whole object; send `{}` to clear it. |
| `author_type` | AuthorType | no | How a theme groups this byline, for example a founders page listing every `co-founder`. A display label only: it grants no access to anything. |
| `is_ai_generated` | boolean | no |  |
| `is_default` | boolean | no |  |

**Responses**

| Status | Data | Description |
|---|---|---|
| 201 | Author | The author was created. |
| 400 | - | `INVALID_REQUEST`. Bad JSON, an unusable query parameter value, or a missing required header. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 409 | - | The request collides with the current state. Read `code`: - `SLUG_CONFLICT` - the slug is taken. - `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT` - key reused with another body, or still running. - `CONFLICT` - the object changed under you. Re-read and retry. - `APPROVAL_INVALID` - the `Writavo-Approval` is expired, used, unknown, for another request, or its effect changed. Retry without it for a new approval. - `PREREQUISITE_MISSING` / `FEATURE_UNAVAILABLE` (settings, delivery, SEO) - something is not set up (the message names what sets it up), or Writavo has it switched off (the message names the free alternative). Nothing was done or charged. |
| 422 | - | `VALIDATION_FAILED`. The values are not acceptable; `error.fields` maps each field to a message. Common causes: publishing without a title, slug or content; scheduling in the past; sending `status` on a create or update. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl -X POST https://api.writavo.com/v1/authors \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "<name>"
}'
```

### POST /authors/bulk-delete

Delete many authors

- **Operation id**: `bulkDeleteAuthors`
- **Scope**: `authors:write`
- **Permission**: `taxonomy.manage`
- **Rate limit class**: write
- **Plan feature**: `none`
- **Spends credits**: no
- **AI agent approval**: `author.delete`, asked while approvals are on
- **Publishable key may call it**: no

Deletes up to 100 authors in one call, exactly as `DELETE /authors/{id}` does for each. No
article is deleted: articles by a deleted author stay published and lose their byline
(`author_id` becomes `null`).

Each id is deleted exactly as the single `DELETE` does it, with its own audit entry and
webhook. An id that names no author on this Site, whether it never existed, was already
deleted or belongs to another Site, is listed in `not_found`. A repeated id counts once. If
one author cannot be deleted it is listed in `failed` with the error the single delete
would have returned, and the rest of the list still runs.

`dry_run: true` deletes nothing and returns the same report: `deleted` then lists the ids
that would be deleted.

**When an AI agent asks.** A key approved for an AI assistant (a device or OAuth sign-in),
or any request from an MCP client, gets `428 APPROVAL_REQUIRED` on the first try while the
organisation requires approvals, with `error.approval.url` for a person to approve or deny
in the dashboard. ONE approval covers the whole list: the person sees how many authors, and whether the Site's default author is one of them.
Retry the same request with `Writavo-Approval: <error.approval.id>` once they have. The
approval is for the exact authors that existed on this Site when it was asked: if one of
them has since been deleted, the retry is `409 APPROVAL_INVALID` and you ask again. When
none of the ids is an author on this Site, or on a dry run, no approval is asked for
and the report comes back directly. Other callers are unaffected.

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `Idempotency-Key` | header | string | yes | A unique key per logical operation, 1 to 255 printable ASCII characters (a UUID works), scoped to the Site and endpoint and kept for 24 hours. - Same key and body: the original response is replayed, so retrying a timeout is safe. - Same key, different body: `409 IDEMPOTENCY_KEY_CONFLICT`. - Same key while the first is running: `409 IDEMPOTENCY_KEY_IN_FLIGHT`; wait and retry. |
| `Writavo-Approval` | header | uuid | no | The `error.approval.id` from a `428 APPROVAL_REQUIRED`, sent on the retry once a person has approved it. Only meaningful for a key approved for an AI agent; every other caller can leave it out. It is single use and bound to the key, to the exact request that asked for it (method, path and body) and to what that request would do: anything else, including a request whose effect changed since it was approved, is `409 APPROVAL_INVALID`. |

**Request body** (required)

| Field | Type | Required | Description |
|---|---|---|---|
| `ids` | uuid[] | yes | The ids to delete. A repeated id counts once. |
| `dry_run` | boolean | no | `true`: delete nothing, and report what the call would do. |

**Responses**

| Status | Data | Description |
|---|---|---|
| 200 | BulkDeleteResult | What happened to each id. Every id sent appears in exactly one list. |
| 400 | - | `INVALID_REQUEST`. Bad JSON, an unusable query parameter value, or a missing required header. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 409 | - | The request collides with the current state. Read `code`: - `SLUG_CONFLICT` - the slug is taken. - `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT` - key reused with another body, or still running. - `CONFLICT` - the object changed under you. Re-read and retry. - `APPROVAL_INVALID` - the `Writavo-Approval` is expired, used, unknown, for another request, or its effect changed. Retry without it for a new approval. - `PREREQUISITE_MISSING` / `FEATURE_UNAVAILABLE` (settings, delivery, SEO) - something is not set up (the message names what sets it up), or Writavo has it switched off (the message names the free alternative). Nothing was done or charged. |
| 422 | - | `VALIDATION_FAILED`. The values are not acceptable; `error.fields` maps each field to a message. Common causes: publishing without a title, slug or content; scheduling in the past; sending `status` on a create or update. |
| 428 | - | `APPROVAL_REQUIRED` or `APPROVAL_PENDING`. Only ever returned to an AI agent: a key from a device or OAuth sign-in, or any request an MCP client makes (it carries `Writavo-Mcp-Tool`) whatever key it holds. For an operation marked `x-writavo-approval-always` it is returned whatever the organisation's approvals switch says, and for the six content actions while approvals are on. Nothing has happened yet. Show `error.approval.url` to a person; once they approve, repeat the identical request with `Writavo-Approval: <error.approval.id>`. Do not change the body or the path: the approval is bound to the exact request, and a different one is `409 APPROVAL_INVALID`. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl -X POST https://api.writavo.com/v1/authors/bulk-delete \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "4f6b2c1e-8a3d-4e5f-9b7a-1c2d3e4f5a6b",
    "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
  ]
}'
```

### GET /authors/{id}

Read one author

- **Operation id**: `getAuthor`
- **Scope**: `authors:read`
- **Permission**: `taxonomy.read`
- **Rate limit class**: read
- **Plan feature**: `none`
- **Spends credits**: no
- **Publishable key may call it**: yes

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | uuid | yes |  |

**Responses**

| Status | Data | Description |
|---|---|---|
| 200 | Author | The author. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 404 | - | `NOT_FOUND`. No such object, or it belongs to another Site. **The two are deliberately indistinguishable and never return 403.** If the id is right, check you are using the right Site's key. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl https://api.writavo.com/v1/authors/3f1b0c7a-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"
```

### PATCH /authors/{id}

Update an author

- **Operation id**: `updateAuthor`
- **Scope**: `authors:write`
- **Permission**: `taxonomy.manage`
- **Rate limit class**: write
- **Plan feature**: `none`
- **Spends credits**: no
- **Publishable key may call it**: no

Setting `is_default: true` clears the flag on whichever author held it, because a Site
has at most one default byline. Setting it to `false` on the current default leaves the
Site with none.

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | uuid | yes |  |
| `If-Match` | header | string | no | The `ETag` from your last read. If the object changed since, you get `412 PRECONDITION_FAILED` and nothing is written. Optional in v1; without it the last write wins. Send it. |

**Request body** (required)

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | no |  |
| `slug` | string | no | The author's stable URL key, unique on the Site. Derived from `name` when omitted, and kept when the author is renamed. |
| `bio` | string | null | no |  |
| `avatar_url` | uri | null | no |  |
| `job_title` | string | null | no | Emitted as the schema.org `jobTitle` of the byline. |
| `socials` | AuthorSocials | no | The author's profiles elsewhere, as network to absolute http(s) URL. Emitted as the schema.org `sameAs` of the byline. A write REPLACES the whole object; send `{}` to clear it. |
| `author_type` | AuthorType | no | How a theme groups this byline, for example a founders page listing every `co-founder`. A display label only: it grants no access to anything. |
| `is_ai_generated` | boolean | no |  |
| `is_default` | boolean | no |  |

**Responses**

| Status | Data | Description |
|---|---|---|
| 200 | Author | The updated author. |
| 400 | - | `INVALID_REQUEST`. Bad JSON, an unusable query parameter value, or a missing required header. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 404 | - | `NOT_FOUND`. No such object, or it belongs to another Site. **The two are deliberately indistinguishable and never return 403.** If the id is right, check you are using the right Site's key. |
| 409 | - | The request collides with the current state. Read `code`: - `SLUG_CONFLICT` - the slug is taken. - `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT` - key reused with another body, or still running. - `CONFLICT` - the object changed under you. Re-read and retry. - `APPROVAL_INVALID` - the `Writavo-Approval` is expired, used, unknown, for another request, or its effect changed. Retry without it for a new approval. - `PREREQUISITE_MISSING` / `FEATURE_UNAVAILABLE` (settings, delivery, SEO) - something is not set up (the message names what sets it up), or Writavo has it switched off (the message names the free alternative). Nothing was done or charged. |
| 412 | - | `PRECONDITION_FAILED`. Your `If-Match` no longer matches: someone edited the object. Nothing was written. Re-read, merge, and retry with the new `ETag`. |
| 422 | - | `VALIDATION_FAILED`. The values are not acceptable; `error.fields` maps each field to a message. Common causes: publishing without a title, slug or content; scheduling in the past; sending `status` on a create or update. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl -X PATCH https://api.writavo.com/v1/authors/3f1b0c7a-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000" \
  -H 'If-Match: W/"1767225600000"' \
  -H "Content-Type: application/json" \
  -d '{}'
```

### DELETE /authors/{id}

Delete an author

- **Operation id**: `deleteAuthor`
- **Scope**: `authors:write`
- **Permission**: `taxonomy.manage`
- **Rate limit class**: write
- **Plan feature**: `none`
- **Spends credits**: no
- **AI agent approval**: `author.delete`, asked while approvals are on
- **Publishable key may call it**: no

**Deleting several?** Use `bulkDeleteAuthors` (POST .../bulk-delete): up to 100 authors in one
call, and an AI agent needs ONE approval for the whole list instead of one per item.

Articles by this author are **not** deleted. Their `author_id` becomes `null`, so they
stay published and lose their byline.

**When an AI agent asks.** A key approved for an AI assistant (a device or OAuth sign-in),
or any request from an MCP client, gets `428 APPROVAL_REQUIRED` on the first try while the
organisation requires approvals, with `error.approval.url` for a person to approve or deny
in the dashboard. Retry the same request with `Writavo-Approval: <error.approval.id>` once
they have. Other callers are unaffected.

**Parameters**

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `id` | path | uuid | yes |  |
| `Writavo-Approval` | header | uuid | no | The `error.approval.id` from a `428 APPROVAL_REQUIRED`, sent on the retry once a person has approved it. Only meaningful for a key approved for an AI agent; every other caller can leave it out. It is single use and bound to the key, to the exact request that asked for it (method, path and body) and to what that request would do: anything else, including a request whose effect changed since it was approved, is `409 APPROVAL_INVALID`. |

**Responses**

| Status | Data | Description |
|---|---|---|
| 204 | - | Deleted. No body. |
| 401 | - | `INVALID_API_KEY`, `API_KEY_REVOKED` or `API_KEY_EXPIRED`: no key, or an unusable one. It never distinguishes "no such key" from "wrong key". |
| 403 | - | `INSUFFICIENT_SCOPE`. The key lacks this operation's scope, or its creator's permissions no longer cover it. Another Site's object is always 404, never this. A 403 may also be: - `AGENT_ACCESS_DISABLED` - AI agent access is turned off. - `APPROVAL_DENIED` - a person denied this action. - `FORBIDDEN` (organisation, team, settings) - a change no scope allows, such as an AI agent changing the access of the person it acts for, anything about ownership, or a setting only a person may change. The message says what to do instead. |
| 404 | - | `NOT_FOUND`. No such object, or it belongs to another Site. **The two are deliberately indistinguishable and never return 403.** If the id is right, check you are using the right Site's key. |
| 409 | - | The request collides with the current state. Read `code`: - `SLUG_CONFLICT` - the slug is taken. - `IDEMPOTENCY_KEY_CONFLICT` / `IDEMPOTENCY_KEY_IN_FLIGHT` - key reused with another body, or still running. - `CONFLICT` - the object changed under you. Re-read and retry. - `APPROVAL_INVALID` - the `Writavo-Approval` is expired, used, unknown, for another request, or its effect changed. Retry without it for a new approval. - `PREREQUISITE_MISSING` / `FEATURE_UNAVAILABLE` (settings, delivery, SEO) - something is not set up (the message names what sets it up), or Writavo has it switched off (the message names the free alternative). Nothing was done or charged. |
| 428 | - | `APPROVAL_REQUIRED` or `APPROVAL_PENDING`. Only ever returned to an AI agent: a key from a device or OAuth sign-in, or any request an MCP client makes (it carries `Writavo-Mcp-Tool`) whatever key it holds. For an operation marked `x-writavo-approval-always` it is returned whatever the organisation's approvals switch says, and for the six content actions while approvals are on. Nothing has happened yet. Show `error.approval.url` to a person; once they approve, repeat the identical request with `Writavo-Approval: <error.approval.id>`. Do not change the body or the path: the approval is bound to the exact request, and a different one is `409 APPROVAL_INVALID`. |
| 429 | - | `RATE_LIMIT_EXCEEDED`. Back off and honour `Retry-After`. Limits are per key, per minute, by endpoint class. The two device sign-in endpoints take no key, so they are limited per IP address. \| Class \| Endpoints \| Limit \| \|---\|---\|---\| \| read \| every GET \| 600 per minute \| \| write \| POST, PATCH and DELETE on content, taxonomy, keys, webhooks, the content plan, formats and prompts, settings, delivery, SEO curation, the team and billing \| 120 per minute \| \| upload \| `POST /media/upload-url` and `POST /seo/backlinks/import` \| 60 per minute \| \| pipeline \| `POST /pipeline/runs`, `POST /pipeline/articles/{id}/requeue`, `POST /delivery/cms/push` and the seven paid SEO scans \| 10 per minute \| \| auth \| `POST /auth/device` and `POST /auth/device/token`, per IP address \| 60 per minute \| `POST /comments` adds three limits, named in `error.limit` on its 429: `reader` (5 per reader in 10 minutes, by `reader_ip`), `site` (300 per Site per hour) and `queue` (2,000 awaiting review). Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
| 500 | - | `INTERNAL_ERROR`. Our fault. Quote the `request_id` from the body if you contact support. Safe to retry; send the same `Idempotency-Key` so a request that did succeed is not repeated. |
| 503 | - | `MAINTENANCE`. Writes are paused, platform wide or for your Site. Reads usually work and your published blog stays up. Retry after `Retry-After`. |

```bash
curl -X DELETE https://api.writavo.com/v1/authors/3f1b0c7a-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer wv_sk_EXAMPLE0000000000000000000000000000"
```
