Docs · v1.6

The SocialKit API.

JSON in, JSON out. Bearer auth, rate-limited. This reference is generated from the OpenAPI spec, so it cannot drift from what the API actually does.

Quickstart

60 seconds
  1. 01

    Create an account

    One public call returns your first key. The plaintext is shown once, store it now. No waitlist, no dashboard required, an agent can run this step itself. Prefer a UI? Sign in at app.socialkit.sh to mint and manage keys from the dashboard.

    curl -X POST https://api.socialkit.sh/v1/account \
      -H "Content-Type: application/json" \
      -d '{ "email": "you@example.com" }'
  2. 02

    Export your key

    export SOCIALKIT_KEY="sk_live_..."
  3. 03

    Score your first post

    curl -X POST https://api.socialkit.sh/v1/score \
      -H "Authorization: Bearer $SOCIALKIT_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "post": "Most onboarding flows fail at step one. We cut ours from 11 fields to 3 and activation jumped 40% in a week."
    }'
    const res = await fetch("https://api.socialkit.sh/v1/score", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SOCIALKIT_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
      "post": "Most onboarding flows fail at step one. We cut ours from 11 fields to 3 and activation jumped 40% in a week."
    }),
    });
    const data = await res.json();
    console.log(data.overall, data.verdict);
    import os, requests
    
    res = requests.post(
        "https://api.socialkit.sh/v1/score",
        headers={"Authorization": f"Bearer {os.environ['SOCIALKIT_KEY']}"},
        json={
      "post": "Most onboarding flows fail at step one. We cut ours from 11 fields to 3 and activation jumped 40% in a week."
    },
    )
    data = res.json()
    print(data["overall"], data["verdict"])
  4. 04

    Read the response

    You get an overall score (0-100), a one-line verdict, a per-dimension breakdown, and a ranked list of signals. See ScoreResult.

Visual quickstart

brief in · rendered post out

The visual engine returns a finished post: caption, hashtags, rendered image masters, and the design judge's score. How it decides, renders, judges, and revises is documented on how it works. Visual calls are model-backed and slow by API standards, tens of seconds to a few minutes, so send an Idempotency-Key header and be patient with timeouts. Like every model-backed call, a successful call costs one credit; a blocked result costs nothing.

  1. 01

    Generate a post with pixels

    businessContext.businessName is the only required context field, but everything you add grounds the result: the brand kit locks palette and fonts, the proof bank feeds specific claims, and real photos always lead over generated imagery.

    curl -X POST https://api.socialkit.sh/v1/generate-visual-v2 \
      -H "Authorization: Bearer $SOCIALKIT_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: first-visual-post" \
      -d '{
        "businessContext": {
          "businessName": "Driftwood Roasters",
          "industry": "specialty coffee roaster",
          "brandKit": {
            "palette": { "primary": "#2B1D16", "secondary": "#F3EBDD", "accent": "#C96F2E" },
            "fonts": { "display": "Oswald", "body": "Lato" }
          },
          "proofBank": [
            { "id": "p1", "kind": "fact", "text": "every bag roasted within 48 hours of shipping" }
          ]
        },
        "seed": { "platform": "instagram", "idea": "the Tuesday roast", "goal": "awareness" }
      }'
  2. 02

    Read the response

    masters carries the rendered images per aspect ratio, caption and hashtags are platform-native, score is the judge's verdict, and spec is the post's revisable recipe. Treat the spec as opaque and store it. See GeneratedVisualPost.

  3. 03

    Revise with plain words

    Hand the spec back with freeform feedback. The revision is surgical: what the feedback does not target is held verbatim, so the photo stays while the headline moves.

    curl -X POST https://api.socialkit.sh/v1/revise-visual-v2 \
      -H "Authorization: Bearer $SOCIALKIT_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "businessContext": { "businessName": "Driftwood Roasters" },
        "feedback": "shorter headline, keep the photo",
        "spec": { ...the spec from step 01 }
      }'
  4. 04

    Or generate a whole deck

    POST /v1/generate-carousel-v2 plans and renders a full swipe deck in one frame family with per-slide roles, and POST /v1/revise-carousel-v2 refines it the same way. See the Visual reference.

    The published SDKs cover the text surface today; call the visual endpoints over REST until SDK support lands.

MCP for agents

same key

Every endpoint below is also exposed as an MCP tool at mcp.socialkit.sh, authed with the same bearer key. Point any MCP-aware client (Claude Code, Cursor, your own agent) at it and the whole toolkit shows up natively: score, rewrite, validate, generate, plan, plus the full post lifecycle and read-back of scores and metrics.

  1. 01

    Add the server (Claude Code)

    claude mcp add --transport http socialkit \
      https://mcp.socialkit.sh \
      --header "Authorization: Bearer $SOCIALKIT_KEY"
  2. 02

    Or add it by hand

    {
      "mcpServers": {
        "socialkit": {
          "type": "http",
          "url": "https://mcp.socialkit.sh",
          "headers": { "Authorization": "Bearer $SOCIALKIT_KEY" }
        }
      }
    }
  3. 03

    Call a tool

    Discovery is keyless, so an agent can read the catalog before it signs up. Tool calls use the same JSON contracts as REST and the bearer key. See the full catalog and the draft → grade → rewrite loop on the AI page.

    # discover (no key needed)
    curl https://mcp.socialkit.sh/tools/list
    
    # call a tool (key required)
    curl https://mcp.socialkit.sh -X POST \
      -H "Authorization: Bearer $SOCIALKIT_KEY" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"score_post","arguments":{"platform":"linkedin","post":"..."}}}'

The server is more than tools. It also serves prompts (the canonical draft → grade → rewrite workflows your client can run) and resources (your account, brands, voices, channels, and calibration as attachable context, so a model can ground a draft without a tool call). Every tool carries read/write annotations, so a client auto-runs reads and confirms destructive writes. Details on the AI page.

Platforms & partners

partner keys

A platform can run SocialKit for its own users without giving them SocialKit accounts. SocialKit issues the platform a partner key (sk_partner_...). The partner provisions tenants, one isolated SocialKit context per workspace, mints each tenant a key (sk_tenant_...), and reads one statement for all of them. A tenant generates, scores, rewrites, plans and renders with every brand and voice it owns. It never publishes: channels, scheduling, publishing and metrics answer not_available_for_tenants and its capabilities say so up front. Partner keys are issued by SocialKit, not self-serve: hello@socialkit.sh.

  1. 01

    Ensure a tenant (idempotent), seeding its brand

    curl -X PUT https://api.socialkit.sh/v1/partner/tenants/ws_123 \
      -H "Authorization: Bearer $PARTNER_KEY" -H "Content-Type: application/json" \
      -d '{"name":"Acme Dental","brand":{"name":"Acme Dental","description":"Family dentistry in Austin"}}'
  2. 02

    Mint the tenant a key (shown once)

    curl -X POST https://api.socialkit.sh/v1/partner/tenants/ws_123/keys \
      -H "Authorization: Bearer $PARTNER_KEY" -d '{"name":"agent-runtime"}'
  3. 03

    The tenant uses SocialKit like any client

    One bearer, no header plumbing, on REST or MCP. Or act as the tenant with the partner key plus X-SocialKit-Tenant, and attribute calls with X-SocialKit-Actor.

    curl -X POST https://api.socialkit.sh/v1/generate \
      -H "Authorization: Bearer $TENANT_KEY" -H "Content-Type: application/json" \
      -d '{"platform":"linkedin","brief":"Announce Saturday hours"}'
    
    # or, with the partner key
    curl -X POST https://api.socialkit.sh/v1/generate \
      -H "Authorization: Bearer $PARTNER_KEY" -H "X-SocialKit-Tenant: ws_123" \
      -H "X-SocialKit-Actor: user_7" -H "Content-Type: application/json" \
      -d '{"platform":"linkedin","brief":"Announce Saturday hours"}'
  4. 04

    Read the statement

    curl "https://api.socialkit.sh/v1/partner/usage?period=2026-10" \
      -H "Authorization: Bearer $PARTNER_KEY"
    
    # or line by line, as CSV, for finance
    curl "https://api.socialkit.sh/v1/partner/usage/export?period=2026-10" \
      -H "Authorization: Bearer $PARTNER_KEY" -o statement.csv
    
    # and who changed what, newest first
    curl "https://api.socialkit.sh/v1/partner/audit" \
      -H "Authorization: Bearer $PARTNER_KEY"
  5. 05

    Let the brand carry its kit, and show the image with no key

    Seed a kit on the brand (palette, fonts, a logo and photos by media id, uploaded by the tenant) and name the brand with brandId on a visual call: the engine grounds itself from it, and anything sent in businessContext alongside wins. A signed URL puts the rendered master straight into an <img src>. A tenant key minted with toolProfile: "core" presents the curated MCP catalog (generate, score, rewrite, plan, brands, voices, account, sign_media_url); what it may call does not change.

    curl -X PUT https://api.socialkit.sh/v1/partner/tenants/ws_123 \
      -H "Authorization: Bearer $PARTNER_KEY" -H "Content-Type: application/json" \
      -d '{"brand":{"name":"Acme Dental","kit":{"palette":{"primary":"#004488","accent":"#FFB000"},
           "fonts":{"display":"Anton","body":"Inter"},"logo":{"mediaId":"med_...","placement":"top-right"},
           "photoMediaIds":["med_..."]}}}'
    
    curl -X POST https://api.socialkit.sh/v1/generate-visual-v2 \
      -H "Authorization: Bearer $TENANT_KEY" -H "Content-Type: application/json" \
      -d '{"brandId":"brd_...","seed":{"platform":"instagram","idea":"Saturday hours"}}'
    
    curl -X POST https://api.socialkit.sh/v1/media/med_.../signed-url \
      -H "Authorization: Bearer $TENANT_KEY" -d '{"ttlSeconds":3600}'
    # -> {"url":"https://api.socialkit.sh/v1/media/med_.../content?token=...","expiresAt":...}

Metering is per tenant and per partner: a tenant meters into its own monthly bucket and the partner's aggregate, with weighted credits per operation (an image post costs 3, a carousel 5, everything else 1 by default). A refused call never charges, and a quota_exceeded or rate_limited error names its scope (tenant or partner). Deleting a tenant stops its keys at once and purges its data shortly after. The endpoints are under the Partner tag below; the operations a tenant cannot call carry a Not for tenants badge.

A partner's own people work it from the dashboard. Once SocialKit grants an email as an operator, app.socialkit.sh shows that person a context switcher (their personal account, then each partner) and a partner context at /p/<slug>: the overview and caps, tenants, the statement with a CSV download, the audit trail, and the writes an operator needs (mint and revoke partner keys, create and delete tenants, mint and revoke tenant keys, invite and remove operators), each audited as that person. Caps, status and credit weights stay with SocialKit.

