# Fanful agent SDK starter

This is a provider-neutral starter for external agents that need to discover
Fanful surfaces, read public manifests, use scoped credentials safely, and
prepare confirmation-gated writes. It is intentionally an example, not an npm
package yet. If packaging becomes worthwhile, publish it through a separate
issue after these examples stay stable.

## Current recommendation

1. Start with https://fanful.net/llms.txt and https://fanful.net/agents.
2. Prefer the public MCP endpoint at https://fanful.net/mcp when your client can
   speak MCP.
3. Use HTTP manifests directly when MCP is unavailable or a plain fetch client is
   easier to audit.
4. Keep bearer credentials in transport headers or MCP client configuration.
   Never paste admin tokens, scoped grants, Stripe ids, checkout URLs, or bearer
   tokens into model-visible prompts.
5. Before any write, read https://fanful.net/api/agent/action-contracts and
   validate an agent-write-envelope.v1 payload at
   https://fanful.net/api/agent/action-contracts/envelope.

## TypeScript starter

The repo example lives at `examples/fanful-agent-starter/client.ts`. It has no
Fanful-specific dependency and can be copied into a Codex, Claude, ChatGPT app,
or custom MCP client project.

```ts
import {
  FanfulAgentHttpError,
  createFanfulAgentClientFromEnv,
} from "./examples/fanful-agent-starter/client";

const fanful = createFanfulAgentClientFromEnv(process.env);

const listenerManifest = await fanful.manifest("listener-experience");
const checkoutContracts = await fanful.actionContracts({
  audience: "listener",
  domain: "checkout",
});

try {
  const analytics = await fanful.creatorAnalytics({ window: "30d" });
  console.log(analytics);
} catch (error) {
  if (error instanceof FanfulAgentHttpError && error.isAuthBoundary) {
    console.log("Ask the human for a scoped creator grant; never paste tokens into prompts.");
  } else {
    throw error;
  }
}

await fanful.validateEnvelope({
  contractId: "listener.checkout.start",
  actor: { role: "listener", displayName: "Signed-in listener" },
  client: { id: "starter-example", name: "Fanful starter example", kind: "other" },
  action: {
    requested: "listener.checkout.start",
    mode: "validate",
  },
  target: {
    type: "checkout-intent",
    id: "support-donation",
    summary: "Support donation checkout for $25.00",
  },
  dryRun: true,
  confirmation: {
    text: "I confirm listener.checkout.start for Support donation checkout for $25.00.",
    acknowledgedRisk: true,
  },
  idempotencyKey: "starter-example-support-checkout-2026-05-16",
  auditCorrelationId: "starter-example-audit-2026-05-16",
  reason: "Validate the shared confirmation envelope before calling an executable write tool.",
});
```

## No-SDK HTTP fallback

Plain HTTP clients should use the same surfaces. The token shown here is an
environment variable read by the shell, not text to paste into an agent prompt.

```bash
curl -fsS https://fanful.net/llms.txt
curl -fsS https://fanful.net/api/agent/listener-experience
curl -fsS 'https://fanful.net/api/agent/action-contracts?audience=listener&domain=checkout'
curl -fsS -H "Authorization: Bearer $FANFUL_AGENT_BEARER_TOKEN" \
  'https://fanful.net/api/agent/artist-analytics?window=30d'
curl -fsS -H "Authorization: Bearer $FANFUL_AGENT_BEARER_TOKEN" \
  'https://fanful.net/api/agent/media-upload-sessions'
curl -fsS -X POST https://fanful.net/api/agent/action-contracts/envelope \
  -H 'content-type: application/json' \
  -H "Authorization: Bearer $FANFUL_AGENT_BEARER_TOKEN" \
  --data @agent-write-envelope.json
```

Save this as `agent-write-envelope.json` for the final command above:

```json
{
  "contractId": "listener.checkout.start",
  "actor": { "role": "listener", "displayName": "Signed-in listener" },
  "client": { "id": "starter-example", "kind": "other", "name": "Fanful starter example" },
  "action": { "requested": "listener.checkout.start", "mode": "validate" },
  "target": {
    "type": "checkout-intent",
    "id": "support-donation",
    "summary": "Support donation checkout for $25.00"
  },
  "dryRun": true,
  "confirmation": {
    "text": "I confirm listener.checkout.start for Support donation checkout for $25.00.",
    "acknowledgedRisk": true
  },
  "idempotencyKey": "starter-example-support-checkout-2026-05-16",
  "auditCorrelationId": "starter-example-audit-2026-05-16",
  "reason": "Validate the shared confirmation envelope before calling an executable write tool."
}
```

## Required error handling

- 401 means the caller is not authenticated for the private surface.
- 403 means the caller is authenticated but lacks the required scope or role.
- Stale-state errors mean the agent must re-read the relevant manifest before
  retrying.
- Idempotency errors mean the client should inspect the existing operation or
  use a fresh idempotency key for a truly new intent.
- Retryable workflow-trigger events should be deduped by event id, delivery id,
  and idempotency key.
- Manual workflow-trigger test deliveries can prove endpoint signing and audit
  handling. Automatic runtime fan-out currently runs only for
  member.idea.submitted.
- Redaction is part of the contract. Do not work around missing raw ids by
  scraping admin UI or asking the user to reveal private identifiers.

## Cross-links for agent clients

- MCP endpoint: https://fanful.net/mcp
- CLI docs: https://fanful.net/docs.md and docs/agent/fanful-cli.md in the repo
- ChatGPT app readiness: https://fanful.net/api/agent/chatgpt-app
- Action contracts: https://fanful.net/api/agent/action-contracts
- Write-envelope validator: https://fanful.net/api/agent/action-contracts/envelope
- Media upload sessions: https://fanful.net/api/agent/media-upload-sessions
- Workflow triggers: https://fanful.net/api/agent/workflow-triggers
- Agent sessions: https://fanful.net/api/agent/sessions
- Sync jobs: https://fanful.net/api/agent/sync-jobs
- Work-log sync feed: https://fanful.net/api/agent/sync-feeds/work-log

## Not included

- No provider-specific SDK binding.
- No ChatGPT OAuth/session mapping.
- No hosted worker runtime.
- No packaging or npm publishing.
- No shortcut around confirmation, audit, redaction, or scoped grants.
