# Link-in-bio page

> One public page per brand for the link in your profiles, with its views and the clicks on each link.

Source: https://postwire.io/docs/bio/ · Base URL: https://postwire.io · OpenAPI: https://postwire.io/openapi.json

Each brand can have a page at `postwire.io/@<slug>`, **free on every plan**: the brand's name, the avatar of a connected account, a short bio, up to 10 links and the connected profiles as icons. The address is 3-30 lowercase letters, digits and hyphens, unique, and not a reserved word (`GET /api/bio?check=<slug>` says whether one is free). Links are http or https only; each is published as a tracked `postwire.io/r/` link, so `GET /api/bio` returns the clicks on each link and the page's views, all time and the last 30 days, counting people only (link previews and bots are left out). The page carries no script, escapes everything and has a "Report this page" link. "Made with PostWire" sits at the bottom and can be switched off (`hide_badge`) on paid plans. The page is cached for a minute, so a change shows within about a minute.

The free tools that need no account are at [postwire.io/tools](/tools/).

- [`GET /api/bio`](#get-api-bio): Read a brand's link-in-bio page, its views and each link's clicks
- [`PUT /api/bio`](#put-api-bio): Create or update a brand's link-in-bio page
- [`DELETE /api/bio`](#delete-api-bio): Remove a brand's link-in-bio page

## Read a brand's link-in-bio page, its views and each link's clicks

`GET /api/bio`

The brand's page at postwire.io/@<slug> (free on every plan): its settings, the public URL, views (people only: link previews and bots are left out) and clicks per link, all time and the last 30 days. With ?check=<slug>, says whether that address is free and allowed instead. Before the server's bio-page table exists the answer is available: false, code bio_unavailable.

Needs an API key: `Authorization: Bearer pw_live_…`.

**Parameters**

| Field | Type | Description |
|---|---|---|
| `brand_id` | query, string (uuid) | The brand; default: the account's first brand |
| `check` | query, string | An address to check: { slug, ok, code?, error? } |

**Responses**

| Status | Means |
|---|---|
| `200` | { available, brand_id, page: { slug, title, bio, links: [{ label, url }], show_socials, hide_badge, updated_at } \| null, url, stats: { views, views_30d, links: [{ label, url, clicks, clicks_30d }] } \| null, socials: [{ platform, handle }], avatar_url, can_hide_badge, limits } |
| `404` | code brand_not_found |

```bash
curl "https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
```

```js
const res = await fetch("https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
```

```python
import os, requests

r = requests.get(
    "https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())
```

## Create or update a brand's link-in-bio page

`PUT /api/bio`

Free on every plan. The address is 3-30 lowercase letters, digits and hyphens, unique, and not a reserved word. Up to 10 links, http or https only; each is published as a tracked postwire.io/r/ link so its clicks are counted. "Made with PostWire" can be switched off on paid plans only (on Free the request is saved with it on, and the answer says so in note). Changes show within about a minute.

Needs an API key: `Authorization: Bearer pw_live_…`.

**Body (JSON)**

| Field | Type | Description |
|---|---|---|
| `brand_id` | string (uuid) |  |
| `slug` (required) | string | postwire.io/@<slug> Up to 30 characters. |
| `title` | string | The name shown; the brand's name when empty Up to 60 characters. |
| `bio` | string | Up to 280 characters. |
| `links` | array of object | Up to 10 items. |
| `links[].label` (required) | string | Up to 60 characters. |
| `links[].url` (required) | string (uri) | Up to 1000 characters. |
| `show_socials` | boolean | The brand's connected profiles as icons Default true. |
| `hide_badge` | boolean | Paid plans only Default false. |

**Responses**

| Status | Means |
|---|---|
| `200` | { ok: true, note?, ...the same body as GET } |
| `400` | code slug_required, slug_length, slug_chars, slug_reserved, bad_url, label_required or too_many_links, with field (e.g. links.2.url) and every problem in errors[] |
| `409` | code slug_taken |
| `503` | code bio_unavailable: bio pages are not switched on on this server yet |

```bash
curl -X PUT "https://postwire.io/api/bio" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brand_id": "b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c",
  "slug": "luna-bakery",
  "title": "Luna Bakery",
  "bio": "Sourdough and custom cakes in Lima. Order by 6 pm for next-day pickup.",
  "links": [
    {
      "label": "Order a cake",
      "url": "https://lunabakery.example/cakes"
    },
    {
      "label": "This week'\''s menu",
      "url": "https://lunabakery.example/menu"
    }
  ]
}'
```

```js
const res = await fetch("https://postwire.io/api/bio", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "brand_id": "b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c",
    "slug": "luna-bakery",
    "title": "Luna Bakery",
    "bio": "Sourdough and custom cakes in Lima. Order by 6 pm for next-day pickup.",
    "links": [
      {
        "label": "Order a cake",
        "url": "https://lunabakery.example/cakes"
      },
      {
        "label": "This week's menu",
        "url": "https://lunabakery.example/menu"
      }
    ]
  }),
});
console.log(res.status, await res.json());
```

```python
import os, requests

r = requests.put(
    "https://postwire.io/api/bio",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
    json={
        "brand_id": "b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c",
        "slug": "luna-bakery",
        "title": "Luna Bakery",
        "bio": "Sourdough and custom cakes in Lima. Order by 6 pm for next-day pickup.",
        "links": [
            {
                "label": "Order a cake",
                "url": "https://lunabakery.example/cakes",
            },
            {
                "label": "This week's menu",
                "url": "https://lunabakery.example/menu",
            },
        ],
    },
)
print(r.status_code, r.json())
```

## Remove a brand's link-in-bio page

`DELETE /api/bio`

The address becomes free for anyone; the page's views and clicks are deleted with it.

Needs an API key: `Authorization: Bearer pw_live_…`.

**Parameters**

| Field | Type | Description |
|---|---|---|
| `brand_id` | query, string (uuid) |  |

**Responses**

| Status | Means |
|---|---|
| `200` | { ok: true, removed: <slug> } |
| `404` | code bio_not_found or brand_not_found |

```bash
curl -X DELETE "https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
```

```js
const res = await fetch("https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c", {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
```

```python
import os, requests

r = requests.delete(
    "https://postwire.io/api/bio?brand_id=b3f1c2d4-8a9e-4f6b-a1c2-3d4e5f6a7b8c",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())
```
