# Webflow

Connect a Webflow site to publish and edit items in its CMS blog collections and to add ad pixels to the site's custom code.

A connected Webflow site is a blog account. Every CMS collection on the site that has a rich text field shows up as a blog, and its items are articles you create, publish, edit and delete through the [Blogs API](https://pozy.ai/docs/commerce/blogs). Webflow accounts are not social accounts and have no inbox or analytics.

## What you can do

| Job | Supported |
| --- | --- |
| Create, publish, edit, unpublish and delete CMS items | Yes |
| Fill your collection's own fields (`fieldData`) | Yes |
| Schedule an item for later | No. Webflow's API cannot schedule |
| SEO fields, localized items, Ecommerce products | No |
| Create, rename or delete collections | No (`405`) |
| Ad pixels | Yes, with the OAuth connection only |

| Property | Value |
| --- | --- |
| Platform id | `webflow` |
| Connect | OAuth with the Pozy Webflow app, or a site API token |
| Blog id | The collection id |
| Status | `publish` or `draft` |

## Before you start

You need a site on a CMS plan with a blog-style collection. The site must have been published at least once before items can go live.

## Connect with Webflow (OAuth)

Use this path when you also want to install pixels. There is nothing to copy.

```bash cURL
curl "https://api.pozy.ai/v1/connect/webflow?profileId=6710c3a9e4b0d2f1a8c95e21&redirect_url=https://app.example.com/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/connect/webflow?profileId=6710c3a9e4b0d2f1a8c95e21&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
```
```python Python
import os, requests

resp = requests.get(
    "https://api.pozy.ai/v1/connect/webflow",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"profileId": "6710c3a9e4b0d2f1a8c95e21", "redirect_url": "https://app.example.com/connected"},
)
```

The user picks exactly one site on Webflow's screen. Picking several connects none and returns `error=webflow_pick_one_site`. Connecting a site that was first connected with a token upgrades the same account.

## Connect with a site API token

Create the token inside Webflow: go to **Site settings > Apps & integrations > API access**, then generate one with **Sites** set to read-only and **CMS** set to read and write. Each site token works for a single site.

```bash cURL
curl -X POST https://api.pozy.ai/v1/connect/webflow/token \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "profileId": "6710c3a9e4b0d2f1a8c95e21", "siteToken": "wf_site_token_..." }'
```

Pozy confirms the token's permissions, looks up the site it belongs to, and saves it encrypted. Webflow cancels site tokens that go unused for 365 days. When a token dies, because someone deleted or regenerated it, the account is marked as needing a reconnect; connect it again with a fresh token.

## Publish an item

Find the collection with `GET /v1/accounts/{accountId}/blogs`, then create the item:

```bash cURL
curl -X POST https://api.pozy.ai/v1/accounts/6710c4d1e4b0d2f1a8c95e4f/blogs/6512f0a8c1d2e3f4a5b6c7d8/articles \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "How we cut page load time in half",
    "bodyHtml": "<p>Three changes did most of the work.</p>",
    "excerpt": "Three changes that halved our load time.",
    "tags": ["performance"],
    "image": { "url": "https://cdn.example.com/speed.png", "altText": "Load time chart" },
    "fieldData": { "reading-time": "4 min" },
    "isPublished": true
  }'
```

Pozy reads the collection's fields and maps article fields by type and slug:

| Article field | Webflow field it fills |
| --- | --- |
| `title`, `handle` | The item's name and slug. No `handle`? The slug is made from the title, plus a suffix when it is already used |
| `bodyHtml` | The collection's rich text field, choosing one called body, content or post when there are several |
| `excerpt` | A plain text field whose slug is summary, excerpt, description or intro |
| `image` | An image field, choosing one called main, cover, featured, hero, header or thumbnail when it exists |
| `tags` | A multi-reference field named tags, categories or topics. Missing tags are created and published |
| `author` | A reference field called author or writer. Pass the id of the item it points to |
| `publishDate` | A date field named publish or date |

Any other field of the collection goes in `fieldData`, keyed by its slug and written the way Webflow expects: an item id for a reference, `{ "url": "...", "alt": "..." }` for an image. If a create would leave required fields empty, you get `400` naming all of them. You also get `400` for a slug the collection lacks, or one Pozy fills from the article fields above.

### Publishing rules

- `isPublished: true` publishes the item to the live site now. Leaving it out, or `false`, keeps a draft.
- `isPublished: false` on a published item unpublishes it. Edits to a published item go live.
- `publishDate` cannot be in the future (`400`). On a draft (`isPublished: false`) it is simply stored in the date field.
- `seo` returns `400`. Code blocks in the HTML are dropped by Webflow. Body images are copied to Webflow's asset hosting.
- Delete unpublishes the item from the live site, then deletes it.

## Pixels

Installing a pixel needs the OAuth connection, not a site token. Webflow only ships custom code when the whole site is published, so the tag starts firing after your next publish, and the install response always says `publishRequired: true`. Pozy leaves publishing to you: publishing the site would also release every pending change in the Designer. See [Site pixels](https://pozy.ai/docs/commerce/site-pixels).

## Errors

| Error | Cause | Fix |
| --- | --- | --- |
| `400` "rejected the site token" | Wrong, deleted or regenerated token | Generate a new site token |
| `400` "missing cms:write" | The token cannot write to the CMS | Generate one with CMS set to read and write (Sites can stay read-only) |
| `400` "scoped to one site" | You sent a workspace token, or a token for more than one site | Switch to a site API token |
| `400` "Publish the Webflow site once" | The site was never published | Publish it once in Webflow, then retry |
| `403` | Webflow does not accept the token | Connect again with a fresh token |
| `404 blog_not_found` | No collection with a rich text field has that id | Check the ids in the account's blog list |
| `429` | Webflow caps each token at 60 to 120 requests per minute, depending on the site's plan | Wait for `Retry-After`, then retry |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/connect/webflow`](https://pozy.ai/docs/api/getWebflowConnectUrl) | Get Webflow OAuth URL |
| [`POST /v1/connect/webflow/token`](https://pozy.ai/docs/api/connectWebflowWithSiteToken) | Connect Webflow with a site API token |
| [`GET /v1/accounts/{accountId}/blogs`](https://pozy.ai/docs/api/listBlogs) | List blogs |
| [`GET /v1/accounts/{accountId}/blogs/{blogId}/articles`](https://pozy.ai/docs/api/listBlogArticles) | List blog articles |
| [`POST /v1/accounts/{accountId}/blogs/{blogId}/articles`](https://pozy.ai/docs/api/createBlogArticle) | Create a blog article |
| [`GET /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}`](https://pozy.ai/docs/api/getBlogArticle) | Get a blog article |
| [`PATCH /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}`](https://pozy.ai/docs/api/updateBlogArticle) | Update a blog article |
| [`DELETE /v1/accounts/{accountId}/blogs/{blogId}/articles/{articleId}`](https://pozy.ai/docs/api/deleteBlogArticle) | Delete a blog article |
