Errors
Every error the Unitley REST API can send, its HTTP status, what it means, and whether to retry the call or fix the request first.
Every failed REST call answers with an HTTP error status and one JSON object, error. The codes are the same for every tool.
Over MCP, a tool that fails returns the same code, message and issues in its result, marked isError. A request the MCP server refuses before any tool runs gets a JSON-RPC error instead. See The MCP server.
The error envelope
| Field | What it holds |
|---|---|
code | What went wrong, as one of the codes below. Branch on this. |
message | A sentence written to be shown to a person. For bad arguments, it names the first problem. |
issues | Every problem the check against the tool's schema found, each with a path (the field, with nested fields joined by dots) and a message. A few argument problems are found after that check, such as a to before from (to: The end of the range is before its start.) or a page cursor that is not valid. Those name the field in message and leave issues empty, as other errors do. The exception is the game in log_bet: a gameId that is not on the schedule (not_found) or is in another sport (invalid_input) has one issue, on gameId. |
retryAfterSeconds | How many seconds to wait. Sent with rate_limited only. |
Write your code against code, not the wording of message, and show message to the person using your app.
Every code
| Code | Status | Meaning |
|---|---|---|
invalid_input | 400 | The arguments or the body are wrong. The message says how, and issues lists what the schema check found. |
invalid_tokenREST only | 401 | No token, or one that is unknown, revoked or expired. Comes in the error envelope, with a WWW-Authenticate challenge. |
invalid_tokenMCP only | 401 | The same token problems over MCP. The body is an OAuth error, not the envelope: error is invalid_token and error_description holds the message, which the WWW-Authenticate challenge repeats. |
insufficient_scope | 403 | A write tool called with a read-only token. WWW-Authenticate names the scope it needs: scope="write". |
forbidden | 403 | The request reached a host that does not serve the API, or came from another site in a browser. |
not_found | 404 | The game, bet or pick does not exist or is not yours, or a tool that sizes a stake finds no bankroll set up. |
unknown_tool | 404 | There is no tool by that name. |
payload_too_large | 413 | The body is over 256 KB. |
unsupported_media_type | 415 | The body is not sent as application/json. |
limit | 422 | A rule refused the request, such as a date range or a page that is too large. |
rate_limited | 429 | A budget is spent for now. retryAfterSeconds and the Retry-After header say when to try again. |
internal | 500 | Something failed on Unitley's side. Try again in a moment. |
unavailable | 503 | Unitley could not serve the call just then, as when its database is out of reach. Try again in a moment. |
Examples
A bad argument: calc_implied_probability with odds of 50, which American odds cannot be.
{
"error": {
"code": "invalid_input",
"message": "americanOdds: American odds must be +100 or more, or -100 or less.",
"issues": [
{
"path": "americanOdds",
"message": "American odds must be +100 or more, or -100 or less."
}
]
}
}A tool that saves to your account, called with a read-only token:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="unitley", error="insufficient_scope", scope="write"
Content-Type: application/json
{
"error": {
"code": "insufficient_scope",
"message": "log_bet saves to your account, and this access token is read-only. Create a token with write access on the Connect page to use it.",
"issues": []
}
}Authentication challenges
A token problem comes with a WWW-Authenticate header that says what is wrong:
| When | Status | WWW-Authenticate |
|---|---|---|
| No token, or one not shaped like a Unitley token | 401 invalid_token | Bearer realm="unitley" |
| A token that is unknown, revoked or expired | 401 invalid_token | Bearer realm="unitley", error="invalid_token" |
| A read-only token calling a tool that saves | 403 insufficient_scope | Bearer realm="unitley", error="insufficient_scope", scope="write" |
The MCP server answers a refused token differently. Both 401s carry Bearer error="invalid_token", error_description="…" with the message, and the body is {"error":"invalid_token","error_description":"…"} rather than the envelope. A read-only token calling a tool that saves gets no challenge there: the tool's result is the insufficient_scope error. See The MCP server.
The messages say what to do:
- No token: "Send a Unitley access token as Authorization: Bearer unt_... (create one on the Connect page)."
- A dead token: "This access token is unknown, revoked or expired. Create a new one on the Connect page."
- The wrong scope: the tool's name, then "saves to your account, and this access token is read-only. Create a token with write access on the Connect page to use it."
Create tokens on the Connect page. See Tokens and security.
Retry-After
Every 429 rate_limited answer has a Retry-After header, in seconds, and the same number in retryAfterSeconds. The message ends with the wait in words, such as "Try again in a minute."
- "Too many requests." means the per-address budget is spent.
- "Too many tool calls." means your per-token or per-user budget is spent.
- "jev_classify is limited to" means that tool's own budget is spent.
- "Jev is busy." means TypeSafe asked Unitley to wait.
Budgets count in fixed windows that reset on the clock. A call made before the wait is over is refused again and still counts, and it does not bring the reset any closer. See Rate limits.
Retry or fix
Some errors go away if you wait. The rest come back until you change the request.
Fix the request first
| Code | What to do |
|---|---|
invalid_input | Fix what issues and message name, or the body. Sending the same call again gets the same answer. |
unsupported_media_type | Send the arguments as JSON, with Content-Type: application/json. |
payload_too_large | Send a smaller body. It must be 256 KB or less. |
unknown_tool | Check the name against the tool list. Names are case-sensitive. |
not_found | Check the id. Get game ids from get_week_slate, and bet ids from get_bet_log. With no bankroll set up, calc_kelly_stake and save_recommendation ask you to set one up first. |
limit | Change the request as the message says, such as a shorter date range, or delete old picks to make room. |
insufficient_scope | Use a token with write access. |
invalid_token | Check the header. If the token was revoked or has expired, create a new one. |
forbidden | Send the request to https://console.unitley.com/api/v1/tools from a server or script, not from a browser on another site. |
Wait and retry
| Code | What to do |
|---|---|
rate_limited | Wait for Retry-After, then send the call again. |
unavailable | Try again in a moment. If the message asks you to add or replace a key, as jev_classify does when no TypeSafe key is saved, do that first. |
internal | Try again in a moment. If it keeps failing, wait longer between tries. |