KEATH Public API v1
This page is written for integration work, not for legal review.
If you only remember one thing, remember this:
- Use
model_idwhen you are choosing a marking model. - Use
assignment_idwhen you are grading work for an assignment that already exists. - Use
POST /evaluations/one-passwhen you want to grade once without creating an assignment.
The safest first integration is:
GET /creditsGET /modelsPOST /assignmentswith amodel_idPOST /evaluationswith the returnedassignment_id- Poll status until
SUCCESS,FAILURE, orCANCEL - Fetch the final result
Base URL
https://keath.ai/api/keath/public/v1
All requests use:
- Header:
X-API-Key: kct_your_api_key - Response format: standard KEATH JSON response body
API keys are created from the product-side authenticated endpoints:
POST /api/keath/public_api/keys/createGET /api/keath/public_api/keys/listPOST /api/keath/public_api/keys/revoke
What Changed
This documentation replaces the old X-TOKEN-KEY + /api/open/evaluation/* flow.
The current public API is centered around:
- Checking credits and accessible models
- Parsing question papers and rubrics from multimodal inputs
- Creating marking models and student assignments after preview confirmation
- Submitting essay evaluations to an existing assignment by text or file
- Rewriting feedback into a different style
Endpoint Overview
| Endpoint | Method | Purpose |
|---|---|---|
/credits | GET | Check current available credits |
/models | GET | List marking models the API key owner can access |
/models | POST | Create/train a marking model |
/assignments | POST | Create a student assignment from an existing model_id |
/questions-ingest-preview | POST | Parse question papers from PDF/image/url assets |
/rubrics-ingest-preview | POST | Parse rubric documents from PDF/image/url assets |
/evaluations | POST | Submit an essay to an existing assignment for grading |
/evaluations/one-pass | POST | Submit model id, question, rubrics, and student work in one request |
/evaluations/batch | POST | Queue 1-100 one-pass evaluations in one request |
/evaluations/:taskId/status | GET | Poll lightweight evaluation status |
/evaluations/:taskId | GET | Fetch the evaluation result payload |
/evaluations/:taskId/cancel | POST | Cancel a queued or in-progress evaluation |
/feedback-rewrite | POST | Rewrite feedback sections into a different style |
/uploads/file | POST | Upload one local PDF/image file and receive a reusable public URL |
Authentication
Example:
X-API-Key: kct_your_api_keyIf the key is missing or invalid, the API returns 401.
This key is the KEATH public API key for your account. Do not send the internal model provider key, such as a Gemini or NewAPI key, to public API endpoints.
Key Scope and Billing
Public API keys currently support:
user: access only the key owner's modelsorganization: access models across the organization
Important details:
- Organization-scoped keys can only be created by an organization admin.
- Organization-scoped model access remains tied to the key owner's current organization-admin permission.
- Credits are still charged to the key owner's KEATH account even for
organizationscope. The current billing mode remainsowner_user.
GET /credits
Returns the current usable credits for the API key owner.
Example:
curl "https://keath.ai/api/keath/public/v1/credits" \
-H "X-API-Key: kct_your_api_key"GET /models
Returns the marking models available to the API key owner. Use model_id from this response when creating assignments or one-pass evaluations.
Example:
curl "https://keath.ai/api/keath/public/v1/models" \
-H "X-API-Key: kct_your_api_key"Typical model item:
{
"model_id": 930,
"assignment_name": "O Level Situational Writing Model",
"assignment_desc": "Reusable marking model",
"post_status": "ready",
"creation_method": "custom",
"total_score": 30
}POST /questions-ingest-preview
Use this endpoint to understand question sheets before creating an assignment.
Supported inputs:
- Existing public URLs through
assets[] - Direct file upload with
multipart/form-data - Optional
specificationto target a specific question when a PDF contains multiple questions
JSON mode
{
"assets": [
{
"url": "https://cdn.example.com/question-pack.pdf",
"mime_type": "application/pdf",
"name": "question-pack.pdf"
}
],
"specification": "Only parse Question 2. Ignore the other pages.",
"assignment_name": "O Level Situational Writing Practice 2",
"assignment_desc": "Situational writing paper for secondary learners"
}Multipart mode
The API now accepts direct uploads.
- Field name:
fileorfiles - Supported MIME types:
application/pdfimage/pngimage/jpegimage/webpimage/gif
- Max files per request:
10 - Max file size per file:
20 MB
You should send an explicit MIME type when possible, for example ;type=application/pdf. If a client sends application/octet-stream or omits the MIME type, KEATH will try to infer the type from the file extension: .pdf, .png, .jpg, .jpeg, .webp, or .gif.
Example:
curl -X POST "https://keath.ai/api/keath/public/v1/questions-ingest-preview" \
-H "X-API-Key: kct_your_api_key" \
-F "files=@./question-pack.pdf;type=application/pdf" \
-F "files=@./page-2.png;type=image/png" \
-F "specification=Only parse Question 2 from this upload set." \
-F "assignment_name=O Level Situational Writing Practice 2"Mixing uploaded files with existing URLs
When you need both direct uploads and existing public URLs in the same request, send:
- uploaded files via
files - URL assets via a JSON-stringified
assetsfield
Example:
curl -X POST "https://keath.ai/api/keath/public/v1/questions-ingest-preview" \
-H "X-API-Key: kct_your_api_key" \
-F "files=@./scan-1.jpg;type=image/jpeg" \
-F 'assets=[{"url":"https://cdn.example.com/appendix.pdf","mime_type":"application/pdf","name":"appendix.pdf"}]' \
-F "specification=Use the uploaded image as the target question and treat the appendix as supporting context."POST /rubrics-ingest-preview
Use this endpoint to extract rubric levels before assignment creation.
It supports the same two transport modes:
- JSON with
assets multipart/form-datawith direct file uploads
Rubric-specific fields:
total_scorecum_method:sumoraverage
JSON example:
{
"assets": [
{
"url": "https://cdn.example.com/rubric.pdf",
"mime_type": "application/pdf"
}
],
"specification": "Use the rubric table on page 3 only.",
"total_score": 30,
"cum_method": "sum"
}Multipart example:
curl -X POST "https://keath.ai/api/keath/public/v1/rubrics-ingest-preview" \
-H "X-API-Key: kct_your_api_key" \
-F "file=@./rubric.pdf;type=application/pdf" \
-F "total_score=30" \
-F "cum_method=sum" \
-F "specification=Use the rubric on the first page only."POST /models
Create a reusable marking model after questions and rubrics have been confirmed.
The request body is aligned with the product's internal model creation DTO. In practice this means you should send the final:
assignment_nameassignment_desc- parsed
questions - parsed
rubrics - scoring metadata such as
total_scoreandcum_method
Recommended workflow:
- Preview questions
- Preview rubrics
- Let the user review and edit the result
- Call
POST /models
POST /assignments
Create a student-facing assignment from an existing marking model. This matches the product UI flow: first choose a model in Model Selection, then create the assignment.
Required fields:
model_id: a ready model fromGET /modelsassignment_nameassignment_descdeadline_timeexpected_number
Optional fields:
project_subjectstart_timefile_type:word,pdf,image, ortext; default ispdf
Example:
{
"model_id": 930,
"assignment_name": "Situation Writing Test",
"assignment_desc": "Student-facing writing assignment",
"project_subject": "English",
"deadline_time": "2026-06-01T00:00:00.000Z",
"expected_number": 30,
"file_type": "pdf"
}Typical response:
{
"assignment_id": 1620,
"model_id": 930,
"assignment_name": "Situation Writing Test",
"status": "active"
}POST /evaluations
Submit one essay to an existing student assignment and start an asynchronous grading task.
Use this endpoint when the assignment already exists in KEATH. The assignment_id is the product assignment/project ID, for example the id in /homeTab/person/assignments/detail?id=1620.
Required fields:
assignment_id: an existing assignment ID fromPOST /assignmentsor the product assignment detail URL- One of:
paper_content: the student's essay text- an uploaded answer file through
multipart/form-data
Optional fields:
current_feedbacks: current rubric-level feedback sections you want KEATH to consider when re-grading or adjusting outputstyle: one ofBullet,Short, orLong; default isBulletstudent_id: your external anonymous student ID; KEATH passes it through with the task metadata and does not create or update a KEATH student record
Supported answer file uploads:
- PDF and images: parsed with Gemini multimodal extraction, including relevant visual content descriptions
- DOCX: extracted server-side
- TXT and RTF: extracted server-side
Multipart field names accepted for answer files:
answer_fileanswer_filespaper_filepaper_filesresponse_fileresponse_filesfileorfiles
Upload limits:
- Max files per request:
10 - Max file size per file:
20 MB
Example:
{
"assignment_id": 1620,
"paper_content": "Dear Mrs Tan,\n\nI am writing to request permission to organize a class recycling drive next Friday...",
"current_feedbacks": [
{
"item": "Content",
"comment": "The response addresses the task but needs a more explicit closing request.",
"score": 6
}
],
"style": "Bullet"
}Multipart example:
curl -X POST "https://keath.ai/api/keath/public/v1/evaluations" \
-H "X-API-Key: kct_your_api_key" \
-F "assignment_id=1620" \
-F "answer_file=@./student-answer.pdf;type=application/pdf" \
-F "student_id=anon-student-001" \
-F "style=Bullet"Typical response payload:
{
"task_id": "task_123",
"status": "PENDING",
"assignment_id": 1620,
"model_id": 930,
"billing_mode": "owner_user",
"scope": "user",
"credits_charged": 8
}POST /evaluations/one-pass
Submit a grading request without creating a new assignment. KEATH uses an existing model for scoring, while the request supplies the question, rubrics, and student work for this single evaluation task.
Use this when an integration already knows which KEATH model to use but does not want to create an assignment first.
Required fields:
model_id: an existing model ID fromGET /models- Student work through either
paper_contentor uploaded answer file(s)
Rubrics can come from one of:
rubrics: structured JSON arrayrubric_text: plain rubric textrubric_file/rubric_files: uploaded rubric files- If none is provided, KEATH falls back to the selected model's stored rubrics
Question/context can come from:
question_textquestion_file/question_filesassignment_desc- If none is provided, KEATH falls back to the selected model's stored description
Supported direct upload formats:
- Question/rubric PDF or images
- Question/rubric DOCX, TXT, or RTF
- Student answer PDF, images, DOCX, TXT, or RTF
Optional fields:
assignment_nameassignment_desccum_method:sumoraverage; defaults to the selected model's settingtotal_score: rubric score hintspecification: parsing instruction such as "only use Question 2"style:Bullet,Short, orLong; default isBulletstudent_id: your external anonymous student IDcurrent_feedbacks
Example:
curl -X POST "https://keath.ai/api/keath/public/v1/evaluations/one-pass" \
-H "X-API-Key: kct_your_api_key" \
-H "Idempotency-Key: eval-student-001-attempt-1" \
-F "model_id=930" \
-F "question_file=@./question.pdf;type=application/pdf" \
-F "rubric_file=@./rubric.docx;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document" \
-F "answer_file=@./student-answer.txt;type=text/plain" \
-F "student_id=anon-student-001" \
-F "specification=Use Question 2 only." \
-F "style=Bullet"Typical response payload:
{
"task_id": "public_task_123",
"status": "QUEUED",
"stage": "queued",
"model_id": 930,
"student_id": "anon-student-001",
"billing_mode": "owner_user",
"scope": "user",
"credits_charged": 0,
"credit_cost": 8,
"retryable": false
}This endpoint returns HTTP 202 Accepted. That means KEATH has safely accepted the work; it does not mean grading is finished. Poll with the returned public task_id.
Use a unique Idempotency-Key for each logical submission. Retrying the same request with the same key returns the same task_id. Reusing that key with different text or files returns 409 instead of creating an accidental duplicate.
Files are stored before the task enters the worker queue. Question, rubric, PDF, image, DOCX, TXT, and RTF processing happens in the queue, so the initial request does not need to remain open for several minutes. Credits are charged only after a complete result is validated. Failed or cancelled tasks keep credits_charged: 0.
POST /evaluations/batch
Submit between 1 and 100 one-pass evaluations as one asynchronous batch. Each item accepts the same fields as /evaluations/one-pass.
curl -X POST "https://keath.ai/api/keath/public/v1/evaluations/batch" \
-H "X-API-Key: kct_your_api_key" \
-H "Idempotency-Key: class-5a-writing-2026-07-15" \
-H "Content-Type: application/json" \
-d '{
"evaluations": [
{
"model_id": 930,
"question_text": "Write a letter proposing one school improvement.",
"paper_content": "Dear Principal, ...",
"student_id": "anon-student-001",
"style": "Bullet"
},
{
"model_id": 930,
"question_text": "Write a letter proposing one school improvement.",
"paper_content": "Dear Principal, I suggest ...",
"student_id": "anon-student-002",
"style": "Bullet"
}
],
"callback_url": "https://integration.example.com/keath/results"
}'The optional callback_url must be a public HTTPS URL. KEATH retries callback delivery. You should still keep status polling as a fallback.
Typical HTTP 202 response:
{
"task_id": "public_batch_123",
"status": "QUEUED",
"stage": "queued",
"count": 2,
"callback_url": "https://integration.example.com/keath/results",
"credits_charged": 0,
"credit_cost": 16,
"retryable": false
}GET /evaluations/:taskId/status
Poll a lightweight task snapshot while the evaluation is still running.
Example:
curl "https://keath.ai/api/keath/public/v1/evaluations/task_123/status" \
-H "X-API-Key: kct_your_api_key"Typical fields:
task_idstatusstagepositionlengthcur_step_namecur_steptotal_stepprogressmessage
status is the uppercase compatibility value. stage is the clearer lowercase lifecycle value:
QUEUED/queuedUPLOADING/uploadingSUBMITTED/submittedPROGRESS/processingSUCCESS/completedFAILURE/failedCANCEL/cancelled
Poll every 3 seconds at first, then back off to at most every 15 seconds. Stop after a bounded wait and retry later rather than keeping an infinite loop.
Compatibility note: POST /evaluations for an existing assignment still uses the legacy task path. It normally starts at PENDING and charges credits when the task is accepted. The new 202, idempotency, durable queue, and completion-only billing behavior described above applies to /evaluations/one-pass and /evaluations/batch.
GET /evaluations/:taskId
Fetch the evaluation record for a task owned by the API key owner.
Example:
curl "https://keath.ai/api/keath/public/v1/evaluations/task_123" \
-H "X-API-Key: kct_your_api_key"The result payload includes:
- aggregate
score - rubric-level
evaluationitems - parsed
rubrics - queue/progress fields when the task is not yet finished
If grading fails, the task status is FAILURE. In that case evaluation may contain an item with type: "error" and a plain error message in comment. Treat that as a failed grading run, not as a score.
Typical successful payload:
{
"task_id": "task_123",
"status": "SUCCESS",
"score": 15,
"evaluation": [
{
"item": "Content",
"score": 7,
"feedback": "Relevant ideas are present but the closing request can be stronger."
},
{
"item": "Language",
"score": 8,
"feedback": "Language is generally clear with a few minor slips."
}
],
"rubrics": [
{
"item": "Content"
},
{
"item": "Language"
}
]
}POST /evaluations/:taskId/cancel
Cancel a queued or in-progress evaluation.
Example:
curl -X POST "https://keath.ai/api/keath/public/v1/evaluations/task_123/cancel" \
-H "X-API-Key: kct_your_api_key"POST /feedback-rewrite
Rewrite existing feedback into a different format.
Supported styles:
bullet_pointsparagraphshort_sentences
You can also:
- provide a free-text
instruction - set
target_itemto rewrite only one criterion
Example:
{
"style": "bullet_points",
"instruction": "Make this shorter and more student-friendly.",
"sections": [
{
"item": "Content",
"comment": "Your answer contains relevant ideas but the explanation remains underdeveloped.",
"score": 7,
"max_score": 10
}
]
}POST /uploads/file
Upload one local PDF/image file and receive a reusable KEATH-hosted URL.
This is optional, but it is useful when:
- your client cannot expose a temporary public file URL
- you want to upload once and reuse the returned URL in
questions-ingest-previeworrubrics-ingest-preview - you are building a CLI or agent integration
Supported file types:
application/pdfimage/pngimage/jpegimage/webpimage/gifapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentapplication/rtftext/rtftext/plain
Request shape:
- Content type:
multipart/form-data - Field name:
file - File count: exactly
1file per request - Max file size:
20 MB
As with the preview endpoints, KEATH prefers an explicit MIME type. If your client sends application/octet-stream or omits the MIME type, KEATH will try to infer the type from the file extension when it is one of the supported PDF/image formats.
Example:
curl -X POST "https://keath.ai/api/keath/public/v1/uploads/file" \
-H "X-API-Key: kct_your_api_key" \
-F "file=@./question-pack.pdf;type=application/pdf"Typical response payload:
{
"name": "question-pack.pdf",
"mime_type": "application/pdf",
"size": 183245,
"path": "keath_prod/public_api/99/1746571871000_question-pack.pdf",
"url": "https://cdn.keath.ai/keath_prod/public_api/99/1746571871000_question-pack.pdf"
}Error Notes
Common request errors:
401: missing or invalidX-API-Key402: insufficient credits403: no access to the model or organization-scoped key is no longer authorized404: assignment, model, or evaluation not found409: model is not ready for evaluation, or the evaluation can no longer be cancelled400: malformedassets, unsupported file type, invalid request body, or missing required text / uploaded files
For multipart requests, if you send assets, it must be a valid JSON array string. For multipart evaluation requests, if you send rubrics or current_feedbacks, they must be valid JSON array strings.
The multimodal preview endpoints were live-tested with:
- JSON
assetspointing to an uploaded image - direct multipart image upload
- direct multipart question PDF upload
- direct multipart rubric PDF upload
The public evaluation endpoints were verified against:
- evaluation creation with an existing assignment
- evaluation creation from uploaded answer files
- one-pass evaluation with model id, supplied context, rubrics, and uploaded student work
- async status polling
- completed-result fetch
- cancellation for a pending task
- organization-scoped model visibility checks