@growthub/cli 0.3.55 → 0.3.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -156
- package/assets/worker-kits/growthub-zernio-social-v1/QUICKSTART.md +209 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/NEW-CLIENT.md +74 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/_template/brand-kit.md +131 -0
- package/assets/worker-kits/growthub-zernio-social-v1/brands/growthub/brand-kit.md +141 -0
- package/assets/worker-kits/growthub-zernio-social-v1/bundles/growthub-zernio-social-v1.json +55 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/ai-caption-layer.md +132 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/local-adapters.md +123 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/platform-coverage.md +112 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/postiz-ui-shell-integration.md +166 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/posts-and-queues-layer.md +208 -0
- package/assets/worker-kits/growthub-zernio-social-v1/docs/zernio-api-integration.md +265 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/analytics-brief-sample.md +97 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/client-proposal-sample.md +106 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/content-calendar-sample.md +74 -0
- package/assets/worker-kits/growthub-zernio-social-v1/examples/social-campaign-sample.md +105 -0
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/README.md +146 -0
- package/assets/worker-kits/growthub-zernio-social-v1/growthub-meta/kit-standard.md +120 -0
- package/assets/worker-kits/growthub-zernio-social-v1/kit.json +104 -0
- package/assets/worker-kits/growthub-zernio-social-v1/output/README.md +63 -0
- package/assets/worker-kits/growthub-zernio-social-v1/output-standards.md +132 -0
- package/assets/worker-kits/growthub-zernio-social-v1/runtime-assumptions.md +170 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/check-deps.mjs +117 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/check-deps.sh +86 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/install-mcp.mjs +177 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/setup.mjs +247 -0
- package/assets/worker-kits/growthub-zernio-social-v1/setup/verify-env.mjs +138 -0
- package/assets/worker-kits/growthub-zernio-social-v1/skills.md +332 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/analytics-brief.md +101 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/caption-copy-deck.md +105 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/client-proposal.md +98 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/content-calendar.md +70 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/platform-publishing-plan.md +86 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/scheduling-manifest.md +92 -0
- package/assets/worker-kits/growthub-zernio-social-v1/templates/social-campaign-brief.md +102 -0
- package/assets/worker-kits/growthub-zernio-social-v1/validation-checklist.md +85 -0
- package/assets/worker-kits/growthub-zernio-social-v1/workers/zernio-social-operator/CLAUDE.md +307 -0
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Postiz UI Shell + Zernio Engine — Integration Recipe
|
|
2
|
+
|
|
3
|
+
This document is the canonical recipe for running `growthub-zernio-social-v1` as the engine layer underneath the stable `growthub-postiz-social-v1` kit's UI shell.
|
|
4
|
+
|
|
5
|
+
**Read order:** `../runtime-assumptions.md` → `./zernio-api-integration.md` → this file.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architectural split
|
|
10
|
+
|
|
11
|
+
| Layer | Owned by | What it does |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Presentation | `growthub-postiz-social-v1` (Postiz UI) | Calendar, compose, analytics shell, team workspace |
|
|
14
|
+
| Engine | `growthub-zernio-social-v1` (this kit) | Posts, queues, media, inbox, analytics transport against Zernio REST API |
|
|
15
|
+
|
|
16
|
+
**Core principle:** Postiz stays the UI. Zernio replaces Postiz's native provider/posting engine. Nothing in Postiz's own database schema, Redis queue runner, React app, or auth/team/workspace system changes. The diff surface is the provider + publish-bridge layers only.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The 7 Module Integration Map
|
|
21
|
+
|
|
22
|
+
### Module 1 — Provider Override (Zernio as the transport)
|
|
23
|
+
|
|
24
|
+
Postiz's provider system is where per-platform OAuth and posting normally live. Replace it with a single `ZernioProvider`:
|
|
25
|
+
|
|
26
|
+
- `baseUrl` → `https://zernio.com/api/v1`
|
|
27
|
+
- `authHeader` → `Authorization: Bearer ${ZERNIO_API_KEY}`
|
|
28
|
+
- All 14 platforms route through this one provider — no per-platform OAuth apps on the Postiz side
|
|
29
|
+
- Postiz's "Connect Account" UI validates `ZERNIO_API_KEY` instead of OAuth dancing — Zernio handles all OAuth upstream
|
|
30
|
+
|
|
31
|
+
The config for this provider mirrors exactly `buildZernioSocialConfig()` in `cli/src/kits/core/index.ts`:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
{
|
|
35
|
+
providerId: "zernio",
|
|
36
|
+
providerName: "Zernio (hosted)",
|
|
37
|
+
providerBaseUrl: "https://zernio.com/api/v1",
|
|
38
|
+
providerAuthField: "Authorization",
|
|
39
|
+
apiKeyEnvVar: "ZERNIO_API_KEY",
|
|
40
|
+
additionalRequiredEnvVars: ["ZERNIO_API_URL"],
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Module 2 — Post Submission Bridge
|
|
45
|
+
|
|
46
|
+
The Zernio operator already produces manifests shaped as valid `POST /api/v1/posts` bodies. The bridge:
|
|
47
|
+
|
|
48
|
+
1. Intercepts Postiz's internal `publishPost()` call
|
|
49
|
+
2. Transforms the Postiz post into a Zernio manifest entry (see `./posts-and-queues-layer.md`)
|
|
50
|
+
3. Fires `POST ${ZERNIO_API_URL}/posts` with `Authorization: Bearer ${ZERNIO_API_KEY}` and `Idempotency-Key: <clientPostId>`
|
|
51
|
+
4. Feeds Zernio's response back into Postiz's job tracker
|
|
52
|
+
|
|
53
|
+
`clientPostId` format stays `<client-slug>-<YYYYMMDD>-<sequence>`. Re-submitting the same manifest under the same key is safe.
|
|
54
|
+
|
|
55
|
+
### Module 3 — Queue Sync Layer
|
|
56
|
+
|
|
57
|
+
Postiz's queue concept maps 1:1 onto Zernio queues (`POST /api/v1/queues`):
|
|
58
|
+
|
|
59
|
+
- Postiz queue scheduler → Zernio `queues` endpoint (create) / `queues/<queueId>` (update)
|
|
60
|
+
- `Idempotency-Key: queue-<name>` prevents double-fire on retry
|
|
61
|
+
- Posts attached to a queue omit `scheduledFor` and include `queueId`
|
|
62
|
+
- The 10-step `zernio-social-operator` workflow becomes the execution contract for each queue job
|
|
63
|
+
|
|
64
|
+
### Module 4 — AI Caption Layer Surface
|
|
65
|
+
|
|
66
|
+
The agent-side `/zernio` command surface replaces Postiz's native AI composer:
|
|
67
|
+
|
|
68
|
+
- Postiz compose textarea → `/zernio captions` (or `/zernio campaign` for full scope)
|
|
69
|
+
- Caption drafts come from the A/B/C variant rules in `./ai-caption-layer.md`
|
|
70
|
+
- Claude Code runs as a background operator session; Postiz compose UI is the front-end trigger
|
|
71
|
+
- The 10 `/zernio` subcommands wire into Postiz's compose surface as slash-quick-actions
|
|
72
|
+
|
|
73
|
+
### Module 5 — Platform Coverage Config
|
|
74
|
+
|
|
75
|
+
`./platform-coverage.md` is the source of truth for Postiz channel configuration:
|
|
76
|
+
|
|
77
|
+
- Disable native Postiz channel handlers
|
|
78
|
+
- Register 14 channels pointing to the Zernio transport from Module 1
|
|
79
|
+
- The per-platform format spec (char limits, aspect ratios, post types, carousel eligibility, thread support, cadence) drives Postiz's per-channel validation + preview rendering
|
|
80
|
+
- `skills.md` per-platform tone rules feed the compose UI's voice hints
|
|
81
|
+
|
|
82
|
+
### Module 6 — ENV + Secret Surface
|
|
83
|
+
|
|
84
|
+
Direct alignment — the cleanest module:
|
|
85
|
+
|
|
86
|
+
| Kit env var | Postiz field |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `ZERNIO_API_KEY` | provider credentials field (replaces all per-platform OAuth tokens) |
|
|
89
|
+
| `ZERNIO_API_URL` | provider baseUrl override (regional/proxy deployments only) |
|
|
90
|
+
| `ZERNIO_PROFILE_ID` | default profile scope for all write requests |
|
|
91
|
+
| `ZERNIO_TIMEZONE` | default posting timezone when the profile's timezone isn't used |
|
|
92
|
+
|
|
93
|
+
`setup/verify-env.mjs` and `setup/check-deps.sh` run as Postiz's pre-start health checks. The secret-hygiene scan (`sk_` + 64-hex leak detection from the kit test suite) is the canonical gate on any output or log line.
|
|
94
|
+
|
|
95
|
+
### Module 7 — Workspace CLI Entry Point
|
|
96
|
+
|
|
97
|
+
`@growthub/cli >= 0.3.57` is the operator terminal that sits beside Postiz:
|
|
98
|
+
|
|
99
|
+
- `growthub kit download growthub-zernio-social-v1` — materialize the kit
|
|
100
|
+
- `growthub kit path growthub-zernio-social-v1` — print the working-directory path
|
|
101
|
+
- Point a Claude Code session at that folder; the operator handles the full 10-step workflow
|
|
102
|
+
- Postiz UI reflects the output — scheduled posts, queue runs, generated captions, analytics
|
|
103
|
+
- The 7 artifacts declared by `buildZernioSocialConfig()` (brief, calendar, publishing plan, caption deck, scheduling manifest, analytics brief, client proposal) are the canonical source of truth for what the UI shows
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Integration Sequence (request path)
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Postiz UI (compose / schedule)
|
|
111
|
+
↓
|
|
112
|
+
Module 2 — Post Submission Bridge
|
|
113
|
+
↓
|
|
114
|
+
Module 1 — ZernioProvider (Authorization: Bearer ZERNIO_API_KEY)
|
|
115
|
+
↓
|
|
116
|
+
Zernio API → POST /api/v1/posts
|
|
117
|
+
+ POST /api/v1/queues
|
|
118
|
+
+ POST /api/v1/media
|
|
119
|
+
+ GET /api/v1/inbox
|
|
120
|
+
+ GET /api/v1/analytics/*
|
|
121
|
+
↑
|
|
122
|
+
Module 3 — Queue Sync reads back status
|
|
123
|
+
↑
|
|
124
|
+
Postiz Calendar / Analytics shell renders state
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Claude Code + the `/zernio` surface sits **laterally** — it feeds the compose box in Postiz before submission, not inside the request path. This keeps the agent lane non-blocking for end-user UI latency.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## What Stays Untouched in Postiz
|
|
132
|
+
|
|
133
|
+
| Layer | Status |
|
|
134
|
+
|---|---|
|
|
135
|
+
| Postgres schema | Unchanged |
|
|
136
|
+
| Redis queue runner | Unchanged (different job payloads, same runner) |
|
|
137
|
+
| React frontend | Unchanged (except the compose AI hook in Module 4) |
|
|
138
|
+
| Auth / team / workspace | Unchanged |
|
|
139
|
+
| Existing Postiz kit payload (`growthub-postiz-social-v1`) | Unchanged — it remains valid for self-hosted-only operators |
|
|
140
|
+
|
|
141
|
+
This keeps the diff surface minimal and keeps `growthub-zernio-social-v1` fully isolated as the engine layer.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Operator Setup Order (when paired with Postiz UI shell)
|
|
146
|
+
|
|
147
|
+
1. Stand up Postiz via its own kit (`growthub kit download growthub-postiz-social-v1`) — optional if only the UI layer is desired
|
|
148
|
+
2. Download this kit: `growthub kit download growthub-zernio-social-v1`
|
|
149
|
+
3. Fill `.env` — `ZERNIO_API_KEY`, `ZERNIO_API_URL`, `ZERNIO_PROFILE_ID`
|
|
150
|
+
4. Run `node setup/verify-env.mjs` (kit-level) and the Postiz pre-start checks
|
|
151
|
+
5. Apply Modules 1–3 inside the Postiz fork (provider override, publish bridge, queue sync). Everything else stays default.
|
|
152
|
+
6. Open Claude Code at the Zernio kit's exported folder for the `/zernio` surface
|
|
153
|
+
|
|
154
|
+
Standalone (without Postiz UI): everything still works — the kit is self-contained, and Modules 1–3 are optional.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Validation Before Go-Live
|
|
159
|
+
|
|
160
|
+
- [ ] Zernio API key scope is `read-write`
|
|
161
|
+
- [ ] `GET /api/v1/accounts?profileId=...` returns at least one connected platform
|
|
162
|
+
- [ ] A dry-run manifest round-trips through Module 2 with a successful 2xx from `POST /api/v1/posts`
|
|
163
|
+
- [ ] A queue definition round-trips through Module 3 with a returned `queueId`
|
|
164
|
+
- [ ] Postiz channel list surfaces all 14 Zernio platform slugs from `./platform-coverage.md`
|
|
165
|
+
- [ ] Compose surface calls into `/zernio captions` and renders A/B/C variants
|
|
166
|
+
- [ ] Kit secret-hygiene scan on the Postiz fork's logs is clean
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Posts + Queues Layer
|
|
2
|
+
|
|
3
|
+
This document specifies the JSON shapes the operator must produce. Every shape is a valid request body for the corresponding Zernio REST endpoint, so manifests can be piped directly into `curl` or the Zernio SDKs with no re-shaping.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Scheduling Manifest
|
|
8
|
+
|
|
9
|
+
The scheduling manifest is the machine-readable record the operator writes to `output/<client-slug>/<project-slug>/scheduling-manifest.json` whenever scheduling is requested.
|
|
10
|
+
|
|
11
|
+
### Top-level shape
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"zernioSchedulingManifest": {
|
|
16
|
+
"version": "1.0",
|
|
17
|
+
"profileId": "<ZERNIO_PROFILE_ID>",
|
|
18
|
+
"timezone": "America/New_York",
|
|
19
|
+
"dryRun": false,
|
|
20
|
+
"generatedAt": "2026-04-15T14:30:00-04:00",
|
|
21
|
+
"notes": "<free-form operator notes — kept with the manifest>",
|
|
22
|
+
"posts": [ /* post entries */ ]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Field | Required | Type | Notes |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `version` | Yes | string | Manifest schema version. Current: `"1.0"`. |
|
|
30
|
+
| `profileId` | Yes | string | Must be a real Zernio profile id in api-live mode. In agent-only mode may be `"placeholder"` provided `dryRun: true`. |
|
|
31
|
+
| `timezone` | Yes | string | IANA name. Default: value of `ZERNIO_TIMEZONE` or the profile's default. |
|
|
32
|
+
| `dryRun` | Yes | boolean | `true` in agent-only mode. `false` in api-live and hybrid modes. |
|
|
33
|
+
| `generatedAt` | Yes | string | ISO 8601 with tz offset. |
|
|
34
|
+
| `notes` | No | string | Free-form notes — kept with the manifest for audit. |
|
|
35
|
+
| `posts` | Yes | array | 1..N entries. |
|
|
36
|
+
|
|
37
|
+
### Per-post entry shape
|
|
38
|
+
|
|
39
|
+
Each entry is a direct body for `POST /api/v1/posts`, with two additional fields the operator uses for bookkeeping (`clientPostId`, `status`).
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"clientPostId": "urban-cycle-20260501-001",
|
|
44
|
+
"content": "Launch day — shipping our spring gravel lineup. Ride it today →",
|
|
45
|
+
"scheduledFor": "2026-05-01T09:00:00-04:00",
|
|
46
|
+
"timezone": "America/New_York",
|
|
47
|
+
"media": [{ "mediaId": "med_HERO_01" }],
|
|
48
|
+
"platforms": [
|
|
49
|
+
{ "platform": "instagram", "accountId": "acc_ig_UrbanCycle" },
|
|
50
|
+
{ "platform": "twitter", "accountId": "acc_x_UrbanCycle" }
|
|
51
|
+
],
|
|
52
|
+
"status": "pending"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Field | Required | Type | Notes |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `clientPostId` | Yes | string | Operator-generated id. Format: `<client-slug>-<YYYYMMDD>-<sequence>`. Used as the `Idempotency-Key` when submitting to Zernio. Not sent in the body. |
|
|
59
|
+
| `content` | Yes | string | The selected caption variant. |
|
|
60
|
+
| `scheduledFor` | Conditional | string | ISO 8601 with tz offset. Required unless the post is attached to a queue via `queueId`. |
|
|
61
|
+
| `timezone` | Yes | string | IANA name. |
|
|
62
|
+
| `media` | No | array | Array of `{ "mediaId": "<id>" }`. In api-live mode must reference real Zernio `mediaId` values obtained from `POST /api/v1/media`. |
|
|
63
|
+
| `platforms` | Yes | array | 1..N `{ platform, accountId }`. Every `platform` must exist in `docs/platform-coverage.md`. |
|
|
64
|
+
| `queueId` | Conditional | string | Only when the post is attached to a queue. Mutually exclusive with `scheduledFor`. |
|
|
65
|
+
| `status` | Yes | string | Operator bookkeeping. Always starts at `"pending"` and transitions to `"scheduled"` / `"published"` / `"failed"` once Zernio confirms. |
|
|
66
|
+
|
|
67
|
+
### Submitting a manifest
|
|
68
|
+
|
|
69
|
+
Each entry becomes one Zernio POST:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
curl -sS -X POST \
|
|
73
|
+
"${ZERNIO_API_URL:-https://zernio.com/api/v1}/posts" \
|
|
74
|
+
-H "Authorization: Bearer ${ZERNIO_API_KEY}" \
|
|
75
|
+
-H "Content-Type: application/json" \
|
|
76
|
+
-H "Idempotency-Key: <clientPostId>" \
|
|
77
|
+
--data-binary @- <<'JSON'
|
|
78
|
+
{
|
|
79
|
+
"profileId": "prof_abc123",
|
|
80
|
+
"content": "...",
|
|
81
|
+
"scheduledFor": "2026-05-01T09:00:00-04:00",
|
|
82
|
+
"timezone": "America/New_York",
|
|
83
|
+
"media": [{ "mediaId": "med_HERO_01" }],
|
|
84
|
+
"platforms": [{ "platform": "twitter", "accountId": "acc_x_111" }]
|
|
85
|
+
}
|
|
86
|
+
JSON
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The operator is responsible for issuing one request per `posts[]` entry and for retrying on 429/5xx with the same `Idempotency-Key`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Recurring Queue Definition
|
|
94
|
+
|
|
95
|
+
Queues define a repeating schedule on a profile. Posts attached to a queue auto-schedule into the next available slot.
|
|
96
|
+
|
|
97
|
+
### Queue shape
|
|
98
|
+
|
|
99
|
+
Body of `POST /api/v1/queues` and `PUT /api/v1/queues/<queueId>`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"profileId": "prof_abc123",
|
|
104
|
+
"name": "weekly-evergreen",
|
|
105
|
+
"timezone": "America/New_York",
|
|
106
|
+
"slots": [
|
|
107
|
+
{ "day": "mon", "time": "09:00", "platforms": ["twitter", "linkedin"] },
|
|
108
|
+
{ "day": "wed", "time": "12:30", "platforms": ["instagram"] },
|
|
109
|
+
{ "day": "fri", "time": "17:00", "platforms": ["bluesky", "threads"] }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Field | Required | Type | Notes |
|
|
115
|
+
|---|---|---|---|
|
|
116
|
+
| `profileId` | Yes | string | Zernio profile owning the queue. |
|
|
117
|
+
| `name` | Yes | string | Lowercase kebab-case recommended. |
|
|
118
|
+
| `timezone` | Yes | string | IANA name. |
|
|
119
|
+
| `slots` | Yes | array | 1..N slots. |
|
|
120
|
+
| `slots[].day` | Yes | string | One of `mon|tue|wed|thu|fri|sat|sun`. |
|
|
121
|
+
| `slots[].time` | Yes | string | `HH:MM` 24-hour. |
|
|
122
|
+
| `slots[].platforms` | Yes | array | Platforms enabled for this slot. |
|
|
123
|
+
|
|
124
|
+
### Post attached to a queue
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"profileId": "prof_abc123",
|
|
129
|
+
"queueId": "que_xyz789",
|
|
130
|
+
"content": "Weekend builder log: three wins, one lesson.",
|
|
131
|
+
"platforms": [{ "platform": "twitter", "accountId": "acc_x_111" }]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The request is still `POST /api/v1/posts` but with `queueId` instead of `scheduledFor`. Zernio returns the computed `scheduledFor` in the response.
|
|
136
|
+
|
|
137
|
+
### Queue deliverable in the kit
|
|
138
|
+
|
|
139
|
+
The operator writes a queue definition alongside the scheduling manifest as:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
output/<client-slug>/<project-slug>/queue-<queue-name>.json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The file contains one JSON object — the `zernioQueue` wrapper below — so that it reads as documentation plus a ready-to-submit request body.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"zernioQueue": {
|
|
150
|
+
"version": "1.0",
|
|
151
|
+
"profileId": "<ZERNIO_PROFILE_ID>",
|
|
152
|
+
"dryRun": false,
|
|
153
|
+
"queue": {
|
|
154
|
+
"profileId": "<ZERNIO_PROFILE_ID>",
|
|
155
|
+
"name": "weekly-evergreen",
|
|
156
|
+
"timezone": "America/New_York",
|
|
157
|
+
"slots": [
|
|
158
|
+
{ "day": "mon", "time": "09:00", "platforms": ["twitter", "linkedin"] }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Media Upload
|
|
168
|
+
|
|
169
|
+
Every image or video referenced by a post must be uploaded first.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
curl -sS -X POST \
|
|
173
|
+
"${ZERNIO_API_URL:-https://zernio.com/api/v1}/media" \
|
|
174
|
+
-H "Authorization: Bearer ${ZERNIO_API_KEY}" \
|
|
175
|
+
-H "Idempotency-Key: media-urban-cycle-20260501-hero-01" \
|
|
176
|
+
-F "file=@./media/urban-cycle/hero-01.png" \
|
|
177
|
+
-F "profileId=prof_abc123"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The response includes `mediaId`. That id is what the scheduling manifest references.
|
|
181
|
+
|
|
182
|
+
In `agent-only` mode the operator emits placeholder `mediaId` values prefixed with `placeholder_` and documents the required asset in the Caption Copy Deck's "Media Notes" column. The user uploads the real media and replaces placeholders before submitting.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Idempotency Contract
|
|
187
|
+
|
|
188
|
+
| Endpoint | Idempotency key source |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `POST /api/v1/posts` | `clientPostId` from the manifest entry |
|
|
191
|
+
| `POST /api/v1/queues` | `queue.name` prefixed with `queue-` |
|
|
192
|
+
| `POST /api/v1/media` | `media-<client-slug>-<YYYYMMDD>-<asset-slug>` |
|
|
193
|
+
|
|
194
|
+
The operator must NEVER send different bodies under the same `Idempotency-Key`. If content changes, increment the manifest version and use a new key.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Validation Rules (pre-submission)
|
|
199
|
+
|
|
200
|
+
Before any submission, re-run through `validation-checklist.md`. Specifically:
|
|
201
|
+
|
|
202
|
+
- Every `posts[].platforms[].platform` exists in `docs/platform-coverage.md`
|
|
203
|
+
- Every `clientPostId` matches the filename conventions in `output-standards.md`
|
|
204
|
+
- `scheduledFor` timestamps all fall inside the campaign window from the Social Campaign Brief
|
|
205
|
+
- `dryRun` matches the declared execution mode
|
|
206
|
+
- No post entry exceeds per-platform character limits from `docs/ai-caption-layer.md`
|
|
207
|
+
|
|
208
|
+
Failure on any of these is a blocker — fix the manifest before submitting.
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Zernio API Integration
|
|
2
|
+
|
|
3
|
+
This document is the reference contract between the `growthub-zernio-social-v1` kit and the [Zernio](https://zernio.com) REST API. It is frozen at kit creation and should be updated when the upstream API contract changes.
|
|
4
|
+
|
|
5
|
+
> Source of truth upstream: [docs.zernio.com](https://docs.zernio.com). When this document conflicts with live Zernio docs, the live docs win; update this file and the kit version.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Base Contract
|
|
10
|
+
|
|
11
|
+
| Field | Value |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Base URL | `https://zernio.com/api/v1` |
|
|
14
|
+
| Env var | `ZERNIO_API_URL` (override only for regional / proxy deployments) |
|
|
15
|
+
| Transport | HTTPS only |
|
|
16
|
+
| Request content type | `application/json` (JSON endpoints) · `multipart/form-data` (`/media` upload) |
|
|
17
|
+
| Response content type | `application/json` |
|
|
18
|
+
| Character encoding | UTF-8 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Authentication
|
|
23
|
+
|
|
24
|
+
Every request carries a single bearer header:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Authorization: Bearer ${ZERNIO_API_KEY}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Key format
|
|
31
|
+
|
|
32
|
+
- Prefix: `sk_`
|
|
33
|
+
- Body: 64 hex characters
|
|
34
|
+
- Total length: 67 characters
|
|
35
|
+
|
|
36
|
+
Regex: `^sk_[0-9a-fA-F]{64}$`
|
|
37
|
+
|
|
38
|
+
### Key scopes
|
|
39
|
+
|
|
40
|
+
| Scope | Purpose |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `read` | Read-only endpoints only (profiles, accounts, analytics, inbox listing) |
|
|
43
|
+
| `read-write` | Everything in `read` plus create/update posts, queues, media, and inbox replies |
|
|
44
|
+
|
|
45
|
+
### Key scope filter
|
|
46
|
+
|
|
47
|
+
An individual key may be scoped `full` (any profile on the account) or `profiles-specific` (restricted to a list of profile IDs).
|
|
48
|
+
|
|
49
|
+
### Idempotency
|
|
50
|
+
|
|
51
|
+
Attach an `Idempotency-Key` header to every write request. The operator uses the `clientPostId` from the scheduling manifest as the idempotency key so the manifest can be re-submitted safely.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Core Resource Model
|
|
56
|
+
|
|
57
|
+
Zernio models social publishing around five primary resources:
|
|
58
|
+
|
|
59
|
+
| Resource | Endpoint root | Role |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Profiles | `/api/v1/profiles` | A container that groups social accounts. Every request is implicitly scoped to one profile for scheduling. |
|
|
62
|
+
| Accounts | `/api/v1/accounts` | A connected social account that belongs to a profile. Identified by `accountId`. |
|
|
63
|
+
| Posts | `/api/v1/posts` | Schedulable content. One post can fan out to many `{ platform, accountId }` pairs. |
|
|
64
|
+
| Queues | `/api/v1/queues` | Recurring time-slot schedule attached to a profile. Posts added to a queue auto-schedule into the next open slot. |
|
|
65
|
+
| Media | `/api/v1/media` | Image, video, and document assets uploaded once and referenced by `mediaId` from a post body. |
|
|
66
|
+
|
|
67
|
+
Two secondary resources:
|
|
68
|
+
|
|
69
|
+
| Resource | Endpoint root | Role |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| Inbox | `/api/v1/inbox` | Unified DM, comment, and review conversations aggregated per profile. |
|
|
72
|
+
| Analytics | `/api/v1/analytics` | Per-post and per-account metrics. |
|
|
73
|
+
|
|
74
|
+
Supporting endpoints:
|
|
75
|
+
|
|
76
|
+
| Endpoint | Role |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `/api/v1/api-keys` | Create, list, rotate, and revoke API keys |
|
|
79
|
+
| `/api/v1/connect/<platform>` | Begin platform OAuth / credential flow for a new account on a profile |
|
|
80
|
+
| `/api/v1/platforms` | Live list of supported platforms and their per-platform capability flags |
|
|
81
|
+
|
|
82
|
+
Extended capability surface (confirmed in `zernio-cli`, may be plan-gated):
|
|
83
|
+
|
|
84
|
+
| Resource | Endpoint root | Role |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Contacts | `/api/v1/contacts` | CRUD + bulk create + custom fields |
|
|
87
|
+
| Broadcasts | `/api/v1/broadcasts` | Create / schedule / send / cancel + recipient management |
|
|
88
|
+
| Sequences | `/api/v1/sequences` | Create / activate / pause + enrollment management |
|
|
89
|
+
| Automations | `/api/v1/automations` | Create / run / inspect execution logs |
|
|
90
|
+
| Webhooks | `/api/v1/webhooks` | Settings (create/update) + logs + signature verification; events include `account.connected`, `post.recycled` |
|
|
91
|
+
|
|
92
|
+
The zernio-social-operator uses these endpoints only when the user explicitly asks for CRM-shaped work (contacts / broadcasts / sequences) or wants an automation wired into a campaign. Default campaign flow stays scoped to posts + queues + media + inbox + analytics.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Endpoints Used By This Kit
|
|
97
|
+
|
|
98
|
+
### Profiles
|
|
99
|
+
|
|
100
|
+
- `GET /api/v1/profiles` — list profiles on the account
|
|
101
|
+
- `GET /api/v1/profiles/<profileId>` — fetch a single profile, including the default timezone
|
|
102
|
+
|
|
103
|
+
### Accounts
|
|
104
|
+
|
|
105
|
+
- `GET /api/v1/accounts?profileId=<id>` — list connected accounts on a profile (platform + handle + accountId)
|
|
106
|
+
|
|
107
|
+
### Media upload
|
|
108
|
+
|
|
109
|
+
- `POST /api/v1/media` — multipart upload; response includes `mediaId`
|
|
110
|
+
- `GET /api/v1/media/<mediaId>` — fetch metadata of an uploaded asset
|
|
111
|
+
|
|
112
|
+
### Posts
|
|
113
|
+
|
|
114
|
+
- `POST /api/v1/posts` — schedule one post with fan-out targets
|
|
115
|
+
- `GET /api/v1/posts?profileId=<id>&status=scheduled` — list scheduled posts
|
|
116
|
+
- `GET /api/v1/posts/<postId>` — fetch a single post
|
|
117
|
+
- `DELETE /api/v1/posts/<postId>` — unschedule a post (only while `status=scheduled`)
|
|
118
|
+
|
|
119
|
+
Minimal `POST /api/v1/posts` body:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"profileId": "prof_abc123",
|
|
124
|
+
"content": "Launch day — shipping our new kit. →",
|
|
125
|
+
"scheduledFor": "2026-05-01T09:00:00-04:00",
|
|
126
|
+
"timezone": "America/New_York",
|
|
127
|
+
"media": [{ "mediaId": "med_imgA" }],
|
|
128
|
+
"platforms": [
|
|
129
|
+
{ "platform": "twitter", "accountId": "acc_x_111" },
|
|
130
|
+
{ "platform": "linkedin", "accountId": "acc_li_222" }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Queues (recurring schedules)
|
|
136
|
+
|
|
137
|
+
- `POST /api/v1/queues` — create a recurring queue
|
|
138
|
+
- `GET /api/v1/queues?profileId=<id>` — list queues on a profile
|
|
139
|
+
- `PUT /api/v1/queues/<queueId>` — replace queue configuration
|
|
140
|
+
- `DELETE /api/v1/queues/<queueId>` — remove queue (future slots stop; already-scheduled posts remain)
|
|
141
|
+
|
|
142
|
+
Minimal queue body:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"profileId": "prof_abc123",
|
|
147
|
+
"name": "weekly-evergreen",
|
|
148
|
+
"timezone": "America/New_York",
|
|
149
|
+
"slots": [
|
|
150
|
+
{ "day": "mon", "time": "09:00", "platforms": ["twitter", "linkedin"] },
|
|
151
|
+
{ "day": "wed", "time": "12:30", "platforms": ["instagram"] },
|
|
152
|
+
{ "day": "fri", "time": "17:00", "platforms": ["bluesky", "threads"] }
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Posts attached to a queue omit `scheduledFor` and include `queueId` instead:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"profileId": "prof_abc123",
|
|
162
|
+
"queueId": "que_xyz789",
|
|
163
|
+
"content": "Weekend builder log: three wins, one lesson.",
|
|
164
|
+
"platforms": [{ "platform": "twitter", "accountId": "acc_x_111" }]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Inbox (DMs, comments, reviews)
|
|
169
|
+
|
|
170
|
+
- `GET /api/v1/inbox?profileId=<id>` — unified conversation list
|
|
171
|
+
- `GET /api/v1/inbox/<conversationId>` — conversation thread
|
|
172
|
+
- `POST /api/v1/inbox/<conversationId>/reply` — reply to a conversation
|
|
173
|
+
|
|
174
|
+
### Analytics
|
|
175
|
+
|
|
176
|
+
- `GET /api/v1/analytics/posts?profileId=<id>&from=<date>&to=<date>` — per-post metrics
|
|
177
|
+
- `GET /api/v1/analytics/accounts?profileId=<id>&from=<date>&to=<date>` — per-account summary
|
|
178
|
+
|
|
179
|
+
### API keys
|
|
180
|
+
|
|
181
|
+
- `POST /api/v1/api-keys` — create a new key with `scope` and `permission`
|
|
182
|
+
- `GET /api/v1/api-keys` — list existing keys
|
|
183
|
+
- `DELETE /api/v1/api-keys/<keyId>` — revoke a key
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Error Model
|
|
188
|
+
|
|
189
|
+
All non-2xx responses return:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"error": {
|
|
194
|
+
"code": "<machine_readable_code>",
|
|
195
|
+
"message": "<human readable>",
|
|
196
|
+
"requestId": "<uuid>"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Well-known codes the operator handles:
|
|
202
|
+
|
|
203
|
+
| HTTP | Zernio code | Behavior |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| 401 | `auth_invalid` | Key missing / malformed / revoked — surface to user, fall back to agent-only |
|
|
206
|
+
| 403 | `permission_denied` | Key lacks `read-write` scope — surface with rotate-instructions |
|
|
207
|
+
| 404 | `profile_not_found` / `account_not_found` | Correct the id or fall back to dry-run |
|
|
208
|
+
| 409 | `conflict` | Typically an idempotency collision; safe to treat as success |
|
|
209
|
+
| 422 | `validation_failed` | Manifest shape bug — fix and retry |
|
|
210
|
+
| 429 | `rate_limited` | Back off using `Retry-After` header |
|
|
211
|
+
| 5xx | `internal_error` | Retry with exponential backoff up to 3 attempts |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Plans and Quotas (as-of kit freeze)
|
|
216
|
+
|
|
217
|
+
| Plan | Monthly | Annual (per mo) | Profiles | Posts / month | Notes |
|
|
218
|
+
|---|---|---|---|---|---|
|
|
219
|
+
| Free | $0 | $0 | 2 | 20 | Full REST API access; useful for prototypes |
|
|
220
|
+
| Build | $19 | $16 | 10 | 120 | First paid tier |
|
|
221
|
+
| Accelerate | $49 | $41 | 50 | unlimited | Recommended for agencies + multi-client operators |
|
|
222
|
+
| Unlimited | $999 | $833 | unlimited | unlimited | Enterprise; includes higher support SLA |
|
|
223
|
+
|
|
224
|
+
All plans ship the same REST contract (posts, queues, accounts, profiles, media, inbox, analytics, contacts, broadcasts, sequences, automations, webhooks, api-keys, connect, platforms). The post-count gate is the main throttle across tiers. Rate limit is 60 requests/minute per API key at every tier.
|
|
225
|
+
|
|
226
|
+
The operator must degrade gracefully when a quota is hit: surface the Zernio error code (`rate_limited`, `quota_exhausted`) to the user, offer to switch the session into `agent-only` mode, and never silently drop scheduled posts.
|
|
227
|
+
|
|
228
|
+
## Rate Limit Handling
|
|
229
|
+
|
|
230
|
+
Default plan: 60 requests/minute per API key. The operator batches reads during account inspection and never issues more than one write per post per second.
|
|
231
|
+
|
|
232
|
+
When a 429 is returned:
|
|
233
|
+
|
|
234
|
+
1. Read `Retry-After` header (seconds)
|
|
235
|
+
2. Sleep for the returned value plus 500ms jitter
|
|
236
|
+
3. Retry the same request with the same `Idempotency-Key`
|
|
237
|
+
4. On third failure, stop and report to the user
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## SDK + Harness Options (informational)
|
|
242
|
+
|
|
243
|
+
Zernio ships official SDKs for Node.js, Python, Go, Ruby, Java, PHP, .NET, and Rust. Source repos live under [github.com/zernio-dev](https://github.com/zernio-dev).
|
|
244
|
+
|
|
245
|
+
Two primitives are directly relevant to local AI coding environments:
|
|
246
|
+
|
|
247
|
+
| Primitive | Distribution | Use from | Install |
|
|
248
|
+
|---|---|---|---|
|
|
249
|
+
| Official MCP server | bundled inside `zernio-python` | Claude Desktop, Claude Code, Cursor, any MCP-compatible IDE | `pip install zernio-sdk[mcp]` |
|
|
250
|
+
| Zernio API Claude Code skill | `zernio-api` repo | Claude Code only | `npx clawhub@latest install zernio-api` |
|
|
251
|
+
|
|
252
|
+
This kit intentionally uses the **raw REST contract** via Node's built-in `fetch()` so the operator behaves identically regardless of which local IDE the user has installed. If the user additionally plugs in Zernio's MCP server or the Claude Code skill, that is strictly complementary — the kit does not depend on either.
|
|
253
|
+
|
|
254
|
+
See `docs/local-adapters.md` for the per-IDE setup matrix and `setup/install-mcp.mjs` for the copy-paste MCP config JSON blocks.
|
|
255
|
+
|
|
256
|
+
Kit files never install or require any Zernio SDK. If the user wants to install one locally for their own tooling, that is their choice and does not affect kit behavior.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Security
|
|
261
|
+
|
|
262
|
+
- `ZERNIO_API_KEY` lives only in `.env` (ignored by git) and the runtime environment
|
|
263
|
+
- Outputs never include the raw key, request headers, or response payloads that contain keys
|
|
264
|
+
- `scheduling-manifest.json` contains only `clientPostId`, content, timestamps, and Zernio resource IDs — no secrets
|
|
265
|
+
- Any snippet of curl help in documentation uses `${ZERNIO_API_KEY}` as an expanded env var reference, never a literal
|