Delete topic
Deletes a topic and every subtopic nested inside it.
prompts is required and has no default. That mirrors the app, which will not delete a
topic until you have chosen an outcome from a radio and typed the word “delete” to confirm.
There is no equivalent of a typing step over HTTP, so the explicit parameter is the guardrail.
Requires the write:topics scope, and the workspace must be enabled for API writes. Topics are
their own scope rather than part of write:prompts because the blast radius is not comparable:
a topic sits above the prompts beneath it, so changing one reaches its whole subtree.
DELETE /v1/brands/{brand_id}/topics/{topic_id}Authorization
| Header | Type | Required |
|---|---|---|
Authorization | Bearer asky_sk_… | required |
Idempotency-Key | string | required |
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
brand_id | uuid | required | From List brands |
topic_id | uuid | required | From List topics |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
prompts | string | required | What happens to prompts beneath the deleted topics. One of: reassign, delete. No default. Choosing is required, mirroring the confirmation the app demands. |
dry_run | boolean | optional | Report what would be deleted, and delete nothing. |
Request
cURL
curl --request DELETE \
--url 'https://api.askylabs.com/v1/brands/242546de-4cee-4a7d-952e-f6aa60c63ef8/topics/4beb0694-7c2e-488b-84c1-613738e7eaf6?prompts=reassign' \
--header 'Authorization: Bearer <api-key>' \
--header 'Idempotency-Key: <unique-per-attempt>' \Response
200
{
"data": {
"deleted_topic_ids": [
"4beb0694-7c2e-488b-84c1-613738e7eaf6",
"3249738e-6af1-4d5c-9fb3-65de7ac15199"
],
"prompts_reassigned": 34,
"prompts_deleted": 0,
"dry_run": false
},
"request_id": "req_1a9c47e0b825431fa9d2"
}Response fields
| Field | Type | Description |
|---|---|---|
data.deleted_topic_ids | uuid[] | required. The topic and every subtopic beneath it |
data.prompts_reassigned | integer | required. Moved to Uncategorized. Always 0 with prompts=delete |
data.prompts_deleted | integer | required. Always 0 with prompts=reassign |
data.dry_run | boolean | required. true means nothing was changed |
request_id | string | required. Also returned as the x-request-id header. Quote it when contacting support |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A parameter is unknown or malformed. The message names it |
| 401 | invalid_token | The key is missing, malformed, or does not exist |
| 401 | token_expired | The key passed its expiry date |
| 401 | token_revoked | The key was revoked |
| 403 | insufficient_scope | The key does not carry the scope this endpoint needs |
| 403 | plan_required | This workspace is not enabled for API writes |
| 404 | not_found | The brand does not exist, or this key is not allowed to reach it. The two are deliberately indistinguishable |
| 429 | rate_limited | Over 60 requests per minute. See the Retry-After header |
Notes
Nested subtopics always go with the parent, under both values of prompts. There is no
way to delete a topic and keep its subtopics — the choice only decides what happens to the
prompts. deleted_topic_ids returns the full list so you can see exactly what went.
prompts=reassign is the safe one. Prompts survive, lose their topic, and show as
Uncategorized. Nothing measured is lost. The detach and the delete run in one transaction,
so a failure cannot leave prompts detached and then removed anyway.
prompts=delete is irreversible. It removes each prompt along with its per-prompt
analytics — the history every trend for those questions is built on — and any content
opportunities derived from the topic. Call dry_run=true first; the counts tell you exactly
what is at stake before anything is touched.
There is no typed confirmation over HTTP. The app escalates to one at 25 prompts, and a script deleting in a loop hits no such friction. The write rate limit is the only backstop. Build your own check before looping.
Idempotency-Key is required. Send a unique value per attempt and reuse it when retrying
that attempt: a retry replays the original response instead of executing again, which is what
stops a lost response from becoming a duplicate. See Idempotency.
It earns its keep here more than anywhere else on the surface. Without it, a retry after a
lost response would return 404 for a topic that was in fact deleted successfully, which
reads as failure and sends you investigating something that already worked.