{
  "markdown": "# HumanSurvey\n\n**Website:** [humansurvey.co](https://www.humansurvey.co) · **Docs:** [humansurvey.co/docs](https://www.humansurvey.co/docs) · **FAQ:** [humansurvey.co/faq](https://www.humansurvey.co/faq)\n\n[![human-survey MCP server](https://glama.ai/mcp/servers/sunsiyuan/human-survey/badges/card.svg)](https://glama.ai/mcp/servers/sunsiyuan/human-survey)\n\nAttribution for the channels that have no referrer.\n\nHumanSurvey asks one question — how did you hear about us — inside the host's own signup or\npayment flow, at a granularity that is actually actionable: the platform first, then which\ncreator, podcast, event or store.\n\n```text\nAgent configures a form   → platforms from the catalog, creators supplied by the caller\nHost embeds /s/{id}       → in its signup flow, its payment flow, or both\nRespondent answers        → picks a platform; that pick expands the follow-up in place\nHost pushes conversions   → POST /api/attribution/events, keyed on its own user id\nAgent reads back          → rollup, raw response stream, free text awaiting a mapping\n```\n\n## What is this?\n\nAn API and MCP server for self-reported attribution. TikTok in-app, Instagram, podcasts,\ncommunities, word of mouth, AI assistants: the exposure happens where tracking cannot\nreach, and asking a human is the only always-on signal that survives every referrer leak.\n\nTwo placements answer different questions. In the payment flow, the respondent is already\na paying customer, so the answer joins to revenue with no conversion ingest at all. In the\nsignup flow, it is the only way to see the people a channel sends who never pay. Divide a\nchannel's share of the paying population by its share of the signup population. Above 1 it\nconverts better than your average, below 1 worse. Multiply that ratio by your overall\nsignup-to-paid rate to get the channel's own rate.\n\nIt is designed for:\n- hosts embedding a form in their own onboarding or checkout\n- agents that keep the candidate list current and read the results back\n\nIt is not designed for:\n- general-purpose surveys — arbitrary question types, Markdown authoring and conditional\n  logic were removed in the attribution pivot\n- a human-facing analytics dashboard: the aggregates are an API resource, and the agent is\n  the dashboard\n- reaching your audience for you — HumanSurvey never contacts respondents; the transports\n  it offers (the `/s/{id}` URL and the iframe embed) are ones you control\n\n## Features\n\n- **Progressive disclosure, not pagination** — POST the platform answer, PATCH the\n  follow-up. The first answer is durable before the second is asked, and a respondent who\n  abandons the follow-up is still real data.\n- **Rotation by default** — the orderable candidates are permuted per respondent, seeded\n  by a client-minted `render_id`, so the raw share is unbiased by construction. `fixed`\n  order exists for callers who want it and does not hide its bias.\n- **Retroactive remapping** — free text is stored verbatim and resolved against the remap\n  table on every read, so one mapping fixes months of history with no backfill.\n- **Immutable config snapshots** — a response is joined to the version it was rendered\n  against, so reconfiguring cannot rewrite what history says was shown.\n- **One join key, both directions** — `external_id` brings revenue in and carries\n  per-user attribution back out to your own user table.\n- **Cursor reads** — a response becomes visible once it is complete, is emitted exactly\n  once, and is final when emitted. Nothing downstream has to upsert.\n\n## Product Principles\n\n- **AI-first I/O**: agents configure the form and consume the results; humans are in the middle.\n- **Everything is an API**: creator functionality must be available over authenticated HTTP and MCP.\n- **Narrow scope wins**: one question, asked well. A feature that mainly serves a human survey operator probably does not belong here.\n- **No confident percentages**: every number ships beside the denominator it was computed over, and a number we cannot compute honestly is null rather than smoothed.\n\n## Quick Start\n\n### Get an API key\n\n```bash\ncurl -X POST https://www.humansurvey.co/api/auth/code \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"email\": \"you@example.com\" }'\n\ncurl -X POST https://www.humansurvey.co/api/auth/verify \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"email\": \"you@example.com\", \"code\": \"481920\", \"grant\": \"api_key\" }'\n```\n\nAnonymous key creation is gone. Every key belongs to an account from birth, which is what\ngives a lost key a recovery path and makes rotation free.\n\n### Create a form, then configure it\n\n```bash\ncurl -X POST https://www.humansurvey.co/api/attribution/forms \\\n  -H \"Authorization: Bearer hs_sk_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Checkout — how did you hear about us\",\n    \"allowed_origins\": [\"https://app.example.com\"]\n  }'\n```\n\n```json\n{\n  \"id\": \"abc123efgh45\",\n  \"form_url\": \"https://www.humansurvey.co/s/abc123efgh45\",\n  \"warnings\": [\"this form has no config yet; PUT /api/attribution/forms/abc123efgh45 with {nodes} before embedding it\"]\n}\n```\n\nA form renders nothing until it has a config. `PUT` stores one as an immutable snapshot:\n\n```bash\ncurl -X PUT https://www.humansurvey.co/api/attribution/forms/abc123efgh45 \\\n  -H \"Authorization: Bearer hs_sk_...\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"nodes\": [\n      {\n        \"id\": \"channel\",\n        \"prompt\": \"Where did you first hear about us?\",\n        \"candidates\": [\n          { \"id\": \"tiktok\", \"catalog_slug\": \"tiktok\", \"expands\": \"creator\" },\n          { \"id\": \"reddit\", \"catalog_slug\": \"reddit\" },\n          { \"id\": \"friend\", \"label\": \"A friend or colleague\" },\n          { \"id\": \"dunno\", \"label\": \"I don'\\''t remember\", \"pinned\": \"end\", \"dont_remember\": true }\n        ]\n      },\n      {\n        \"id\": \"creator\",\n        \"prompt\": \"Which account was it?\",\n        \"candidates\": [\n          { \"id\": \"oecuid_8812\", \"label\": \"Jade\", \"handle\": \"@jade.work0\" }\n        ]\n      }\n    ]\n  }'\n```\n\nPlatform labels, marks and aliases come from `GET /api/attribution/catalog` and are copied\ninto the snapshot. Creator candidates are yours: the product renders a candidate set and\nreturns the id that was chosen, and matching a vague description against a creator\ndatabase is upstream work.\n\n### Read the results\n\n```bash\ncurl \"https://www.humansurvey.co/api/attribution/rollup?form_id=abc123efgh45&by=candidate&from=2026-07-01&to=2026-08-01\" \\\n  -H \"Authorization: Bearer hs_sk_...\"\n```\n\nAlso on the read side: `GET /api/attribution/forms/{id}/responses` (cursor stream, or one\nidentity via `?external_id=`), `.../unresolved` for free text awaiting a mapping, and\n`POST .../remaps` to resolve it retroactively. Full request and response shapes are in\n[the OpenAPI document](https://www.humansurvey.co/api/openapi.json).\n\n### Use with Claude Code\n\n```json\n{\n  \"mcpServers\": {\n    \"survey\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"humansurvey-mcp\"],\n      \"env\": {\n        \"HUMANSURVEY_API_KEY\": \"hs_sk_your_key_here\"\n      }\n    }\n  }\n}\n```\n\nThe server name stays `survey` and the package stays `humansurvey-mcp` — both sit inside\nevery existing user's config. Its ten tools now speak the attribution API — see\n[`packages/mcp-server/README.md`](./packages/mcp-server/README.md). npm publishes separately\nfrom this repo, so the version on npm can lag what is here.\n\n## Public Surface\n\n- Docs page: `https://www.humansurvey.co/docs`\n- OpenAPI: `https://www.humansurvey.co/api/openapi.json`\n- AI index: `https://www.humansurvey.co/llms.txt`\n\n## Tech Stack\n\n| Component | Technology |\n|-----------|-----------|\n| Framework | Next.js (App Router) |\n| Database | Neon (serverless Postgres) |\n| Frontend | React + Tailwind CSS |\n| MCP Server | @modelcontextprotocol/sdk |\n| Deployment | Vercel |\n\n## Project Structure\n\n```\n├── apps/web/            # Next.js app (API + respondent page + site)\n│   ├── lib/attribution/ # config, responses, reads, rollup, remap\n│   └── supabase/migrations/  # applied through scripts/migrate.sh, with a ledger\n├── packages/mcp-server/ # MCP server for Claude Code\n└── docs/                # architecture, roadmap, design docs\n```\n\n## Contributing\n\nRead [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a PR. The most important rule is scope discipline: new UI variants, analytics dashboards, and human-operator features are usually out of scope.\n\n## Development\n\n```bash\npnpm install\npnpm dev               # Start Next.js dev server\npnpm test              # node --test over apps/web/lib/**/*.test.ts\npnpm build             # Build all packages\n```\n\n## License\n\nMIT\n",
  "bytes": 8610,
  "sha": "b286036c95b3b3a782da2cc8b46265693ae5e345bedf83cd4d96fe0d5a131b64",
  "repo_slug": "sunsiyuan/human-survey",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sunsiyuan_human_survey_9bc8d3b8/readme"
}