Developer API
Programmatic access to ModelChorus leaderboard data, model rankings, battle results, and votes. Build research tools, dashboards, and integrations on top of community-driven AI evaluations.
Authentication
Most read endpoints (leaderboard, models, votes, stats, suggestions) require no authentication. Polling endpoints (GET /api/v1/battles/:id and GET /api/v1/chats/:id) require a standard-tier key. Write endpoints (creating battles, voting, sending messages) require a write-tier key.
| Tier | Key prefix | Capabilities |
|---|---|---|
| Anonymous | None | Leaderboard, models, votes, stats, suggestions |
| Standard | mc_ | All anonymous endpoints + poll battle and chat status |
| Write | mc_ | All standard endpoints + create battles, vote, send chat messages |
Self-service keys are standard tier (read + poll). Write-tier access (create battles, vote, send messages) is granted by MeetKai admins — contact us to request it.
API keys are intended for server-to-server use only. Do not embed them in client-side code or browsers — the CORS policy allows all origins, so a leaked key can be used by anyone.
Authorization header
Authorization: Bearer mc_0123456789abcdef0123456789abcdefBase URL
All endpoints are served from the Convex deployment URL. Append the route path directly — no additional prefix is needed.
# Base URL
https://http-actions.modelchorus.com
# Full endpoint example
GET https://http-actions.modelchorus.com/api/v1/leaderboardThe base URL is your *.convex.site host. Set NEXT_PUBLIC_CONVEX_SITE_URL explicitly, or it is derived automatically from NEXT_PUBLIC_CONVEX_URL.
Response Format & Errors
All responses are JSON. Success responses wrap the payload in a data field. Errors use a consistent envelope.
// Success
{ "data": { ... } }
// Paginated success
{ "data": [...], "pagination": { "hasMore": true, "nextCursor": "eyJ..." } }
// Error
{ "error": { "code": "RATE_LIMITED", "message": "Daily quota exceeded." } }| Error code | HTTP | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| FORBIDDEN | 403 | Key lacks the required tier for this endpoint |
| NOT_FOUND | 404 | The requested resource does not exist |
| BAD_REQUEST | 400 | Malformed request body or query parameters |
| QUOTA_EXCEEDED | 429 | Daily battle quota exceeded for this key |
| RATE_LIMITED | 429 | Too many requests — slow down |
| TOO_EARLY | 429 | Vote submitted before responses finished streaming |
| VOTE_FAILED | 400 | Vote rejected — already voted, wrong state, or battle not ready |
| NOT_SUPPORTED | 400 | Operation not supported (e.g. multi-turn continuation on image battles) |
| INTERNAL_ERROR | 500 | Unexpected server error |
Rate Limits
- Write-tier keys: 100 battles per 24 hours (default). Quota uses a rolling 24-hour window from the first battle of each period — not a fixed midnight UTC reset.
- Read endpoints: 120 req/min per IP for anonymous callers, per key for standard keys; 600 req/min per key for partner (read/write) keys. Server-side cache applies per the Cache column on each endpoint.
- Maximum prompt/message length: 10,000 characters.
- The
limitquery parameter on list endpoints accepts 1–50 (default 20).
Quickstart
Run your first battle in under a minute. Steps 1 and 3 require a write-tier API key. Step 2 requires a standard-tier key.
- 1. Create a battle — POST your prompt. The response includes an
idandpollIntervalMs. - 2. Poll until ready — GET the battle on the interval until
responsesReadyistrue. The server also enforces a 30-second minimum from creation before a vote is accepted. - 3. Vote — POST your winner choice. The response reveals both model identities in
modelsRevealed.modelAandmodelsRevealed.modelB.
BASE_URL="https://http-actions.modelchorus.com"
API_KEY="mc_your_key_here"
# 1. Create a battle (write-tier key required)
RESPONSE=$(curl -s -X POST "$BASE_URL/api/v1/battles" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Explain quantum entanglement simply.", "modality": "text"}')
BATTLE_ID=$(echo "$RESPONSE" | jq -r '.data.id')
START=$(date +%s)
# 2. Poll until both responses are ready
while true; do
BATTLE=$(curl -s "$BASE_URL/api/v1/battles/$BATTLE_ID" \
-H "Authorization: Bearer $API_KEY")
READY=$(echo "$BATTLE" | jq -r '.data.responsesReady')
[ "$READY" = "true" ] && break
sleep 2
done
# Server enforces a 30s minimum from creation before voting
ELAPSED=$(( $(date +%s) - START ))
[ "$ELAPSED" -lt 30 ] && sleep $((30 - ELAPSED))
# 3. Vote (write-tier key required)
curl -s -X POST "$BASE_URL/api/v1/battles/$BATTLE_ID/vote" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"winner": "A"}' | jq '.data.modelsRevealed'
# { "modelA": "GPT-4o", "modelB": "Claude 3.5 Sonnet" }Leaderboard
/api/v1/leaderboardNo auth requiredCache: 60sReturns ranked model standings for text battles, ordered by Elo score descending.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | no | Filter by locale code (e.g. en, es, fr). Omit for global rankings. |
| category | string | no | Filter by category slug. Requires locale to be set. |
Example request
curl https://http-actions.modelchorus.com/api/v1/leaderboardExample response
{
"data": [
{
"_id": "jd7abc123xyz",
"_creationTime": 1714000000000,
"modelName": "GPT-4o",
"eloRating": 1247,
"rank": 1,
"wins": 831,
"losses": 412,
"ties": 94,
"totalVotes": 1337,
"winRate": 0.669,
"inputCostPer1M": 5.0,
"outputCostPer1M": 15.0,
"valueScore": 249.4
}
]
}/api/v1/leaderboard/imageNo auth requiredCache: 60sReturns ranked model standings for image generation battles, ordered by Elo score descending.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | no | Filter by locale code. |
Example request
curl https://http-actions.modelchorus.com/api/v1/leaderboard/image/api/v1/leaderboard/categoriesNo auth requiredCache: 60sReturns per-category Elo rankings across all models.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | no | Filter by locale code. |
Example request
curl https://http-actions.modelchorus.com/api/v1/leaderboard/categories/api/v1/leaderboard/head-to-headNo auth requiredCache: 60sReturns head-to-head win/loss statistics between all model pairs.
| Parameter | Type | Required | Description |
|---|---|---|---|
| modality | "text" | "image" | no | Filter by battle modality. Defaults to "text" when omitted. |
| locale | string | no | Filter by locale code. |
| localeMode | "exact" | "language" | "hybrid" | no | How locale matching is applied. Default: exact. |
Example request
curl "https://http-actions.modelchorus.com/api/v1/leaderboard/head-to-head?modality=text"/api/v1/leaderboard/localesNo auth requiredCache: 300sReturns all locales that have battle data as string arrays.
| Parameter | Type | Required | Description |
|---|---|---|---|
| modality | "text" | "image" | no | Filter by modality. Omit to return both text and image locale lists. |
Example request
curl https://http-actions.modelchorus.com/api/v1/leaderboard/localesExample response
{
"data": {
"text": ["en", "es", "fr", "ar"],
"image": ["en", "es"]
}
}Models
/api/v1/modelsNo auth requiredCache: 300sReturns all available models with full metadata including provider, modality support, and configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | no | Locale context. Defaults to global. |
Example request
curl https://http-actions.modelchorus.com/api/v1/modelsExample response
{
"data": [
{
"id": "jn7abc123def456",
"name": "GPT-4o",
"modelId": "gpt-4o",
"provider": "OpenAI",
"inputModalities": ["text", "image"],
"outputModalities": ["text"],
"isPinned": true
}
]
}/api/v1/models/availabilityNo auth requiredCache: 300sReturns a lightweight list of all models with name, modelId, and supported modalities. No stats or metadata.
Example request
curl https://http-actions.modelchorus.com/api/v1/models/availabilityExample response
{
"data": [
{
"name": "GPT-4o",
"modelId": "gpt-4o",
"inputModalities": ["text", "image"],
"outputModalities": ["text"]
},
{
"name": "DALL-E 3",
"modelId": "dall-e-3",
"inputModalities": ["text"],
"outputModalities": ["image"]
}
]
}/api/v1/models/:nameNo auth requiredCache: 300sReturns a single model by its display name or modelId.
:name accepts either the human-readable display name or the modelId slug (e.g. gpt-4o).
Example request
curl https://http-actions.modelchorus.com/api/v1/models/gpt-4o/api/v1/models/:name/comparisonsNo auth requiredCache: 30sReturns recent battle comparisons in which this model participated. User identity is stripped from all results.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | number | no | Number of comparisons to return. Range 1–50. Default 20. |
Example request
curl "https://http-actions.modelchorus.com/api/v1/models/gpt-4o/comparisons?limit=5"Votes & Stats
/api/v1/votes/recentNo auth requiredCache: 15sReturns the most recent votes that include user-submitted feedback (hasFeedback = true). This is a feedback-only feed — votes without comments are excluded. User identity is stripped from all results.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | number | no | Number of votes to return. Range 1–50. Default 20. |
Example request
curl https://http-actions.modelchorus.com/api/v1/votes/recentExample response
{
"data": [
{
"_id": "jn7abc123def456",
"_creationTime": 1714000000000,
"winner": "A",
"userLocale": "en",
"prompt": "Explain quantum entanglement.",
"modality": "text",
"feedback": "Model A was more concise.",
"hasFeedback": true
}
]
}/api/v1/votes/feedbackNo auth requiredCache: 15sReturns paginated votes that include user-submitted feedback text. User identity is stripped.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | number | no | Page size. Range 1–50. Default 20. |
| cursor | string | no | Pagination cursor from the previous response. |
Example request
curl https://http-actions.modelchorus.com/api/v1/votes/feedbackExample response
{
"data": [...],
"pagination": { "hasMore": true, "nextCursor": "eyJ..." }
}/api/v1/statsNo auth requiredCache: 60sReturns platform-wide totals: total models, total votes cast, and vote breakdown by modality.
Example request
curl https://http-actions.modelchorus.com/api/v1/statsExample response
{
"data": {
"totalModels": 34,
"totalTextModels": 28,
"totalImageModels": 6,
"totalTextVotes": 98432,
"totalImageVotes": 12000,
"totalVotes": 110432
}
}/api/v1/suggestionsNo auth requiredCache: 300sReturns AI-generated prompt suggestions for the challenge UI, by locale and modality.
| Parameter | Type | Required | Description |
|---|---|---|---|
| locale | string | no | Locale for suggestions. Default: en. |
| modality | "text" | "image" | no | Suggestion type. Default: text. |
Example request
curl "https://http-actions.modelchorus.com/api/v1/suggestions?locale=en&modality=text"Battles
/api/v1/battlesAPI key (write)Create a new blind battle. Two anonymous models receive the prompt and generate responses in parallel.
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | yes | The prompt to send. Max 10,000 characters. |
| modality | "text" | "image" | no | Battle type. Default: "text". |
| locale | string | no | Locale context for model selection. Default: "en". |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/battles \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"prompt": "Explain quantum entanglement simply.", "locale": "en"}'Example response
{
"data": {
"id": "abc123",
"status": "initializing",
"pollUrl": "/api/v1/battles/abc123",
"pollIntervalMs": 2000
}
}/api/v1/battles/:idAPI key (standard)Poll the status of a battle. Returns per-turn responses, model names (hidden as 'Model A' / 'Model B' until voted), and vote status.
No cache. Poll until the top-level responsesReady is true before voting. Model names are revealed only after a vote is cast for that turn.
Example request
curl https://http-actions.modelchorus.com/api/v1/battles/jn7abc123def456 \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef"Example response
{
"data": {
"id": "jn7abc123def456",
"status": "active",
"responsesReady": true,
"modality": "text",
"title": "Explain quantum entanglement simply.",
"turnCount": 1,
"votedCount": 0,
"createdAt": 1714000000000,
"comparisons": [
{
"id": "jn7cmp789xyz012",
"turnNumber": 1,
"status": "pending",
"responsesReady": true,
"modelA": "Model A",
"modelB": "Model B",
"responseA": "Quantum entanglement is a phenomenon where two particles...",
"responseB": "Great question! When two particles become entangled...",
"vote": null
}
],
"turns": [
{
"turnNumber": 1,
"branchA": [
{ "role": "user", "content": "Explain quantum entanglement simply.", "messageIndex": 0 },
{ "role": "assistant", "content": "Quantum entanglement is...", "messageIndex": 1 }
],
"branchB": [
{ "role": "user", "content": "Explain quantum entanglement simply.", "messageIndex": 0 },
{ "role": "assistant", "content": "Great question!...", "messageIndex": 1 }
]
}
],
"errorCode": null,
"errorMessage": null
}
}/api/v1/battles/:id/voteAPI key (write)Submit a vote for a completed battle. Reveals model identities in the response.
The API enforces a minimum 30-second wait from battle creation before a vote is accepted. Poll until responsesReady is true first, then vote.
| Parameter | Type | Required | Description |
|---|---|---|---|
| winner | "A" | "B" | "tie" | "both_bad" | yes | Which model you prefer, or indicate a tie or both bad. |
| feedback | string | no | Optional freeform feedback text. |
| rating | number | no | Optional 1–5 star rating. |
| criteria | { accuracy?: number; helpfulness?: number; clarity?: number; creativity?: number } | no | Optional per-criterion scores (1–5 integer each). |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/battles/jn7abc123def456/vote \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"winner": "A", "feedback": "Model A was more concise."}'Example response
{
"data": {
"success": true,
"modelsRevealed": {
"modelA": "GPT-4o",
"modelB": "Claude 3.5 Sonnet"
}
}
}/api/v1/battles/:id/feedbackAPI key (write)Attach feedback to a battle you have already voted on. At least one field required.
A vote must be cast for the battle before submitting feedback. At least one of feedback, rating, or criteria must be provided.
| Parameter | Type | Required | Description |
|---|---|---|---|
| feedback | string | no | Freeform feedback text. |
| rating | number | no | 1–5 star rating. |
| criteria | { accuracy?: number; helpfulness?: number; clarity?: number; creativity?: number } | no | Per-criterion scores (1–5 integer each). |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/battles/jn7abc123def456/feedback \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"feedback": "Both responses lacked citations."}'Example response
{ "data": { "success": true } }/api/v1/battles/:id/continueAPI key (write)Continue a text battle with a follow-up prompt (multi-turn). Only supported for text modality — image battles are single-turn.
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | yes | Follow-up prompt. Max 10,000 characters. |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/battles/jn7abc123def456/continue \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"prompt": "Now explain it to a 10-year-old."}'Example response
{
"data": {
"turnNumber": 2,
"status": "initializing",
"pollUrl": "/api/v1/battles/jn7abc123def456",
"pollIntervalMs": 2000
}
}Chats
/api/v1/chatsAPI key (write)Create a new single-model chat session. Use GET /api/v1/models/availability to list valid model identifiers.
Returns the session id only. pollIntervalMs is returned by POST /api/v1/chats/:id/message, not by this endpoint.
| Parameter | Type | Required | Description |
|---|---|---|---|
| modelId | string | yes | Model identifier — display name or modelId slug. Use /models/availability to enumerate. |
| locale | string | no | Locale context. Default: "en". |
| modality | "text" | "image" | no | Session modality. Default: "text". |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/chats \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"modelId": "gpt-4o", "locale": "en"}'Example response
{
"data": {
"id": "jn7cht456uvw789",
"modelName": "GPT-4o"
}
}/api/v1/chats/:idAPI key (standard)Poll the current state of a chat session, including all messages and streaming status.
No cache — intended for real-time polling. Use the pollIntervalMs returned by POST /api/v1/chats/:id/message.
| Parameter | Type | Required | Description |
|---|---|---|---|
| cursor | string | no | Pagination cursor to fetch messages after a given point. |
Example request
curl https://http-actions.modelchorus.com/api/v1/chats/jn7cht456uvw789 \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef"Example response
{
"data": {
"id": "jn7cht456uvw789",
"modelName": "GPT-4o",
"modelId": "gpt-4o",
"modality": "text",
"messageCount": 2,
"isActive": true,
"createdAt": 1714000000000,
"messages": [
{ "id": "msg_001", "role": "user", "content": "What is the capital of France?", "createdAt": 1714000001000 },
{ "id": "msg_002", "role": "assistant", "content": "The capital of France is Paris.", "createdAt": 1714000003000 }
],
"pagination": { "hasMore": false, "nextCursor": null }
}
}/api/v1/chats/:id/messageAPI key (write)Send a new message to a chat session. Poll the session after sending to retrieve the response.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | yes | The message text to send. Max 10,000 characters. |
Example request
curl -X POST https://http-actions.modelchorus.com/api/v1/chats/jn7cht456uvw789/message \
-H "Authorization: Bearer mc_0123456789abcdef0123456789abcdef" \
-H "Content-Type: application/json" \
-d '{"message": "What is the capital of France?"}'Example response
{
"data": {
"status": "processing",
"messageCount": 2,
"pollUrl": "/api/v1/chats/jn7cht456uvw789",
"pollIntervalMs": 2000
}
}Changelog
v1.0 — April 2026
- Initial public release.
- 21 endpoints across leaderboard, models, votes, battles, and chats.
- Four auth tiers: anonymous, standard, read (partner rate limits, no write access), write.