sayintel
Endpoint

Compare models

POST/api/public/v1/model-comparisonswrite

Founder-only model benchmark: compares Claude, xAI Grok, Groq, Google Gemini (through the Lovable gateway / direct Google, GEMINI_COMPARE_MODEL, default google/gemini-3.1-pro-preview) and Perplexity (PERPLEXITY_API_KEY, PERPLEXITY_COMPARE_MODEL, default sonar-pro, may web-search). Writes nothing to campaign data (only spend records). mode 'fixtures' (default) runs eval-fixtures/dataset-v1.json through the real QA judge rubric and reports accuracy / precision / recall on hard fails, mean scores, agreement with the first provider, latency and cost. mode 'judge' (campaign_id) runs the real qaReviewMessages on up to `limit` qualified speakers' stored, scored trios with each provider as the judge and reports per-slot scores, trio pass rate, agreement with the stored verdicts and with claude, rewrite rate, plus example disagreements. mode 'draft' (campaign_id) has each provider write M1 from the identical real skill_v1 prompt, concurrently per speaker, then scores every candidate with the hard-fail gate and (unless judge=false) the Claude judge; returns side-by-side samples with each provider's M1, gate codes, word count, latency and cost next to the stored M1, never persists a candidate. A provider that errors (e.g. a missing key, named in the error) is counted under its errors and the others still return. mode 'human' (campaign_id optional) scores each provider as the QA judge against your own decisions: drafts you rejected with a rewrite request or substantively hand-edited are BAD, your hand-edited versions and messages sent exactly as drafted are GOOD; reports good_pass_rate, bad_catch_rate, balanced_accuracy, auc, pair_win_rate (your edit vs the draft), mean scores, p50/p95 latency and cost per message. mode 'founder' scores each provider as the founder judge: it predicts the founder's own send / don't-send call (and his reason) on his calibration marks in eval-fixtures/founder-labels.json, held-out marks when there are any, otherwise leave-one-out; reports agreement, send_agree, dont_agree, rule_match, cost per message and passes_gate (at least 18 of 20 held-out marks), plus every disagreement in full. mode 'founder_draft' (campaign_id) is the blind writing test: per speaker it puts the stored draft ('current'), the recipe engine ('recipe', with this campaign's m1_recipe or an inline recipe for this campaign) and each writer model (writers, several models per provider allowed) with one identical short founder-style prompt side by side, plus any extra_candidates you pass, and judges every message with the founder judge (judge_provider / judge_model); returns each speaker's inputs, every message with word count, checks and the judge's call, and per-writer totals; never persists. Pass writers=[], include_baselines=false and judge=false to get the speakers' inputs only, with no model calls. mode 'signals' (campaign_id) asks Perplexity, Gemini (Google grounding), Grok (web + X search), Claude (web search), Firecrawl and Apify the same question (this speaker's, or with target='event' this conference's, recent dated signals) and scores every finding the same way: URL loads, cited by the source, dated within 90 days, judged relevant by Claude; reports usable findings per speaker, hit rate, latency and cost per source. mode 'x_signals' (campaign_id) asks xAI live X search for each qualified speaker's handle and recent posts and says whether that signal is new. Each provider stops once its estimated spend passes max_cost_usd. One call does a small page and stops taking new items after 40s: page with offset until next_offset is null; elapsed_ms says how long it took. Pass dry_run=true for the call count first; keys_configured shows which of the five providers have keys.

Request

curl -X POST "https://sayintel.com/api/public/v1/model-comparisons" \
  -H "Authorization: Bearer $SAYINTEL_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'

Try it out

Try it outPOST/api/public/v1/model-comparisonswrite

Sent as a bearer token to this site only, and kept in this tab's session storage. It is never written to disk or sent anywhere else.

Paste an API key to run.

Body

NameInTypeRequiredDescription
modebodystringfixtures | judge | draft | x_signals | human | signals | founder | founder_draftnofixtures | judge | draft | x_signals | human | signals | founder | founder_draft.
campaign_idbodystringnoRequired for judge, draft, founder_draft and x_signals. Optional for human (narrows the labelled set to one of your campaigns; omitted = every campaign across the founder's own accounts).
providersbodyarraynoAny subset of claude, grok, groq, gemini, perplexity, openrouter (OPENROUTER_API_KEY; choose the model with models.openrouter, e.g. "openai/gpt-5-mini", "deepseek/deepseek-chat", "qwen/qwen3-235b-a22b"); defaults to ["claude","grok"]. For a four-way writing comparison use ["claude","grok","gemini","perplexity"] in draft mode. All work in fixtures, human, founder, judge and draft; gemini runs at the gateway drafting temperature (0.7) in every mode. The first is the agreement baseline in fixtures mode. x_signals always uses grok.
limitbodynumbernoItems this call: fixtures default 12 max 30; labelled messages for human default 8 max 30; marks for founder default 10 max 30; speakers for founder_draft default 3 max 8; speakers for signals default 2 max 5; speakers for judge default 4 max 10, draft default 3 max 8, x_signals default 3 max 8. Sized to finish inside ~45s.
offsetbodynumbernoSkip the first N items (fixture rows, or speakers in id order). Default 0. Call again with the returned next_offset until it is null.
judgebodybooleannodraft mode: false skips the Claude judge call on each candidate and returns the deterministic gate verdict plus the texts, so one speaker finishes well inside a chat-turn timeout. founder_draft: false skips the founder judge.
modelsbodyobjectnoPer-provider model override for this call, e.g. {"groq":"moonshotai/kimi-k2-instruct","claude":"claude-haiku-4-5","gemini":"google/gemini-3.8-flash"}. Omitted = the configured default. One model per provider per call; call again to compare two models of the same provider. A model id the provider does not serve shows up as that provider's error. Cost for a model missing from the price table is over-estimated at the most expensive rate.
sourcesbodyarraynosignals mode only: which search sources to compare (default all six). Model ids for perplexity/gemini/grok/claude come from `models`.
targetbodystringspeakers | eventnosignals mode only: 'speakers' (default) searches each qualified speaker; 'event' searches news and posts about the conference itself.
judge_contextbodystringmessage | fullnohuman mode only: 'message' (default) judges each labelled message alone with a campaign-free rubric; 'full' runs the production judge with the speaker's real campaign context and trio, the labelled text in its slot, scored like rescore_drafts (gate caps included). Use limit 3-4 with 'full'.
judge_providerbodystringclaude | grok | groq | gemini | perplexity | openrouternodraft mode only: which provider scores the candidates (default claude). Run once with a non-Claude judge to check Claude is not grading its own writing up. founder_draft: which provider plays the founder judge (default claude).
judge_modelbodystringnofounder_draft only: the founder judge's model id (default: the judge provider's default model).
speaker_idsbodyarraynofounder_draft only: these speakers (in this campaign) instead of the campaign's eligible ones, paged by offset / limit.
writersbodyarraynofounder_draft only: writer models, several per provider allowed, e.g. [{"provider":"claude","model":"claude-opus-5-5"},{"provider":"openrouter","model":"openai/gpt-5.5"}]. Default: providers with models. [] = no writer models.
include_baselinesbodybooleannofounder_draft only: include the stored draft ('current') and the recipe engine ('recipe'). Default true.
recipebodyunknownnofounder_draft only: this campaign's m1_recipe, when it is not in its campaign_config yet. Its client_id and campaign_id must be this campaign's, or the call is refused.
extra_candidatesbodyarraynofounder_draft only: hand-written messages to judge alongside, one per speaker per label.
max_cost_usdbodynumbernoEstimated spend cap per provider for this call (default 1.5, max 5).
dry_runbodybooleannoWhen true nothing is written, started, or spent. The tool returns only cost_preview so an agent can estimate credits first.

Response

{
  "ok": true,
  "tool": "compare_models",
  "kind": "write",
  "data": { ... },
  "error": null
}

Notes

  • Scope required: write. How scopes work.
  • Accepts Idempotency-Key. A retry with the same key replays the first result instead of repeating the work.
  • Accepts dry_run: true and always returns cost_preview before spending anything.
  • Same capability over MCP as the tool compare_models. MCP setup.