* Say when a connector catalog is loading, missing, or unconfigured. The Connect card treated a /v1/provider read that had not answered as "not in the catalog". Cards now say whether that read is in flight, failed, the network is absent, or present without app credentials — and name the connect-plane variables, keeping the publish plane's names when they differ. The read asks for each social network by id instead of the unfiltered registry. Co-authored-by: z <[email protected]> * Let an organization keep several accounts on one network. Connect already listed every handle the social API returned, but it never said which was a person and which was the company. Each account can now be personal or the network's own business kind — company on X, a Company Page on LinkedIn, a Page on Facebook, Business on Instagram — and the composer counts those kinds. The kind is stored on /v1/settings/social-audiences so it does not share a document with the Postiz post settings. Co-authored-by: z <[email protected]> * Add a Keys page for an organization's own app credentials. GET and PUT /v1/social/credentials are the contract the connect plane is growing: the public client id comes back, the secret never does, an omitted secret is left sealed, and an empty secret clears it. The screen masks both. A deployment that answers 404 or 501 has not shipped the route, and Keys stays off the rail until it has. Co-authored-by: z <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: z <[email protected]>
Hanzo Social
Write once. Publish to every account your organization owns — now, or at a time you pick.
Seven networks: X, Facebook, Instagram, LinkedIn, TikTok, YouTube, Threads. Text, photos and video on every one of them that takes video, which is all seven.
Run it
pnpm install
pnpm dev # http://localhost:3300
The port is pinned because it is half of a redirect: IAM returns the browser to the address the client registered, and that address carries the port.
vite.config.ts proxies /v1 to api.hanzo.ai on both the dev server and the
preview of a build, so a localhost origin reaches the gateway without failing a
preflight. Point it elsewhere with VITE_HANZO_API. pnpm start does the same
proxy, with HANZO_API.
How it is put together
src/social.ts |
Every /v1 address this app speaks, in one file |
src/network.ts |
What each network accepts, and whether this post satisfies it |
src/status.tsx |
How a state is drawn on a palette with no hues |
src/client.tsx |
The platform client, and the organization it speaks for |
src/page.tsx |
The heading, the signed-out gate, the way a refusal reads |
src/post.tsx |
One post, as every pane shows it |
Six screens, one per address: Overview, Compose, Posts, Calendar, Accounts,
Analytics. src/routes.tsx is the table, and the sidebar reads the same one.
Two planes, not one
A network needs both before anything goes out, and the Accounts screen shows them side by side:
/v1/provider— the organization's credential for a network. The OAuth consent a person gives once, sealed into KMS.Connectleads here./v1/social— the accounts a post fans out to, and the posts themselves.
No credential ever reaches this code. GET /v1/connection/:id/token
is the platform's one custody exit and nothing here calls it: a browser has no
use for a provider token, and a surface holding one would be a place to leak it
from.
Two doors
The native API is what agents should call. Postiz is a second contract for a client that already speaks it. The two stay separate. The catalog pages are the API, the MCP tool, and the CLI.
| Door | Who uses it | Where |
|---|---|---|
| Hanzo API | This app, MCP, the CLI, the SDKs | https://api.hanzo.ai/v1/social. MCP tool social at https://api.hanzo.ai/v1/mcp. CLI hanzo social. |
| Postiz drop-in | Grok's "Use self-hosted", and any Postiz client moving onto Hanzo | https://social.hanzo.ai — /mcp, /api/mcp, /public/v1, /api/public/v1 |
The frontend on *.hanzo.ai already calls https://api.hanzo.ai. It does not call Postiz paths, and those paths are not under /v1/social. On this process a /v1 request, including /v1/social, is proxied to HANZO_API (default https://api.hanzo.ai). Postiz paths are answered here. Every other path is the app.
Use it from Postiz
An install that already runs Postiz keeps that client. The instance URL is this host, and the key is a Hanzo key.
| Field | Value |
|---|---|
| Instance URL | https://social.hanzo.ai. http://localhost:3300 answers the same routes for a client on this machine. Postiz Cloud can only reach a public https origin. |
| API key | A Hanzo API key from platform.hanzo.ai/api-keys. |
The same key is accepted as Authorization: Bearer, as the raw Authorization value, or inside https://<origin>/mcp/<api key>.
/mcp and /api/mcp are the MCP session. /public/v1 is the public REST API. Image generation calls /v1/images/generations, video calls /v1/videos/generations, clips are a Studio video.edit run, uploads are kept in the Library, provider helpers call /v1/provider/{provider}/run, and per-post settings are stored with /v1/settings/social.
pnpm start serves a production build the same way, on port 3000.
On social.hanzo.ai, /mcp, /api/mcp, /public/v1, and /api/public/v1 are the worker hanzo-social-postiz (server/edge.mjs), which runs this same handler. The app and /v1/social stay on the origin. The API key is a Hanzo key, not a Postiz key.
What it does not do
The platform records no engagement — no views, likes or reach — and no endpoint serves any, so the Analytics screen measures delivery and says so. Bluesky is not here because the platform's channel vocabulary does not carry it. Media is given as an address rather than uploaded, because a presigned URL expires and a network fetches the media when the post goes out.