API Reference

The CrowdProof API lets you create and manage simulations programmatically. Integrate social simulation into your workflow, CI/CD pipeline, or custom applications.

API key access requires an Enterprise plan. All plans can use session-cookie authentication from the browser. Enterprise customers can create API keys from Dashboard > API Keys. See Pricing for plan details.

Authentication

API requests accept two authentication methods:

  • Session cookie (all plans) - set automatically when you log in via the browser.
  • Bearer token (Enterprise plan) - pass an API key in the Authorization header. Keys are created and managed at /dashboard/keys.
Authorization: Bearer cp_live_...

Keys carry a scope: full (read and write) or read (read-only). A read-only key receives a 403 on anything that creates or mutates data. It works on every one of these routes, including both exports, so a pipeline that only pulls results never needs a full-scope key:

  • GET /api/sim/list
  • GET /api/sim/{sim_id}
  • GET /api/sim/{sim_id}/report
  • GET /api/sim/{sim_id}/report/export
  • GET /api/sim/{sim_id}/feed.csv
  • GET /api/sim/{sim_id}/sentiment
  • GET /api/sim/{sim_id}/factions
  • GET /api/sim/{sim_id}/validation
  • GET /api/sim/{sim_id}/survey/results
  • GET /api/sim/{sim_id}/survey/responses.csv

Create Simulation

POST/api/sim/create

Create and start a new simulation. Returns immediately; the simulation runs in the background.

Request Body

{
  "seed_text": "We are raising prices by 15% starting next month.",
  "audience_preset": "general_public",
  "agent_count": 100,
  "rounds": 10,
  "platforms": ["pulse", "forum", "circle", "network", "board"],
  "industry": "saas",
  "webhook_url": "https://example.com/hooks/crowdproof"
}
FieldTypeRequiredDescription
seed_textstringYesThe stimulus for agents to react to (1-10,000 chars). Can contain URLs. The run reads up to the first three links. A page it cannot fetch, and any extra link, stays as URL text. The report says which pages were read.
audience_presetstringNogeneral_public (default), tech_twitter, or small_business_owners
audience_customstringNoFree-text audience description (max 1,000 chars). When set, the engine blends archetypes to match.
audience_interviewsstringNoRaw interview notes, survey verbatims, or quotes from real people (max 8,000 chars). Used only alongside audience_custom: the blend grounds archetype selection in what those people actually said.
interview_corpus_idstringNoId of a saved interview corpus to ground the audience in. Resolved server-side into audience_interviews; pasted notes win when both are sent. Corpora are created and managed in the dashboard, not through this API.
agent_countintegerNo10-5,000, clamped to your plan limit. Omit the field to use 200, or 100 on Free.
roundsintegerNo5-100, clamped to your plan limit. Omit the field to use 20, or 10 on Free.
platformsstring[]NoSubset of pulse, forum, circle, network, board. Your plan sets how many a run may enable (Free 1, Pro 3, Team and Enterprise 5). Asking for more than that returns 403 and starts nothing, so a run never quietly moves to rooms you did not pick. Omit the field to take whatever your plan allows.
industrystringNoOne of: saas, consumer, ecommerce, fintech, healthcare, media, education, gaming, nonprofit, other
webhook_urlstringNoPublic https URL POSTed when the run completes or fails.
notify_on_completebooleanNoEmail the owner when the crowd finishes, with a report link, or when the run fails, with no report. Default false.

Grounding an audience in real people. Send audience_custom to describe the audience in your own words, and add audience_interviews (or interview_corpus_id) to ground that blend in real interview notes. The engine weights archetypes, and can invent new ones, from what your respondents actually said instead of from the description alone. Without audience_custom the interview fields are ignored and the run uses audience_preset.

Response

{
  "id": "a1b2c3d4-...",
  "title": "Simulation 2026-07-13 21:05",
  "status": "seeding",
  "share_token": "abc123...",
  "stream_url": "/api/sim/a1b2c3d4-.../stream"
}

share_token is the public share-link token; build the read-only spectator/report URL as https://crowdproof.xyz/sim/{share_token}/share without a follow-up request.

Example

curl -X POST https://api.crowdproof.xyz/api/sim/create \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "seed_text": "We are raising prices by 15% starting next month.",
    "audience_preset": "general_public",
    "agent_count": 100,
    "rounds": 10
  }'

Create A/B Experiment

POST/api/sim/ab

Launch two variant simulations from one request (Team and Enterprise plans). Both variants share an identical agent pool and follow network; only the seed text differs.

Request Body

{
  "seed_a": "We are raising prices by 15% starting next month.",
  "seed_b": "We are introducing a new premium tier with additional features.",
  "audience_preset": "general_public",
  "agent_count": 100,
  "rounds": 10
}

