One calendar, gaps included
Works todayDated and undated posts returned apart, a pipeline board over the same rows (brainstorm / in progress / ready), and a duplicate that always starts as a draft.
Socie plans, drafts, approves and publishes social posts across fourteen networks — eleven of which it can send to today. A calendar that surfaces undated work, an approval an edit genuinely re-opens, and a crisis hold that can freeze a whole workspace instantly.
Socie is where a post gets planned, drafted and approved before it goes anywhere: a calendar that shows dated and undated work apart, AI captions in your saved brand voice, a bulk CSV import, a media library, a link-in-bio page, and a review queue that a caption edit — or a fresh AI draft — sends straight back to review.
Say this first, because it changes what every other sentence on this page means: Socie does not publish to Instagram, Facebook, LinkedIn, X, YouTube or Pinterest yet. 'Scheduled' means approved and dated, not sent, because that needs a Meta app review this deployment has not started, and SocialAccount deliberately has no OAuth token column yet to even hold the credential such a review would unlock. Twenty-two of twenty-three specified capabilities are built to at least a requires_configuration point; the one still absent — competitor benchmarking — needs that same unstarted review or an equivalent platform API, and is named plainly, not hidden.
It will not post to any network — not yet, for anyone
There is no publish adapter and no account holds a token. A 'scheduled' post sits on your calendar for the day and a person posts it. This sentence is returned by the calendar, the accounts list, every approval response and the bulk-import response — everywhere a status could be mistaken for a promise.
It will not let a drag onto the board approve anything
Moving a card into the Ready lane is refused with a 409 naming the real route — approving is a named person signing off on particular wording under an owner-or-admin check, and a board that could do that silently would be a fifth route around the reviewer.
It will not let an approval survive an edit — including its own AI redraft
Editing an approved post's caption on even one network, or asking Socie to redraft it, drops the post back to review. Machine-written text replacing approved wording is the same problem as a human edit, so the same reopen fires either way.
It will not reach published without a person having approved it
mark-published refuses a post that was never approved, a duplicate always starts as a draft, a recycle run produces a draft rather than a repost, and bulk import has no column that can set a status. Four different ways around a reviewer, all closed at the schema level.
It will not touch the bytes of anything you upload
Registering a file returns a presigned PUT signed for that exact size and content type; the browser uploads straight to the bucket and Socie never receives the file. SVG and HTML are refused outright — both can carry script.
It will not record anything about who tapped your bio link
No IP address, no user agent, no per-click row — only a counter incremented by one conditional UPDATE. The count is taps, not unique visitors, and the API says so.
Getting started
Tone, audience and an avoid list — this is what every AI caption and idea draws on, and Lekha reads the exact same profile, so your tone never forks by agent.
Write one by hand, or paste a month of CSV — up to 200 rows, every row lands as a draft and a bad row is reported with its reason rather than silently dropped.
Submit a draft for review, have an owner or admin approve it, and it appears dated on the calendar — the gaps route shows you what's undated so nothing silently sits in the backlog.
Capabilities
Dated and undated posts returned apart, a pipeline board over the same rows (brainstorm / in progress / ready), and a duplicate that always starts as a draft.
Submit, approve, or request changes. Approving needs owner or admin. A crisis hold suspends the whole workspace instantly — nothing can be approved or recorded as published while it's open — and lifting it restores the calendar exactly, with no post silently re-dated.
The approval queue reports whether the caller can decide, rather than a client inferring it from a greyed-out button. Per-account scoping and a second team model beside the workspace's own are not built, on purpose.
One post, a caption and hashtags per network, and either a date or a slot in the queue. A queued draft is still a draft — the queue sets a date and touches no status. Per-network rules are enforced at submit and again at approve, because approve is reachable directly from draft. On the day, a worker sends it to every connected account, exactly once even if it restarts mid-send. What each account needs is your own app registered with that platform.
Parsed in memory and capped at 200 rows. A bad row is reported with its reason instead of dropped, and every row becomes a draft — the parser has no column that could set a status.
Counts the slots this workspace has actually posted in and states plainly it has no engagement data. Stays unconfident below eight posts and refuses rather than inventing a time with no history. No credential needed — ranking hours by reach instead of frequency would need each platform's own Insights API, which nothing here is connected to.
Marked AI-written until a human edits it. An unconfigured model returns a 503, never filler text. Saved caption templates work with no provider at all.
Needs: LLM_API_KEY. Saved templates and hashtag sets work without one.
Works with nothing configured for the idea backlog itself; batch generation in your brand voice needs a model and will not repeat titles already planned. No festival calendar — you name the occasion, because a stale one is confidently wrong about a date.
Needs: LLM_API_KEY, for the AI batch-generation route only.
Drawn only from your caption and your saved hashtag sets, each tagged with where it came from. No credential needed and nothing invented. Deliberately no trending-tag list — banned tags change without notice and a stale list costs reach, so this stays a saved-set tool rather than a trend predictor.
Logo, colours and fonts work today with no credential — the logo is a reference into the media register, not a pasted link. Image generation is a real pipeline now, not a placeholder: a success path uploads the bytes straight from memory into the bucket, registers a ready media asset, and links it to the post — never a local write. It is deliberately its own credential rather than the shared free LLM pool or the object-storage key, so turning it on is a separate, deliberately-budgeted choice. Unset, the route still writes the prompt as a blocked queue entry naming exactly what's missing, the same honest refusal as before.
Needs: SOCIE_IMAGE_API_KEY (its own credential, separate from the LLM pool), plus the object-storage credential media-library already needs.
The campaign is written from your own sentence plus whatever Business DNA really contains — with no DNA it says so and makes no model call, so an empty workspace is never charged for a guess. The brief's ideas become creative rows immediately with no picture, which is the honest state: the idea exists and the image does not yet. One credit per AI call, none when you brought your own LLM key, and a refused call is refunded — including the refusal a paid model raises before any request leaves the building.
Needs: An image provider credential and the object-storage credential before a creative can get a picture; the brief itself only needs the LLM pool.
Will not: It will not post a creative for you. Turning one into a post creates a draft, which still goes through the same approval and publish path as anything else.
Five guided template shapes and a free prompt, both ending in the media library through the path the rest of image generation already uses: bytes straight from memory into the bucket, a ready register row, never a local write. Editing a picture you uploaded is offered only by providers that actually accept image input — asking one that does not is refused with the reason rather than served an unrelated picture. Stock photographs cannot be an edit input at all, because their bytes live on the library's servers. With nothing configured the page says which credential is missing and the Generate button is not rendered at all.
Needs: An image provider credential (the platform key is charged; a workspace key of your own costs nothing) plus the object-storage credential media-library already needs.
Will not: It will not retouch a real person's photograph or claim a generated picture is a photograph. Every generated asset is registered as generated, with the provider named.
Upcoming tiles in your own dragged order, beside what's recorded as published. Reordering a published tile is a 400 with the reason — no platform API can move an item in a live feed.
Only a post already published can carry a rule, and a repeat limit is mandatory and checked before every run. A due recycle produces a new draft for review, never a silent repost, so recycling cannot become a way past approval. Fully self-contained against Socie's own tables — actually resharing to a platform needs the publishing rail this product deliberately does not have yet.
Output and process counts always work with no credential. Live engagement — likes, comments, impressions, engagement rate — aggregates in when a channel can actually supply it; SocialAccount deliberately has no OAuth token column yet, so today that is nobody, and the summary says so with a clear note instead of fabricating zeros or a plausible-looking number.
Post counts by status and by platform, how many were recorded as manually published, and how many were recycled from, over a day range you choose — output and process health with no credential needed. Post Insights layers live likes, comments, impressions and engagement rate with period-over-period deltas on top when a connected channel can supply them, and falls back to the honest own-data counts with a clear note the moment none can — never fabricating a number to fill the gap.
A campaign is an explicit, named set of tagged posts, never a date-range guess, and its report counts status, platform, published and recycled the same honest way the analytics summary does — never reach or engagement. Viewing a report and freezing a revocable, expiring, unauthenticated share link both work today with no credential. Only turning the report into a PDF is gated, because that needs a rendering service this host cannot run locally.
Needs: SOCIE_REPORT_PDF_API_KEY and SOCIE_REPORT_PDF_API_BASE, for PDF export only — the JSON report and share link work without them.
Filename, type, size, checksum, dimensions, alt text and tags, deduplicated per workspace on checksum. An asset a post still uses cannot be deleted — the usage table names the posts instead of cascading.
Needs: Object-storage credentials. Unset means every byte-carrying route returns 503 naming them.
One page per workspace on a slug you choose, buttons restricted to http and https, and an unauthenticated read that 404s until published. Visitors open /bio/{slug}; you configure it at /socie/link-in-bio. Taps are counted per button and nothing about who tapped is stored — no IP, no user agent, no per-click row.
Will not: Taps are not visitors. A refresh counts again, and no analytics identify anyone.
One description of the business — name, logo, colours, fonts, tone, values, location, hours, links — that the caption drafter and the image prompt builder already read. Pasting your website URL produces a proposal, never a save: Socie reads at most 12 pages and 2 MB, obeys robots.txt, refuses any address inside the deployment on every redirect hop it follows, and shows each field beside where it came from. Nothing reaches the saved profile until you tick fields and apply.
Needs: The written fields (tagline, values, tone, overview, keywords) are one billed AI call. Without an LLM credential the crawl still returns the colours, fonts, contact details and hours it read off the page.
Will not: A crawl is never applied for you, and a price is never scraped out of a page's visible text — only one the page publishes as structured data.
Publishes an interactive online brand book straight from Business DNA — colour swatches, typography, voice principles, values and any custom sections you add — at a slug you choose. The management UI, the live preview and the public page all work with nothing extra configured; only the downloadable PDF export needs the external rendering credential, and asking for one without it returns a real 503 naming what is missing rather than a blank or fabricated file.
Needs: The report_pdf rendering credential, for the PDF export only — the online brand book still publishes and is publicly viewable without it.
Generates a responsive landing page or website from Business DNA, your product catalogue and a prompt. Regenerating never edits a live page in place — each generation is a new version (v1, v2, ...), every prior version stays retrievable, and only the version you publish is what a visitor at the public slug ever sees.
Needs: An LLM provider credential to generate a version, and object-storage configuration to publish and host one live.
Unified community stream for comments and mentions across supported channels (Facebook, Instagram, Threads, YouTube, Mastodon, Bluesky). Lists comments and mentions by post or in a single inbox, and sends replies directly back to the native platform. Replying debits 0.10 credits per external reply. Unsupported or partner-gated platforms report honest reasons via the sources endpoint rather than appearing as an empty inbox. Ingesting live incoming messages requires connected channel credentials.
Needs: Connected channel credentials (OAuth tokens for supported networks). External replies debit 0.10 credits.
Will not: Does not invent mock comments when accounts are disconnected. Partner-gated endpoints (such as Threads replies pending app review) are surfaced honestly.
Named, not hidden
Understating the product is a smaller sin than overstating it, but it is still a page that stopped telling the truth. So the gaps are here, not left for you to discover.
Not built. Tracking a rival's followers, cadence and engagement needs public-profile API access on each platform — for Meta, the same app review publishing needs, which has not started.
API surface
Every route below is mounted and reachable today. Requests and responses are real shapes, not illustrations.
/api/v1/socie/calendarDated and undated posts, returned apart.
Response
{
"dated": [
{
"id": "p-1",
"title": "Diwali collection",
"scheduled_at": "2026-10-20T09:00:00Z"
}
],
"undated": [
{
"id": "p-2",
"title": "Behind the scenes"
}
]
}/api/v1/socie/board/cards/{card}/moveThe one refusal every drag onto Ready hits — approving is a person's decision, not a drag.
Request
{
"to_column": "ready"
}Response
{
"message": "Ready means approved, and approving is a person signing off on particular wording. Dragging cannot do it.",
"do_this_instead": "POST /socie/posts/{id}/submit, then /socie/posts/{id}/approve (owner or admin)"
}/api/v1/socie/crisis-holdSuspend the whole workspace instantly. Drafting keeps working; nothing reaches published.
Request
{
"reason": "Product recall in progress — holding all scheduled posts."
}Response
{
"id": "hold-4",
"reason": "Product recall in progress — holding all scheduled posts.",
"started_by": "usr_9",
"holding_now": [
{
"post_id": "p-1",
"title": "Diwali collection",
"status": "scheduled"
}
],
"note": "Nothing was rescheduled and no date was cleared. Lifting the hold puts the calendar back exactly as it is now."
}/api/v1/socie/approvalsThe review queue, with whether the caller may decide.
Response
{
"items": [
{
"id": "p-3",
"title": "Weekend sale",
"status": "in_review",
"can_decide": true
}
]
}/api/v1/socie/posts/{post_id}/draftDraft a caption in your brand voice. Refused (503) rather than filled with plausible filler when no model is configured.
Request
{
"platform": "instagram",
"idea": "New festive collection launch"
}Response
{
"detail": "No AI model is configured for this deployment. Set LLM_API_KEY to enable drafting; captions can still be written by hand."
}/api/v1/socie/bulk-importPaste CSV text. Capped at 200 rows; every row lands as a draft.
Request
{
"csv": "title,body,platforms,scheduled_at\nDiwali sale,25% off everything,instagram;facebook,2026-10-20",
"source_name": "October plan"
}Response
{
"id": "imp-1",
"status": "completed",
"total_rows": 1,
"created_count": 1,
"error_count": 0,
"note": "1 draft posts created. Every imported post is a draft and still has to go through approval — bulk import is not a way to schedule past review.",
"row_limit": 200
}/api/v1/socie/mediaRegister a file and receive a presigned PUT. Bytes never touch this server.
Request
{
"filename": "diwali-hero.jpg",
"mime_type": "image/jpeg",
"byte_size": 482113,
"content_sha256": "3f9a…64chars"
}Response
{
"id": "asset-1",
"filename": "diwali-hero.jpg",
"status": "pending",
"upload": {
"method": "PUT",
"url": "https://bucket.example.com/...&X-Amz-Signature=...",
"required_headers": {
"Content-Type": "image/jpeg",
"Content-Length": "482113"
},
"expires_in_seconds": 900
},
"note": "The file is not in your library until you call confirm. Socie never receives the bytes — they go from the browser to the bucket."
}/api/v1/socie/bio-page/{slug}/links/{link_id}/clickCount a tap. One conditional UPDATE, no IP or user agent stored.
Response
{
"url": "https://wa.me/919876543210"
}Limits and gotchas
Parsed synchronously and in memory, not a queued worker — the cap is what keeps that honest.
The slot queue sets a date and touches no status. Scheduling and approval are two separate facts about a post.
Socie publishes on the day you scheduled, to any account you have connected. Connecting uses your own app registered with that platform, and Socie walks you through registering it. Platforms Socie cannot publish to yet are marked as such on the connections screen rather than failing on the day.
It refuses to rank hours by anything but this workspace's own posting history, and says so rather than guessing.
Both can carry script; a bucket serving one to a customer's browser is a security hole, not a graphic.
Suspension is stored separately from scheduling, so lifting the hold restores the calendar exactly — nothing is silently re-dated.
No IP address, no user agent, no per-click row — only a counter per button. The number can be inflated by a refresh, and says so rather than pretending to be visitors.
What this needs from you
Everything else on this page works with none of these connected.
| Credential | Unlocks |
|---|---|
| LLM_API_KEY | AI caption drafting, AI idea generation. Saved templates, hashtag sets and the fact-based hashtag suggester work without one. |
| Object-storage credentials | The media library, and AI-generated images once SOCIE_IMAGE_API_KEY produces the bytes. Unset means every byte-carrying route returns 503 naming what's missing. |
| SOCIE_IMAGE_API_KEY | AI image generation. Its own, separately-budgeted credential — deliberately not the shared free LLM pool or the object-storage key — so turning it on is a distinct operator choice. Unset, a generation request is recorded as a blocked queue entry naming exactly what's missing, never a placeholder image. |
| SOCIE_REPORT_PDF_API_KEY and SOCIE_REPORT_PDF_API_BASE | Turning a campaign report into a PDF. The JSON report itself and its revocable share link work today with neither — this host has no local PDF-rendering service, so export calls out to one over HTTP instead. |
Questions
Socie publishes on the day you scheduled, to any account you have connected. Connecting uses your own app registered with that platform, and Socie walks you through registering it. Platforms Socie cannot publish to yet are marked as such on the connections screen rather than failing on the day.
Approved, and dated. Nothing reaches published without a person — recording a post as published refuses one that was never approved, a duplicate always starts as a draft, a recycle run produces a draft, and bulk import has no column that could set any other status.
No. Dragging a card into Ready is refused with a 409 that names the real route — approving needs a named person, owner or admin, to sign off on particular wording.
No — editing any network's caption on an approved post, or asking Socie to redraft it with AI, sends it straight back to review. An approval is of specific wording, not a permanent state.
Nothing about the person — no IP, no user agent, no per-click row. Only a counter on the button itself.
In your own object storage, not on this server. Registering a file returns a signed upload URL and the browser sends the bytes straight to the bucket.
No, and this is not a near-term gap — SocialAccount deliberately has no OAuth token column yet, so no platform Insights API has ever been called from here, and connecting one needs the same Meta app review publishing itself is waiting on, which has not started. Post-performance analytics and campaign reports show real counts instead — how many posts, by status and platform, published or recycled — and say so plainly rather than showing a zero that would read as 'nobody engaged.'
Coming soon