There is no sandbox or test mode. Every key is live, every graded copy spends real credits, and exams you create appear in the institute’s dashboard. Test with short exams and a handful of copies, and delete test exams you no longer need.
1. Keys per environment
API keys belong to an institute. An admin of that institute creates them in the Vacademy admin dashboard under Settings → Integrations → API keys (the card for Evaluation API keys), once Evalezy has enabled the Evaluation API for the institute (hello@evalezy.com).- One key per environment and per system. Separate keys for staging and production, and for each system that calls the API (for example the ERP backend and a nightly sync job). You can then revoke one without stopping the others, and tell their traffic apart.
- Give each key only the scopes it needs. New keys get
evaluation:readandevaluation:write.
Keep
evaluation:finalize on the one system that publishes results.
- Set an expiry for keys used in pilots or by contractors.
- Check each key with
GET /me. It needs no scope and returns who the key is and what it can do:
Response
Production and staging use different keys, each with the fewest scopes it needs, and
GET /me returns the institute you expect.2. Store keys as secrets
A key isvak_eval_ followed by 48 hexadecimal characters, and it is shown once, when it is created.
- Server-to-server only. Never put a key in a web page, a mobile app or any code that runs on a user’s device. Your app calls your backend; your backend calls Evalezy.
- Keep it in a secret manager or an environment variable injected at deploy time, never in source control.
- Never log the full key. Log the first 16 characters if you need to tell keys apart.
- Rotate without downtime. Create the new key, deploy it, confirm traffic with
GET /me, then revoke the old key. Revocation takes effect within about a minute. - Revoke at once if a key may have leaked, then create a new one.
The key lives only in your server-side secret store, appears in no logs or client code, and you have rotated it once in staging.
3. Retries with Idempotency-Key
Networks fail. EveryPOST accepts an optional Idempotency-Key header, so a retried request never creates a second exam, submission or charge.
- Derive the key from your own IDs, for example
sub-{exam_id}-{student_id}-{upload_id}, so a retry after a crash reuses it. Any printable ASCII string up to 255 characters works. - Same key, same request: you get the stored response again, with the header
Idempotent-Replayed: true. - Same key, different request:
422 idempotency_key_reused. This is a bug in how you build keys. - Same key while the first request is still running:
409 request_in_progress. Wait a moment and retry with the same key. - Keys are kept for 48 hours, per institute, so a retry from a second key of the same institute replays too.
Every
POST your integration makes sends an Idempotency-Key built from your own IDs, and every exam has an external_ref.4. Error handling
Every error has the same shape, and every response carries anX-Request-Id header:
error.code, never on message. Codes never change once published; messages may. Log request_id with every failure.
A grading failure is not an HTTP error. The submission was accepted, and later its
status became failed with an error.code such as copy_unreadable, language_not_supported or timed_out. Handle these in your sync worker.
Successful responses can also carry warnings[], for example auto_rubric, negative_marks_ignored or pages_beyond_vision_limit. Log them and show the important ones to the person who set up the exam.
Text you send
- All text is plain text, never rendered as HTML. Send text, not markup, and render what you read back as text.
- Titles, question and option labels, section names, criterion names and candidate names, roll numbers and classes cannot contain
<or>. Such a value returns422 validation_failedwith the field codeinvalid_characters. - A NUL character (
\u0000) anywhere in a request body returns422 validation_failedwith the field codeinvalid_characters. Strip it from text copied out of other systems. metadatais a JSON object of up to 2 KB.
Your client branches on
error.code, logs request_id, retries only 409 request_in_progress, 429 and any 5xx (parsing error bodies defensively), and surfaces failed submissions to a person.5. Credits
Grading is paid with credits from the institute’s balance. Prices are fixed and known before grading:
Institutes on a contract price see
"rate_source": "contract" in quotes. Credits are bought in the Vacademy admin dashboard; current packs are on evalezy.com/pricing.
Monitor the balance from your side:
GET /creditsreturnsbalance,committed(quoted for copies still in the queue),available(what new submissions can use), the institute’srate_cardandspent_30d.POST /credits/quoteprices a batch before you submit it. Send exactly one ofpages,upload_ids(exact page counts of uploads, up to 100) ortyped_answers. The answer hastotal,availableandsufficient.- A submission that can’t be paid for returns
402 insufficient_creditswithrequiredandavailableindetails. Nothing is queued.
Your integration checks
GET /credits on a schedule, alerts below a threshold, and quotes big batches first.6. Quotas and rate limits
Daily copy quota. Each institute can submit a fixed number of copies per UTC day, 2,000 by default; re-evaluations count too. A key can also have its own lower daily cap (daily_copy_cap in GET /me). Past the limit, submissions return 429 daily_quota_exceeded with details.quota, details.limit, details.resets_at and a Retry-After header. Quotas reset at 00:00 UTC. For exam seasons or large programmes, ask hello@evalezy.com for a higher quota well before the date.
Rate limits. Requests are limited per key and per institute:
GET requests, POST /exams/search, POST /candidates/search and POST /credits/quote count as reads; other requests count as writes. POST /uploads also counts each file presigned. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A refused request returns 429 rate_limited with Retry-After.
Size limits to design around:
Your uploads and submissions are paced under the write limits, your client honours
Retry-After, and your quota covers your busiest day.7. The teacher review step
The AI never publishes results. Every mark is a draft until you finalize it, and nothing is sent to students or parents when you do; publishing is up to you.- Decide who reviews. Teachers can review on the institute’s Vacademy dashboard, where API exams are tagged Source: API, or in your own UI through
PATCH /submissions/{id}/questions/{question_id}andPOST /submissions/{id}/approve. - Review flagged copies first.
needs_reviewistruewhen a question failed, the AI’s confidence was low, or a copy had more than 40 pages. Filter withneeds_review=true. - Finalize deliberately.
POST /exams/{id}/finalizelocks results: overrides and re-evaluation then return409 submission_finalized. Partially graded and failed copies are skipped unless you sendallow_partial. - Plan for rechecks.
POST /submissions/{id}/unfinalizewith areasonputs one result back on hold; correct it, then finalize again.
Your product makes clear which marks are AI drafts, gives teachers a place to review them, and only publishes finalized results.
8. Know what is not available yet
Check that your launch doesn’t depend on something on the Roadmap: webhooks (poll instead, see Syncing results), phone photos (send one PDF), bulk batches matched by name, creating an exam from a question-paper PDF, rubric generation on request and rubric locking, hosted review links, CSV results, Hindi and regional languages, and SDKs. Choice groups (“attempt any N of M”) are in beta and enabled on request.Support
Email hello@evalezy.com. For a problem with a specific request, include therequest_id from the error (or the X-Request-Id header), the endpoint, and the time in UTC. Never send your API key.
Handwritten term exams
The full school flow, end to end.
Syncing results
The polling worker your launch needs.