Getting started

Quickstart

Create an API key, connect an account, publish and schedule a post, and receive your first signed webhook in about ten minutes.

This page takes you from an empty workspace to a published post and a verified webhook. You need a Pozy account and a login for one social platform. The examples use LinkedIn, but every OAuth platform works the same way.

Your first two connected accounts are free and need no card. X is the exception: it bills every API call, so connecting an X account asks for a card first (pricing).

#Create an API key

Open API keys in the dashboard and click Create key. Give it a name you will recognise later, such as quickstart, and keep the defaults (full access, read and write).

Copy the key straight away. Pozy stores only a hash of it, so it is shown once. Keys start with pz_live_.

Put it in an environment variable. Every example below reads it from there.

Terminal
export POZY_API_KEY="pz_live_..."

The Node examples use the fetch built into Node.js 18 and later, and the Python examples use requests. There is nothing Pozy-specific to install.

#Check the key

GET /v1/auth/verify tells you whether a credential works and what it can reach. It is the cheapest call to make when something looks wrong.

curl https://api.pozy.ai/v1/auth/verify \
  -H "Authorization: Bearer $POZY_API_KEY"
Response
{
  "valid": true,
  "userId": "usr_4fQ9kT2mZx8LwB1nR7cY",
  "name": "Alex Rivera",
  "email": "alex@example.com",
  "authType": "api_key",
  "scope": "full"
}

A 401 with "code": "invalid_api_key" means the key was mistyped, revoked or expired.

#Create a profile

A profile is a container for connected accounts: one per brand, client or customer of yours. Create one for this walkthrough and keep its _id.

curl -X POST https://api.pozy.ai/v1/profiles \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Quickstart" }'
Response (201)
{
  "message": "Profile created successfully",
  "profile": {
    "_id": "6720f3b1c9e4a85d2f0b7c14",
    "name": "Quickstart",
    "externalId": null,
    "isDefault": false,
    "accountCount": 0
  }
}

#Connect an account

Ask for a connect URL for the platform and the profile. Open the authUrl in a browser, sign in to LinkedIn and approve.

curl "https://api.pozy.ai/v1/connect/linkedin?profileId=6720f3b1c9e4a85d2f0b7c14" \
  -H "Authorization: Bearer $POZY_API_KEY"

Pozy hosts every screen of the flow, including the page that asks whether to connect your personal LinkedIn profile or a company page. When you pass a redirect_url, the browser comes back to it with the new account's accountId in the query string. Without one, the flow ends on a Pozy page that says the account is connected.

Connecting accounts covers branded connect pages, headless flows for your own UI and the platforms that use credentials instead of OAuth.

#Find the account id

Posts target accounts by their _id, never by username. List the accounts in the profile:

curl "https://api.pozy.ai/v1/accounts?profileId=6720f3b1c9e4a85d2f0b7c14" \
  -H "Authorization: Bearer $POZY_API_KEY"
Response (trimmed)
{
  "accounts": [
    {
      "_id": "6720f41a8d3be96c1e5a02f7",
      "platform": "linkedin",
      "profileId": "6720f3b1c9e4a85d2f0b7c14",
      "username": "alex-rivera",
      "displayName": "Alex Rivera",
      "isActive": true
    }
  ],
  "hasAnalyticsAccess": true
}

#Publish a post now

Send the text, the target account and publishNow: true. The Idempotency-Key header makes the call safe to retry: if the network drops and you send it again with the same key, you get the same post back instead of a second one.

curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-first-post" \
  -d '{
    "content": "Hello from the Pozy API.",
    "platforms": [{ "platform": "linkedin", "accountId": "6720f41a8d3be96c1e5a02f7" }],
    "publishNow": true
  }'

The post comes back with an overall status and one entry per target in platforms, each with its own status, platformPostId and platformPostUrl once it is live.

#Schedule a post

Leave out publishNow and send scheduledFor instead. Pair it with timezone so the time is read the way you wrote it:

curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-scheduled-post" \
  -d '{
    "content": "This one goes out tomorrow morning.",
    "platforms": [{ "platform": "linkedin", "accountId": "6720f41a8d3be96c1e5a02f7" }],
    "scheduledFor": "2026-11-02T09:00:00",
    "timezone": "Europe/Madrid"
  }'

Until it publishes, you can change or cancel it with PUT /v1/posts/{postId} and DELETE /v1/posts/{postId}. Scheduling and timezones has the details.

#Receive a webhook

Instead of polling the post, let Pozy tell you when it goes live. You need a public HTTPS URL. For local testing, run a tunnel to your machine and use its URL.

This small Node server accepts deliveries and checks the signature:

server.mjs
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.POZY_WEBHOOK_SECRET;

createServer((req, res) => {
  let raw = "";
  req.on("data", (chunk) => (raw += chunk));
  req.on("end", () => {
    const expected = createHmac("sha256", SECRET).update(raw).digest("hex");
    const got = req.headers["x-pozy-signature"] ?? "";
    const ok = got.length === expected.length && timingSafeEqual(Buffer.from(got), Buffer.from(expected));
    if (!ok) { res.writeHead(401).end(); return; }

    const event = JSON.parse(raw);
    console.log(req.headers["x-pozy-event"], event.id);
    res.writeHead(200).end(); // answer within 5 seconds
  });
}).listen(3000);

Pick a signing secret, start the server with it, then register the endpoint for the post events:

export POZY_WEBHOOK_SECRET="$(openssl rand -hex 32)"

curl -X POST https://api.pozy.ai/v1/webhooks/settings \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Quickstart",
    "url": "https://your-tunnel.example.com/",
    "secret": "'"$POZY_WEBHOOK_SECRET"'",
    "events": ["post.published", "post.failed", "post.partial"]
  }'

Send a test delivery to check the wiring before the real post goes out:

Terminal
curl -X POST https://api.pozy.ai/v1/webhooks/test \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "webhookId": "whk_8Jq2LmT4vX9cR1sZp0Ya3Kd", "event": "post.published" }'

When the scheduled post publishes, your server prints post.published and the event id. The body looks like this:

post.published
{
  "id": "evt_8a1f4c2e9b7d3a60",
  "event": "post.published",
  "timestamp": "2026-11-02T08:00:03.412Z",
  "profiles": [{ "id": "6720f3b1c9e4a85d2f0b7c14", "externalId": null }],
  "post": {
    "id": "6721a0c47b2e5f9d3a18c6e0",
    "content": "This one goes out tomorrow morning.",
    "status": "published",
    "scheduledFor": "2026-11-02T08:00:00.000Z",
    "publishedAt": "2026-11-02T08:00:03.120Z",
    "platforms": [
      {
        "platform": "linkedin",
        "status": "published",
        "accountId": "6720f41a8d3be96c1e5a02f7",
        "publishedUrl": "https://www.linkedin.com/feed/update/urn:li:share:7255..."
      }
    ]
  }
}
Always set a secret

Endpoints created without a secret receive unsigned deliveries. Set one and reject any request whose X-Pozy-Signature does not match. Verify signatures shows the check in Node and Python.

#Where to go next