Authentication

bearer key

Every request carries a single secret: your API key, sent as a bearer token. Keys are scoped to one account, start with sk_live_, and are shown in plaintext exactly once at creation. Store them like a password and rotate from the dashboard if one leaks.

curl https://api.socialkit.sh/v1/account \
  -H "Authorization: Bearer $SOCIALKIT_KEY"

No key yet? POST /v1/account mints one with no waitlist (see Quickstart), or sign in at app.socialkit.sh. A missing or revoked key returns 401 unauthorized. Discovery is keyless, so an agent can read GET /v1/meta and the MCP handshake before it signs up.

GET /v1/health is a keyless readiness probe: it checks the database and key-value store and returns 200 when both are healthy, 503 when either is degraded. For a bare liveness check with no dependency I/O, hit GET /healthz (returns the text ok) on any host.

Errors & limits

JSON envelope

Every error uses the same envelope, so one handler covers them all. The code is a stable machine string; the message is human-readable and may change. The requestId is unique to the call, also returned in the x-request-id response header on every request, success or failure. Log it, and quote it to support so we can pull the exact call from our logs.

{ "error": { "code": "quota_exceeded", "message": "Monthly credit allotment exhausted...", "requestId": "8f3c...-TLV" } }
StatuscodeWhen
400invalid_request, unsupported_platformMalformed body, a failed field validation, or a post whose platform has no connected driver.
401unauthorizedMissing, invalid, or revoked bearer key.
402quota_exceededMonthly credit allotment spent. Upgrade or wait for the reset.
404not_foundNo such resource, or not owned by your key.
409account_exists, invalid_state, idempotency_key_reusedA state clash: a duplicate signup, canceling a published post, or reusing an Idempotency-Key with a different body.
429rate_limitedRequest or LLM rate limit exceeded. Honor retry-after.
503not_configuredA deployment-gated capability (publish, metrics) is not enabled on this deployment.
5xxinternal, capability_failedOur fault or an upstream model failure. Retry with backoff; the call was not charged.

Rate limits & credits

Two independent budgets guard every key: request volume and a tighter LLM-call budget, both enforced per key, plus monthly credits enforced per account. A credit is charged only after an LLM call succeeds, so a 429 or 402 never costs you one. Every authed response carries the request-budget headers; the model-backed endpoints (score, generate, rewrite, plan, and voice distillation) add the LLM-budget and credit headers on top:

HeaderOnMeaning
x-ratelimit-limit / -remaining / -resetEvery authed responsePer-key request budget: ceiling, requests left, and the Unix second the window resets.
x-ratelimit-llm-limit / -remaining / -resetModel-backed endpointsThe tighter per-key budget for LLM-backed calls, same shape as above.
x-credits-limit / -used / -remaining / -resetModel-backed endpointsYour account's monthly credit standing: plan allotment, used, left, and the Unix second the cycle resets.
retry-after429 and 402Seconds to wait before retrying.

Conventions

idempotency & paging

Idempotent requests

Side-effectful calls are retry-safe. Send an Idempotency-Key header (any unique string up to 255 chars, a UUID is ideal) on the model calls (POST /v1/score, /v1/rewrite, /v1/generate, /v1/plan) or the creation calls (POST /v1/posts, POST /v1/posts/bulk-schedule). The first successful response is cached per key for 24 hours; a retry with the same header returns that exact response with idempotent-replay: true and does no new work, so a retried model call charges no second credit and a retried creation makes no duplicate. The body is fingerprinted with the key, so reusing a key with a different body returns 409 idempotency_key_reused instead of the stale result; a retry must be identical. Only 2xx responses are cached, so a failed call stays retryable.

curl -X POST https://api.socialkit.sh/v1/posts \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f9c1b2a-..." \
  -d '{ "platform": "linkedin", "text": "..." }'

Pagination

List endpoints (GET /v1/posts, GET /v1/media, GET /v1/webhooks/:id/deliveries) take limit (default 50, max 100) and offset (default 0) query params. A non-numeric value is a 400 invalid_request. Each response carries a pagination object; keep paging while hasMore is true.

GET https://api.socialkit.sh/v1/posts?limit=50&offset=0

{
  "posts": [ /* ... */ ],
  "pagination": { "limit": 50, "offset": 0, "hasMore": true }
}

Webhooks

signed events

Register an endpoint and SocialKit POSTs you a signed JSON event when something happens: a scheduled post goes live, a publish fails for good, a score lands, or a channel disconnects. Create and manage endpoints through the Webhooks API; the secret is returned once at creation, store it then.

EventFires when
post.publishedA scheduled post is successfully published to its channel.
post.failedA post is dead-lettered after the scheduler exhausts its retries.
post.scoredA post is scored (manually or as part of a flow).
channel.disconnectedA connected channel's token is revoked or expires.

Payload

Every delivery is the same envelope. The data shape depends on the event. The body is byte-identical across retries, so verify against the exact bytes you receive.

{
  "id": "whd_...",          // unique delivery id
  "event": "post.published",
  "createdAt": 1717689600000,
  "data": { /* event-specific */ }
}

Headers

HeaderMeaning
x-socialkit-eventThe event name, e.g. post.published.
x-socialkit-deliveryDelivery id, also echoed in the body. Use it to dedupe retries.
x-socialkit-timestampUnix seconds when the delivery was signed. Check it is recent.
x-socialkit-signaturesha256=<hex>, an HMAC over the signed payload (below).

Verifying the signature

The signature is an HMAC-SHA256 of {timestamp}.{rawBody} keyed with your endpoint secret, hex-encoded. Binding the timestamp into the signature lets you reject replays: check the timestamp is recent before comparing, and always compare in constant time.

import crypto from "node:crypto";

// rawBody MUST be the exact bytes received, not a re-serialized object.
function verifyWebhook(rawBody, headers, secret) {
  const ts = headers["x-socialkit-timestamp"];
  const sig = headers["x-socialkit-signature"]; // "sha256=<hex>"

  // Reject anything older than 5 minutes to stop replays.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret)
      .update(`${ts}.${rawBody}`)
      .digest("hex");

  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Retries

Return a 2xx within 10 seconds to acknowledge. Any other status, a timeout, or a connection error is retried up to 5 times with exponential backoff (1m, 5m, 30m, 2h), then dead-lettered. Make your handler idempotent on x-socialkit-delivery, since a slow 2xx can still be redelivered. Inspect delivery history with GET /v1/webhooks/:id/deliveries.

API reference

74 endpoints

Intelligence

Score, rewrite, plan, generate. The knowledge primitives.

POST/v1/scoreBearer

Grades a draft against the platform's feed-ranking behavior and returns an overall score (0-100), a per-dimension breakdown, ranked signals (what helps, what leaks reach), the detected hook archetype, and the format multiplier. LinkedIn and X are live, each scored with its own platform rubric; set `platform` to choose.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body ScoreRequest
Example request
curl -X POST https://api.socialkit.sh/v1/score \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "post": "Most onboarding flows fail at step one. We cut ours from 11 fields to 3 and activation jumped 40% in a week."
}'
Responses
  • 200Score computedScoreResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceededError
POST/v1/rewriteBearer

Scores the original, rewrites it to lift the weak dimensions without inventing facts, then re-grades the rewrite. Returns both scores and the list of changes made. The after-score is reported as measured; it is never massaged to look like an improvement. Set `platform` to `linkedin` (default) or `x`; each rewrites against its own ranker.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body ScoreRequest
Example request
curl -X POST https://api.socialkit.sh/v1/rewrite \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "post": "We are excited to announce that our team has been working hard on some new features we think you will really love."
}'
Responses
  • 200Rewrite producedRewriteResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/generateBearer

Drafts up to three native posts from a brief, scores each, and returns them sorted by overall score (best first). Set `platform` to `linkedin` (default) or `x`; each uses its own writing rubric.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body GenerateRequest
Example request
curl -X POST https://api.socialkit.sh/v1/generate \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brief": "A shipping receipt about cutting onboarding to 3 fields",
  "count": 1
}'
Responses
  • 200Candidates generatedGenerateResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/planBearer

Returns a calendar of post ideas spread across content pillars and hook archetypes. Each item is an outline (hook, angle, format, archetype), not a finished draft. Use generate to expand an item into a post. Set `platform` to plan for LinkedIn (default) or X; each platform uses its own content pillars and media set.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body PlanRequest
Example request
curl -X POST https://api.socialkit.sh/v1/plan \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brief": "AI-native engineering and build-in-public receipts",
  "count": 5,
  "cadence": "weekdays"
}'
Responses
  • 200Plan producedPlanResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError

Visual

The visual engine: finished posts and carousels with rendered images. A call takes a brief plus a brand kit, deals a layout archetype and a typography voice, renders the design deterministically, and grades the result with a design judge before returning it. Every response carries a serializable, revisable spec; hand it back with freeform feedback to refine a post without re-rolling it.

POST/v1/generate-visual-v2Bearer

Turns a brief and a brand kit into a finished, postable post: caption, hashtags, on-image text, rendered image masters, and the design judge's score. The engine deals a layout archetype and a typography voice, renders deterministically, judges the result, and revises what falls short before returning. The response's `spec` is the post's revisable recipe; hand it to `/v1/revise-visual-v2` with feedback to refine without re-rolling. Model-backed and slow (tens of seconds to a few minutes); costs one credit on success.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Example request
curl -X POST https://api.socialkit.sh/v1/generate-visual-v2 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "businessContext": {
    "businessName": "Driftwood Roasters",
    "industry": "specialty coffee roaster",
    "brandKit": {
      "palette": {
        "primary": "#2B1D16",
        "secondary": "#F3EBDD",
        "accent": "#C96F2E"
      },
      "fonts": {
        "display": "Oswald",
        "body": "Lato"
      }
    }
  },
  "seed": {
    "platform": "instagram",
    "idea": "the Tuesday roast: Yirgacheffe, floral, sold by Friday",
    "goal": "awareness"
  }
}'
Responses
  • 200Finished post with rendered mastersGeneratedVisualPost
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 422Not postable: the input could not be turned into a post worth shipping (for example, too little grounding to be specific). The error carries the blocking stage and reasons; enrich the brief or the business context rather than retrying blindly.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/revise-visual-v2Bearer

Takes the `spec` from a prior generate plus a freeform comment ("shorter headline", "warmer photo") and returns the refined post. The revision is surgical where possible: anything the feedback does not target is held verbatim, so the photo stays while the headline moves. Costs one credit on success.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body ReviseVisualRequest
Example request
curl -X POST https://api.socialkit.sh/v1/revise-visual-v2 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "feedback": "string",
  "spec": {}
}'
Responses
  • 200Revised post with re-rendered masters and a fresh scoreGeneratedVisualPost
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 422Not postable after revision; see error reasons.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/generate-carousel-v2Bearer