Consumes two monthly sim credits. Returns both simulation IDs.

This endpoint accepts the same optional fields as Create Simulation, including audience_custom, audience_interviews, interview_corpus_id, notify_on_complete, and webhook_url. The audience is blended once and shared by both variants, so only the seed text differs between them. Each variant emails a report link when that crowd finishes, or that there is no report when it fails. A webhook is sent per variant for every terminal outcome. The webhook payload also carries the shared experiment_id and the variant's variant_label, so a pipeline can pair the two callbacks without a further request.


List Simulations

GET/api/sim/list

List all simulations for the authenticated user.

Response

[
  {
    "id": "a1b2c3d4-...",
    "title": "Price increase test",
    "status": "completed",
    "agent_count": 100,
    "rounds": 10,
    "current_round": 10,
    "audience_preset": "general_public",
    "topic_summary": "The crowd debated whether a 15% price hike is justified.",
    "final_sentiment": -0.18,
    "alignment_pct": 82,
    "cost_usd": 0.45,
    "created_at": "2026-07-01T10:30:00Z",
    "completed_at": "2026-07-01T10:33:45Z"
  }
]

Get Simulation

GET/api/sim/{'{sim_id}'}

Get the current status and details of a simulation.

Response

{
  "id": "a1b2c3d4-...",
  "title": "Price increase test",
  "status": "completed",
  "seed_content": "We are raising prices by 15% starting next month.",
  "audience_preset": "general_public",
  "agent_count": 100,
  "rounds": 10,
  "current_round": 10,
  "platforms": ["pulse", "forum", "circle", "network", "board"],
  "industry": "saas",
  "topic_summary": "The crowd debated whether a 15% price hike is justified.",
  "cost_usd": 0.45,
  "share_token": "abc123...",
  "created_at": "2026-07-01T10:30:00Z",
  "completed_at": "2026-07-01T10:33:45Z"
}

Example

curl https://api.crowdproof.xyz/api/sim/a1b2c3d4-... \
  -H "Authorization: Bearer cp_live_..."

Rename or Archive

PATCH/api/sim/{'{sim_id}'}

Rename a simulation or toggle the archive flag.

Request Body

{
  "title": "Updated title",
  "archived": false
}

Send title, archived, or both.


Delete Simulation

DELETE/api/sim/{'{sim_id}'}

Permanently delete a finished simulation and its data (survey responses, injections, state). Returns 409 while the simulation is still active; stop it first.


Get Report

GET/api/sim/{'{sim_id}'}/report

Get the full synthesis report for a completed simulation.

Response

{
  "summary": "Price increase met with significant pushback...",
  "stats": {
    "total_agents": 100,
    "total_posts": 620,
    "total_actions": 2408,
    "virality_index": 3.2
  },
  "outcome": {
    "stance": { "positive_pct": 32, "neutral_pct": 23, "negative_pct": 45 },
    "intent": { "likely_pct": 28, "maybe_pct": 35, "unlikely_pct": 37 }
  },
  "sentiment": {
    "summary": { "start": 0.0, "end": -0.18, "min": -0.31, "max": 0.08, "total_change": -0.18, "volatility": 0.06 },
    "history": [{ "round": 1, "overall": 0.02, "by_faction": {} }],
    "shifts": [{ "round": 6, "direction": "negative", "change": -0.21 }]
  },
  "factions": {
    "final": [{ "id": "faction_0", "name": "Loyal but Wary", "agent_count": 44, "avg_sentiment": -0.31, "trend": "stable" }],
    "history": [{ "round": 5, "factions": [...] }]
  },
  "viral_content": {
    "top_posts": [{ "id": "post_1", "agent_name": "Sarah", "content": "15% increase with 0% improvement?", "engagement": 47, "faction_id": "faction_0", "stance": -0.6, "platform": "pulse", "round": 3 }],
    "viral_moments": [{ "round": 4, "content": "...", "engagement": 47, "std_devs": 2.8 }]
  },
  "key_arguments": [{ "faction": "Loyal but Wary", "argument": "No warning given.", "support_count": 44, "sentiment": -0.31, "agent_name": "Sarah" }],
  "platforms": [{ "platform": "pulse", "posts": 310, "engagement": 1415, "avg_stance": -0.22 }],
  "hashtags": [{ "tag": "PriceHike", "uses": 34, "engagement": 890, "avg_stance": -0.4, "first_round": 2 }],
  "influencers": [{ "agent_name": "Sarah", "agent_handle": "sarah_m", "followers": 47, "posts": 12, "engagement": 234, "stance": -0.45 }],
  "insights": ["..."],
  "recommendations": ["..."]
}

Example

