# Credentials and bots

Connect Bluesky, self-hosted WordPress and WooCommerce, Webflow, Shopify custom apps, WhatsApp, ChatGPT Ads and Slack without the standard OAuth redirect. Telegram and Discord are coming soon.

Not every platform connects through an OAuth consent screen. Some take a token or app password the user creates on the platform. Others connect by adding Pozy's bot to a channel. This page covers each of them.

> **Never ask for a main password:**
> 
> Every credential on this page is an app-specific password or a token the user can revoke on the platform without changing their login. Never collect a user's normal account password. You do not need to keep these credentials after the connect call succeeds, so do not store them.

All credential calls take the `profileId` the account joins and return the new account. Make them from your server.

## Bluesky

Bluesky connects with an [app password](https://bsky.app/settings/app-passwords), created under **Settings → Privacy and security → App passwords**. It is separate from the account password and can be revoked at any time.

`state` is `{userId}-{profileId}`, where `userId` is `currentUserId` from [`GET /v1/users`](https://pozy.ai/docs/api/listUsers).

```bash cURL
curl -X POST "https://api.pozy.ai/v1/connect/bluesky/credentials" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "acme.bsky.social",
    "appPassword": "abcd-efgh-ijkl-mnop",
    "state": "usr_4f8a2c19d0b3-6650c1f2a9e4b7d3c8a1f0e2"
  }'
```
```ts Node
const usersRes = await fetch("https://api.pozy.ai/v1/users", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const users = await usersRes.json();
if (!usersRes.ok) throw new Error(users.error);

const res = await fetch("https://api.pozy.ai/v1/connect/bluesky/credentials", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    identifier: "acme.bsky.social",
    appPassword: process.env.BLUESKY_APP_PASSWORD!,
    state: `${users.currentUserId}-6650c1f2a9e4b7d3c8a1f0e2`,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.account._id);
```
```python Python
import os
import requests

headers = {"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"}
users = requests.get("https://api.pozy.ai/v1/users", headers=headers, timeout=30).json()

resp = requests.post(
    "https://api.pozy.ai/v1/connect/bluesky/credentials",
    headers=headers,
    json={
        "identifier": "acme.bsky.social",
        "appPassword": os.environ["BLUESKY_APP_PASSWORD"],
        "state": f"{users['currentUserId']}-6650c1f2a9e4b7d3c8a1f0e2",
    },
    timeout=30,
)
account_id = resp.json()["account"]["_id"]
```

`identifier` is the handle (`acme.bsky.social`, or a custom domain handle) or the account's email address.

## WordPress and WooCommerce

WordPress.com sites use OAuth: start with [`GET /v1/connect/wordpress`](https://pozy.ai/docs/api/getWordPressAuthUrl) and redirect the user like any [hosted connect](https://pozy.ai/docs/connect/hosted-connect).

Self-hosted WordPress connects with an **application password**. The site owner creates one in WordPress admin under **Users → Profile → Application Passwords**. Then send the site URL, their username and that password:

```bash
curl -X POST "https://api.pozy.ai/v1/connect/wordpress/token" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "6650c1f2a9e4b7d3c8a1f0e2",
    "siteUrl": "https://blog.acme.com",
    "username": "ana",
    "applicationPassword": "Ab12 Cd34 Ef56 Gh78 Ij90 Kl12"
  }'
```

- `siteUrl` must be the site's HTTPS address. Never put the username or password inside the URL.
- Pozy checks that it can reach the REST API and that the user can publish posts and upload media. If not, the call fails and nothing is saved.
- A site running WooCommerce connected this way is also a store for [Commerce](https://pozy.ai/docs/commerce).
- One account is one site. It powers [Blogs](https://pozy.ai/docs/commerce/blogs), not social posts.

## Webflow

Webflow supports OAuth ([`GET /v1/connect/webflow`](https://pozy.ai/docs/api/getWebflowConnectUrl)) or a site API token. The site owner generates the token in the site's settings in Webflow, then you send it:

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

A site token covers blog publishing. Installing tracking tags on the site needs the OAuth connection. Connecting the same site over OAuth later upgrades the existing account in place.

## Shopify custom apps

The standard Shopify connect is OAuth with the store domain ([`GET /v1/connect/shopify?shop=...`](https://pozy.ai/docs/api/getShopifyConnectUrl)). When a merchant cannot use the browser flow, they can create a custom app in Shopify admin with the `read_content`, `write_content`, `read_products` and `write_products` scopes and give you its Admin API access token:

```bash
curl -X POST "https://api.pozy.ai/v1/connect/shopify/token" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "6650c1f2a9e4b7d3c8a1f0e2",
    "shop": "acme-coffee.myshopify.com",
    "accessToken": "shpat_..."
  }'
```

`shop` is required here because an Admin token does not say which store it belongs to. After an OAuth connect, the store the merchant approved can differ from the `shop` you sent (merchants often type a brand name), so always key on the returned `accountId`.

## WhatsApp with a system user token

The usual WhatsApp connect is Meta's Embedded Signup through `GET /v1/connect/whatsapp`, covered in [Connect WhatsApp](https://pozy.ai/docs/whatsapp). If you already manage the WhatsApp Business Account in Meta Business Manager, connect a number server to server with a system user token:

```bash
curl -X POST "https://api.pozy.ai/v1/connect/whatsapp/credentials" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "6650c1f2a9e4b7d3c8a1f0e2",
    "accessToken": "EAAG...",
    "wabaId": "102938475610293",
    "phoneNumberId": "109283746501928",
    "pin": "482913"
  }'
```

- The token needs the `whatsapp_business_messaging` and `whatsapp_business_management` permissions on that WhatsApp Business Account.
- Send `pin` when the number has two-step verification turned on. Without it, registration fails.
- The response can include `registrationWarning` or `webhookNotice`. Show them to whoever set up the number: they mean the number connected but needs attention.

## ChatGPT Ads

ChatGPT Ads connects with an API key from the advertiser's account:

```bash
curl -X POST "https://api.pozy.ai/v1/connect/openai-ads/credentials" \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "profileId": "6650c1f2a9e4b7d3c8a1f0e2", "apiKey": "sk-..." }'
```

See [ChatGPT Ads](https://pozy.ai/docs/ads/chatgpt-ads) for what the connection can do.

## Telegram and Discord

> **Coming soon:**
> 
> Telegram and Discord cannot be connected yet. [`GET /v1/connect/telegram`](https://pozy.ai/docs/api/getConnectUrl), [`GET /v1/connect/discord`](https://pozy.ai/docs/api/getConnectUrl) and the Telegram access-code endpoints answer `403` with `code: "feature_not_available"`. [Telegram](https://pozy.ai/docs/platforms/telegram) and [Discord](https://pozy.ai/docs/platforms/discord) describe how they will connect once they open.

## Slack

Slack installs the bot into a workspace, then the user picks channels.

1. `GET /v1/connect/slack?profileId=...` with no other parameters returns an `authUrl`. The user approves the workspace on Slack.
2. In the standard flow, Pozy then shows a channel picker. In [headless mode](https://pozy.ai/docs/connect/headless), your callback gets a `pendingDataToken`: list the channels with `GET /v1/connect/slack?profileId=...&pendingDataToken=...` (public and private, up to 2,000; `isMember` says whether the bot is already in the channel).
3. `POST /v1/connect/slack` with `channelId` or `channelIds` and the `pendingDataToken` saves each channel as an account.

To add another channel from a workspace you already connected, list with `accountId` of an existing Slack account instead of `pendingDataToken`, then post with `accountId`. No second install needed.

## Errors

| Status | When |
| --- | --- |
| 400 `invalid_field_value` | A field is malformed, for example a `siteUrl` that is not HTTPS or a Bluesky `state` without a profile id. |
| 400 with the platform's reason | The token or app password was rejected. `error` says why. Ask the user to create a new one. |
| 404 `profile_not_found` | The profile is not in this workspace. |
| 409 | The account is already connected somewhere it cannot be shared, for example a WhatsApp number held by another workspace. |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`POST /v1/connect/bluesky/credentials`](https://pozy.ai/docs/api/connectBlueskyCredentials) | Connect Bluesky account |
| [`GET /v1/connect/wordpress`](https://pozy.ai/docs/api/getWordPressAuthUrl) | Get WordPress.com OAuth connect URL |
| [`POST /v1/connect/wordpress/token`](https://pozy.ai/docs/api/connectWordPressWithApplicationPassword) | Connect self-hosted WordPress with an application password |
| [`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/connect/shopify`](https://pozy.ai/docs/api/getShopifyConnectUrl) | Get Shopify OAuth connect URL |
| [`POST /v1/connect/shopify/token`](https://pozy.ai/docs/api/connectShopifyWithToken) | Connect a Shopify store with a custom-app Admin token |
| [`POST /v1/connect/whatsapp/credentials`](https://pozy.ai/docs/api/connectWhatsAppCredentials) | Connect WhatsApp via credentials |
| [`POST /v1/connect/openai-ads/credentials`](https://pozy.ai/docs/api/connectOpenAIAdsCredentials) | Connect an OpenAI Ads account |
| [`GET /v1/connect/slack`](https://pozy.ai/docs/api/listSlackChannels) | List Slack channels for the channel picker |
| [`POST /v1/connect/slack`](https://pozy.ai/docs/api/connectSlackChannel) | Connect a Slack channel |
