Executable today
MCP tools, HTTP manifests, confirmed-write envelopes, workflow-trigger subscriptions, sync feeds, and the repo CLI.
Fanful's machine contract is available today through HTTP agent manifests, MCP, action contracts, workflow triggers, sync feeds, and the repo CLI. Official language SDK packages are planned but not published yet; this page documents the release checklist instead of pretending they exist.
MCP tools, HTTP manifests, confirmed-write envelopes, workflow-trigger subscriptions, sync feeds, and the repo CLI.
The TypeScript starter exists; official Python/TypeScript packages need versioning, schemas, release automation, and support policy.
Use headers, environment variables, scoped grants, and webhook secret references. Do not paste bearer tokens or signing secrets into model-visible text.
Plain English
They are a public, machine-readable description of which agent actions exist, who may call them, what auth/scopes are required, which writes need exact confirmation, and what idempotency, audit, stale-state, and redaction rules apply.
They are safe to publish only while they stay metadata-only: no bearer tokens, signing secrets, raw listener rows, provider secret ids, signed media URLs, or private dashboards. Private reads and writes still require scoped session, bearer, or admin authorization at the actual tool/API boundary.
Resources
Use before any mutation to discover safe confirmation and redaction rules.
Read visible channels/messages and post confirmed listener messages through MCP.
Creator lesson, shop, membership, Stripe Price, and community-channel contracts.
Schedule, update, preview, and status-change live events with confirmed wrappers.
Listener entitlements, profile/preferences, confirmed display-name writes, support checkout, purchases, and Listen Along boundaries.
Webhook-style trigger families, samples, signatures, subscriptions, and delivery status.
Authenticated registry for active external webhook endpoints and manual signed test deliveries.
Redacted run/session contract and sample event replay for future durable agent runs.
Durable redacted checkpoints for catalog/import sync work.
Public cursor feed of Fanful work-log updates for external agents.
Examples
Start here before any write. It tells agents which operations are executable, preview-only, or blocked.
curl -fsS https://fanful.net/api/agent/action-contracts \
-H 'accept: application/json'Confirmed writes use an envelope with actor attribution, client attribution, exact confirmation, idempotency, reason, and audit correlation.
curl -fsS https://fanful.net/api/agent/action-contracts/envelope \
-H 'content-type: application/json' \
-d '{
"action": {
"requested": "fanful_creator_live_schedule_create",
"risk": "audience-visible"
},
"actor": { "role": "artist-admin" },
"client": { "kind": "agent", "name": "Example integration" },
"target": {
"type": "live_event",
"summary": "Laurel live at 10:30pm Pacific"
},
"confirmation": {
"text": "I confirm creator.live.schedule-and-status for Laurel live at 10:30pm Pacific."
},
"idempotencyKey": "example-live-2026-05-20-1030pm",
"auditCorrelationId": "example-audit-live-1030pm",
"reason": "Artist asked the agent to schedule and notify followers."
}'Workflow-trigger subscription writes require admin/scoped authorization. Keep the bearer token and signing secret out of prompts.
curl -fsS https://fanful.net/api/agent/workflow-triggers/subscriptions \
-H 'authorization: Bearer $FANFUL_AGENT_BEARER_TOKEN' \
-H 'content-type: application/json' \
-d '{
"action": "create_subscription",
"endpointUrl": "https://example.com/fanful/webhooks",
"triggerFamilies": ["member.idea.submitted"],
"signingSecretReference": "secret:fanful-webhook-production",
"description": "Route new member ideas into our moderation queue."
}'Use the raw request body, timestamp header, and shared signing secret. Do not ask an AI agent to handle the raw secret in a prompt.
import hashlib
import hmac
import time
def verify_fanful_signature(raw_body: bytes, headers: dict[str, str], secret: str) -> bool:
timestamp = headers.get("x-fanful-timestamp", "")
signature = headers.get("x-fanful-signature", "")
if not timestamp or not signature:
return False
if abs(time.time() - int(timestamp)) > 300:
return False
signed = timestamp.encode("utf-8") + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)When Fanful emits a signed subscriber/member event, verify the webhook, read the linked manifest for current state, then update your own platform. Replace the Mailchimp URL with the provider you use.
export async function handleFanfulWebhook(request: Request) {
const rawBody = await request.text();
const ok = await verifyFanfulWebhook(rawBody, request.headers, process.env.FANFUL_WEBHOOK_SECRET!);
if (!ok) return new Response("invalid signature", { status: 401 });
const event = JSON.parse(rawBody);
if (event.type !== "member.idea.submitted" && event.type !== "listener.support-checkout.completed") {
return Response.json({ ignored: true });
}
// Read the current Fanful manifest before taking action; webhook payloads are redacted notifications.
await fetch(event.links.currentManifest, { headers: { accept: "application/json" } });
await fetch("https://usX.api.mailchimp.com/3.0/lists/LIST_ID/members", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.MAILCHIMP_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
email_address: event.actor?.emailHash ? undefined : event.actor?.email,
status_if_new: "subscribed",
tags: ["fanful"],
}),
});
return Response.json({ ok: true, fanfulEventId: event.id });
}