Book reading level matcher
A reading level is most useful when it helps turn a long book list into a manageable set of choices.
Run — free
This matcher compares one student's grade-equivalent level with the level assigned to every supplied book, then returns only titles inside an inclusive suitability range. Use the default range of one grade below and above, or adjust either side for a more supported or more challenging selection. The original order is preserved, making the result easy to use in a lesson plan, library list, or reading conference.
Build a useful candidate list
Start with the student's current grade-equivalent reading level and a list of books whose levels come from the same assessment system or a reasonably comparable source. Each book needs a title and a numeric reading level. Consistency matters because two publishers can estimate text difficulty differently even when both display a grade-like number. The matcher does not invent, infer, or look up levels; it evaluates the values you provide. By default, it accepts books from one grade below through one grade above the student's level. Both boundaries are inclusive, so a student at 4.5 receives a book at 3.5 or 5.5 as well as books between them. Results retain the order of the input list, which lets an educator arrange titles by topic, availability, or personal preference before filtering. A book outside the computed interval is simply omitted. An empty result is valid and signals that the supplied collection needs different titles or a wider range, not that the calculation failed. This clear separation keeps matching predictable and auditable.
Adjust support and challenge independently
The lower and upper distances can be changed separately with range_below and range_above. That asymmetry is useful because suitable reading is not always centered on one score. For independent practice, an educator might allow more room below the measured level while keeping the upper boundary close. For guided reading with adult support, the upper distance might be larger. A value of zero makes that side of the interval equal to the student's level. The lower boundary never drops below grade level zero, even when the requested distance would cross it. Treat these controls as selection aids rather than diagnoses. Grade-equivalent scores summarize aspects of text difficulty, but they do not capture a reader's background knowledge, motivation, decoding profile, language experience, or interest in a subject. After filtering, review the titles for content, purpose, format, and the support available during reading. The best range is the one that produces productive choices for a particular activity while leaving room for professional judgment and student voice.
Interpret and reuse the result
The response reports the student level, the exact minimum and maximum levels used, the number of matches, and the matching book records. This makes the decision easy to explain: every returned title has a supplied level greater than or equal to the minimum and less than or equal to the maximum. Because the algorithm is deterministic, the same input always produces the same output, and it never calls an external catalog or sends the list elsewhere for enrichment. That behavior suits classroom tools, library workflows, and repeatable data pipelines. Save the range alongside a generated list when you need an audit trail, especially if different activities use different tolerances. If a book's reading level changes in your source data, update the supplied record and run the match again. Do not interpret a missing title as a judgment about literary value or student ability; it only means the numeric level fell outside this run's interval. Final selection should also consider accessibility, age appropriateness, curriculum goals, and the student's own interests.
What you can do with it
Prepare an independent reading shelf
Filter a classroom inventory to a supportive numeric band before letting a student choose among topics and formats.
Plan a guided reading session
Use a wider upper range when instruction and discussion will support books that are somewhat more challenging.
Screen a library recommendation list
Reduce a long exported list to level-compatible candidates while preserving its curated order.
FAQ
What range is used by default?
The default includes books from one grade level below through one grade level above the student's grade-equivalent level, including both boundaries.
Can the lower and upper ranges be different?
Yes. Set range_below and range_above independently to reflect the support, purpose, and challenge intended for the reading activity.
Does this tool look up book reading levels?
No. It only filters the titles and reading levels supplied in the request; it does not search an external book database.
What happens when no books are in range?
The result contains an empty matched_books array and a matched_count of zero. You can then adjust the range or supply another list.
Is a grade-equivalent match a complete recommendation?
No. It is a numeric screening step. Educators should also consider interest, content, accessibility, language background, instructional purpose, and available support.
What does an API request cost?
Each API request costs $0.002. The browser version can run the same deterministic matching logic without a network lookup.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/edu/reading-level-book-match \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"student_level":4.5,"books":[{"title":"The Wild Robot","reading_level":4.2},{"title":"Charlotte'\''s Web","reading_level":4.4},{"title":"The Giver","reading_level":6.8}]}'const res = await fetch("https://api.kit.forhosting.com/edu/reading-level-book-match", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"student_level": 4.5,
"books": [
{
"title": "The Wild Robot",
"reading_level": 4.2
},
{
"title": "Charlotte's Web",
"reading_level": 4.4
},
{
"title": "The Giver",
"reading_level": 6.8
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/edu/reading-level-book-match",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"student_level": 4.5,
"books": [
{
"title": "The Wild Robot",
"reading_level": 4.2
},
{
"title": "Charlotte's Web",
"reading_level": 4.4
},
{
"title": "The Giver",
"reading_level": 6.8
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/edu/reading-level-book-match", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"student_level":4.5,"books":[{"title":"The Wild Robot","reading_level":4.2},{"title":"Charlotte\'s Web","reading_level":4.4},{"title":"The Giver","reading_level":6.8}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"student_level":4.5,"books":[{"title":"The Wild Robot","reading_level":4.2},{"title":"Charlotte's Web","reading_level":4.4},{"title":"The Giver","reading_level":6.8}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/edu/reading-level-book-match", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"student_level": 4.5,
"books": [
{
"title": "The Wild Robot",
"reading_level": 4.2
},
{
"title": "Charlotte's Web",
"reading_level": 4.4
},
{
"title": "The Giver",
"reading_level": 6.8
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "edu.reading_level_book_match",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |