Errors
Building a hosted tool? The Voshi API client turns every one of these into a thrown error with a name that says what happened — see Errors in the API section. This page is the HTTP contract behind it, for self-hosted tools calling the REST APIs directly.
All Voshi API errors share one JSON envelope:
{
"id": "Ie4rR0r1d",
"api": "api",
"time": "2026-07-27T18:10:00+00:00",
"error": "ValidationError",
"message": "score must be between 0 and 1",
"details": {}
}
idstringA unique ID for this error occurrence. Include it when contacting support — it lets the Voshi team find the corresponding server-side report.
errorstringThe error class, matching the status code below.
messagestringA human-readable description of what went wrong.
detailsobjectStructured extras, when the error has them.
The response format is content-negotiated on the Accept header: API clients sending Accept: application/json (or no preference) get the JSON envelope; a browser gets an HTML error page carrying the same report ID.
Status codes
| Status | Error | When |
|---|---|---|
401 | AuthenticationError | Missing, malformed, invalid, or expired credentials — a bad or rotated API key, a suspended tool, an API key on a route that only takes a user's own token, or a stale api.token. |
403 | NotAuthorized | Authenticated, but not allowed — e.g. an storage token used on another user's row, or an API key used for another tool or for a course your tool isn't placed in. |
404 | Http404 | The resource doesn't exist — or belongs to another tool. Voshi deliberately doesn't distinguish these, so other tools' data can't be probed. |
422 | ValidationError | The request was understood but a value is invalid. The message says which — see the per-endpoint error tables. Also how an unprovisioned course or location row answers. |
500 | ApiException | Something failed on Voshi's side. Retry, and report the error id if it persists. |
Validation failures return 422, not 400. If your HTTP client only treats 400 as a validation error, widen the check.
Provisioning errors
Two 422s come from the provisioning flow:
| Message | Cause |
|---|---|
App … has not been provisioned for Context … | A GET/PUT/PATCH on a context or location storage row whose level isn't provisioned yet. |
App … must be provisioned before its locations are provisioned | A PUT to a provision.location URL while that course's provision.context is still unfinished. Call the course URL first. |
Errors that don't reach your code
Launch-time failures (draft tool, broken callback URL, a course that isn't provisioned yet) happen before your endpoint is called: the user sees a Voshi page with a report ID, and your tool never hears about the launch. See errors on Voshi's side.
Deep-link failures work the same way, in the other direction: if your locations endpoint times out, refuses the request, or returns something Voshi can't read, the instructor sees the error and your tool is told nothing.
A rejected grade is not an HTTP error
A score the LMS refuses still returns 200: the call was valid and was made, and the refusal is reported in the attempt itself as status: "failed", with the reason in data.error. Check status on every submission — a tool that only checks the HTTP code will think a rejected score reached the gradebook. To retry, post again; each submission is its own attempt. See failed attempts vs. errors.