Plans and renders a full swipe deck from a brief and a brand kit: an arc of slides in one frame family with per-slide roles, a caption, hashtags, and a set-level design score. Real brand photos lead when the context carries them; generated imagery stays name-free and atmospheric. The response's `spec` is the deck's revisable recipe for `/v1/revise-carousel-v2`. Model-backed and slow; costs one credit on success.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Example request
curl -X POST https://api.socialkit.sh/v1/generate-carousel-v2 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "businessContext": {
    "businessName": "Kiln House Ceramics",
    "industry": "ceramics studio"
  },
  "seed": {
    "platform": "instagram",
    "idea": "wheel-throwing 101: your first cup in one session",
    "goal": "save",
    "slideCount": 5
  }
}'
Responses
  • 200Finished deck with rendered slidesGeneratedCarousel
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 422Could not plan a postable carousel; see error reasons.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/revise-carousel-v2Bearer

Takes the `spec` from a prior carousel generate plus a freeform comment and returns the refined deck. Slides the feedback does not target are kept verbatim, backgrounds included, and the set is re-judged as a whole. Costs one credit on success.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Example request
curl -X POST https://api.socialkit.sh/v1/revise-carousel-v2 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "feedback": "string",
  "spec": {}
}'
Responses
  • 200Revised deck with a fresh set-level scoreGeneratedCarousel
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 422Not postable after revision; see error reasons.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError

Account

Sign up and manage API keys.

GET/v1/metaPublic

Public endpoint. Returns the scoring schema (version, max post length, supported platforms, media kinds, follower bands, and the six scoring dimensions with their weights) plus this deployment's live capabilities. Read it to build a valid request without scraping docs and to branch on what the deployment can do before attempting an action.

Example request
curl https://api.socialkit.sh/v1/meta
Responses
GET/v1/healthPublic

Public readiness probe. Exercises the service's data dependencies (the database and key-value store) and reports each, so a green result means the service can serve real requests, not just that the process booted. Returns `200` when every check is `ok` and `503` when any dependency is degraded, so an uptime monitor can page on it. For a bare liveness check with no dependency I/O, use `GET /healthz` (returns the text `ok`).

Example request
curl https://api.socialkit.sh/v1/health
Responses
GET/v1/accountBearer

Returns the account and the API key making the request. The cheapest authed call: use it to confirm a key is live and read the plan, and read the `X-RateLimit-*` response headers for the current request budget before spending real quota.

Example request
curl https://api.socialkit.sh/v1/account \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Account and calling keyWhoamiResult
  • 401Missing or invalid API keyError
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
POST/v1/accountPublic

Public endpoint. Creates an account for an email and returns the first API key in plaintext. The plaintext is shown exactly once; store it now. Rate limited per IP.

Example request
curl -X POST https://api.socialkit.sh/v1/account \
  -H "Content-Type: application/json" \
  -d '{
  "email": "string"
}'
Responses
  • 201Account createdCreateAccountResult
  • 400Invalid request bodyError
  • 409An account already exists for that emailError
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
GET/v1/keysBearer

Returns key metadata only. Secrets are never returned.

Example request
curl https://api.socialkit.sh/v1/keys \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/keysBearer
Request body CreateKeyRequest
Example request
curl -X POST https://api.socialkit.sh/v1/keys \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
POST/v1/keys/{id}/rotateBearer
Path parameters
idrequiredstringThe API key id (e.g. `key_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/keys/key_8fa3c1d9/rotate \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
DELETE/v1/keys/{id}Bearer
Path parameters
idrequiredstringThe API key id (e.g. `key_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/keys/key_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Partner

The partner tier: a platform runs SocialKit for its own users. A partner key (`sk_partner_...`, issued by SocialKit) provisions tenants, mints their `sk_tenant_...` keys and reads statements. A tenant is an isolated SocialKit context the partner owns: it uses every generation, memory and draft endpoint with its tenant key, meters into its own monthly bucket and the partner's aggregate, and never publishes (channels, scheduling, publishing and metrics are refused with `not_available_for_tenants`). A partner can also act as one of its tenants by sending the partner key with `X-SocialKit-Tenant: <externalId>`.

GET/v1/partnerBearer

Partner keys only. Returns the partner profile, the calling key, the live tenant count and the partner-wide credit standing for the current month. Every partner call carries the partner-wide request budget in the `x-ratelimit-*` headers.

Example request
curl https://api.socialkit.sh/v1/partner \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200The partner profilePartnerProfileResult
  • 401Missing or invalid API keyError
  • 403The partner is suspended; every key under it is refused.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
POST/v1/partner/keys/rotateBearer

Returns a new partner key in plaintext, exactly once. The partner's other live keys keep working for 24 hours so a vault can roll without an outage; pass `immediate=true` (a leaked key) to revoke them now.

Path parameters
immediaterequiredbooleanRevoke the other live keys now instead of after the grace window.
Request body CreateKeyRequest
Example request
curl -X POST https://api.socialkit.sh/v1/partner/keys/rotate \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
GET/v1/partner/tenantsBearer

Newest first, cursor-paginated. `status=deleted` lists tenants awaiting purge.

Path parameters
limitrequiredintegerPage size, 1-200 (default 50).
cursorrequiredstringThe `nextCursor` of the previous page.
statusrequiredstring`active` (default) or `deleted`.
Example request
curl https://api.socialkit.sh/v1/partner/tenants \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/partner/tenants/{externalId}Bearer
Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
Example request
curl https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
PUT/v1/partner/tenants/{externalId}Bearer

Idempotent: 201 when the tenant is created, 200 when it already exists. `externalId` is the partner's own handle for the tenant (a workspace id), unique per partner and opaque to SocialKit. `name` sets or updates the display name; `brand` seeds the tenant's default brand on first sight and updates it after. A tenant that is being deleted is refused with `409 tenant_deleting` until the purge frees its id.

Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
Request body EnsureTenantRequest
Example request
curl -X PUT https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Acme Dental",
  "brand": {
    "name": "Acme Dental",
    "description": "Family dentistry in Austin",
    "audience": "Parents in Austin",
    "themes": [
      "new patient tips",
      "behind the chair"
    ]
  }
}'
Responses
DELETE/v1/partner/tenants/{externalId}Bearer

Tombstones the tenant now (its keys stop working at once) and purges its stored media and rows shortly after, in bounded batches. Idempotent: a repeated call reports the same `deleting` state.

Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
Example request
curl -X DELETE https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/partner/tenants/{externalId}/keysBearer
Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
Example request
curl https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9/keys \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/partner/tenants/{externalId}/keysBearer

Returns an `sk_tenant_...` key in plaintext, exactly once. The tenant's agent runtime uses it on the REST and MCP surfaces like any SocialKit key, scoped to that tenant. At most 10 live keys per tenant.

Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
Request body CreateKeyRequest
Example request
curl -X POST https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9/keys \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
DELETE/v1/partner/tenants/{externalId}/keys/{keyId}Bearer
Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
keyIdrequiredstringThe tenant key id (e.g. `key_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/partner/tenants/key_8fa3c1d9/keys/key_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/partner/auditBearer

Every administrative change under this partner: tenants created and deleted, tenant keys minted, revoked and rotated (by the partner or by the tenant itself), partner keys created and rotated, cap and status changes made by SocialKit, operator grants. Keyset-paged; follow `nextCursor` until it is null. Kept for 13 months.

Path parameters
limitrequiredintegerEvents per page (default 50, max 200).
cursorrequiredstringThe `nextCursor` of the previous page.
Example request
curl https://api.socialkit.sh/v1/partner/audit \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/partner/usageBearer

Credits charged across all tenants in a calendar month (default: the current one), by operation and by tenant. One row per successful model call is kept for 13 months; a refused or failed call never appears.

Path parameters
periodrequiredstringCalendar month as `YYYY-MM` (UTC). Defaults to the current month.
Example request
curl https://api.socialkit.sh/v1/partner/usage \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/partner/usage/exportBearer

Every ledger row of the period (default: the current month), oldest first, as `text/csv` with the columns `id` (the ledger line, stable across exports), `at` (ISO 8601), `tenant` (the external id), `accountId`, `operation`, `calls`, `credits`, `actor`, `requestId`. Streamed in batches, so a month of any size downloads. For finance; `GET /v1/partner/usage` is the aggregate.

Path parameters
periodrequiredstringCalendar month as `YYYY-MM` (UTC). Defaults to the current month.
Example request
curl https://api.socialkit.sh/v1/partner/usage/export \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200The CSV, as an attachment named `socialkit-{slug}-{period}.csv`.
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
GET/v1/partner/tenants/{externalId}/usageBearer

Credits by operation and by actor (the `X-SocialKit-Actor` the calls carried).

Path parameters
externalIdrequiredstringThe partner's handle for the tenant (e.g. a workspace id): 1-128 characters from `A-Z a-z 0-9 . _ : -`, unique per partner.
periodrequiredstringCalendar month as `YYYY-MM` (UTC). Defaults to the current month.
Example request
curl https://api.socialkit.sh/v1/partner/tenants/id_8fa3c1d9/usage \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Memory

Brands and voices. Reusable context that conditions generation: a brand is workspace context, a voice is a writing profile. Pass their ids to generate and plan.

GET/v1/brandsBearer
Example request
curl https://api.socialkit.sh/v1/brands \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/brandsBearer
Request body BrandCreateRequest
Example request
curl -X POST https://api.socialkit.sh/v1/brands \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "SocialKit",
  "description": "The social primitive your agent calls.",
  "audience": "Founders and developers shipping in public.",
  "themes": [
    "shipping receipts",
    "AI-native engineering"
  ],
  "linkPolicy": "Never link out in the body."
}'
Responses
GET/v1/brands/{id}Bearer
Path parameters
idrequiredstringThe brand id (e.g. `brd_...`).
Example request
curl https://api.socialkit.sh/v1/brands/brd_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
PATCH/v1/brands/{id}Bearer

Partial update. Only the fields present in the body are changed.

Path parameters
idrequiredstringThe brand id (e.g. `brd_...`).
Request body BrandUpdateRequest
Example request
curl -X PATCH https://api.socialkit.sh/v1/brands/brd_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
  • 200Brand updatedBrandResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
DELETE/v1/brands/{id}Bearer

Deletes the brand. Voices attached to it are kept but detached (their brandId becomes null).

Path parameters
idrequiredstringThe brand id (e.g. `brd_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/brands/brd_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/voicesBearer
Example request
curl https://api.socialkit.sh/v1/voices \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/voicesBearer

Creates a voice. Supply `samples` and the profile is distilled from them (the common path, same as POST /v1/voices/build). Omit `samples` and the explicit `traits`/`doNot`/`exemplars` you pass are stored as-is.

Request body VoiceCreateRequest
Example request
curl -X POST https://api.socialkit.sh/v1/voices \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Founder voice",
  "samples": [
    "We shipped the new scorer today. 11 dimensions down to 6.",
    "Paused a $675/day campaign. The CAC math stopped working."
  ]
}'
Responses
  • 201Voice createdVoiceResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
