# Hashtag sets

7 endpoints.

## List a workspace's hashtag sets

```http
GET https://orbitwrite.com/api/v1/hashtag-sets
```

Requires `hashtagSet:read`. Every saved set, sorted by name — the list the composer's Hashtags panel inserts from.

**Authentication:** bearer token or session cookie.

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

### Responses

#### 200

The workspace's hashtag sets

- `hashtagSets` (HashtagSet[], required)
  - `id` (string, required)
  - `name` (string, required)
  - `hashtags` (string[], required) — Without the leading #, in the order the set was saved. Write them into a post as `#tag`.
  - `createdAt` (string, required)
  - `updatedAt` (string, 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 hashtag set

```http
POST https://orbitwrite.com/api/v1/hashtag-sets
```

Requires `hashtagSet:create`. Tags may be sent with or without the leading `#` and are stored without it. A tag with characters no platform links as part of a hashtag (a hyphen, an emoji), an all-digit tag, or a repeat is a 400 naming it — never silently dropped. Set names are unique per workspace, case-insensitively (409).

**Authentication:** bearer token or session cookie.

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

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

- `name` (string, required)
- `hashtags` (string[], required) — Up to 30 tags, with or without the leading #. Letters, digits and underscores only; stored without the #.

### Responses

#### 201

The set as stored

- `hashtagSet` (HashtagSet, required)
  - `id` (string, required)
  - `name` (string, required)
  - `hashtags` (string[], required) — Without the leading #, in the order the set was saved. Write them into a post as `#tag`.
  - `createdAt` (string, required)
  - `updatedAt` (string, 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)

#### 409

Conflicts with an existing resource

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

## Read one hashtag set

```http
GET https://orbitwrite.com/api/v1/hashtag-sets/{id}
```

Requires `hashtagSet:read`.

**Authentication:** bearer token or session cookie.

### Path parameters

- `id` (string, required)

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

### Responses

#### 200

The set as stored

- `hashtagSet` (HashtagSet, required)
  - `id` (string, required)
  - `name` (string, required)
  - `hashtags` (string[], required) — Without the leading #, in the order the set was saved. Write them into a post as `#tag`.
  - `createdAt` (string, required)
  - `updatedAt` (string, 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)

#### 404

The resource does not exist

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

## Rename a hashtag set or replace its tags

```http
PATCH https://orbitwrite.com/api/v1/hashtag-sets/{id}
```

Requires `hashtagSet:update`. An omitted field is left alone; `hashtags`, when sent, replaces the whole list (same rules as create). Posts the set was already inserted into keep their text.

**Authentication:** bearer token or session cookie.

### Path parameters

- `id` (string, required)

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

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

- `name` (string, optional)
- `hashtags` (string[], optional)

### Responses

#### 200

The set as stored

- `hashtagSet` (HashtagSet, required)
  - `id` (string, required)
  - `name` (string, required)
  - `hashtags` (string[], required) — Without the leading #, in the order the set was saved. Write them into a post as `#tag`.
  - `createdAt` (string, required)
  - `updatedAt` (string, 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)

#### 404

The resource does not exist

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

#### 409

Conflicts with an existing resource

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

## Delete a hashtag set

```http
DELETE https://orbitwrite.com/api/v1/hashtag-sets/{id}
```

Requires `hashtagSet:delete`. Posts the set was inserted into keep their text.

**Authentication:** bearer token or session cookie.

### Path parameters

- `id` (string, required)

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

### Responses

#### 204

The set was deleted

#### 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)

#### 404

The resource does not exist

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

## Add a hashtag set to a post's text

```http
POST https://orbitwrite.com/api/v1/hashtag-sets/{id}/apply
```

Requires `hashtagSet:read`. Returns `text` with the set added the way the composer's Insert does it: tags the text already has are skipped, and with a `platform` the platform's ceiling is respected (Instagram's 5, Threads' one topic tag, YouTube's 60). **Saves nothing** — write the returned text into the post with `POST /posts` or `/posts/draft`, where the post's own rules still apply. An unknown `platform` is a 400 rather than a silent "no ceiling".

**Authentication:** bearer token or session cookie.

### Path parameters

- `id` (string, required)

### Query parameters

- `organizationId` (string, optional) — Session callers only — an API key already determines the workspace.

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

- `text` (string, required) — The post text to add the set to. Empty is fine — the result is just the tags.
- `platform` (string, optional) — The platform the text is for (a channel's `platform`, e.g. instagram). Sets the hashtag ceiling; omit for no ceiling.

### Responses

#### 200

The text with the set applied, and what was skipped

- `text` (string, required) — The text with the inserted tags appended as a closing paragraph. Unchanged when none fit.
- `insert` (string[], required) — Tags that were added, without the #.
- `alreadyPresent` (string[], required) — Tags the text already carried (case-insensitively), so skipped.
- `overLimit` (string[], required) — Tags left out because the platform's hashtag ceiling was reached.

#### 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)

#### 404

The resource does not exist

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

## Hashtag limits and tips per platform

```http
GET https://orbitwrite.com/api/v1/hashtags/guidance
```

Every platform's hashtag ceiling and conventions, the platforms where hashtags matter first. With `platform` (any channel `platform` value, e.g. `linkedin_page`), just that one — an empty list when there is nothing to say. Needs any credential and no workspace.

**Authentication:** bearer token or session cookie.

### Query parameters

- `platform` (string, optional)

### Responses

#### 200

The guidance

- `platforms` (HashtagGuidance[], required)
  - `platform` (string, required) — Canonical key — `linkedin` covers LinkedIn profiles and pages.
  - `displayName` (string, required)
  - `linksHashtags` (boolean, required) — Whether a #tag does anything there. False for Discord and Slack, which publish it as plain text.
  - `limit` (HashtagLimit | any | null, required)
  - `tips` (string[], required)

#### 401

Not signed in

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