All endpoints are wrappers for calibredb
subcommands.
- GET Book
- GET Books
- POST Book
- POST Empty Book
- PUT Book
- DELETE Book
- Batch DELETE
- Export Book
- Batch Export
Returns JSON data of a single book
- Methods:
GET
- Parameters:
id > 0
- Headers:
Accept: application/json
- Code:
200 OK
- Content:
books
- A single book object
{
"books": {
"author_sort": "Tchaikovsky, Adrian",
"authors": "Adrian Tchaikovsky",
"cover": "/books/Adrian Tchaikovsky/Children of Time (1)/cover.jpg",
"formats": [
"/books/Adrian Tchaikovsky/Children of Time (1)/Children of Time - Adrian Tchaikovsky.epub"
],
"id": 1,
"identifiers": {
"isbn10": "1447273281"
},
"isbn": "9781447273288",
"languages": ["eng"],
"last_modified": "2023-05-16T06:19:44+00:00",
"pubdate": "2015-06-04T00:00:00+00:00",
"publisher": "Tor Books",
"series": "Children of Time",
"series_index": 1.0,
"size": 518137,
"tags": [
"Science Fiction",
"Aliens",
"Uplift",
"First Contact"
],
"template": "TEMPLATE ERROR 'NoneType' object has no attribute 'startswith'",
"timestamp": "2022-10-28T07:43:27+00:00",
"title": "Children of Time",
"uuid": "89076f50-09a9-4384-867a-4e58491f7f22"
}
}
- Code:
404 Not Found
- Content:
{
"error": "404 Not Found: book 1 does not exist"
}
- Code:
400 Bad Request
- Content:
{
"error": "400 Bad Request: id cannot be <= 0"
}
Examples
Curl
$ curl localhost:5000/books/1
Python
import requests
resp = requests.get("localhost:5000/books/1")
Returns JSON data of multiple books.
- Methods:
GET
- Headers:
Accept: application/json
start
(optional) - Offset index for pagination, defaults to 1.limit
(optional) - Limit on the number of results to return, defaults to 20.
# page 2 of results
$ curl localhost:5000/books?start=11&limit=10
sort
(optional) - Sort results by given field, defaults to ascendingid
. Supports descending sort by prepending with hyphen-
.
# sort by descending id
$ curl localhost:5000/books?sort=-id
# sort by descending title and tags
$ curl localhost:5000/books?sort=-title&sort=tags
search
(optional) - Search query string that supports Calibre's search interface.
# simple title search
$ curl localhost:5000/books?search=title:foobar
# equality search for the tag "fiction"
$ curl --get --data-urlencode "search=tags:=fiction" localhost:5000/books
See examples for more.
- Code:
200 OK
- Content:
books
: List of books returnedmetadata
:count
: Total count of all results (unpaginated)self
: Current page's queryprev
: Previous page's querynext
: Next page's query
{
"books": [
{
"author_sort": "Tchaikovsky, Adrian",
"authors": "Adrian Tchaikovsky",
"cover": "/books/Adrian Tchaikovsky/Children of Time (1)/cover.jpg",
"formats": [
"/books/Adrian Tchaikovsky/Children of Time (1)/Children of Time - Adrian Tchaikovsky.epub"
],
"id": 1,
"identifiers": {
"isbn10": "1447273281"
},
"isbn": "9781447273288",
"languages": ["eng"],
"last_modified": "2023-05-16T06:19:44+00:00",
"pubdate": "2015-06-04T00:00:00+00:00",
"publisher": "Tor Books",
"series": "Children of Time",
"series_index": 1.0,
"size": 518137,
"tags": [
"Science Fiction",
"Aliens",
"Uplift",
"First Contact"
],
"template": "TEMPLATE ERROR 'NoneType' object has no attribute 'startswith'",
"timestamp": "2022-10-28T07:43:27+00:00",
"title": "Children of Time",
"uuid": "89076f50-09a9-4384-867a-4e58491f7f22"
}
],
"metadata": {
"start": 1,
"limit": 10,
"count": 100,
"self": "/books?start=1&limit=10&search=title:~^foo",
"prev": "",
"next": "/books?start=11&limit=10&search=title:~^foo"
}
}
- Code:
204 No Content
- Content:
{
"books": []
}
- Condition:
start
is more than number of returned results - Code:
400 Bad Request
- Content:
{
"error": "400 Bad Request: 100 is larger than number of books 5"
}
Examples
Curl
# search for tags fiction and title foo
$ curl localhost:5000/books?search=tags:fiction&search=title:foo
# boolean search
$ curl --get --data-urlencode "search=title:'foo or bar'" localhost:5000/books
# regex
$ curl --get --data-urlencode "search=title:~^foo.*bar$" localhost:5000/books
Python
import requests
params = {"search": "title: 'foo or bar'"}
# or
params = {"search": "title:~^foo.*bar$"}
resp = requests.get("localhost:5000/books", params=params)
Create book(s) with file(s) and JSON data
- Methods:
POST
- Headers:
Content-Type: multipart/form-data
- Data: File(s) and optional JSON data
- File must have a valid ebook extension.
- Filename cannot start with hyphen
-
. - Multiple files are supported.
NOTE: When POST-ing multiple files with JSON data, all new entries will be created with same metadata. This will fail unless
automerge=new_record
is included.
- The following keys are supported:
{
"authors": "[array of strings]",
"cover": "[string]",
"identifiers": "[object of key-value strings]",
"isbn": "[string]",
"languages": "[array of strings]",
"series": "[string]",
"series_index": "float >= 0",
"tags": "[array of strings]",
"title": "[string]",
"automerge": "[ignore|overwrite|new_record]"
}
Automerge
The automerge
key modifies the behaviour of calibredb when a book is found to
already exist in the library.
automerge=ignore
(default): Ignore the duplicate and return a 409 Conflict error. This will not add any new records or files.automerge=overwrite
: Overwrite the existing file with the new file, leaving only a single record.automerge=new_record
Create a new record entirely. This will result in two different records.
NOTE: If the same file is uploaded with different JSON metadata, a new record will be created, regardless of the value given to
automerge
.
NOTE: If the same file exists across multiple different entries in the same library as a result of using
automerge=new_record
, and we add another instance of the same file withautomerge=overwrite
, the new file would overwrite ALL existing entries with the same file in the library.
- Code:
201 CREATED
- Content:
id
: List of ids of added or overwritten book(s).
{
"id": ["2"]
}
- Condition: Incorrect headers
- Code:
415 Unsupported Media Type
- Content:
{
"error": "Unsupported Media Type: Only multipart/form-data allowed"
}
- Condition: File data failed validation, e.g. Filename not supported
- Code:
422 Unprocessable Entity
- Content:
{
"error": "400 Bad Request: Invalid filename (foo.abc)"
}
- Condition: JSON data failed validation
- Code:
422 Unprocessable Entity
- Content:
{
"errors": [
{"languages": "1 is not of type string"},
{"series_index": "-2 is less than the minimum of 0.0"}
]
}
- Condition: Book already exists
- Code:
409 Conflict
- Content:
{
"error": "Book /tmp/foo.epub already exists. Include automerge=overwrite to overwrite."
}
Examples
Curl
# single file
$ curl -X POST -H "Content-Type:multipart/form-data" --form "file=@foo.epub" http://localhost:5000/books
# multiple files
$ curl -X POST --H "Content-Type: multipart/form-data" --form "file=@bar.epub" --form "file=@foo.epub" http://localhost:5000/books
# file and JSON data
$ curl -X POST --H "Content-Type: multipart/form-data" --form "data=data.json" --form "file=@foo.epub" http://localhost:5000/books
Python
import requests
# single file
files = {"file": open("foo.epub", "rb")}
# multiple files (file key does not matter)
files = [
("file", open("test.txt", "rb")),
("other", open("foo.txt", "rb")),
]
payload = {
"authors": ["John Doe", "Ben Adams"],
"identifiers": {"isbn": "abcd1234", "asin": "foobar123"},
"title": "foo",
}
# The (optional) payload must be serialized into JSON and wrapped in a dict
# with the "data" key. This allows Flask to access it as form data with the
# correct key. The "json" argument cannot be used as it will attempt to set
# the Content-Type as "application/json", causing Flask's request.form to
# be empty.
resp = requests.post(
"localhost:5000/books",
files=files,
data={"data": json.dumps(payload)},
)
Create empty book with JSON data.
- Methods:
POST
- Headers:
Content-Type: multipart/form-data
- Data:
- JSON data with the following OPTIONAL keys:
{
"authors": "[array of strings]",
"cover": "[string]",
"identifiers": "[object of key-value strings]",
"isbn": "[string]",
"languages": "[array of strings]",
"series": "[string]",
"series_index": "float >= 0",
"tags": "[array of strings]",
"title": "[string]",
"automerge": "[ignore|overwrite|new_record]"
}
Automerge
The automerge
key modifies the behaviour of calibredb when a book is found to
already exist in the library.
automerge=ignore
(default): Ignore the duplicate and return a 409 Conflict error. This will not add any new records or files.automerge=overwrite
: Overwrite the existing file with the new file, leaving only a single record.automerge=new_record
Create a new record entirely. This will result in two different records.
NOTE: If the same file is uploaded with different JSON metadata, a new record will be created, regardless of the value given to
automerge
.
- Code:
201 CREATED
- Content:
id
: List of ids of added or overwritten book(s).
{
"id": ["2"]
}
- Condition: Incorrect headers
- Code:
415 Unsupported Media Type
- Content:
{
"error": "Unsupported Media Type: Only application/json allowed"
}
- Condition: JSON data failed validation
- Code:
422 Unprocessable Entity
- Content:
{
"errors": [
{"languages": "1 is not of type string"},
{"series_index": "-2 is less than the minimum of 0.0"}
]
}
- Condition: Book already exists
- Code:
409 Conflict
- Content:
{
"error": "Book /tmp/foo.epub already exists. Include automerge=overwrite to overwrite."
}
Examples
Curl
# file only
$ curl -X POST -H "application/json" --data-binary=@foo.json http://localhost:5000/books/empty
Python
import requests
payload = {
"authors": ["John Doe", "Ben Adams"],
"identifiers": {"isbn": "abcd1234", "asin": "foobar123"},
"title": "foo",
}
resp = requests.post("localhost:5000/books/empty", json=payload)
Update book with JSON data
- Methods:
PUT
- Parameters:
id > 0
- Headers:
Content-Type: application/json
- Data:
- JSON data with the following OPTIONAL keys:
{
"authors": "[array of strings]",
"author_sort": "[string]",
"comments": "[string]",
"id": "integer >= 0",
"identifiers": "[object of key-value strings]",
"isbn": "[string]",
"languages": "[array of strings]",
"pubdate": "[string]",
"publisher": "[string]",
"rating": "[string]",
"series": "[string]",
"series_index": "float >= 0",
"size": "integer >= 0",
"tags": "[array of strings]",
"timestamp": "[string]",
"title": "[string]"
}
It is not recommended to modify the id and timestamp of the book.
- Code:
200 OK
- Content:
books
- Updated book object
{
"books": {
"author_sort": "Tchaikovsky, Adrian",
"authors": "Adrian Tchaikovsky",
"cover": "/books/Adrian Tchaikovsky/Children of Time (1)/cover.jpg",
"formats": [
"/books/Adrian Tchaikovsky/Children of Time (1)/Children of Time - Adrian Tchaikovsky.epub"
],
"id": 1,
"identifiers": {
"isbn10": "1447273281"
},
"isbn": "9781447273288",
"languages": ["eng"],
"last_modified": "2023-05-16T06:19:44+00:00",
"pubdate": "2015-06-04T00:00:00+00:00",
"publisher": "Tor Books",
"series": "Children of Time",
"series_index": 1.0,
"size": 518137,
"tags": [
"Science Fiction",
"Aliens",
"Uplift",
"First Contact"
],
"template": "TEMPLATE ERROR 'NoneType' object has no attribute 'startswith'",
"timestamp": "2022-10-28T07:43:27+00:00",
"title": "Children of Time",
"uuid": "89076f50-09a9-4384-867a-4e58491f7f22"
}
}
- Condition: Book does not exist
- Code:
404 Not Found
- Content:
{
"error": "404 Not Found: book 1 does not exist"
}
- Condition: id is invalid
- Code:
400 Bad Request
- Content:
{
"error": "400 Bad Request: id cannot be <= 0"
}
- Condition: JSON data failed validation
- Code:
422 Unprocessable Entity
- Content:
{
"errors": [
{"languages": "1 is not of type string"},
{"series_index": "-2 is less than the minimum of 0.0"}
]
}
Examples
Curl
$ curl -X PUT --H "Content-Type: application/json" --data-binary=@data.json http://localhost:5000/books/1
Python
import requests
payload = {
"authors": ["John Doe", "Ben Adams"],
"identifiers": {"isbn": "abcd1234", "asin": "foobar123"},
"title": "foo",
}
resp = requests.put("localhost:5000/books/1", json=payload)
Delete book with id
- Methods:
DELETE
- Parameters:
id > 0
- Code:
200 OK
- Data: Empty response
- Condition: id is invalid
- Code:
400 Bad Request
- Content:
{
"error": "400 Bad Request: id cannot be <= 0"
}
- Condition: Book was not deleted
- Code:
500 Internal Server Error
- Content:
{
"error": "500 Internal Server Error: book 1 was not deleted"
}
Examples
Curl
$ curl -X DELETE http://localhost:5000/books/1
Python
import requests
resp = requests.delete("localhost:5000/books/1")
Batch delete books
- Methods:
DELETE
- id (mandatory): Comma separated values or separate parameters.
/books?id=1,2
/books?id=1&id=2
See DELETE /books/{id}
.
Examples
Curl
$ curl -X DELETE http://localhost:5000/books?id=1,2
$ curl -X DELETE http://localhost:5000/books?id=1&id=2
Python
import requests
resp = requests.delete("localhost:5000/books", params={"id": "1,2"})
Export book with id
- Methods:
GET
- Parameters:
id > 0
- Code:
200 OK
- Content:
- A single file object
- Condition: id is invalid
- Code:
400 Bad Request
- Content:
{
"error": "400 Bad Request: id cannot be <= 0"
}
- Condition: Book does not exist
- Code:
404 Not Found
- Content:
{
"error": "404 Not Found: No book with id 1 present"
}
Examples
Curl
$ curl http://localhost:5000/export/1 -o foo.epub
Python
import requests
resp = requests.get("localhost:5000/export/1", stream=True)
with open("foo.epub", "wb") as f:
for chunk in resp:
f.write(chunk)
Batch export books
- Methods:
GET
- id (mandatory): Comma separated values or separate parameters.
/export?id=1,2
/export?id=1&id=2
- Code:
200 OK
- Content:
- A zip file object
exports.zip
containing all files
- A zip file object
See GET /export/{id}
.
Examples
Curl
$ curl http://localhost:5000/export?id=1,2 -o foo.zip
$ curl http://localhost:5000/export?id=1&id=2 -o foo.zip
Python
import requests
import zipfile
resp = requests.get("localhost:5000/export", params={"id": "1,2"}, stream=True)
z = zipfile.ZipFile(BytesIO(resp.content))