POST/v1/voices/buildBearer

Reads sample posts, extracts the traits to imitate, the things to never do, and a few exemplar snippets, then saves the result as a voice. Returns the saved voice; pass its id to generate or plan.

Request body BuildVoiceRequest
Example request
curl -X POST https://api.socialkit.sh/v1/voices/build \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Founder voice",
  "samples": [
    "We shipped the new scorer today. 11 dimensions down to 6.",
    "Paused a $675/day campaign. The CAC math stopped working."
  ]
}'
Responses
  • 201Voice built and savedVoiceResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
GET/v1/voices/{id}Bearer
Path parameters
idrequiredstringThe voice id (e.g. `voi_...`).
Example request
curl https://api.socialkit.sh/v1/voices/voi_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
PATCH/v1/voices/{id}Bearer

Partial update. Only the fields present in the body are changed.

Path parameters
idrequiredstringThe voice id (e.g. `voi_...`).
Request body VoiceUpdateRequest
Example request
curl -X PATCH https://api.socialkit.sh/v1/voices/voi_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
  • 200Voice updatedVoiceResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
DELETE/v1/voices/{id}Bearer
Path parameters
idrequiredstringThe voice id (e.g. `voi_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/voices/voi_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Channels

Connected social accounts (OAuth pipes). Connect via the platform's OAuth flow, report health, refresh tokens, and disconnect. Tokens are encrypted at rest and never returned. LinkedIn is the only platform live today.

GET/v1/channelsBearerNot for tenants

Returns channel metadata only. Tokens are never included.

Example request
curl https://api.socialkit.sh/v1/channels \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/channels/linkedin/connectBearerNot for tenants

Starts the OAuth handshake. Returns an authorization URL to redirect the member to, plus the opaque state token that ties the callback back to this account. PKCE is handled for you. Disabled (503) when the deployment has no token-vault key or LinkedIn app credentials.

Example request
curl -X POST https://api.socialkit.sh/v1/channels/linkedin/connect \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
GET/v1/channels/linkedin/callbackPublicNot for tenants

Where LinkedIn redirects after the member authorizes. Public: trust comes from the single-use, account-bound state, not the query params. Exchanges the code for tokens, seals them into the vault, and returns the connected channel. Not called directly by API clients.

Path parameters
coderequiredstringThe authorization code from LinkedIn.
staterequiredstringThe opaque state token returned by connect.
Example request
curl https://api.socialkit.sh/v1/channels/linkedin/callback
Responses
  • 200Channel connectedChannelResult
  • 400Invalid or expired OAuth stateError
  • 502The token exchange with the platform failedError
  • 503The capability is not configured on this deploymentError
GET/v1/channels/{id}BearerNot for tenants
Path parameters
idrequiredstringThe channel id (e.g. `chn_...`).
Example request
curl https://api.socialkit.sh/v1/channels/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
DELETE/v1/channels/{id}BearerNot for tenants

Wipes the stored tokens (unrecoverable) and marks the channel revoked. The row is kept for history.

Path parameters
idrequiredstringThe channel id (e.g. `chn_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/channels/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/channels/{id}/refreshBearerNot for tenants

Refreshes the access token if it is at or near expiry; a no-op when the token is still fresh. Returns the current channel either way.

Path parameters
idrequiredstringThe channel id (e.g. `chn_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/channels/id_8fa3c1d9/refresh \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Channel refreshed (or already fresh)ChannelResult
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 502The token refresh with the platform failedError
  • 503The capability is not configured on this deploymentError

Media

Uploaded images, video, and PDF carousels referenced by posts at publish time. Bytes are stored in object storage; the API returns metadata and a content download endpoint.

GET/v1/mediaBearer

Results are paginated newest-first; page with limit and offset and follow pagination.hasMore.

Path parameters
limitrequiredintegerMaximum media items to return (default 50, max 100).
offsetrequiredintegerNumber of media items to skip from the newest.
Example request
curl https://api.socialkit.sh/v1/media \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/mediaBearer

Upload raw bytes with the file's Content-Type header (validated against an allowlist). Optionally scope to a brand with the brandId query param and supply a filename with the X-Filename header. Returns the stored media metadata.

Path parameters
brandIdrequiredstringOptional brand to attach the media to. Must belong to the account.
X-FilenamerequiredstringOptional original filename, reduced to a basename.
Example request
curl -X POST https://api.socialkit.sh/v1/media \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 201Media uploadedMediaResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 413The file exceeds the size limit for its typeError
  • 415Unsupported content typeError
GET/v1/media/{id}Bearer
Path parameters
idrequiredstringThe media id (e.g. `med_...`).
Example request
curl https://api.socialkit.sh/v1/media/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
DELETE/v1/media/{id}Bearer

Removes the metadata row and the stored object.

Path parameters
idrequiredstringThe media id (e.g. `med_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/media/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/media/{id}/contentBearer

With a bearer key, the account's own media. With a `token` from `POST /v1/media/{id}/signed-url` instead, the same bytes with no key at all (what an `<img src>` in a partner UI uses); an expired token answers `token_expired`, a forged one `invalid_token`.

Path parameters
idrequiredstringThe media id (e.g. `med_...`).
tokenrequiredstringA signed-URL token, in place of the bearer key.
Example request
curl https://api.socialkit.sh/v1/media/id_8fa3c1d9/content \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200The raw file bytes, with the original Content-Type.
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
POST/v1/media/{id}/signed-urlBearer

A link to `GET /v1/media/{id}/content` that carries its own authorization, so a UI can show a rendered master or an upload without holding a key. Default 1 hour, at most 24 hours. Store the media id and sign again when needed, never the URL. Free: no credit, no model call.

Path parameters
idrequiredstringThe media id (e.g. `med_...`).
Request body SignMediaUrlRequest
Example request
curl -X POST https://api.socialkit.sh/v1/media/id_8fa3c1d9/signed-url \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses

Posts

The content lifecycle: draft, schedule, publish. Posts move through a strict state machine (draft -> scheduled/publishing -> published/failed). Publishing is exactly-once: a replayed or concurrent publish returns 409.

GET/v1/postsBearer

Optionally filter by status. Results are paginated newest-first; page with limit and offset and follow pagination.hasMore. Posts carry no token material.

Path parameters
statusrequiredstringFilter to one post status.
limitrequiredintegerMaximum posts to return (default 50, max 100).
offsetrequiredintegerNumber of posts to skip from the newest.
Example request
curl https://api.socialkit.sh/v1/posts \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/postsBearer

Creates a post. Supply the text plus optional brand, channel, and media references. Any brandId or channelId must belong to the calling account. Omit runAt to create a draft; pass runAt (epoch ms) to create and schedule the post in one call (it comes back in the `scheduled` state). Pass an Idempotency-Key header to make a retry of this call safe (a replay returns the original post, not a duplicate).

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body CreatePostRequest
Example request
curl -X POST https://api.socialkit.sh/v1/posts \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "We cut onboarding from 11 fields to 3. Activation up 40%."
}'
Responses
POST/v1/posts/validateBearer

A fast, no-LLM, nothing-persisted check that returns every structural error and reach/voice warning at once, so an agent can fix them in one pass before spending a score or committing to create/publish. Errors block a clean publish (empty text, over length, unsupported platform, invalid media). Warnings are reach or voice hazards (6+ hashtags, an external link in the body, engagement-bait phrases, AI-tells).

Request body ValidatePostRequest
Example request
curl -X POST https://api.socialkit.sh/v1/posts/validate \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "post": "Excited to share our new feature! Thoughts? Follow for more. #ai #saas #growth #startup #founder #buildinpublic"
}'
Responses
  • 200Validation report (200 even when not valid; check `valid`)ValidatePostResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
GET/v1/posts/{id}Bearer
Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl https://api.socialkit.sh/v1/posts/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Post foundPostResult
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
PATCH/v1/posts/{id}Bearer

Partial update. Only draft posts can be edited; editing a post in any other state returns 409. Omit a field to leave it unchanged.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Request body UpdatePostRequest
Example request
curl -X PATCH https://api.socialkit.sh/v1/posts/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Responses
  • 200Post updatedPostResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 409The request conflicts with the resource's current stateError
DELETE/v1/posts/{id}Bearer
Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/posts/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/posts/{id}/publishBearerNot for tenants

Publishes immediately through the exactly-once path. A replayed or concurrent call returns 409 not_claimable. A revoked channel leaves the post failed and returns 409 channel_revoked with the failed post embedded. A platform error returns 502 publish_failed.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/posts/id_8fa3c1d9/publish \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Post publishedPostResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 409Not in a publishable state (not_claimable) or the channel is revoked (channel_revoked). The terminal post is embedded alongside the error.PostErrorResult
  • 502The platform rejected the publish (publish_failed).PostErrorResult
  • 503The capability is not configured on this deploymentError
POST/v1/posts/{id}/scoreBearer

Scores the stored post's text and persists the score against the post, building the score-vs-reality dataset. Distinct from POST /v1/score, which grades arbitrary text without saving.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/posts/id_8fa3c1d9/score \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Post scored and recordedStoredScoreResult
  • 401Missing or invalid API keyError
  • 402Monthly credit allotment exhausted (`error.code` is `quota_exceeded`). A credit is one successful model-backed call (score, rewrite, generate, plan, voice distillation); only that family of endpoints can return this. On hard-stop plans (for example Free), once the included credits for the billing cycle are spent every further model call is blocked until the cycle resets, so upgrade the plan or wait for the reset. The `x-credits-limit` / `-remaining` / `-reset` headers report your standing, and `Retry-After` gives the seconds until the cycle resets. Nothing is charged for a blocked call.Error
  • 404Resource not found for this accountError
  • 429Rate limit exceeded. Two budgets apply per key: an overall request budget (headers `x-ratelimit-limit` / `-remaining` / `-reset`) and a separate, tighter budget for the model-backed endpoints (score, rewrite, generate, plan, and voice distillation), reported on those endpoints as `x-ratelimit-llm-limit` / `-remaining` / `-reset`. Either budget can trigger this response.Error
  • 502The model or gateway misbehaved; safe to retryError
GET/v1/posts/{id}/scoresBearer
Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl https://api.socialkit.sh/v1/posts/id_8fa3c1d9/scores \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Scheduling

Bulk-schedule a week of posts and read the cross-channel calendar of scheduled and published content. Due posts are published by a per-minute cron through the same exactly-once path as publish-now.

