# PostZen — integrate into the codebase

You are a coding agent integrating PostZen into your user's project. This
assumes `POSTZEN_API_KEY` is already set (if not, do
`https://www.postzen.dev/auth.md` first).

PostZen is a REST API for publishing and scheduling social media posts across
9 platforms: X (Twitter), Instagram, TikTok, LinkedIn, Facebook, YouTube,
Threads, Pinterest, and Bluesky.

- Base URL: `https://api.postzen.dev` — every API path is prefixed with `/v1`
  (e.g. `https://api.postzen.dev/v1/posts`)
- Auth: `Authorization: Bearer $POSTZEN_API_KEY` on every request; requests
  with a body also need `Content-Type: application/json`
- Full machine-readable docs: `https://docs.postzen.dev/llms-full.txt`
- OpenAPI spec: `https://docs.postzen.dev/api/openapi`

The SDK names, method names, and request shapes below are exact. Do not guess
variants (the Node package is `@postzen/node` with a default-export `PostZen`
class; the Python package is `postzen-sdk` but imports as `postzen`; the Node
method is `posts.createPost`, the Python method is `posts.create_post`). If
unsure, read the SDK's own README or `https://docs.postzen.dev/llms-full.txt`.

## Step 1 — Detect the stack and install the SDK

Pick the SDK that matches the project's primary language. If none fits, call
the REST API directly with the project's HTTP client.

**Node.js / TypeScript**

```bash
npm install @postzen/node
```

**Python**

```bash
pip install postzen-sdk
```

For any other language, follow the REST calls below (they map 1:1 to the
OpenAPI spec at `https://docs.postzen.dev/api/openapi`).

## Step 2 — Initialize the client

Both SDKs read `POSTZEN_API_KEY` from the environment by default. Never
hardcode the key. Confirm `.env` is gitignored.

**Node.js / TypeScript**

```ts
import PostZen from '@postzen/node'

// Reads process.env.POSTZEN_API_KEY by default.
// Or pass it explicitly: new PostZen({ apiKey: process.env.POSTZEN_API_KEY })
const postzen = new PostZen()
```

**Python**

```python
from postzen import PostZen

# Reads POSTZEN_API_KEY by default. Or: PostZen(api_key="...")
client = PostZen()
```

Every API area is a namespace on the one client: `profiles`, `accounts`,
`connect`, `media`, `posts`.

## Step 3 — Verify with a read-only call

Confirm auth and connectivity with a safe call that does not publish anything.
Posts target connected social accounts, so you also need an account `_id` (the
`accountId`) from here before you can create a post.

**Node.js / TypeScript** — methods take one options object and resolve to
`{ data }`:

```ts
const { data } = await postzen.accounts.listAccounts({ query: {} })

for (const account of data.accounts) {
  console.log(account._id, account.platform, account.username)
}
```

**Python** — methods return a Pydantic model read by attribute. Field names
match the API verbatim, **except `_id`, which surfaces as `field_id`**:

```python
response = client.accounts.list_accounts()

for account in response.accounts:
    print(account.field_id, account.platform, account.username)
```

**REST**

```bash
curl -sS https://api.postzen.dev/v1/accounts \
  -H "Authorization: Bearer $POSTZEN_API_KEY"
```

## Step 4 — Create a post

> This publishes or schedules real content. Every post must set **exactly one**
> of `publishNow: true` (posts immediately), `scheduledFor` (an ISO-8601 UTC
> time at least 60 seconds in the future), or `isDraft: true` (saves without
> publishing — use this while testing). `platforms` must reference real account
> `_id`s from Step 3. For idempotency, send an `x-request-id` header —
> repeating the same value returns the original post instead of a duplicate.

**Node.js / TypeScript** — method is `posts.createPost`, args go under `body`.
The created post is nested under `data.post` with a Mongo-style `_id`:

```ts
const { data } = await postzen.posts.createPost({
  body: {
    content: 'Hello from PostZen',
    platforms: [{ platform: 'twitter', accountId: 'jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e' }],
    scheduledFor: '2026-08-01T10:00:00Z',
  },
})

console.log(data.post._id, data.post.status)
```

**Python** — method is `posts.create_post`, snake_case kwargs, and the
`platforms` objects use `account_id`:

```python
response = client.posts.create_post(
    content="Hello from PostZen",
    platforms=[{"platform": "twitter", "account_id": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e"}],
    scheduled_for="2026-08-01T10:00:00Z",
)

print(response.post.field_id, response.post.status)
```

**REST (any language)** — note `content` (not `text`) and the `platforms`
objects `{ platform, accountId }`:

```bash
curl -sS -X POST https://api.postzen.dev/v1/posts \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "content": "Hello from PostZen",
    "platforms": [{ "platform": "twitter", "accountId": "jh72r5nqk9wx3v8m1t4cz6bs0fy5dg3e" }],
    "scheduledFor": "2026-08-01T10:00:00Z"
  }'
```

A successful request returns `201` with `{ "post": { "_id": "...",
"status": "...", ... }, "message": "..." }`. Platform values: `x` (alias
`twitter`), `instagram`, `tiktok`, `linkedin`, `facebook`, `youtube`,
`threads`, `pinterest`, `bluesky`.

## Next steps

If the user has no connected accounts yet, they must connect them in the
PostZen dashboard (`https://app.postzen.dev`) before posts can publish — or
programmatically via `GET /v1/connect/{platform}` (returns an `authUrl` to
send the user to).

To attach images or video, request a presigned upload with
`POST /v1/media/presign`, `PUT` the file to the returned `uploadUrl`, then
reference the returned `publicUrl` in the post's `mediaItems`.

For the full endpoint surface (profiles, account connections, media uploads,
per-platform settings), read `https://docs.postzen.dev/llms-full.txt`.