curl https://api.crowdproof.xyz/api/sim/a1b2c3d4-.../report \
  -H "Authorization: Bearer cp_live_..."

Export Report as Markdown

GET/api/sim/{'{sim_id}'}/report/export

When the crowd finishes, download the report as Markdown. A stopped or failed run has no report. Includes all report sections plus real-world validation data when available.

Example

curl -O -J https://api.crowdproof.xyz/api/sim/a1b2c3d4-.../report/export \
  -H "Authorization: Bearer cp_live_..."

Export Feed as CSV

GET/api/sim/{'{sim_id}'}/feed.csv

Export the raw feed as CSV when the crowd has posted. A paused, stopped, or failed run with posts can too. Columns: round, timestamp, platform, agent name/handle/archetype, faction, content, engagement counts.

Example

curl -O -J https://api.crowdproof.xyz/api/sim/a1b2c3d4-.../feed.csv \
  -H "Authorization: Bearer cp_live_..."

Get Sentiment

GET/api/sim/{'{sim_id}'}/sentiment

Get the sentiment history for a simulation (round-by-round overall and per-faction values).


Get Factions

GET/api/sim/{'{sim_id}'}/factions

Get the current or final faction breakdown.


Inject Event

POST/api/sim/{'{sim_id}'}/inject

Inject a new event into a running simulation. Requires a session cookie (not API keys).

Request Body

{
  "content": "BREAKING: Company releases statement clarifying the price change.",
  "injection_type": "news",
  "platform": "pulse",
  "at_round": 8
}
FieldTypeDescription
contentstringThe event content to inject.
injection_typestringpost (anonymous user, default), news (news outlet), or influencer (verified account). Controls the synthetic author identity.
platformstringOptional. One of the sim's enabled platforms. Defaults to the first enabled one.
at_roundintegerOptional. Schedule the injection at a future round instead of applying it immediately. Must be after the current round and no later than the run's last round, since the injection is applied at the start of the round it names.

Response (immediate)

{
  "post_id": "inject_abc12345",
  "round": 5,
  "platform": "pulse"
}

Response (scheduled)

{
  "scheduled": true,
  "round": 8,
  "injection_id": "..."
}

Scheduled injections can be listed or cancelled:

GET/api/sim/{'{sim_id}'}/injections

List the simulation's injections (scheduled and applied).

DELETE/api/sim/{'{sim_id}'}/injections/{'{injection_id}'}

Cancel a scheduled injection (409 once applied).


Pause and Resume

POST/api/sim/{'{sim_id}'}/pause

Pause a running simulation. Requires a session cookie.

POST/api/sim/{'{sim_id}'}/resume

Resume a paused simulation. Requires a session cookie.

Pausing is not how you end a run. A paused simulation still counts as active: it cannot be deleted, it never produces a report, and it keeps holding the plan credit it consumed. Use Stop below.


Stop Simulation

POST/api/sim/{'{sim_id}'}/cancel

Stop a run that has not finished, for good. Accepts a session cookie or a full-scope API key.

Any status before the run finishes is accepted, including the start-up ones, so a run can be stopped from the moment it is created. During start-up, setup work already in flight finishes and the engine stops before round one. During a round, it stops at the next status check. A run that has already finished returns 409.

Response

{
  "status": "cancelled"
}

Example

curl -X POST https://api.crowdproof.xyz/api/sim/a1b2c3d4-.../cancel \
  -H "Authorization: Bearer cp_live_..."

The stopped run is terminal. It fires the completion webhook with "event": "simulation.cancelled", it can then be deleted, and the sim credit it consumed is not returned: the refund path covers a run the system broke, not one you ended. A stopped run has no report.


Stream Simulation (SSE)

GET/api/sim/{'{sim_id}'}/stream

Stream simulation events in real time via Server-Sent Events. Requires a session cookie.

Event Types

EventDescription
agent_actionAn agent posted, replied, liked, reposted, etc.
agent_quoteAn agent quote-posted another post.
agent_threadAn agent posted a multi-part thread.
agent_reactA Circle reaction (love, haha, wow, angry, sad).
agent_downvoteAn agent downvoted a post (Forum).
agent_awardAn agent awarded a post (Forum).
agent_hashtagAn agent posted with a hashtag.
sentiment_updateOverall and per-faction sentiment for the round.
faction_updateFaction membership changed.
network_changeAn agent followed or unfollowed another agent.
agent_endorseAn agent endorsed another agent.
viral_alertA post crossed the virality threshold.
sim_seedingA start-up milestone before round 1 (step, detail): reading the stimulus, building the crowd, summarising the topic, mapping the knowledge graph. The current milestone is replayed when a stream connects, so a client that joins mid-setup reads where the run is instead of waiting for the next step. It is the only event that is replayed, and a reconnect during setup can therefore deliver the same milestone twice.
round_startA new round began (active agent count).
round_completeA round finished (duration, actions taken, faction shifts).
synthesis_readyThe report is stored and ready to read.
sim_paused / sim_resumedThe owner paused the run, and started it again. Sent once each, not once per second, so a viewer can tell a paused run from a stalled one.
sim_completeThe simulation has finished (verdict, totals).
sim_failedThe run failed or was cancelled (terminal signal).