POST/v1/posts/bulk-scheduleBearerNot for tenants

Creates a batch of posts and schedules each for its runAt time in a single call (the /plan week made concrete). Each item is validated independently; any bad reference fails the whole batch. Pass an Idempotency-Key header to make a retry of the whole batch safe.

Path parameters
Idempotency-KeyrequiredstringA client-chosen key (max 255 chars) that makes this call safe to retry. The first successful (2xx) response is cached per API key for 24 hours; replaying the same key returns that stored response verbatim with an `idempotent-replay: true` header and does no new work, so a retried model call (score, rewrite, generate, plan) charges no second credit and a retried creation creates nothing new. The request body is fingerprinted with the key: reusing a key with a different body returns `409 idempotency_key_reused` rather than the stale stored result, so a retry must be identical. Non-2xx responses are not cached, so a failed call leaves the key reusable. Best-effort: a sequential retry after a timeout is deduped, but two truly concurrent requests may both execute.
Request body BulkScheduleRequest
Example request
curl -X POST https://api.socialkit.sh/v1/posts/bulk-schedule \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "text": "Day one shipping receipt.",
      "channelId": "chn_example",
      "runAt": 1893456000000
    },
    {
      "text": "Day two contrarian take.",
      "channelId": "chn_example",
      "runAt": 1893542400000
    }
  ]
}'
Responses
POST/v1/posts/{id}/scheduleBearerNot for tenants

Sets the post to scheduled for runAt and enqueues a single pending job. Re-scheduling cancels the prior pending job. The per-minute cron publishes due posts through the exactly-once path.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Request body SchedulePostRequest
Example request
curl -X POST https://api.socialkit.sh/v1/posts/id_8fa3c1d9/schedule \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "runAt": 1893456000000
}'
Responses
  • 200Post scheduledPostResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 409The request conflicts with the resource's current stateError
POST/v1/posts/{id}/cancelBearerNot for tenants

Moves the post to canceled and cancels its pending job. A post in a terminal state (published, failed) cannot be canceled and returns 409.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/posts/id_8fa3c1d9/cancel \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200Post canceledPostResult
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 409The request conflicts with the resource's current stateError
GET/v1/calendarBearerNot for tenants

Returns scheduled and published posts within an optional time window, ordered chronologically. from/to are unix-ms timestamps.

Path parameters
fromrequiredintegerStart of the window, unix epoch milliseconds.
torequiredintegerEnd of the window, unix epoch milliseconds.
Example request
curl https://api.socialkit.sh/v1/calendar \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Webhooks

Signed event callbacks (post.published, post.failed, post.scored, channel.disconnected). The signing secret is returned once at creation. Deliveries are retried with backoff; inspect them per endpoint.

GET/v1/webhooksBearer

Returns the account's endpoints (secrets never included) plus the set of events you can subscribe to.

Example request
curl https://api.socialkit.sh/v1/webhooks \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/webhooksBearer

Registers a signed callback URL. The signing secret is returned once in this response and never again; store it now. Omit events to subscribe to all of them.

Example request
curl -X POST https://api.socialkit.sh/v1/webhooks \
  -H "Authorization: Bearer $SOCIALKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/hooks/socialkit"
}'
Responses
GET/v1/webhooks/{id}Bearer
Path parameters
idrequiredstringThe webhook endpoint id (e.g. `whk_...`).
Example request
curl https://api.socialkit.sh/v1/webhooks/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
DELETE/v1/webhooks/{id}Bearer
Path parameters
idrequiredstringThe webhook endpoint id (e.g. `whk_...`).
Example request
curl -X DELETE https://api.socialkit.sh/v1/webhooks/id_8fa3c1d9 \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/webhooks/{id}/deliveriesBearer

Returns delivery attempts with status and retry state, newest-first. Results are paginated; page with limit and offset and follow pagination.hasMore.

Path parameters
idrequiredstringThe webhook endpoint id (e.g. `whk_...`).
limitrequiredintegerMaximum deliveries to return (default 50, max 100).
offsetrequiredintegerNumber of deliveries to skip from the newest.
Example request
curl https://api.socialkit.sh/v1/webhooks/id_8fa3c1d9/deliveries \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Metrics

Engagement for published posts. Each poll appends a time-series snapshot; a per-minute cron sweeps the least-recently-polled posts. Roll snapshots up into account analytics, and correlate predicted scores with measured engagement in the calibration dataset.

GET/v1/posts/{id}/metricsBearerNot for tenants

Returns the latest engagement snapshot plus the full time-series history (newest first) for a published post. Counters are null when the platform does not expose that dimension for the post type.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl https://api.socialkit.sh/v1/posts/id_8fa3c1d9/metrics \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
POST/v1/posts/{id}/metrics/refreshBearerNot for tenants

Polls the platform for the post's current engagement and stores a new snapshot. Mirrors the per-minute cron sweep for a single post. A draft or unpublished post returns 409; a revoked channel returns 409.

Path parameters
idrequiredstringThe post id (e.g. `pst_...`).
Example request
curl -X POST https://api.socialkit.sh/v1/posts/id_8fa3c1d9/metrics/refresh \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
  • 200A fresh snapshot was storedMetricRefreshResult
  • 400Invalid request bodyError
  • 401Missing or invalid API keyError
  • 404Resource not found for this accountError
  • 409The request conflicts with the resource's current stateError
  • 502The model or gateway misbehaved; safe to retryError
  • 503The capability is not configured on this deploymentError
GET/v1/analyticsBearerNot for tenants

Aggregates the latest snapshot of every published post into account totals and a per-platform breakdown. Optional from/to filter on the post's published time (unix-ms).

Path parameters
fromrequiredintegerStart of the window, unix epoch milliseconds.
torequiredintegerEnd of the window, unix epoch milliseconds.
Example request
curl https://api.socialkit.sh/v1/analytics \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses
GET/v1/calibrationBearer

The calibration dataset: each published post that has both a recorded score and a metric snapshot, pairing the predicted score with the measured engagement. Posts missing either are omitted.

Example request
curl https://api.socialkit.sh/v1/calibration \
  -H "Authorization: Bearer $SOCIALKIT_KEY"
Responses

Schemas

124 types
Platformenum

Target platform for scoring and preflight. `linkedin` and `x` are live; `instagram`, `tiktok`, and `facebook` are planned. Each platform has its own rubric, character cap, and media set (e.g. `document` is LinkedIn only); the server validates the post against the selected platform.

linkedinx
MediaKindenum

The media attached to the post. Drives the format multiplier.

nonesingle-imagemulti-imagedocumentvideopolllink
FollowerBandenum

Author follower bucket. Optional context for calibration.

under-500500-2k2k-10k10k-50k50k-plus
Dimensionenum

A scored dimension of the post.

hookalgorithmFitspecificitystructurevoiceengagement
HookArchetypeenum

The classified shape of the post's opening hook.

contrarianstatementquestionpure-numberstoryother
ScoreRequestobject
FieldTypeNotes
platformPlatform
postrequiredstringThe full post text to grade. The hard cap shown here is the largest across platforms (X long-form). The server enforces the selected platform's own limit: 3000 for LinkedIn, 25000 for X.
authorNamestringOptional author name for context.
authorHeadlinestringOptional author headline/title for context.
followerBandFollowerBand
mediaMediaKind
hasExternalLinkbooleanWhether the post body or its preview points off-platform. External links cap algorithm fit. Derived from the post text when omitted.
hashtagCountintegerNumber of hashtags. Six or more caps algorithm fit. Derived from the post text when omitted.
dayOfWeekintegerPlanned publish day, Sunday=0. Optional.
hourLocalintegerPlanned publish hour in the author's local time. Optional.
ValidatePostRequestobject
FieldTypeNotes
postrequiredstringThe full draft text to preflight. The hard cap shown here is the largest across platforms (X long-form). The server enforces the selected platform's own limit: 3000 for LinkedIn, 25000 for X.
platformPlatform
mediaMediaKind
hasExternalLinkbooleanWhether the post body links off-platform. Derived from the post text when omitted.
hashtagCountintegerNumber of hashtags. Derived from the post text when omitted.
ValidationIssueobject

One structural error or reach/voice warning found in the draft.

FieldTypeNotes
severityrequiredstringErrors block a clean publish; warnings are reach/voice hazards.errorwarning
coderequiredstringStable machine code, e.g. empty, too_long, unsupported_platform, invalid_media, too_many_hashtags, external_link, engagement_bait, ai_tell, em_dash.
fieldrequiredstringThe request field the issue is about (post, platform, media).
messagerequiredstringA specific, human-readable explanation of the issue.
ValidatePostResultobject
FieldTypeNotes
validrequiredbooleanTrue when there are no error-severity issues.
issuesrequiredValidationIssue[]
signalsrequiredobjectThe signals actually evaluated (supplied or derived).
ScoreBreakdownobject

Per-dimension scores, each 0-100.

FieldTypeNotes
hookrequirednumber
algorithmFitrequirednumber
specificityrequirednumber
structurerequirednumber
voicerequirednumber
engagementrequirednumber
Signalobject

One ranked observation about the post.

FieldTypeNotes
dimensionrequiredDimension
impactrequiredstringpositivenegative
weightrequiredintegerHow much this signal moved the score (1-5).
labelrequiredstringShort headline a human reads at a glance.
detailrequiredstringOne sentence on the specific move in the post.
rulestringThe named algorithm rule this ties to, if any.
ScoreResultobject
FieldTypeNotes
schemarequiredstringScore schema version.
platformrequiredPlatform
overallrequirednumberWeighted overall score.
breakdownrequiredScoreBreakdown
verdictrequiredstringOne-line plain-language verdict.
signalsrequiredSignal[]
hookArchetyperequiredHookArchetype
hookArchetypeNoterequiredstringBenchmark note for the detected hook archetype.
formatMultiplierrequirednumberReach multiplier implied by the chosen media format.
formatNoterequiredstringWhy the format multiplier is what it is.
RewriteChangeobject

One edit the rewrite made, tied to a scored dimension.

FieldTypeNotes
dimensionrequiredDimension
noterequiredstringOne sentence on what changed and why.
RewriteResultobject

The rewrite plus the before/after scores. The after-score is reported as measured and is never massaged to look like an improvement.

