Metadata Providers
Metadata providers tell Cinder how to fetch book information from external APIs. They power two features: search (finding book metadata by title/author) and discovery (homepage sections like trending, new releases, etc.).
Cinder ships with built-in providers (Apple Books, Open Library), but you can add your own to pull metadata from any API that returns JSON.
Schema Overview
A metadata provider config is a JSON object with these top-level fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique identifier (kebab-case, e.g. "my-provider") |
name | string | ✓ | Display name shown in the app |
version | string | ✓ | Semver string (e.g. "1.0.0") |
description | string | ✓ | Short description shown in UI |
icon | string | Ionicons icon name (e.g. "library") | |
capabilities | object | ✓ | What this provider supports |
auth | object | Authentication config | |
search | object | * | Search endpoint config |
discover | object | * | Discovery sections config |
rateLimit | object | Rate limiting config |
* Required if the corresponding capability is enabled.
Capabilities
The capabilities object declares what your provider supports:
{
"capabilities": {
"search": true,
"discover": true
}
}
search— Provider can search for books by query. Requires asearchconfig.discover— Provider can serve discovery sections for the Discover tab. Requires adiscoverconfig.
true but don't provide the matching config object, validation will fail on import.
Search Configuration
The search object defines how Cinder calls the API and parses results. It contains a request and response:
"search": {
"request": {
"method": "GET",
"url": "https://api.example.com/search?q={QUERY}&limit=25",
"headers": { "Accept": "application/json" },
"timeout": 15000
},
"response": {
"type": "json",
"resultsPath": "data.books",
"mapping": {
"title": "name",
"author": "writer",
"cover": "coverUrl"
}
}
}
Request Fields
method—"GET"or"POST"(required)url— API endpoint with template variables (required)headers— Custom HTTP headers (optional)body— JSON body for POST requests (optional)timeout— Milliseconds before timeout, default 30000 (optional)
Response & Mapping
resultsPath
Dot-notation path to the results array in the JSON response:
{ "results": [...] }→"results"{ "docs": [...] }→"docs"{ "data": { "books": [...] } }→"data.books"- Root is the array →
""or"."
Dot-Notation Mapping
Use dot notation to access nested fields and array elements in each result object:
"title"→result.title"author_name.0"→result.author_name[0](first element)"file.cover_url"→result.file.cover_url"formats.application/epub+zip"→result.formats["application/epub+zip"]
BookMetadata Fields
These are all the fields Cinder understands for book metadata. Map them in your response.mapping:
| Field | Type | Description |
|---|---|---|
title | string | Book title (required) |
author | string | Primary author name (required) |
authors | string[] | Array of all author names |
subtitle | string | Book subtitle |
cover | string | Cover image URL |
description | string | Book description / synopsis |
isbn | string | ISBN number |
publisher | string | Publisher name |
publishedYear | string | Year of publication |
pages | number | Page count |
language | string | Language code |
genres | string[] | Genre / subject tags (trimmed to 5) |
rating | number | Average rating |
ratingsCount | number | Number of ratings |
seriesName | string | Series name (e.g. "Harry Potter") |
seriesPosition | string | Position in series (e.g. "3") |
Discovery Configuration
Discovery sections appear in the Discover tab. Each section has its own request and optional response config:
"discover": {
"sections": [
{
"id": "trending",
"title": "📈 Trending Now",
"icon": "trending-up",
"request": {
"method": "GET",
"url": "https://api.example.com/trending?limit=20"
},
"response": {
"type": "json",
"resultsPath": "works",
"mapping": {
"title": "title",
"author": "author_name.0",
"cover": "cover_i"
}
}
}
]
}
response config, it inherits the mapping from the search config — saves you from duplicating the mapping!
Template Variables
Use these placeholders in your url and body fields — Cinder substitutes them at runtime:
| Variable | Replaced With |
|---|---|
{QUERY} | The user's search text |
{USER_API_KEY} | User's configured API key |
{PAGE} | Current page number (pagination) |
All values are URL-encoded automatically. Unused variables are silently removed.
Authentication
If the API requires authentication, add an auth object:
"auth": {
"type": "apiKey",
"headerName": "X-API-Key",
"userProvided": true
}
"apiKey"— SendsX-API-Keyheader (or customheaderName)"bearer"— SendsAuthorization: Bearer <key>"basic"— SendsAuthorization: Basic <key>"none"— No authentication
When userProvided is true, users configure their key in-app (stored securely in device keychain).
Example Config
A complete metadata provider with search and discovery:
{
"id": "my-book-api",
"name": "My Book API",
"version": "1.0.0",
"description": "Search and discover books via My Book API",
"capabilities": { "search": true, "discover": true },
"search": {
"request": {
"method": "GET",
"url": "https://api.example.com/books?q={QUERY}&limit=25",
"timeout": 15000
},
"response": {
"type": "json",
"resultsPath": "results",
"mapping": {
"title": "title",
"author": "author_name",
"cover": "cover_url",
"description": "synopsis",
"publishedYear": "year",
"genres": "categories",
"rating": "avg_rating"
}
}
},
"discover": {
"sections": [
{
"id": "trending",
"title": "📈 Trending Now",
"request": {
"method": "GET",
"url": "https://api.example.com/trending?limit=20",
"timeout": 10000
}
}
]
}
}
Importing
Three ways to add a metadata provider to Cinder:
- URL import — Host your JSON config at a public URL. In the app, go to Settings → Metadata Sources → Add → From URL.
- JSON paste — Copy the JSON and paste it directly in the app's import dialog.
Download Sources
Download sources tell Cinder how to search for ebook files from external APIs. When you browse a book and tap download, Cinder queries all your enabled sources and shows the results.
Schema Overview
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Unique identifier (kebab-case) |
name | string | ✓ | Display name shown in the app |
version | string | ✓ | Semver string (e.g. "1.0.0") |
type | string | ✓ | "directDownload" or "debrid" |
description | string | Short description | |
icon | string | Ionicons icon name | |
request | object | ✓ | How to call the API |
response | object | ✓ | How to parse results |
matching | object | Result filtering config | |
auth | object | Authentication config | |
rateLimit | object | Rate limiting config | |
pagination | object | Multi-page fetching | |
downloadResolve | object | Two-step download resolution | |
debridLinkTemplate | string | Template for constructing debrid links | |
trackers | string[] | Tracker URLs for debrid links | |
legal | object | Legal metadata & disclaimer |
Source Types
"directDownload"— Returns direct download URLs (HTTP links to epub/pdf/mobi files). Cinder downloads the file straight from the URL."debrid"— Returns magnet links, info hashes, or torrent URLs. Cinder routes these through your configured debrid service, which resolves them into direct download links.
Request Configuration
"request": {
"method": "GET",
"url": "https://api.example.com/search?q={TITLE}+{AUTHOR}",
"headers": { "Content-Type": "application/json" },
"timeout": 15000
}
For POST requests, add a body object — template variables inside strings are substituted automatically:
"request": {
"method": "POST",
"url": "https://api.example.com/v1/search",
"headers": { "Content-Type": "application/json" },
"body": {
"query": "{TITLE}",
"search_field": "title",
"size": 50,
"categories": [9001000, 9000000]
}
}
Response Parsing
Tell Cinder how to extract results from the JSON response:
"response": {
"type": "json",
"resultsPath": "hits",
"mapping": {
"title": "name",
"url": "downloadUrl",
"size": "fileSize",
"format": "extension",
"seeders": "seeders"
}
}
Download Result Fields
| Field | Type | Description |
|---|---|---|
title | string | Result title (required) |
url | string | Download URL / magnet / identifier (required) |
format | string | File type (epub, pdf, mobi, etc.) |
size | number | File size in bytes |
seeders | number | Seeder count (torrent sources) |
quality | string | Quality indicator |
source | string | Source name label |
date | string | Upload / publish date |
Result Matching
Cinder can automatically filter results to find the best matches for the book you're looking for:
"matching": {
"field": "title",
"threshold": 0.6,
"algorithm": "fuzzy"
}
"fuzzy"— Fuzzy string matching (recommended). Handles typos, word order differences."contains"— Result field must contain the search term."exact"— Exact case-insensitive match only.
Threshold ranges from 0.0 (match everything) to 1.0 (exact only). 0.5–0.6 is recommended for most APIs.
Template Variables
| Variable | Replaced With |
|---|---|
{TITLE} | Book title |
{AUTHOR} | Book author |
{ISBN} | ISBN (if available) |
{YEAR} | Publication year |
{SERIES} | Series name |
{SERIES_POSITION} | Position in series |
{USER_API_KEY} | User's configured API key |
{PAGE} | Current page number |
{DOWNLOAD_ID} | ID from search results (for two-step downloads) |
All values are URL-encoded automatically. Unused variables are silently removed.
Two-Step Downloads
Some APIs don't return direct links — instead they return an identifier that you pass to a second endpoint to get the real download URL. Add a downloadResolve config:
"downloadResolve": {
"request": {
"method": "GET",
"url": "https://api.example.com/get_links?id={DOWNLOAD_ID}",
"timeout": 60000
}
}
Flow: Search returns identifier in url field → user taps download → Cinder substitutes it into {DOWNLOAD_ID} → calls the resolve endpoint → gets the real download URL.
Debrid Link Templates
For debrid sources, debridLinkTemplate lets you construct the link sent to the debrid service from result fields. Use {field_name} placeholders:
"debridLinkTemplate": "magnet:?xt=urn:btih:{infoHash}&dn={title}",
"trackers": [
"udp://tracker.opentrackr.org:1337/announce",
"udp://open.stealth.si:80/announce"
]
If you don't set debridLinkTemplate on a debrid source, Cinder uses the raw url value as-is.
Pagination
For APIs that return paginated results:
"pagination": {
"type": "page",
"paramName": "page",
"startAt": 1,
"maxPages": 3,
"hasMorePath": "meta.hasMore"
}
Use {PAGE} in your request URL. Cinder stops when maxPages is reached, no results are returned, or hasMorePath is false.
Built-in Sources
Cinder ships with two built-in download sources for public domain content:
- Open Library Free Books — Free public domain ebooks from Open Library & Internet Archive.
- Project Gutenberg — Over 70,000 free public domain ebooks.
These are always available and cannot be removed.
Example Configs
Direct Download (GET)
{
"id": "my-ebook-source",
"name": "My Ebook Source",
"version": "1.0.0",
"type": "directDownload",
"description": "Search for ebooks with direct download links",
"request": {
"method": "GET",
"url": "https://api.example.com/search?q={TITLE}+{AUTHOR}",
"timeout": 15000
},
"response": {
"type": "json",
"resultsPath": "results",
"mapping": {
"title": "title",
"url": "download_url",
"format": "extension",
"size": "filesize"
}
},
"matching": {
"field": "title",
"threshold": 0.5,
"algorithm": "fuzzy"
}
}
Debrid Source (POST)
{
"id": "torrent-ebooks",
"name": "Torrent Ebooks",
"version": "1.0.0",
"type": "debrid",
"description": "Torrent ebook search via debrid",
"request": {
"method": "POST",
"url": "https://api.example.com/v1",
"headers": { "Content-Type": "application/json" },
"body": {
"query": "{TITLE}",
"search_field": "title",
"size": 50,
"categories": [9001000, 9000000]
}
},
"response": {
"type": "json",
"resultsPath": "hits",
"mapping": {
"title": "title",
"url": "magnetUrl",
"size": "bytes",
"seeders": "seeders"
}
},
"matching": {
"field": "title",
"threshold": 0.6,
"algorithm": "fuzzy"
}
}
Two-Step Direct Download
{
"id": "shelfmark-server",
"name": "Shelfmark Server",
"version": "1.0.0",
"type": "directDownload",
"request": {
"method": "GET",
"url": "https://api.cinderreader.com/api/resolve?q={TITLE}+{AUTHOR}",
"timeout": 15000
},
"response": {
"type": "json",
"resultsPath": "results",
"mapping": {
"title": "title",
"url": "mirrors.0.url",
"format": "format",
"size": "size"
}
},
"downloadResolve": {
"request": {
"method": "GET",
"url": "https://api.cinderreader.com/api/get_links?md5={DOWNLOAD_ID}",
"timeout": 60000
}
}
}
Importing a Source
Same three methods as metadata providers:
- URL import — Host your JSON at a public URL. Settings → Download Sources → Add → From URL.
- JSON paste — Paste the JSON directly in the import dialog.
Debrid Services
Debrid services act as a middleman — they take magnet links from your download sources and convert them into direct download URLs. Cinder is not a torrent client and never downloads torrents directly. Instead, it passes magnet links to your debrid provider, which handles everything and returns a downloadable link.
Configure your debrid provider and API key in Settings → Debrid Service.
Setup
Get your API key
Sign up for a debrid service and generate an API key from their website.
Add it in Cinder
Go to Settings → Debrid Service, select your provider, and paste your API key.
That's it
Magnet results from your download sources will now be resolved via your debrid provider into direct download links.