API, MCP and agents
Developer documentation
An agent can submit a choice, receive a clarifying question, then get a recommendation with its reasons. No account or provider key is required.
Connect with MCP
https://dilem.si/api/v1/mcpRemote HTTP transport, without OAuth. Pass the application token to tools, not in the server configuration. Allow 110 seconds for a decision call.
Choose your client. Merge the example into your existing configuration rather than replacing it. Reload MCP tools, then check that start_session, get_usage and decide are available.
Claude Code
claude mcp add --transport http dilem https://dilem.si/api/v1/mcpCodex
~/.codex/config.toml[mcp_servers.dilem]
url = "https://dilem.si/api/v1/mcp"
tool_timeout_sec = 110Cursor
.cursor/mcp.json{
"mcpServers": {
"dilem": {
"url": "https://dilem.si/api/v1/mcp"
}
}
}OpenCode
opencode.json{
"mcp": {
"dilem": {
"type": "remote",
"url": "https://dilem.si/api/v1/mcp",
"enabled": true,
"oauth": false
}
}
}Hermes Agent
hermes mcp add dilem --url https://dilem.si/api/v1/mcpMistral Vibe
~/.vibe/config.toml[[mcp_servers]]
name = "dilem"
transport = "streamable-http"
url = "https://dilem.si/api/v1/mcp"
tool_timeout_sec = 110
[mcp_servers.auth]
type = "static"
headers = {}OpenClaw
~/.openclaw/openclaw.json{
"mcp": {
"servers": {
"dilem": {
"url": "https://dilem.si/api/v1/mcp",
"transport": "streamable-http",
"enabled": true,
"connectionTimeoutMs": 10000,
"requestTimeoutMs": 110000
}
}
}
}Pi
~/.pi/agent/mcp.json{
"mcpServers": {
"dilem": {
"url": "https://dilem.si/api/v1/mcp",
"timeout": 110
}
}
}MCP reference
start_session
WriteCreate or renew the client’s private identity.
sessionstring | nulloptional · Default null
Response: Session
POST /api/v1/session
Input
Headers
AuthorizationSession token sent as Bearer <session>.
No request body required.
Response
200 application/json
{
"session": "OPAQUE_SESSION_KEEP_PRIVATE",
"expires_in": 7776000
}sessionPrivate client token to retain for subsequent calls.
expires_inSession validity period, in seconds.
Value:
7776000
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
get_usage
ReadRead credits, reset time and optional case state.
sessionstringrequiredcase_idstring | nulloptional · Default null
Response: Usage
GET /api/v1/usage
Input
Headers
AuthorizationSession token sent as Bearer <session>.
X-Dilem-Case-IDKeep this identifier for every turn of the same dilemma.
Technical format
^[a-zA-Z0-9_-]{1,80}$
No request body required.
Response
200 application/json
{
"day": "2026-01-01",
"resets_at": "2026-01-02T00:00:00Z",
"collective": {
"completed": 42
},
"dilemmas": {
"used": 1,
"limit": 20,
"remaining": 19
},
"case": {
"id": "my-unique-dilemma-id",
"admitted": true,
"completed": false
},
"voice": {
"seconds_used": 0,
"seconds_reserved": 0,
"seconds_remaining": 1200,
"seconds_limit": 1200,
"max_seconds": 60
},
"budget": {
"daily_usd": 5,
"available": true
}
}dayUTC day of the counters.
ISO 8601 date
resets_atDate and time of the next credit reset.
ISO 8601 date and time
collectiveShared count of completed dilemmas.
Fields
completedNumber of completed dilemmas in the shared counter.
dilemmasUsed, remaining and allowed dilemma credits.
Fields
usedCredits already used.
Minimum: 0
limitCredit limit from the active policy.
Minimum: 0
remainingCredits still available.
Minimum: 0
caseState of the dilemma identified by the supplied Case-ID.
Fields
idRequested Case-ID, or null when none was supplied.
admittedWhether this dilemma has already been admitted under the quota.
completedWhether a first recommendation completed this dilemma.
voiceDictation durations and limits, in seconds.
Fields
seconds_usedDictation seconds already used.
seconds_reservedSeconds temporarily reserved by an active capture.
seconds_remainingDictation seconds still available.
seconds_limitTotal dictation limit, in seconds.
max_secondsMaximum capture duration, in seconds.
budgetShared financial budget and service availability.
Fields
daily_usdDaily service budget limit, in US dollars.
availableWhether the budget still allows calculations.
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
decide
AI calculationSubmit a decision turn: question, recommendation or error.
sessionstringrequiredcase_idstringrequiredtextstringrequiredlanguage"fr" | "en" | "it" | "de" | "es" | "pt"optional · Default "fr"refinementsstring[] | nulloptional · Default nullforce_decisionbooleanoptional · Default falseclarification_countintegeroptional · Default 0clarification_version2optional · Default 2
Response: DecisionResponse
POST /api/v1/decide
Input
Headers
AuthorizationSession token sent as Bearer <session>.
X-Dilem-Case-IDKeep this identifier for every turn of the same dilemma.
Technical format
^[a-zA-Z0-9_-]{1,80}$
Body application/json
{
"text": "Train or bus for my commute?",
"language": "en",
"refinements": [],
"clarification_version": 2
}textInitial choice, retained on every turn.
1–6 000 characters
languageInterface language. Replies follow the user’s own words.
Allowed values:
"fr","en","it","de","es","pt"refinementsActual questions and answers in chronological order.
Up to 40 items · Up to 6 000 characters per item · Default:
[]force_decisionOnly when the user requests a recommendation without further questions.
Default:
falseclarification_countNumber of clarification answers.
0–20 · Default:
0clarification_versionContract version, always 2.
Default:
2· Value:2
{
"session": "<session from start_session>",
"case_id": "my-unique-dilemma-id",
"text": "Train or car for a weekend trip?",
"language": "en"
}Response
200 application/json
Clarifying question Clarification
A question to relay, or a notice according to purpose. insufficient_evidence means there is no defensible basis for ranking; do not retry without new information from the user.
{
"type": "clarification",
"question": "How far is your commute?",
"choices": [],
"format": "number",
"unit": "km",
"summary": "Train or bus"
}typeKind of response returned.
Value:
"clarification"questionQuestion to relay to the user before continuing.
choicesSuggested answers, when supplied with the question.
Up to 6 items
formatAnswer format expected by the question.
Allowed values:
"single","multiple","text","number"unitUnit of a numeric answer, or null.
summarySummary of the dilemma.
purposePurpose of the question or scope notice.
Allowed values:
"decision_scope","scope_notice","insufficient_evidence"metricsTechnical processing metrics.
Recommendation Result
Recommendation, options, reasons and sources. Weights express relative preferences, not success probabilities. result_id may be absent.
{
"type": "result",
"summary": "Train or bus for my commute?",
"options": [
{
"id": "train",
"label": "Take the train",
"description": "Use the direct rail connection.",
"probability": 0.7
},
{
"id": "bus",
"label": "Take the bus",
"description": "Use the bus stop near home.",
"probability": 0.3
}
],
"selected": "train",
"sources": [],
"result_id": "0123456789abcdef0123456789abcdef"
}typeKind of response returned.
Value:
"result"summarySummary of the dilemma.
contextContext used for the recommendation.
assumptionsAssumptions used to compare options.
criteriaCriteria used to compare options.
optionsCompared options and associated information.
2–8 items
selectedIdentifier of the recommended option.
sourcesSources returned with the recommendation.
cautionSpecific caution, or null.
timingsDurations of processing stages.
modelsModel information for this turn.
metricsTechnical processing metrics.
result_idIdentifier of a result owned by this client.
Error Error
Pipeline refusal or failure. Check type even after HTTP 200; MCP also reports isError.
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
Render the received result as a card, without AI or image storage.
sessionstringrequiredresultCardResultrequiredlanguage"fr" | "en" | "it" | "de" | "es" | "pt"optional · Default "fr"theme"light" | "dark"optional · Default "light"
Response: image/png
POST /api/v1/result-card
Input
Headers
AuthorizationSession token sent as Bearer <session>.
Body application/json
{
"result": {
"summary": "Train or bus for my commute?",
"selected": "train",
"options": [
{
"id": "train",
"label": "Take the train",
"description": "Use the direct rail connection.",
"probability": 0.7
},
{
"id": "bus",
"label": "Take the bus",
"description": "Use the bus stop near home.",
"probability": 0.3
}
]
},
"language": "en",
"theme": "light"
}resultPreviously received result to supply for PNG rendering.
Fields
summarySummary of the dilemma.
1–6 000 characters
selectedIdentifier of the recommended option.
Up to 32 characters
optionsCompared options and associated information.
2–8 items
Fields
idUnique identifier of this option within the result.
Technical format
^[a-z][a-z0-9_]{0,31}$labelOption label on the card.
1–120 characters
descriptionShort description shown with the option.
Up to 240 characters · Default:
""probabilityPreference weight, not a probability of success.
0–1
languagePresentation language.
Allowed values:
"fr","en","it","de","es","pt"· Default:"fr"themeVisual theme of the PNG card.
Allowed values:
"light","dark"· Default:"light"
Response
200 image/png
Private PNG attachment. Its filename is returned in Content-Disposition.
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 · 415 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
Read the owning client’s result vote.
sessionstringrequiredresult_idstringrequired
Response: Feedback
GET /api/v1/feedback/{result_id}
Input
Headers
AuthorizationSession token sent as Bearer <session>.
Path parameters
result_idIdentifier of a result owned by this client.
Technical format
^[0-9a-f]{32}$
No request body required.
Response
200 application/json
{
"result_id": "0123456789abcdef0123456789abcdef",
"helpful": false,
"reason": "missing_constraint"
}result_idIdentifier of a result owned by this client.
Technical format
^[0-9a-f]{32}$helpfulRecorded vote, or null when no vote was given.
reasonReason for a negative vote, or null.
Allowed values:
null,"misunderstood","missing_constraint","too_many_questions","too_vague"
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 · 404 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
set_result_feedback
WriteRecord the user’s explicit vote; never infer satisfaction.
sessionstringrequiredresult_idstringrequiredhelpfulbooleanrequiredreason"misunderstood" | "missing_constraint" | "too_many_questions" | "too_vague" | nulloptional · Default null
Response: Feedback
POST /api/v1/feedback
Input
Headers
AuthorizationSession token sent as Bearer <session>.
Body application/json
{
"result_id": "0123456789abcdef0123456789abcdef",
"helpful": false,
"reason": "missing_constraint"
}result_idIdentifier of a result owned by this client.
Technical format
^[0-9a-f]{32}$helpfulUser’s explicit vote on the response.
reasonReason for a negative vote, or null.
Allowed values:
null,"misunderstood","missing_constraint","too_many_questions","too_vague"
Response
200 application/json
{
"result_id": "0123456789abcdef0123456789abcdef",
"helpful": false,
"reason": "missing_constraint"
}result_idIdentifier of a result owned by this client.
Technical format
^[0-9a-f]{32}$helpfulRecorded vote, or null when no vote was given.
reasonReason for a negative vote, or null.
Allowed values:
null,"misunderstood","missing_constraint","too_many_questions","too_vague"
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 · 415 · 404 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
send_user_feedback
WriteSend an explicitly authorized message or report.
sessionstringrequiredrequest_idstringrequiredmessagestringrequiredlanguage"fr" | "en" | "it" | "de" | "es" | "pt"optional · Default "fr"kind"general" | "response"optional · Default "general"emailstringoptional · Default ""responseobject | nulloptional · Default null
Response: FeedbackAck
POST /api/v1/user-feedback
Input
Headers
AuthorizationSession token sent as Bearer <session>.
Body application/json
{
"request_id": "0123456789abcdef0123456789abcdef",
"kind": "general",
"message": "Please explain how the recommendation uses my constraints.",
"email": "",
"language": "en",
"source": "agent",
"version": "unknown",
"screen": "other",
"response": null
}request_idIdentifier to retain for deduplicating the same submission.
Technical format
^[0-9a-f]{32}$kindGeneral message or report of a response.
Allowed values:
"general","response"messageComment expressly provided by the user.
5–4 000 characters
emailContact address, or an empty string.
Up to 254 characters
languagePresentation language.
Allowed values:
"fr","en","it","de","es","pt"sourceEntry point from which the message originated.
Allowed values:
"agent","menu","help","response"versionApplication SHA, or unknown.
Technical format
^[0-9a-f]{40}$|^unknown$screenScreen from which the message is sent.
Allowed values:
"home","clarification","result","other"responseExact response attached to the report, otherwise null.
Fields
nullnullAttached response
ReportedResponsekindKind of response attached to the report.
Allowed values:
"clarification","recommendation"textExact text of the attached response.
1–12 000 characters
referenceReference of the response concerned.
Technical format
^[A-Za-z0-9._:-]{1,80}$
Response
200 application/json
{
"ok": true
}okConfirms the message was recorded.
Value:
true
Errors and recovery
403 · 408 · 409 · 413 · 422 · 429 · 503 · 415 application/json
{
"type": "error",
"code": "case_completed"
}typeKind of response returned.
Value:
"error"codeCode to inspect when handling a refusal or failure.
messageReadable explanation of the refusal or failure.
retryableWhether another attempt may be considered.
resets_atDate and time of the next credit reset.
ISO 8601 date and time
Quickstart
Requires curl and Python 3. Create an identity once, store its token privately and restore it on later visits.
Create a session and save the session value. Send it as Authorization: Bearer *** on subsequent calls. It lasts 90 days; renew it with the same call and token.
DILEM_SESSION=$(curl --fail-with-body --silent --show-error --max-time 15 \
https://dilem.si/api/v1/session \
-H 'Content-Type: application/json' -d '{}' | \
python3 -c 'import json, sys; print(json.load(sys.stdin)["session"])')curl --fail-with-body --max-time 15 https://dilem.si/api/v1/usage \
-H "Authorization: Bearer ${DILEM_SESSION:?}" \
-H 'X-Dilem-Case-ID: my-unique-dilemma-id'Choose a Case-ID for your dilemma and keep it throughout the conversation. You receive a question or a recommendation. To answer, append the question and your answer to refinements, then increment clarification_count by 1.
curl --fail-with-body --max-time 110 --output result.json https://dilem.si/api/v1/decide \
-H "Authorization: Bearer ${DILEM_SESSION:?}" \
-H 'X-Dilem-Case-ID: my-unique-dilemma-id' \
-H 'Content-Type: application/json' \
-d '{"text":"Train or car for a weekend trip?","language":"en","refinements":[],"clarification_count":0,"clarification_version":2}'Session and authentication
Without a token, start_session creates an identity. With session, it renews the same identity for 90 days. Invalid or expired tokens are refused, never silently replaced. REST and MCP share this token; neither reads browser cookies.
Use one unique Case-ID per dilemma until its result: 1–80 characters from a-z, A-Z, 0-9, _ and -. It identifies the case, but does not make a calculation idempotent.
Dilemma lifecycle
- Submit the initial choice with a new Case-ID.
- On clarification, inspect purpose: insufficient_evidence is a notice to show, not a question or invitation to invent a ranking. Otherwise ask the question and wait for the actual answer; append question + answer to refinements and increment clarification_count.
- On result, retain the JSON: this case is complete. Use a new Case-ID for another calculation.
- On error, inspect code and retryable. Never automatically replay a calculation after an ambiguous outcome.
The agent passes questions to you and waits for your answers, without inventing missing information. You can ask it for a recommendation without further questions.
Decision parameters
textrequiredInitial choice, retained on every turn.
string · 1–6000languagerequiredInterface language. Replies follow the user’s own words.
fr | en | it | de | es | ptrefinementsoptionalActual questions and answers in chronological order.
array · ≤40 × 6000clarification_countoptionalNumber of clarification answers.
integer · 0–20clarification_versionoptionalContract version, always 2.
2force_decisionoptionalOnly when the user requests a recommendation without further questions.
boolean
Text + refinements: 24,000 characters total. HTTP body: 300,000 bytes received within 10 seconds; an oversized provider envelope can also be refused before consumption.
Responses
Clarifying question type: clarification
A question to relay, or a notice according to purpose. insufficient_evidence means there is no defensible basis for ranking; do not retry without new information from the user.
type, question, choices, format, unit, summary, purpose, metrics
Recommendation type: result
Recommendation, options, reasons and sources. Weights express relative preferences, not success probabilities. result_id may be absent.
type, summary, context, assumptions, criteria, options, selected, sources, caution, timings, models, metrics, result_id
Error type: error
Pipeline refusal or failure. Check type even after HTTP 200; MCP also reports isError.
type, code, message, retryable, resets_at
{
"type": "clarification",
"question": "How far is your commute?",
"choices": [],
"format": "number",
"unit": "km",
"summary": "Train or bus"
}Results and feedback
Export a result as PNG
Pass the received result as result, with language and theme (light or dark). Private PNG, filename in Content-Disposition, Cache-Control: no-store. No dilemma credit and no publication. MCP returns a native image and file metadata.
python3 -c 'import json, sys; print(json.dumps({"result": json.load(sys.stdin), "language": "fr", "theme": "light"}))' < result.json > card-input.jsoncurl --fail-with-body --max-time 15 https://dilem.si/api/v1/result-card \
-H "Authorization: Bearer ${DILEM_SESSION:?}" \
-H "Content-Type: application/json" \
--data-binary @card-input.json --output dilem.pngRead or record a vote
result_id belongs to this identity. helpful is a boolean; reason is optional for a negative vote only: misunderstood, missing_constraint, too_many_questions or too_vague. Missing, expired and foreign results return the same 404.
GET /api/v1/feedback/{result_id}{"result_id": "<result_id from your result>", "helpful": false, "reason": "missing_constraint"}curl --fail-with-body --max-time 15 https://dilem.si/api/v1/feedback \
-H "Authorization: Bearer ${DILEM_SESSION:?}" \
-H "Content-Type: application/json" \
--data-binary @feedback-input.jsonSend a message or report a response
request_id: 32 lowercase hexadecimal characters. message: at least 5 non-whitespace characters, 4,000 maximum. kind=general for a message; kind=response for a report with the exact response attached. Email is optional. Keep identifier and payload stable; no automatic replay after ambiguous failure.
{
"request_id": "<32 lowercase hex characters>",
"kind": "general",
"message": "<user-authorized message>",
"email": "",
"language": "fr",
"source": "agent",
"version": "unknown",
"screen": "other",
"response": null
}curl --fail-with-body --max-time 15 https://dilem.si/api/v1/user-feedback \
-H "Authorization: Bearer ${DILEM_SESSION:?}" \
-H "Content-Type: application/json" \
--data-binary @user-feedback-input.jsonCredits
One credit covers a dilemma through its first recommendation, including clarification questions. Any new calculation after that recommendation uses another credit.
Use get_usage over MCP or GET /api/v1/usage over REST to check your remaining credits and when they renew.
Limits come from the active policy, not a fixed value in this guide. PNG export and feedback use no dilemma credit, but retain technical and anti-abuse limits.
Errors and recovery
403: missing, invalid or expired session, rejected origin or forbidden cookies. 408/413/422: timeout, size or invalid input. 409: completed case. 429: quota, budget or capacity. 503: unavailable service. Also check the response type: pipeline errors can occur after admission. Never automatically replay a calculation after an ambiguous failure; Case-ID is not a calculation idempotency key.
A disconnect may leave consumed work. Reading usage cannot reconstruct a lost response.
Browser agents
In a compatible browser, WebMCP lets your agent use the open page with your history and credits. Otherwise, use remote MCP or the API. Remote access accepts text; voice dictation remains available in the app.
Read get_dilemma first. submit_dilemma sends a choice or answer; recommend_dilemma requests a recommendation without another question. Result actions use the visible case, not the remote token.
WebMCP adds local image export with preview, votes and reports, plus list_saved_dilemmas and get_saved_dilemma for this browser’s history. The image URL expires when the preview closes. Neither that history nor browser cookies are shared with remote MCP.
Technical reference
OpenAPI schemas describe complete request and response bodies. The Markdown guide is an agent entry point; the catalog and Server Card expose MCP discovery.
The service retains text exchanges for 30 days. Browser history stays on your device and is not shared with a remote agent.
Privacy and providers