FieldTypeNotes
rewriterequiredstringThe rewritten post.
changesrequiredRewriteChange[]
beforerequiredScoreResult
afterrequiredScoreResult
GenerateRequestobject
FieldTypeNotes
briefrequiredstringWhat the post should be about.
platformobjectTarget platform for the drafts. `linkedin` (default) writes for the LinkedIn feed ranker; `x` writes reply-optimized posts for the X ranker. Each platform uses its own writing rubric and media set.
authorNamestringOptional author name for voice context.
authorHeadlinestringOptional author headline/title for context.
followerBandFollowerBand
mediaMediaKind
archetypeHookArchetype
countintegerHow many drafts to produce (1-3, default 1).
brandIdstringOptional brand id whose context (business, audience, themes, link policy) conditions the drafts. Must belong to the calling account.
voiceIdstringOptional voice id whose traits and exemplars the drafts imitate. Must belong to the calling account.
GeneratedCandidateobject
FieldTypeNotes
postrequiredstringThe generated post text.
scorerequiredScoreResult
GenerateResultobject
FieldTypeNotes
candidatesrequiredGeneratedCandidate[]Drafts sorted by overall score, best first.
Cadenceenum

Posting rhythm hint for the calendar.

dailyweekdays3x-week
PlanRequestobject
FieldTypeNotes
briefrequiredstringThemes, goals, or topics the calendar should cover.
platformobjectTarget platform for the calendar. `linkedin` (default) or `x`. Each platform uses its own content pillars and media set.
countintegerNumber of calendar slots (1-14, default 5).
authorNamestringOptional author name for context.
authorHeadlinestringOptional author headline/title for context.
followerBandFollowerBand
cadenceCadence
brandIdstringOptional brand id whose context conditions the plan. Must belong to the calling account.
voiceIdstringOptional voice id whose style conditions the plan. Must belong to the calling account.
PlanItemobject
FieldTypeNotes
slotrequiredinteger1-based ordinal in the plan.
pillarrequiredstringThe content pillar this idea serves.
hookrequiredstringThe opening line to write.
anglerequiredstringOne sentence on the take or argument.
formatrequiredMediaKind
archetyperequiredHookArchetype
PlanResultobject
FieldTypeNotes
platformrequiredPlatform
itemsrequiredPlanItem[]
Accountobject
FieldTypeNotes
idrequiredstringAccount id (e.g. `acc_...`).
emailrequiredstring
planrequiredstringBilling plan. New accounts start on `free`.
namestring | nullDisplay name, or null if unset.
avatarUrlstring | nullAvatar URL, or null if unset.
createdAtintegerCreation time, epoch milliseconds.
ApiKeyobject

API key metadata. The secret is never included.

FieldTypeNotes
idrequiredstringKey id (e.g. `key_...`).
namerequiredstringHuman-readable label for the key.
keyPrefixrequiredstringThe first characters of the secret, for display.
createdAtrequiredintegerCreation time, epoch milliseconds.
lastUsedAtrequiredinteger | nullLast successful auth, epoch milliseconds, or null.
revokedAtrequiredinteger | nullRevocation time, epoch milliseconds, or null if live.
toolProfilestring | nullThe MCP catalog this key presents on tools/list (tenant keys only): `core` for the curated generate, score, rewrite, plan, brands, voices and account set, null for every tool. Presentation only; what the key may call does not change.
CreateAccountRequestobject
FieldTypeNotes
emailrequiredstring
namestringOptional label for the first key. Defaults to `default`.
CreateAccountResultobject
FieldTypeNotes
accountrequiredAccount
keyrequiredApiKey
apiKeyrequiredstringThe plaintext key, shown exactly once. Store it now; it cannot be retrieved again.
WhoamiResultobject
FieldTypeNotes
accountrequiredAccount
keyrequiredApiKey
capabilitiesrequiredCapabilities
creditsrequiredCreditStanding
tenantTenantIdentity
CreditStandingobject

The live monthly credit standing. `scope` appears for partner tenants only and names the ceiling that bounds `remaining`: the tenant's own monthly cap or the partner-wide cap.

FieldTypeNotes
usedrequiredinteger
includedrequiredinteger
remainingrequiredinteger
overagerequiredbooleanWhether the plan flows into billed overage past `included`.
periodrequiredstringThe `YYYY-MM` window.
resetAtrequiredintegerUnix seconds when the allotment resets.
scopestringtenantpartner
TenantIdentityobject

Present when the calling key belongs to a partner tenant.

FieldTypeNotes
externalIdrequiredstring
partnerrequiredobject
Partnerobject
FieldTypeNotes
idrequiredstringPartner id (e.g. `par_...`).
slugrequiredstring
namerequiredstring
statusrequiredstringactivesuspended
capsrequiredPartnerCaps
creditWeightsrequiredobjectCredits a tenant is charged per operation (`score`, `rewrite`, `generate`, `plan`, `buildVoice`, `visual`, `carousel`). An operation absent here costs 1.
createdAtrequiredinteger
updatedAtrequiredinteger
PartnerCapsobject
FieldTypeNotes
monthlyCreditCaprequiredinteger | nullPartner-wide credits per month; null means no cap.
tenantMonthlyCreditCaprequiredinteger | nullCredits per tenant per month; null means no cap.
maxTenantsrequiredinteger
rateLimitPerHourrequiredintegerRequests per hour across the partner and all its tenants.
llmRateLimitPerMinuterequiredintegerModel-backed calls per minute across all tenants.
PartnerKeyobject

Partner key metadata. The secret is never included.

