API
Everything you can do in Divination — open questions, forecast them, settle them, read the leaderboard — you can also do over HTTP with an API token. Point a CI job, a cron script or your own dashboard at it.
Getting a token
There are two kinds, and they work the same way: the secret starts with dvn_ and is shown once, because Divination only stores its hash — if you lose it, revoke it and make another. Whichever you mint, you choose exactly what it may do (see scopes) and when it expires.
Personal tokens
Minted at Settings → API tokens, for anything acting on your behalf.
- A personal token acts as you. It reaches exactly the groups you belong to, and has the same rights you do in each one: any member can open questions and predict, only admins can edit, settle, annul or reopen.
- Those rights are re-checked on every request, so losing admin (or leaving a group) takes effect immediately.
- Actions are attributed to you, and notify the group by email and Slack exactly as if you had used the app.
Group tokens
Minted by a group admin under the group's Settings, for a script or dashboard that belongs to the group rather than to one person — it keeps working when whoever set it up moves on.
- A group token acts for its group. It reaches that one group and nothing else, and holds admin rights in it, so it can settle and edit questions as well as read them.
- It cannot predict: a group has no forecast of its own, so
predictions:writeisn't on offer. Use a personal token to forecast. - Anything it writes is credited to the admin who created it, and notifies the group as usual. It keeps its rights even if that admin is demoted or leaves — any admin of the group can see and revoke it.
Scopes
A token carries only the permissions ticked when it was created, and every endpoint below names the one it needs. Grant as little as the job needs: a dashboard usually wants the three :read scopes and nothing more.
| Scope | Allows |
|---|---|
groups:read | Group details, member lists and leaderboards, for the groups the token can reach. |
questions:read | List questions, and read one with its options and resolution. |
questions:write | Open new questions, and edit an active one's wording, close time or prediction visibility. |
questions:resolve | Resolve, annul and reopen questions, awarding or deleting the points that go with it. |
predictions:read | Read forecast histories, subject to the group's anchoring delay. |
predictions:write | Record and revise forecasts. Personal tokens only — a group has no forecast of its own. |
Scopes are a ceiling, not a grant: a token still can't do anything the person or group behind it couldn't. A token asked to do something outside its scopes answers 403 and names the scope it is missing.
Making a request
Send the token as a bearer credential. Bodies are JSON; so are all responses. Timestamps are ISO 8601 in UTC, and calendar dates are plain YYYY-MM-DD.
export DIVINATION_TOKEN=dvn_… curl -s https://divination.io/api/v1/me \ -H "Authorization: Bearer $DIVINATION_TOKEN"
That answers with what the token is, what it may do, and the groups it can reach — the quickest way to check a token works and to find the group id you need. actor is the person its writes are credited to: its owner, or the admin who created it if it belongs to a group.
{
"token": {
"id": "3f2a…",
"name": "CI pipeline",
"type": "user",
"scopes": ["groups:read", "questions:read", "questions:write"]
},
"actor": { "id": "user_2a…", "name": "Ada", "email": "ada@example.com" },
"groups": [
{
"id": "9c1e…",
"name": "Team forecasts",
"description": null,
"role": "admin",
"predictionHideDays": 7,
"predictionHideFromOpen": false,
"createdAt": "2026-01-04T09:12:44.001Z",
"url": "https://divination.io/groups/9c1e…"
}
]
}A group token answers the same shape with "type": "group" and exactly one group.
Endpoints
All paths are relative to /api/v1.
You and your groups
| Endpoint | What it does | Scope |
|---|---|---|
| GET /me | What the token is, its scopes, and the groups it reaches. | — |
| GET /groups | Groups the token reaches, oldest membership first. | groups:read |
| GET /groups/{groupId} | One group, including the token's role in it. | groups:read |
| GET /groups/{groupId}/members | Members with their roles and join dates. | groups:read |
| GET /groups/{groupId}/leaderboard | Points from resolved questions, highest first. Add ?month=2026-09 for one UTC month. | groups:read |
Questions
| Endpoint | What it does | Scope |
|---|---|---|
| GET /groups/{groupId}/questions | List them, newest first. Filters: ?status=active,resolved,annulled, ?limit= (1–200, default 50) and ?offset=. The response carries hasMore. | questions:read |
| POST /groups/{groupId}/questions | Open a question. Any member may. Answers 201 with the created question. | questions:write |
| GET /questions/{questionId} | One question, with its options and resolution. | questions:read |
| PATCH /questions/{questionId} | Admins only. Edit title, description (null clears it) or closesAt on an active question, or set predictionsPublic: true. | questions:write |
| POST /questions/{questionId}/resolve | Admins only. Settle it and score everyone. | questions:resolve |
| POST /questions/{questionId}/annul | Admins only. Void it without scoring. No body. | questions:resolve |
| POST /questions/{questionId}/unresolve | Admins only. Reopen a settled question and delete the points it awarded. The body is optional: a closesAt moves the close time too, so it takes predictions again instead of coming back closed. | questions:resolve |
Predictions
| Endpoint | What it does | Scope |
|---|---|---|
| GET /questions/{questionId}/predictions | The forecast history, honouring the group's anchoring delay. | predictions:read |
| POST /questions/{questionId}/predictions | Record or revise your own forecast. Answers 201. Personal tokens only. | predictions:write |
Opening a question
The fields mirror the “Ask a question” form. Every type needs title and closesAt (at least five minutes out), and optionally takes description and predictionsPublic. What else it needs depends on type:
type | Extra fields |
|---|---|
binary | — (a plain yes/no question) |
multiple_choice | options: 2–10 distinct labels |
numeric | rangeMin, rangeMax — the plausible range scoring is calibrated against — and an optional unit |
date | rangeMinDate, rangeMaxDate (YYYY-MM-DD) |
curl -s -X POST \
"$HOST/api/v1/groups/$GROUP_ID/questions" \
-H "Authorization: Bearer $DIVINATION_TOKEN" \
-H 'content-type: application/json' \
-d '{
"type": "binary",
"title": "Will the release ship this week?",
"description": "Counts the moment the tag is pushed.",
"closesAt": "2026-12-31T17:00:00Z"
}'The 201 response is the question itself. Keep its id — you need it to predict on or settle the question:
{
"id": "6b0d…",
"groupId": "9c1e…",
"type": "binary",
"title": "Will the release ship this week?",
"description": "Counts the moment the tag is pushed.",
"status": "active",
"openedAt": "2026-08-19T10:00:00.000Z",
"closesAt": "2026-12-31T17:00:00.000Z",
"closed": false,
"predictionsPublic": false,
"createdBy": { "id": "user_2a…", "name": "Ada", "email": "ada@example.com" },
"resolvedAt": null,
"resolution": null,
"url": "https://divination.io/groups/9c1e…/questions/6b0d…"
}Type-specific fields come back the same way they go in: options on a multiple-choice question, rangeMin/rangeMax/unit on a numeric one, rangeMinDate/rangeMaxDateon a date one. Fields that don't apply are left out.
Predicting
The question's type decides the fields, so you never repeat it. Posting again revises your forecast — the history is kept, and scoring is the time-average of it, so predicting early counts for more.
# binary — p is the probability of yes, 0.01–0.99
-d '{ "p": 0.72 }'
# multiple_choice — one probability per option, summing to 1.
# Key them by option label or by option id.
-d '{ "probs": { "Alice": 0.5, "Bob": 0.3, "Carol": 0.2 } }'
# numeric — your estimate, and the half-width of your central 50% interval
-d '{ "value": 1200, "spread": 150 }'
# date — same idea, with the uncertainty in days
-d '{ "date": "2026-09-01", "spreadDays": 10 }'Reading them back gives every revision, each with the raw payload and a human-readable summary. While a question is open, other members' recent forecasts are withheld — the same anti-anchoring delay the app applies — and othersDelayedtells you when that is happening. Once the question closes or is settled the delay is dropped and the whole history comes back. Your own predictions are always returned; a group token has none of its own, so the delay applies to every member's.
Settling a question
Which field carries the outcome depends on the question's type. Multiple choice accepts an option label (matched case-insensitively) as well as an optionId, and a binary outcome accepts true/false as well as "yes"/"no".
curl -s -X POST \
"$HOST/api/v1/questions/$QUESTION_ID/resolve" \
-H "Authorization: Bearer $DIVINATION_TOKEN" \
-H 'content-type: application/json' \
-d '{ "outcome": "yes" }' # binary
-d '{ "option": "Alice" }' # multiple_choice
-d '{ "value": 1187 }' # numeric
-d '{ "date": "2026-09-03" }' # dateThe response is the resolved question, with status: "resolved" and its resolution. Got the outcome wrong? Call /unresolve, then resolve it again — the points it awarded are deleted and recomputed. Settled too soon? Reopen it with a new deadline and members can forecast on it again:
curl -s -X POST \
"$HOST/api/v1/questions/$QUESTION_ID/unresolve" \
-H "Authorization: Bearer $DIVINATION_TOKEN" \
-H 'content-type: application/json' \
-d '{ "closesAt": "2026-12-31T17:00:00Z" }'Errors
Anything but a 2xx carries the same shape. code is stable and safe to branch on; message is for humans. Rejected input adds fieldErrors, keyed by field.
{
"error": {
"code": "invalid_request",
"message": "Close time must be at least 5 minutes from now",
"fieldErrors": { "closesAt": "Close time must be at least 5 minutes from now" }
}
}| Status | code | When |
|---|---|---|
| 400 | invalid_request | Malformed or invalid body, or a bad query parameter. |
| 401 | unauthorized | Missing token, or one that is unknown, revoked or expired. |
| 403 | forbidden | The token is missing the scope the endpoint needs, or the person or group behind it isn't an admin of the group and the action is admin-only. |
| 404 | not_found | No such group or question — orone you can't see, which is deliberately indistinguishable. |
| 409 | conflict | The question's state rules the action out, e.g. resolving one that is already resolved. |
A worked example
Open a question when a release branch is cut, and settle it when the deploy finishes:
#!/usr/bin/env bash
set -euo pipefail
HOST=https://divination.io
AUTH="Authorization: Bearer $DIVINATION_TOKEN"
JSON='content-type: application/json'
# 1. Open the question, keeping its id
QUESTION_ID=$(curl -sf -X POST "$HOST/api/v1/groups/$GROUP_ID/questions" \
-H "$AUTH" -H "$JSON" \
-d "{
\"type\": \"binary\",
\"title\": \"Will $RELEASE ship without a rollback?\",
\"closesAt\": \"$(date -u -d '+7 days' +%Y-%m-%dT%H:%M:%SZ)\"
}" | jq -r .id)
# 2. …a week later, settle it
curl -sf -X POST "$HOST/api/v1/questions/$QUESTION_ID/resolve" \
-H "$AUTH" -H "$JSON" \
-d '{ "outcome": "yes" }' > /dev/null
# 3. See who is winning
curl -sf "$HOST/api/v1/groups/$GROUP_ID/leaderboard" -H "$AUTH" \
| jq -r '.data[] | "\(.rank). \(.user.name // .user.email) \(.total)"'Curious how the points are worked out? How scoring works.