Completion Webhook

Pass a webhook_url when creating a simulation. CrowdProof will POST a JSON payload the moment the run finishes, so pipelines can react without polling. The URL must be a publicly reachable https endpoint; private and localhost hosts are rejected at creation time.

Payload

{
  "event": "simulation.completed",
  "simulation_id": "a1b2c3d4-...",
  "title": "Price increase test",
  "status": "completed",
  "experiment_id": null,
  "variant_label": null,
  "verdict": "Strong positive shift in public opinion",
  "report_url": "https://crowdproof.xyz/sim/a1b2c3d4-.../report",
  "api_report_url": "https://api.crowdproof.xyz/api/sim/a1b2c3d4-.../report"
}

The verdict is the run's one-line outcome, so a pipeline can branch on the result (positive / negative / polarizing / stable) without a second call to fetch the report. The report_url opens the owner's browser report, while api_report_url is the report endpoint a pipeline can fetch with the same bearer key used to create the simulation. Failed runs deliver"event": "simulation.failed","status": "failed", "verdict": null, and both report URLs as null (a failed run never produces a report). Failed runs also refund the consumed sim credit automatically.

A run the owner stops from the cockpit is terminal too, and delivers "event": "simulation.cancelled", "status": "cancelled", "verdict": null, and both report URLs as null. A stopped run writes no report and its sim credit is not returned, so treat any status other than completed as the end of the run rather than waiting for a later callback.

A run launched by an A/B experiment names its pair: experiment_id is the same value on both variants and variant_label is "A" or "B". Both variants carry the one webhook_url you registered, so these two fields are how a callback says which scenario it reports and which other callback it belongs with. A single run sends both as null.

Delivery and retries

Your endpoint should answer with any 2xx or 3xx status. If it times out, refuses the connection, or answers 5xx, 408, or 429, CrowdProof retries twice more (after 1 second, then 3 seconds). Any other 4xx is treated as a final answer and the delivery stops there.

Because a delivery is retried when the response is lost, the same callback can arrive more than once. Treat simulation_id plus event as the deduplication key, and answer quickly: do the real work after you respond.


Real-World Validation

POST/api/sim/{'{sim_id}'}/validation

Compare manually entered real survey shares against the simulated stance distribution.

{
  "positive_pct": 35,
  "neutral_pct": 25,
  "negative_pct": 40,
  "source": "Post-launch survey, n=200",
  "respondents": 200
}
GET/api/sim/{'{sim_id}'}/validation

Retrieve the stored validation result (alignment score, sim-vs-real shares, question-level and demographic breakdowns).

POST/api/sim/{'{sim_id}'}/survey

Create a public survey link for collecting real responses. Returns the survey URL.

POST/api/sim/{'{sim_id}'}/validation/from-survey

Score the simulation against collected survey responses (minimum 5). Stores per-question, per-age-band, and per-wave alignment breakdowns.


API Key Management

Enterprise plan only. Requires session-cookie authentication.

POST/api/keys/create

Create a new API key. The plaintext secret is shown once in the response.

{
  "name": "CI Pipeline",
  "scopes": "full"
}

scopes is "full" (default) or "read".

GET/api/keys/list

List your API keys (metadata only, never the plaintext).

POST/api/keys/{'{key_id}'}/rotate

Rotate a key: new secret, same key record. The old secret stops working immediately.

DELETE/api/keys/{'{key_id}'}

Revoke an API key permanently.


Rate Limits

API requests are subject to plan-based limits:

PlanMonthly simulationsAgents per simRounds per simPlatforms per sim
Free2100101
Pro15500203
Team502,000305
EnterpriseUnlimited5,000505

A request over any of these limits returns 403 with the limit named, and no simulation credit is consumed.

Error Codes

CodeMeaning
400Bad request (invalid parameters, invalid webhook URL, invalid platform)
401Invalid or missing authentication
403Plan does not support this feature, or read-only key on a write route
404Simulation not found
409Conflict (e.g. deleting an active simulation, cancelling an applied injection)
422Validation error in request body
500Server error

Next Steps

Pay once for one full simulation: 500 people, 20 rounds, 3 platforms. Getting Started, Simulations, Audiences, and Accuracy stay above.