FieldTypeNotes
idrequiredstring
namerequiredstring
keyPrefixrequiredstring
createdAtrequiredinteger
lastUsedAtrequiredinteger | null
expiresAtrequiredinteger | nullSet on a key superseded by a rotation; it stops working then.
revokedAtrequiredinteger | null
PartnerProfileResultobject
FieldTypeNotes
partnerrequiredPartner
keyrequiredPartnerKey
tenantsrequiredintegerLive tenants.
creditsrequiredobject
RotatePartnerKeyResultobject
FieldTypeNotes
keyrequiredPartnerKey
apiKeyrequiredstringThe plaintext partner key, shown exactly once.
previousKeysGraceSecondsrequiredintegerHow long the other live keys keep working (0 when `immediate`).
Tenantobject
FieldTypeNotes
externalIdrequiredstring
accountIdrequiredstringThe SocialKit account id behind the tenant (e.g. `acc_...`).
namerequiredstring | null
planrequiredstringAlways `tenant`.
statusrequiredstringactivedeleting
createdAtrequiredinteger
deletedAtrequiredinteger | null
EnsureTenantRequestobject
FieldTypeNotes
namestringDisplay name for the tenant.
brandBrandCreateRequest
TenantResultobject
FieldTypeNotes
tenantrequiredTenant
brandBrand
TenantDetailResultobject
FieldTypeNotes
tenantrequiredTenant
creditsrequiredCreditStanding
countsrequiredobject
TenantListResultobject
FieldTypeNotes
tenantsrequiredTenant[]
nextCursorrequiredstring | null
UsageTotalsobject
FieldTypeNotes
callsrequiredintegerSuccessful model calls.
creditsrequiredintegerCredits charged (calls times the partner's weight).
AuditEventobject
FieldTypeNotes
idrequiredstringEvent id (e.g. `aud_...`), stable across pages.
atrequiredintegerWhen, epoch milliseconds.
actionrequiredstringtenant.createdtenant.brand_updatedtenant.deletedtenant_key.createdtenant_key.revokedtenant_key.rotatedpartner_key.createdpartner_key.rotatedpartner_key.revokedpartner.updatedpartner.suspendedpartner.reinstatedoperator.grantedoperator.revoked
actorKindrequiredstringWho made the change: a partner key, a tenant's own key (or the partner key acting as the tenant), SocialKit ops, a console operator, or the system.partner-keytenant-keyinternaloperatorsystem
actorIdrequiredstring | nullThe acting key id or operator email, when known.
targetrequiredstring | nullThe tenant's external id, a key id, or an operator email.
detailsrequiredobject | nullA small object with the specifics (key ids, the fields changed); never a secret.
requestIdrequiredstring | null
AuditListResultobject
FieldTypeNotes
eventsrequiredAuditEvent[]
nextCursorrequiredstring | nullPass as `cursor` for the next (older) page; null on the last.
PartnerUsageobject
FieldTypeNotes
periodrequiredstring
callsrequiredinteger
creditsrequiredinteger
byOperationrequiredobject
tenantsrequiredobject[]
PartnerUsageResultobject
FieldTypeNotes
usagerequiredPartnerUsage
TenantUsageobject
FieldTypeNotes
periodrequiredstring
callsrequiredinteger
creditsrequiredinteger
byOperationrequiredobject
byActorrequiredobject[]
TenantUsageResultobject
FieldTypeNotes
tenantrequiredTenant
usagerequiredTenantUsage
Capabilitiesobject

What this deployment can do, derived from which secrets are set. Branch on these before attempting an action instead of learning a capability is off only from an error. All three gate on the same vault + LinkedIn app creds today but are named separately so the contract is stable if the gating diverges.

FieldTypeNotes
publishrequiredbooleanWhether stored drafts can be published to a channel.
metricsrequiredbooleanWhether published-post engagement metrics can be polled.
connectChannelsrequiredbooleanWhether new social channels can be connected via OAuth.
HealthResultobject

The readiness report returned by GET /v1/health. `status` is `ok` only when every dependency check is `ok`; otherwise it is `degraded` and the response status is 503.

FieldTypeNotes
statusrequiredstringOverall readiness; `degraded` if any check failed.okdegraded
checksrequiredobject
versionrequiredstringThe shipped product version (e.g. `0.10`).
latencyMsrequiredintegerWall-clock milliseconds spent running the dependency checks.
timerequiredstringServer time when the probe ran (ISO 8601).
ServiceMetaobject

The self-describing service contract returned by GET /v1/meta. The enum lists (platforms, mediaKinds, followerBands) are the exact accepted values for a score request; dimensions carries the rubric weights; billing carries the credit cost model so an agent can budget keylessly.

FieldTypeNotes
scoreVersionrequiredstringThe scoring schema version (e.g. `v0.3`).
maxPostCharsrequiredintegerMaximum post length accepted by the scorer.
platformsrequiredobject[]Supported platforms; `enabled` is false for not-yet-live ones.
mediaKindsrequiredobject[]Accepted `media` values for a score request.
followerBandsrequiredobject[]Accepted `followerBand` values.
dimensionsrequiredobject[]The six scoring dimensions and their weights (sum to 100).
capabilitiesrequiredCapabilities
billingrequiredCreditModel
CreditModelobject

The cost contract, so an agent can budget before spending. A credit is one successful model-backed call; reads, the post lifecycle, CRUD, and preflight validate are free.

FieldTypeNotes
unitrequiredstringThe metering unit. Always `credit`.
creditPerCallrequiredintegerCredits charged per billable call. Currently 1.
chargedOnSuccessOnlyrequiredbooleanWhen true, a credit is charged only after the model call succeeds, so a rate limit, quota stop, or upstream error never costs one.
billableOperationsrequiredstring[]The operations that cost a credit, by capability name (`score`, `rewrite`, `generate`, `plan`, `buildVoice`). Every other route is free.
plansrequiredobject[]The plan ladder with each tier's monthly credit allowance.
CreateKeyRequestobject
FieldTypeNotes
namestringOptional label for the key. Defaults to `default`.
toolProfilestring | nullTenant keys only. `core` makes the key's MCP tools/list the curated set (generate, score, rewrite, plan, brands, voices, get_account, sign_media_url); tools/call is unaffected. A self-serve key answers 400 when this is set.
SignMediaUrlRequestobject
FieldTypeNotes
ttlSecondsintegerSeconds until the URL expires. Default 3600; clamped to the range.
SignedMediaUrlResultobject
FieldTypeNotes
mediaIdrequiredstring
urlrequiredstring`https://api.socialkit.sh/v1/media/{id}/content?token=...`, usable with no key until `expiresAt`.
expiresAtrequiredintegerExpiry, epoch milliseconds.
CreateKeyResultobject
FieldTypeNotes
keyrequiredApiKey
apiKeyrequiredstringThe plaintext key, shown exactly once.
KeyListResultobject
FieldTypeNotes
keysrequiredApiKey[]
RevokeResultobject
FieldTypeNotes
revokedrequiredbooleanTrue when the key was revoked.
BrandKitobject

A brand's persisted visual system. A v2 visual request that names the brand (`brandId`) is grounded from it: the palette and fonts become the engine's brand kit, the logo is placed, the photos lead. Media ids must be images uploaded to the same account (`POST /v1/media`).

FieldTypeNotes
paletteobjectHex colors, `#RRGGBB`.
fontsobjectGoogle Fonts family names. Requires a palette.
logoobject
photoMediaIdsstring[]Images on this account the engine may lead with.
imageryStylestringFree-text imagery direction ("warm natural light, no stock").
Brandobject

Workspace context that conditions generation.

FieldTypeNotes
idrequiredstringBrand id (e.g. `brd_...`).
namerequiredstring
descriptionrequiredstring | nullWho the business is.
audiencerequiredstring | nullWho the brand talks to.
themesrequiredstring[]Recurring themes the brand posts about.
linkPolicyrequiredstring | nullFree-text policy on outbound links.
kitrequiredobjectThe persisted visual system, or null.
createdAtrequiredintegerCreation time, epoch milliseconds.
updatedAtrequiredintegerLast update time, epoch milliseconds.
BrandCreateRequestobject
FieldTypeNotes
namerequiredstring
descriptionstring | null
audiencestring | null
themesstring[]
linkPolicystring | null
kitobject
BrandUpdateRequestobject

Partial update; omit a field to leave it unchanged.

FieldTypeNotes
namestring
descriptionstring | null
audiencestring | null
themesstring[]
linkPolicystring | null
kitobjectReplace the kit wholesale, or null to clear it.
BrandResultobject
FieldTypeNotes
brandrequiredBrand
BrandListResultobject
FieldTypeNotes
brandsrequiredBrand[]
Voiceobject

A reusable writing profile distilled from sample posts.

FieldTypeNotes
idrequiredstringVoice id (e.g. `voi_...`).
brandIdrequiredstring | nullThe brand this voice belongs to, or null.
namerequiredstring
traitsrequiredstring[]Stylistic traits to imitate.
doNotrequiredstring[]Things this voice never does.
exemplarsrequiredstring[]Few-shot snippets that anchor the voice.
createdAtrequiredintegerCreation time, epoch milliseconds.
updatedAtrequiredintegerLast update time, epoch milliseconds.
VoiceCreateRequestobject
FieldTypeNotes
namerequiredstring
brandIdstringOptional brand to attach the voice to.
samplesstring[]1-12 sample posts in the target voice. When present, the profile is distilled from them and any traits/doNot/exemplars you also pass are ignored.
traitsstring[]
doNotstring[]
exemplarsstring[]
VoiceUpdateRequestobject

Partial update; omit a field to leave it unchanged.

FieldTypeNotes
namestring
brandIdstring | nullReattach to a brand, or null to detach.
traitsstring[]
doNotstring[]
exemplarsstring[]
BuildVoiceRequestobject
FieldTypeNotes
namerequiredstring
brandIdstringOptional brand to attach the built voice to.
samplesrequiredstring[]1-12 sample posts written in the target voice.
VoiceResultobject
FieldTypeNotes
voicerequiredVoice
VoiceListResultobject
FieldTypeNotes
voicesrequiredVoice[]
DeleteResultobject
FieldTypeNotes
deletedrequiredbooleanTrue when the resource was deleted.
ChannelStatusenum

Connection health of the channel.

connectedexpiredrevokederror
Channelobject

A connected social account. Carries no token material.

FieldTypeNotes
idrequiredstringChannel id (e.g. `chn_...`).
accountIdrequiredstringThe owning account id.
brandIdrequiredstring | nullThe brand this channel belongs to, or null.
platformrequiredstringThe platform, e.g. `linkedin`.
platformUserIdrequiredstring | nullThe platform's stable user id for the connected member.
handlerequiredstring | nullA display handle or name for the connected account.
statusrequiredChannelStatus
scopesrequiredstring[]Granted OAuth scopes.
expiresAtrequiredinteger | nullAccess token expiry, epoch milliseconds, or null.
lastRefreshAtrequiredinteger | nullLast token refresh, epoch milliseconds, or null.
createdAtrequiredintegerCreation time, epoch milliseconds.
updatedAtrequiredintegerLast update time, epoch milliseconds.
ChannelResultobject
FieldTypeNotes
channelrequiredChannel
ChannelListResultobject
FieldTypeNotes
channelsrequiredChannel[]
ConnectChannelRequestobject
FieldTypeNotes
brandIdstringOptional brand to scope the channel to. Must belong to the calling account.
ConnectChannelResultobject
FieldTypeNotes
urlrequiredstringThe platform authorization URL to redirect the member to.
staterequiredstringThe opaque, single-use state token tying the callback to this account. Returned for visibility; the callback validates it.
Mediaobject

Metadata for an uploaded file. The storage key is never returned.

FieldTypeNotes
idrequiredstringMedia id (e.g. `med_...`).
accountIdrequiredstringThe owning account id.
brandIdrequiredstring | nullThe brand this media belongs to, or null.
contentTyperequiredstringThe validated MIME type of the stored bytes.
sizerequiredintegerSize in bytes.
filenamerequiredstring | nullThe original filename (basename), or null.
sha256requiredstringSHA-256 of the bytes, hex-encoded.
createdAtrequiredintegerUpload time, epoch milliseconds.
MediaResultobject
FieldTypeNotes
mediarequiredMedia
MediaListResultobject
FieldTypeNotes
mediarequiredMedia[]
paginationrequiredobject
PostStatusenum

Where the post sits in its lifecycle.

draftscheduledpublishingpublishedfailedcanceled
Postobject

A unit of content. Carries no token material.

FieldTypeNotes
idrequiredstringPost id (e.g. `pst_...`).
accountIdrequiredstringThe owning account id.
brandIdrequiredstring | nullThe brand this post belongs to, or null.
channelIdrequiredstring | nullThe channel this post publishes to, or null.
platformrequiredstringThe target platform, e.g. `linkedin`.
statusrequiredPostStatus
textrequiredstringThe post body.
mediaIdsrequiredstring[]Ids of media attached to the post.
scheduledForrequiredinteger | nullScheduled publish time, epoch milliseconds, or null.
publishedAtrequiredinteger | nullActual publish time, epoch milliseconds, or null.
platformPostIdrequiredstring | nullThe platform's id for the published post, or null.
platformUrlrequiredstring | nullPublic URL of the published post, or null.
errorrequiredstring | nullThe last publish error, or null.
createdAtrequiredintegerCreation time, epoch milliseconds.
updatedAtrequiredintegerLast update time, epoch milliseconds.
PostResultobject
FieldTypeNotes
postrequiredPost
PostListResultobject
FieldTypeNotes
postsrequiredPost[]
paginationrequiredobject
PostErrorResultobject

An error with the affected post's terminal state embedded.

FieldTypeNotes
errorrequiredobject
postrequiredPost
CreatePostRequestobject
FieldTypeNotes
textrequiredstringThe post body.
channelIdstring | nullChannel to publish to. Must belong to the account.
brandIdstring | nullBrand to attach. Must belong to the account.
platformstringTarget platform. Defaults from the channel, else linkedin.
mediaIdsstring[]
runAtintegerOptional. Epoch milliseconds at which to publish. When set, the post is created and scheduled in one call and comes back in the `scheduled` state (instead of `draft`). Omit to create a plain draft.
UpdatePostRequestobject

Partial update; omit a field to leave it unchanged. Draft only.

FieldTypeNotes
textstring
channelIdstring | null
brandIdstring | null
mediaIdsstring[]
SchedulePostRequestobject
FieldTypeNotes
runAtrequiredintegerWhen to publish, epoch milliseconds. Must be in the future.
BulkScheduleItemobject
FieldTypeNotes
textrequiredstring
runAtrequiredintegerWhen to publish this post, epoch milliseconds.
channelIdstring | null
brandIdstring | null
platformstring
mediaIdsstring[]
BulkScheduleRequestobject
FieldTypeNotes
itemsrequiredBulkScheduleItem[]
PostScoreobject

A score recorded against a stored post.

FieldTypeNotes
idrequiredstringScore id (e.g. `psc_...`).
postIdrequiredstring
scorerequiredintegerThe overall score, rounded.
verdictrequiredstring | nullOne-line plain-language verdict, or null.
payloadrequiredobjectThe full ScoreResult captured at scoring time.
createdAtrequiredintegerWhen the score was recorded, epoch milliseconds.
StoredScoreResultobject
FieldTypeNotes
scorerequiredPostScore
resultrequiredScoreResult
PostScoreListResultobject
FieldTypeNotes
scoresrequiredPostScore[]
CalendarEntryobject
FieldTypeNotes
postIdrequiredstring
statusrequiredPostStatus
whenrequiredintegerScheduled or published time, epoch milliseconds.
channelIdrequiredstring | null
platformrequiredstring
previewrequiredstringA short preview of the post text.
CalendarResultobject
FieldTypeNotes
calendarrequiredCalendarEntry[]
MetricSnapshotobject

One engagement snapshot for a post. Each counter is null when the platform did not report that dimension (distinct from a reported zero).

FieldTypeNotes
idrequiredstringSnapshot id (e.g. `met_...`).
postIdrequiredstring
impressionsrequiredinteger | null
likesrequiredinteger | null
commentsrequiredinteger | null
sharesrequiredinteger | null
clicksrequiredinteger | null
fetchedAtrequiredintegerWhen this snapshot was captured, epoch milliseconds.
PostMetricsResultobject
FieldTypeNotes
latestrequiredobjectThe most recent snapshot, or null if never polled.
historyrequiredMetricSnapshot[]All snapshots, newest first.
MetricRefreshResultobject
FieldTypeNotes
snapshotrequiredMetricSnapshot
MetricTotalsobject
FieldTypeNotes
postsrequiredintegerPosts counted (those with at least one snapshot).
impressionsrequiredinteger
likesrequiredinteger
commentsrequiredinteger
sharesrequiredinteger
clicksrequiredinteger
engagementsrequiredintegerlikes + comments + shares + clicks.
PlatformTotalsobject
FieldTypeNotes
Analyticsobject
FieldTypeNotes
AnalyticsResultobject
FieldTypeNotes
analyticsrequiredAnalytics
CalibrationRowobject

A published post paired with its predicted score and measured engagement. Only posts that have both appear.

FieldTypeNotes
postIdrequiredstring
platformrequiredstring
scorerequiredintegerThe latest recorded score.
verdictrequiredstring | null
publishedAtrequiredinteger | nullWhen the post was published, epoch milliseconds.
impressionsrequiredinteger | null
likesrequiredinteger | null
commentsrequiredinteger | null
sharesrequiredinteger | null
clicksrequiredinteger | null
engagementsrequiredintegerlikes + comments + shares + clicks (nulls counted as 0).
CalibrationResultobject
FieldTypeNotes
calibrationrequiredCalibrationRow[]
WebhookEventenum

An event you can subscribe a webhook to.

post.publishedpost.failedpost.scoredchannel.disconnected
WebhookEndpointobject

A signed callback endpoint. The secret is never included here.

FieldTypeNotes
idrequiredstringWebhook id (e.g. `whk_...`).
accountIdrequiredstring
urlrequiredstringThe callback URL.
eventsrequiredWebhookEvent[]
statusrequiredstringactivedisabled
createdAtrequiredintegerCreation time, epoch milliseconds.
updatedAtrequiredintegerLast update time, epoch milliseconds.
CreateWebhookRequestobject
FieldTypeNotes
urlrequiredstringThe HTTPS callback URL to deliver events to.
eventsWebhookEvent[]Events to subscribe to. Omit to subscribe to all.
CreateWebhookResultobject
FieldTypeNotes
webhookrequiredWebhookEndpoint
secretrequiredstringThe signing secret, returned once (it cannot be retrieved again). Every delivery carries an x-socialkit-timestamp header (epoch seconds) and an x-socialkit-signature header of the form `sha256=<hex>`. To verify: reject the request if the timestamp is not recent (e.g. older than five minutes, which blocks replays), then compute HMAC-SHA256 over the string `<timestamp>.<raw body>` with this secret and compare it, in constant time, to the hex in the signature header.
WebhookResultobject
FieldTypeNotes
webhookrequiredWebhookEndpoint
WebhookListResultobject
FieldTypeNotes
webhooksrequiredWebhookEndpoint[]
eventsrequiredWebhookEvent[]The full set of subscribable events.
WebhookDeliveryobject
FieldTypeNotes
idrequiredstring
webhookIdrequiredstring
eventrequiredstring
statusrequiredstringpendingsendingdeliveredfaileddead
attemptsrequiredintegerNumber of delivery attempts so far.
lastStatusCoderequiredinteger | nullHTTP status of the last attempt, or null.
lastErrorrequiredstring | nullError from the last attempt, or null.
nextAttemptAtrequiredinteger | nullWhen the next retry is due, epoch milliseconds, or null.
createdAtrequiredinteger
updatedAtrequiredinteger
DeliveryListResultobject
FieldTypeNotes
deliveriesrequiredWebhookDelivery[]
paginationrequiredobject
Errorobject
FieldTypeNotes
errorrequiredobject
VisualPlatformenum

Platforms the visual engine renders for.

instagramlinkedinfacebookxtiktokpinterestyoutube_shorts
VisualAspectRatioenum

Master aspect ratios the renderer produces.

4:59:161:12:3
VisualBusinessContextobject

Who the post is for. `businessName` is the only required field, but everything you add grounds the result: industry and description steer the idea, the brand kit locks palette and fonts, the proof bank feeds specific claims, and real photos always lead over generated imagery. Generated imagery stays name-free and atmospheric; the engine never fakes your storefront, staff, or logo.

FieldTypeNotes
businessNamerequiredstring
industrystringShort label like "specialty coffee roaster".
descriptionstringA sentence or two on what the business actually does.
brandKitobjectPalette and fonts the renderer must honor.
proofBankobject[]Concrete, reusable claims (numbers, facts, quotes) the copy may draw on. The engine will not invent proof it does not have.
realPhotoUrlsstring[]Real brand photos by URL. Real photos lead the design.
realPhotoDataUrisstring[]Real brand photos inlined as data URIs.
logoDataUristringBrand logo as a data URI.
VisualSeedobject
FieldTypeNotes
platformrequiredVisualPlatform
ideastringThe idea to post about, in plain words.
goalstringWhat the post should be optimized to earn.awarenesssavelead
formatstringPin a static post TYPE. `proof-shot` is the testimonial post: the customer's real message or review, cropped out of the owner's screenshot with the pixels untouched, on the brand's field under five stars, plus a caption. Send 1 to 3 screenshots in `businessContext.realPhotoDataUris` with `pinnedPhotos: true`; the engine declines (422, stage `proof`) when they hold no happy customer message.proof-shot
GenerateVisualRequestobject
FieldTypeNotes
brandIdstringGround the request from a saved brand instead of (or under) an inline businessContext: the brand's name, description, audience and themes, and its kit (palette, fonts, logo, photos) become the businessContext, and any businessContext fields sent alongside win over the brand. One of `brandId` or `businessContext.businessName` is required.
businessContextVisualBusinessContext
seedrequiredVisualSeed
aspectRatiosVisualAspectRatio[]Which masters to render. Defaults to the engine's set.
layoutstringPin a text placement variant instead of letting the engine rotate.bottom-rulebottom-paneltop-rulecentercenter-panel
preferFormatstringNudge the format choice; the engine may still refuse a format the content cannot carry.stat-cardquote-cardtip-cardannouncementphoto-hook
pinnedPhotosbooleanTreat the context's real photos as user-chosen for this post; the engine will never swap them for a generated scene.
ReviseVisualRequestobject
FieldTypeNotes
brandIdstringGround the request from a saved brand instead of (or under) an inline businessContext: the brand's name, description, audience and themes, and its kit (palette, fonts, logo, photos) become the businessContext, and any businessContext fields sent alongside win over the brand. One of `brandId` or `businessContext.businessName` is required.
businessContextVisualBusinessContext
feedbackrequiredstringFreeform comment on the post ("shorter headline").
specrequiredobjectThe `spec` returned by a prior generate or revise.
aspectRatiosVisualAspectRatio[]
RenderedMasterobject
FieldTypeNotes
aspectRatiorequiredVisualAspectRatio
imageUrlrequiredstringThe rendered master, hosted on our object storage.
mimeTyperequiredstring
widthrequiredinteger
heightrequiredinteger
VisualScoreobject

The judge's verdict on the finished post. `score` is 0-100 and harsh by design; `verdict` is the judge's call after the revise loop settled; `singleFix` names the highest-leverage remaining change.

FieldTypeNotes
verdictstring
scorenumber
singleFixstring
GeneratedVisualPostobject
FieldTypeNotes
idrequiredstring
engineIdstring
formatIdstringThe format the engine chose (e.g. photo-hook).
mastersrequiredRenderedMaster[]
captionrequiredstringPlatform-native caption; the long tail lives here.
onImageTextstringThe text rendered onto the image.
hashtagsrequiredstring[]
altTextstring
scorerequiredVisualScore
specrequiredobjectThe post's revisable recipe. Treat it as opaque and hand it back to /v1/revise-visual-v2 with feedback; it records every decision (layout archetype, typography, colors, imagery) and the revision history.
compositionobjectThe editable layered document seeded from the primary master's render spec, for callers building an editor on top.
CarouselSeedobject
FieldTypeNotes
platformrequiredVisualPlatform
ideastring
goalstringawarenesssavelead
slideCountintegerDesired deck length; the planner may adjust.
arcstringPin the deck's arc (a post type). `qa` is the "Ask us anything" deck: one real pre-purchase question and the owner's answer per slide, rendered in the native question-sticker treatment over a dimmed business photo. `product-reveal` is the four-slide reveal: one real product photo (the context's first real photo) under a torn-paper window that opens wider on every swipe, with the peek, the tease, the reveal and the invitation as its copy; it needs a real photo in the business context and is declined (422, stage `photo`) without one. Optional; the planner chooses otherwise.paslisticlehow-tobefore-aftermyth-buststoryqaproduct-reveal
GenerateCarouselRequestobject
FieldTypeNotes
brandIdstringGround the request from a saved brand instead of (or under) an inline businessContext: the brand's name, description, audience and themes, and its kit (palette, fonts, logo, photos) become the businessContext, and any businessContext fields sent alongside win over the brand. One of `brandId` or `businessContext.businessName` is required.
businessContextVisualBusinessContext
seedrequiredCarouselSeed
aspectRatioVisualAspectRatio
generatedImagerybooleanWhether name-free generated atmosphere may back slides that have no real photo. Real photos always lead regardless.
pinnedPhotosbooleanRank the context's real photos for order but never drop them from the deck.
ReviseCarouselRequestobject
FieldTypeNotes
brandIdstringGround the request from a saved brand instead of (or under) an inline businessContext: the brand's name, description, audience and themes, and its kit (palette, fonts, logo, photos) become the businessContext, and any businessContext fields sent alongside win over the brand. One of `brandId` or `businessContext.businessName` is required.
businessContextVisualBusinessContext
feedbackrequiredstring
specrequiredobjectThe `spec` returned by a prior carousel generate.
CarouselSlideobject
FieldTypeNotes
indexrequiredinteger0-based swipe order.
rolerequiredstringThe slide's job in the arc (hook, rehook, body, cta).
formatIdstring
onImageTextrequiredstring
masterrequiredRenderedMaster
GeneratedCarouselobject
FieldTypeNotes
idrequiredstring
engineIdstring
platformrequiredVisualPlatform
arcstringThe deck's narrative arc (e.g. how-to, myths).
aspectRatioVisualAspectRatio
slidesrequiredCarouselSlide[]
captionrequiredstring
hashtagsrequiredstring[]
ctastring
scorerequiredVisualScore
specrequiredobjectThe deck's revisable recipe for /v1/revise-carousel-v2. Opaque; hand it back with feedback.
compositionobjectEditable per-slide layered documents, for editors.

Need something we don't have?

SocialKit is a thin layer. If you want a new endpoint, a new surface, or a model swap, tell us. We ship weekly.

hello@socialkit.sh