A fixed contract for other programs
Nexview, nexbeat and nexdeck talk to nexcrate through the same interface that is open to you: /api/v1. It stays as it is while the pages of the interface move on. This page explains how to get in, and lists every address below.
The reference describes nexcrate 0.2.0, contract v1, stage V5, 43 addresses. It is generated from nexcrate's code.
Two interfaces
nexcrate's own interface uses an API under /api, with the session cookie. It follows the pages of the interface and changes with them. Other programs use /api/v1 and nothing else.
/api/v1opens only with a key, never with the interface's cookie.- Requests that change something need no
X-Requested-Withheader there. - No key reaches indexers, download clients, profiles, the account or stored secrets. There is no address for them under
/api/v1.
Every running nexcrate describes itself: /api/openapi.json is the OpenAPI document, /api/docs the page for it.
Keys and scopes
Under Settings › System › API keys press New key. Give every program its own key; then one can be revoked without disturbing the others. The key is shown exactly once; nexcrate keeps only its fingerprint.
| Scope | What it allows |
|---|---|
| Read | Read the library, versions, states, storage, health, the queue, problems, history, the calendar, ratings and events. Every key has it. |
| Request | Request titles and more of them, watch or freeze them, have them searched, take them back and move files into the recycle bin. |
| Operate | Try stuck downloads again, remove them or have them searched again, confirm a path and assign files by hand. |
The key travels in a header, never in the address:
Authorization: Bearer nxc_…
Pairing
A program can ask for a key instead of someone typing it in. That is how Nexview, nexbeat and nexdeck connect. The two addresses for it are the only ones under /api/v1 that need no key.
- Ask
POST /api/v1/pairingwith the program's name and the scopes it wants. The answer carries apairing_id, asecretand a shortcode. The program shows the code. - ConfirmIn nexcrate the request shows at the top of every page and under Settings › System › API keys in Programs that want to connect. The owner compares the code and presses Connect or Refuse; they may grant fewer scopes than asked for.
- CollectThe program asks
GET /api/v1/pairing/{pairing_id}with the headerX-Pairing-Secret, everypoll_seconds. The first answer withconfirmedcarries the key, exactly once.
curl -s -X POST http://nexcrate:8390/api/v1/pairing \
-H 'Content-Type: application/json' \
-d '{"app": "My script", "scopes": ["read", "request"]}'
The first request
Which versions there are and whether they are ready:
curl -s http://nexcrate:8390/api/v1/versions \ -H "Authorization: Bearer $NEXCRATE_KEY"
Request a movie in one version and have it searched right away. Asking twice gives the same, never a double; origin is your own reference and comes back on the title:
curl -s -X POST http://nexcrate:8390/api/v1/requests \
-H "Authorization: Bearer $NEXCRATE_KEY" \
-H 'Content-Type: application/json' \
-d '{"kind": "movie", "ref": "tmdb:28503", "versions": ["<version id>"],
"search_now": true, "origin": "my-script:1"}'
Rules of the answers
- A title is
kindplusref.refnames its source:tmdb:,imdb:, for shows alsotvdb:, for musicmbid:. What concerns only one kind sits under a key with its name. - A field is never missing. An unknown value is
null. - Errors are flat:
{"code": "…", "message": "…", "params": {…}}. Thecodeis stable, themessagean English fallback. Every address lists its error codes in the reference. - Every answer carries
X-Request-Id. A program may send its own; nexcrate writes it on every log line of that request. - Every key has a limit on requests per second. Going over it gets
429withRetry-After. - Stable means: the contract changes its major number only with a break, and that number is in the address.
GET /api/v1/systemnames the major number and the stage; a higher stage only adds.
Events and webhooks
What happens in nexcrate gets a running number. GET /api/v1/events?after=… returns what came since a number, and next_after for the next question. GET /api/v1/events/stream returns the same as Server-Sent Events. The library itself can be read the same way: GET /api/v1/titles?after=… only returns what changed.
nexcrate sends webhooks to addresses the owner enters under Settings › System › Webhooks; no key reaches there. Every delivery carries X-Nexcrate-Event, X-Nexcrate-Delivery and a signature:
X-Nexcrate-Signature: t=<unix time>,v1=<HMAC-SHA256 of "<t>.<body>">
Verify it with the target's secret over the raw body, exactly as it arrived:
import hashlib, hmac def genuine(secret: str, header: str, body: bytes) -> bool: parts = dict(piece.split("=", 1) for piece in header.split(",")) expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"])
If a delivery fails, nexcrate tries again after 1, 5, 30 and 120 minutes. A new target starts at the newest event, not with everything that came before.
System and versions
Who answers, which versions there are, how much space is free behind them and what is wrong right now.
GET/api/v1/healthWhat is wrong with this nexcrateRead
Findings from what nexcrate already knows: asking sends no request anywhere and wakes no disk. Translate by code; params carry the names.
Response 200 HealthOut
| Field | Type | Meaning |
|---|---|---|
items * | array of FindingOut | The worst first; empty when nothing is wrong. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 500
internal_error
GET/api/v1/statesThe states a version, a season or an episode can haveRead
The list is part of the contract. A later stage may add a state; a program shows one it does not know as unknown instead of failing.
Response 200 StatesOut
| Field | Type | Meaning |
|---|---|---|
items * | array of StateOut | In the order of precedence: the first that applies wins. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 500
internal_error
GET/api/v1/storageFree space behind every versionRead
Free and total space of the disk each version's folder lies on. Never a path.
Response 200 StorageListOut
| Field | Type | Meaning |
|---|---|---|
items * | array of StorageOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 500
internal_error
GET/api/v1/systemWhat this nexcrate is and can doRead
The version, the stage of the contract, what works per kind, and the fixed id of the installation. Ask this first: a program that needs more than this nexcrate carries can say so instead of failing.
Response 200 SystemOut
| Field | Type | Meaning |
|---|---|---|
app * | string | |
version * | string | The running nexcrate. |
contract * | Contract | |
installation_id * | string | Fixed for this installation, whatever address it answers at. Tells a moved nexcrate from another. |
scopes * | array of string | What the key of this request may do. |
capabilities * | Capabilities | |
web_url * | string | null | The address the owner gave for reaching nexcrate, sub path included; null without one. |
links * | object of string | The jumps into nexcrate's pages, to put behind web_url (or the address you reach nexcrate at): title, version, profile, download, problems, recycle_bin, calendar. They stay whatever the pages are called. |
update * | app__routers__v1__UpdateOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 500
internal_error
GET/api/v1/versionsList the versions and whether they are readyRead
A version is a named slot per kind, such as HD and 4K for movies. A version that is not ready does nothing by itself: offer only ready ones, and tell the owner the reasons of the others.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind | query | string | null |
Response 200 VersionsOut
| Field | Type | Meaning |
|---|---|---|
items * | array of VersionOut | Per kind none, one or several: assume no number. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
kind_unsupported,invalid_input - 500
internal_error
Reading titles
The library as a whole or as the changes since a marker, one title with its seasons and episodes, its history and why something is still missing.
GET/api/v1/titlesThe library, whole or what changed since a numberRead
With after=0 the whole library in pages; afterwards ask with the next_after of the last answer and get only what changed since, and what went away. A title's number moves whenever anything of what this list says about it changes, some seconds after the change. A series carries its seasons; episodes are not in the list: fetch the season when its number moved. marker_too_old: the number is older than 90 days of removals, start over at 0.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
after | query | integer | The next_after of the last answer; 0 for everything. |
kind | query | string | null | |
limit | query | integer |
Response 200 TitlesOut
| Field | Type | Meaning |
|---|---|---|
items * | array of ChangedTitleOut | Titles that are new or changed, the oldest change first. |
removed * | array of RemovedOut | Titles that went away. Never part of a fetch from 0. |
next_after * | integer | Ask with this next. |
more * | boolean | True: ask again at once, there is more. |
latest * | integer | The highest number given so far. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 410
marker_too_old - 422
kind_unsupported,invalid_input - 500
internal_error
POST/api/v1/titles/lookupLook up many titles at onceRead
Up to 100 titles in one call, for a page of tiles that each need a badge. Answers in the order asked, straight from the library and without the delay of the change numbers. An entry nexcrate cannot answer carries error; the batch never fails for one entry.
Request LookupIn
| Field | Type | Meaning |
|---|---|---|
items * | array of LookupItemIn |
Response 200 LookupOut
| Field | Type | Meaning |
|---|---|---|
items * | array of LookupItemOut | In the order asked. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
invalid_input - 500
internal_error
GET/api/v1/titles/series/{ref}/seasons/{season}The episodes of one seasonRead
Every episode TMDB lists for the season, in TMDB's numbers, with every version's state, size and quality. Season 0 holds the specials.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
ref * | path | string | |
season * | path | integer |
Response 200 SeasonDetailOut
| Field | Type | Meaning |
|---|---|---|
season * | integer | |
name * | string | null | |
episodes * | array of app__routers__v1__EpisodeOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found,season_not_found - 409
ref_ambiguous - 422
ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
POST/api/v1/titles/whyWhy many titles are not there yetRead
The same for up to 50 titles, in the order asked; an entry nexcrate cannot answer carries error and breaks nothing else.
Request WhyManyIn
| Field | Type | Meaning |
|---|---|---|
items * | array of WhyItemIn |
Response 200 WhyManyOut
| Field | Type | Meaning |
|---|---|---|
items * | array of WhyManyItemOut | In the order asked. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
invalid_input - 500
internal_error
GET/api/v1/titles/{kind}/{ref}One title, a series with its seasonsRead
The title as the list shows it; a series also with every season and, per version, its state, numbers and size on disk. ref takes every source nexcrate knows for the kind: tmdb and imdb, for a series also tvdb.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found - 409
ref_ambiguous - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
GET/api/v1/titles/{kind}/{ref}/historyThe history of a titleRead
Loaded, filed (with the replaced file's quality and size), failed, deleted, restored, requested, taken back, and what programs did, newest first in pages.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string | |
before | query | integer | null | The next_before of the last page. |
limit | query | integer |
Response 200 HistoryOut
| Field | Type | Meaning |
|---|---|---|
items * | array of HistoryItemOut | Newest first. Entries from before V3 carry only kind and time. |
next_before * | integer | null | Ask with before set to this for older entries; null at the end. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found - 409
ref_ambiguous - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
GET/api/v1/titles/{kind}/{ref}/whyWhy a title is not there yetRead
Per version one reason as a code with params, the weightiest first: loading, stuck, not released yet, the last search found only releases the profile refuses, the next search, waiting out a delay. Build the sentence from the code. Asks no indexer.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found - 409
ref_ambiguous - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
Requesting and taking back
Request a title or more of it, ask first what there would be, freeze, have it searched, take it back and remove it.
POST/api/v1/previewAsk before a requestRequest
Whether the title is there, when it comes out, whether each version would search and load by itself, and with search a search of the indexers without loading (follow it with GET /api/v1/preview/{preview_id}). A title nexcrate does not have comes from TMDB; a series nexcrate does not have cannot be searched yet.
Request app__routers__v1_round__PreviewIn
| Field | Type | Meaning |
|---|---|---|
kind * | string | |
ref * | string | A title nexcrate does not have only by tmdb:. |
versions | array of string | null | Fixed version ids; left out, every version of the kind. |
search | boolean | Also search the indexers, without loading, within their budget. |
series | PreviewSeriesIn | null |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 409
ref_ambiguous,search_busy,tmdb_not_configured,tmdb_token_unreadable - 422
kind_unsupported,ref_invalid,ref_source_unknown,ref_not_addable,version_unknown,scope_not_for_kind,invalid_input - 500
internal_error - 502
tmdb_token_rejected,tmdb_rate_limited,tmdb_unavailable,tmdb_unreachable,tmdb_http_error - 504
tmdb_timeout
GET/api/v1/preview/{preview_id}What a preview's search foundRequest
Per version the release nexcrate would take, or why nothing fits. Kept 30 minutes.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
preview_id * | path | string |
Response 200 PreviewResultOut
| Field | Type | Meaning |
|---|---|---|
preview_id * | string | |
state * | string | running or done. |
releases * | integer | Releases of the title found. |
indexers * | array of PreviewIndexerOut | |
versions * | array of PreviewResultVersionOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
preview_not_found - 422
invalid_input - 500
internal_error
POST/api/v1/requestsRequest a title, or request more of itRequest
One call for both: a title nexcrate lacks is added from TMDB, versions it lacks are added, and the seasons and episodes under series are switched on. Asking twice gives the same, never a double. Requesting more never switches anything off, and what the owner switched by hand stays. A version a Radarr or Sonarr connection feeds answers 409. search_now puts the title into the order of the automatic, even with its switch off, within the indexers' budget. 200 when the title was there, 201 when it came with this call.
Request RequestIn
| Field | Type | Meaning |
|---|---|---|
kind * | string | |
ref * | string | A new title only by tmdb:, a new album or artist by mbid: (its release group or artist). |
versions | array of string | null | Fixed version ids. Needed for a movie or series; an album takes the one music version without it, an artist has none. |
series | SeriesScopeIn | null | Only for a series; a movie answers 422. |
artist | ArtistScopeIn | null | Only for an artist: which albums to watch. |
search_now | boolean | Search as soon as the order of the automatic allows. |
origin | string | null | The program's own reference, at most 100 printable characters; listed back with the title and its versions. |
Response 200 RequestOut
| Field | Type | Meaning |
|---|---|---|
created * | boolean | True when the title came in with this request. |
versions * | array of RequestedVersionOut | |
albums_watched | integer | null | An artist: how many of its albums this request watched; null otherwise. |
search * | string | queued, not_asked, nothing_wanted, or not_possible (an anime series while capabilities.anime is false; the note anime_not_supported says it). |
notes * | array of app__routers__v1_write__NoteOut | |
title * | TitleOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found,version_not_found - 409
ref_ambiguous,version_fed_by_source,tmdb_not_configured,tmdb_token_unreadable - 422
kind_unsupported,ref_invalid,ref_source_unknown,version_unknown,version_kind_mismatch,scope_not_for_kind,season_not_found,episode_not_found,track_not_found,ref_not_addable,invalid_input - 500
internal_error - 502
tmdb_token_rejected,tmdb_rate_limited,tmdb_unavailable,tmdb_unreachable,tmdb_http_error - 504
tmdb_timeout
DELETE/api/v1/titles/{kind}/{ref}Remove a title from the libraryRequest
As the button in nexcrate: nexcrate's own versions go, and the title when none is left. Its running downloads are stopped at their client first. With delete_files the files go into the recycle bin before; ⚠️ a title that is gone cannot take them back, so add it again first. Versions a Radarr or Sonarr connection feeds stay, and the answer is 409 title_has_source_versions.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string | |
delete_files | query | boolean | Move the files into the recycle bin first. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found - 409
ref_ambiguous,download_finished,title_has_source_versions - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
POST/api/v1/titles/{kind}/{ref}/delete-filesMove files into the recycle binRequest
The files of the scope go into the recycle bin with their subtitles; they come back with POST /api/v1/recycle-bin/{entry_id}/restore until the bin's time is up. A file of a series that holds several episodes goes whole when one of them is named.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Request app__routers__v1_write__DeleteFilesIn
| Field | Type | Meaning |
|---|---|---|
versions | array of string | null | Fixed version ids (v_…); without it every version of nexcrate's own. |
series | SeriesScopeIn | null | |
album | AlbumScopeIn | null | Only for an album: single tracks. |
Response 200 DeleteFilesOut
| Field | Type | Meaning |
|---|---|---|
versions * | array of DeletedVersionOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found,version_not_found - 409
ref_ambiguous,version_fed_by_source,download_importing - 422
kind_unsupported,ref_invalid,ref_source_unknown,version_unknown,version_kind_mismatch,scope_not_for_kind,season_not_found,episode_not_found,track_not_found,invalid_input - 500
internal_error
PUT/api/v1/titles/{kind}/{ref}/monitoringFreeze or thawRequest
Only the switches: no download stops, no file moves. Freezing a whole series also keeps seasons TMDB names later out; thawing it takes them in again.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Request MonitoringIn
| Field | Type | Meaning |
|---|---|---|
monitored * | boolean | False freezes: nothing is searched or upgraded, files stay. True thaws. An artist frozen is Lidarr's unmonitored artist: none of its albums is searched on its own, their switches stay. |
versions | array of string | null | Fixed version ids (v_…); without it every version of nexcrate's own. |
series | SeriesScopeIn | null | |
artist | ArtistMonitoringIn | null |
Response 200 MonitoringOut
| Field | Type | Meaning |
|---|---|---|
versions * | array of MonitoredVersionOut | |
title * | TitleOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found,version_not_found - 409
ref_ambiguous,version_fed_by_source - 422
kind_unsupported,ref_invalid,ref_source_unknown,version_unknown,version_kind_mismatch,scope_not_for_kind,season_not_found,episode_not_found,track_not_found,invalid_input - 500
internal_error
POST/api/v1/titles/{kind}/{ref}/searchAsk for a searchRequest
The title goes into the order of the automatic, right after the replacements, even with its switch off, and within the indexers' budget: a hundred asks at once are searched one after the other.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Response 202 app__routers__v1_write__SearchOut
| Field | Type | Meaning |
|---|---|---|
search * | string | queued, running (a search of it runs already), nothing_wanted, or not_possible (an anime series while capabilities.anime is false). |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found - 409
ref_ambiguous - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
POST/api/v1/titles/{kind}/{ref}/withdrawTake a request backRequest
Watching off for the scope, running downloads that lie wholly in it stopped at their client (never blocklisted), and with delete_files the files moved into the recycle bin. A download being filed away cannot be stopped: then 409 download_finished and nothing changed. A title or version a program brought and that is empty afterwards goes.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Request WithdrawIn
| Field | Type | Meaning |
|---|---|---|
versions | array of string | null | Fixed version ids (v_…); without it every version of nexcrate's own. |
series | SeriesScopeIn | null | |
delete_files | boolean | Also move the files of the scope into the recycle bin. |
Response 200 WithdrawOut
| Field | Type | Meaning |
|---|---|---|
title_removed * | boolean | A title a program added goes when nothing of it is watched and no file is left. An album stays in its artist's catalogue; an artist a program added goes when none of its albums is left. |
versions * | array of WithdrawnVersionOut | |
albums | array of WithdrawnAlbumOut | null | An artist: what happened per album. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
title_not_found,version_not_found - 409
ref_ambiguous,version_fed_by_source,download_finished,download_importing - 422
kind_unsupported,ref_invalid,ref_source_unknown,version_unknown,version_kind_mismatch,scope_not_for_kind,season_not_found,episode_not_found,track_not_found,invalid_input - 500
internal_error
Downloads
What is loading, what is stuck, what failed, and what a program may do about it.
POST/api/v1/downloads/{download_id}/assignAssign the files of a download by handOperate
A series: each file with its episodes in TMDB's numbers, filed in the background; a file with no episodes is not filed, a file left out keeps its decision. A movie with several videos: exactly one file, the one to file. Replacing a file that is not worse needs confirm: ["not_better"].
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Request app__routers__v1_back__AssignIn
| Field | Type | Meaning |
|---|---|---|
files * | array of AssignFileIn | |
album | AssignAlbumIn | null | Only for an album download. |
confirm | array of "not_better" | Replace a file that is not worse all the same, as the owner may. |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,download_not_assignable,download_busy,assignment_not_better,assignment_covers_more,download_files_changed,download_not_choosable - 422
episode_not_found,episode_twice,scope_not_for_kind,invalid_input,track_not_found,release_not_of_album,track_not_in_release,track_twice,release_fixed - 500
internal_error
POST/api/v1/downloads/{download_id}/clearTake a failed download off the problemsOperate
It stays in the history, and its release stays on the blocklist.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,download_not_failed - 422
invalid_input - 500
internal_error
POST/api/v1/downloads/{download_id}/confirm-mappingConfirm the proposed path of a downloadOperate
Stores the proposal of path_not_found with the download client and files the download away again.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,mapping_not_proposed - 422
invalid_input - 500
internal_error
GET/api/v1/downloads/{download_id}/filesThe files of a stuck downloadRead
Every video with what nexcrate read from its name and decided; for a series every episode in TMDB's numbers with the file the version holds for it. name is relative to the download, never a path of the disk.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 422
invalid_input - 500
internal_error
POST/api/v1/downloads/{download_id}/finishFile nothing more of a downloadOperate
The download counts as filed with what is filed; the rest is not filed.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,download_not_assignable,download_busy - 422
invalid_input - 500
internal_error
POST/api/v1/downloads/{download_id}/removeRemove a downloadOperate
The download client drops the job (SABnzbd with its files, a torrent without). With search_again the release goes on the title's blocklist and the title into the order of the automatic, even with its switch off. Filed files are never touched.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,download_finished - 422
invalid_input - 500
internal_error
POST/api/v1/downloads/{download_id}/retryTry a download againOperate
A problem the client reported goes back to the client, any other to filing away.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed,download_not_retryable - 422
invalid_input - 500
internal_error
POST/api/v1/downloads/{download_id}/searchSearch the title of a download againOperate
The title goes into the order of the automatic, even with its switch off, within the indexers' budget. A failed download is taken off the problems with it.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Response 200 ActOut
| Field | Type | Meaning |
|---|---|---|
download * | QueueItemOut | The download as it stands after the action. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 409
action_not_allowed - 422
invalid_input - 500
internal_error
GET/api/v1/foreign-jobsJobs in nexcrate's category that no download followsRead
Put there by hand, by another program, or handed over without an answer and never matched; read from the clients once a minute. nexcrate never imports them by itself. The owner assigns one to a movie in the interface; a program can remove one. Since 22.09.2026.
Response 200 ForeignJobsV1Out
| Field | Type | Meaning |
|---|---|---|
items * | array of ForeignJobV1Out |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 500
internal_error
POST/api/v1/foreign-jobs/{job_id}/importAssign a foreign job to a title of the library and import itOperate
As in the interface: the job becomes a download of the version and is filed away when finished. Only titles of the library; a series job is for the episodes its name names. Needs the scope operate. Since 22.09.2026.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
job_id * | path | integer |
Request ForeignAdoptV1In
| Field | Type | Meaning |
|---|---|---|
kind * | "movie" | "series" | "album" | |
ref * | string | The title as everywhere in /api/v1, for example tmdb:603. |
version_id * | string | The public id of the version definition. |
Response 201 ForeignAdoptV1Out
| Field | Type | Meaning |
|---|---|---|
download_id * | integer | The download the job became; follow it in the queue. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
not_found,title_not_found - 409
foreign_job_failed,foreign_same_release,foreign_no_version,foreign_no_episodes,version_owned_by_source - 422
invalid_input,version_unknown,version_kind_mismatch - 500
internal_error
POST/api/v1/foreign-jobs/{job_id}/removeRemove a foreign job with its filesOperate
The job leaves the download client with its files. Needs the scope operate.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
job_id * | path | integer |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
not_found - 422
invalid_input - 500
internal_error - 502
client_unreachable
GET/api/v1/problemsDownloads with a problem, and what may be doneRead
The downloads of the queue with a problem or a hint, those that need somebody first. Each problem names its allowed actions and which of them an automatic may take without a human.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind | query | string | null |
Response 200 QueueOut
| Field | Type | Meaning |
|---|---|---|
items * | array of QueueItemOut | Newest first. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
kind_unsupported,invalid_input - 500
internal_error
GET/api/v1/queueWhat is loading, stuck or failedRead
Every download that is not finished, one entry per download with its episodes and one state: running, stuck, and failed ones that wait for the owner. Progress and remaining time are only here, never events.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind | query | string | null |
Response 200 QueueOut
| Field | Type | Meaning |
|---|---|---|
items * | array of QueueItemOut | Newest first. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
kind_unsupported,invalid_input - 500
internal_error
GET/api/v1/queue/{download_id}One downloadRead
A download as the queue lists it, also after it finished.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
download_id * | path | integer |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
download_not_found - 422
invalid_input - 500
internal_error
Events
What happened, as a list with a running number or as a stream. Webhooks send the same.
GET/api/v1/eventsWhat happened since a numberRead
Events after after, oldest first; ask again with next_after. Start with the latest of a first call. Events stay 90 days; a number older than that answers 410 marker_too_old: read the library again. Types: title.added, title.changed, title.removed, version.added, version.removed, download.started, download.state, download.imported, download.failed, download.removed, problem.opened, problem.closed, file.deleted, file.restored, request.made, request.withdrawn, series.season_complete, version_definition.added, version_definition.changed, version_definition.removed, health.changed, source.taken_over, source.takeover_undone.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
after | query | integer | The next_after of the last answer. |
limit | query | integer |
Response 200 EventsOut
| Field | Type | Meaning |
|---|---|---|
items * | array of EventOut | Oldest first. |
next_after * | integer | Ask with this next. |
more * | boolean | True: ask again at once. |
latest * | integer | The highest number given so far. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 410
marker_too_old - 422
invalid_input - 500
internal_error
GET/api/v1/events/streamThe events as a streamRead
The same events as Server-Sent Events: id is the number, event the type, data the event as JSON; a comment every 15 seconds keeps proxies from closing. One way only: nexcrate never needs to reach the program. After a break reconnect with the header Last-Event-ID or after. A stream ends after an hour. At most 5 open streams per key (too_many_streams).
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
after | query | integer | null | The number to follow; the latest when left out. |
Last-Event-ID | header | string | null |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 410
marker_too_old - 422
invalid_input - 429
too_many_streams - 500
internal_error
Calendar and ratings
What comes out when, and the ratings from IMDb, Rotten Tomatoes and Metacritic.
GET/api/v1/calendarWhat comes out in a span of daysRead
Movies with their cinema, digital and disc dates, episodes on the day they air, at most 100 days at once. A movie's date is the one of the region (the one set in nexcrate, or region); without a date there, the earliest anywhere, and country names it.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
from * | query | string | YYYY-MM-DD. |
to * | query | string | YYYY-MM-DD. |
kind | query | string | null | |
region | query | string | null | ISO 3166-1; left out, the one set. |
monitored | query | boolean | Only what a version watches. |
missing | query | boolean | Only what has no file yet. |
Response 200 CalendarOut
| Field | Type | Meaning |
|---|---|---|
from * | string | |
to * | string | |
region * | string | The region the movie dates come from; empty is the earliest date anywhere. |
items * | array of CalendarEntryOut | By day. |
truncated * | boolean | The span held more entries than one answer carries: ask a shorter one. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
kind_unsupported,invalid_input - 500
internal_error
POST/api/v1/ratingsIMDb ratings of many titles at onceRead
Up to 100 titles, also titles that are in no library, from nexcrate's own copy of IMDb's daily file; Rotten Tomatoes and Metacritic from the 30-day store of OMDb answers. A tmdb: title nexcrate does not have is looked up at TMDB once for its IMDb number (kept 30 days); nothing else leaves. Information courtesy of IMDb (https://www.imdb.com). Used with permission.
Request RatingsIn
| Field | Type | Meaning |
|---|---|---|
items * | array of RatingItemIn |
Response 200 RatingsOut
| Field | Type | Meaning |
|---|---|---|
items * | array of RatingItemOut | In the order asked. |
imdb * | string | loaded, off (the owner switched it off) or not_loaded (not loaded yet). |
attribution * | string | Show it where the ratings show. |
omdb_attribution * | string | null | Show it too when any Rotten Tomatoes or Metacritic value shows; null when none does. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
invalid_input - 500
internal_error
GET/api/v1/ratings/{kind}/{ref}The ratings of one titleRead
IMDb from nexcrate's copy; with an OMDb key of the owner also Rotten Tomatoes and Metacritic (movies only), kept 30 days and within OMDb's daily limit. Information courtesy of IMDb (https://www.imdb.com). Used with permission.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind * | path | string | |
ref * | path | string |
Response 200 RatingOut
| Field | Type | Meaning |
|---|---|---|
kind * | string | |
ref * | string | |
imdb_ref * | string | null | |
imdb * | ImdbRatingOut | null | |
rotten_tomatoes * | integer | null | The Tomatometer in percent. |
metacritic * | integer | null | The Metascore, 0 to 100. |
sources * | object of string | imdb: loaded, off or not_loaded. omdb: ok, not_found, no_key, key_invalid, limit (the day's requests are used up), failed, or not_for_kind (OMDb has no such values for series). |
attribution * | array of string |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
imdb_unknown - 409
ref_ambiguous - 422
kind_unsupported,ref_invalid,ref_source_unknown,invalid_input - 500
internal_error
Recycle bin
What was replaced or deleted and can still be brought back.
GET/api/v1/recycle-binRead the recycle binRead
What was deleted, by whom, and whether it can come back: movies, series and albums, one entry per file.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
kind | query | string | null |
Response 200 RecycleBinOut
| Field | Type | Meaning |
|---|---|---|
items * | array of RecycleEntryOut | Newest first. |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 422
kind_unsupported,invalid_input - 500
internal_error
POST/api/v1/recycle-bin/{entry_id}/restoreTake a file back out of the recycle binRequest
The file goes back where it was and counts again. Refused when something lies there (recycle_target_taken), when the version or episode has another file by now (recycle_slot_taken), when the title or version left the library (recycle_title_gone) or when the file is gone (recycle_file_gone); the file then stays in the bin.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
entry_id * | path | integer |
Response 200 RestoredOut
| Field | Type | Meaning |
|---|---|---|
title * | TitleOut |
Error codes
- 401
api_key_missing,api_key_invalid - 403
scope_missing - 404
not_found - 409
recycle_target_taken,recycle_slot_taken,recycle_title_gone,recycle_file_gone,version_fed_by_source - 422
invalid_input - 500
internal_error
Pairing
A program asks for a key, the owner confirms in nexcrate. The only addresses without a key.
POST/api/v1/pairingAsk to be connectedno key
Needs no key. nexcrate shows the request with its code to the owner, who confirms it with the scopes of the owner's choice or denies it. Ask GET /api/v1/pairing/{pairing_id} with the secret until it is decided; a request lives 10 minutes, at most 5 wait at once.
Request PairingIn
| Field | Type | Meaning |
|---|---|---|
app * | string | The program's name, shown to the owner. |
scopes * | array of string | read, request, operate; the owner may choose fewer. |
Response 201 PairingOut
| Field | Type | Meaning |
|---|---|---|
pairing_id * | string | |
secret * | string | Send it as X-Pairing-Secret when asking how the request stands. Shown once. |
code * | string | Show it to the owner: nexcrate shows the same. |
expires_at * | string | |
poll_seconds * | integer | Ask again after this many seconds. |
Error codes
- 403
csrf_header_missing - 422
invalid_input - 429
pairing_limit - 500
internal_error
GET/api/v1/pairing/{pairing_id}How a request to be connected standsno key
With the header X-Pairing-Secret. Confirmed, the answer carries the key once; from then on the request is delivered. An unknown request and a wrong secret both answer 404.
Parameters
| Field | In | Type | Meaning |
|---|---|---|---|
pairing_id * | path | string | |
X-Pairing-Secret | header | string | null |
Response 200 PairingStateOut
| Field | Type | Meaning |
|---|---|---|
state * | string | pending, confirmed (this answer carries the key), delivered, denied or expired. |
key * | string | null | The key, exactly once: in the first answer after the owner confirmed. |
scopes * | array of string | null | What the owner granted, with the key. |
expires_at * | string |
Error codes
- 404
pairing_not_found - 422
invalid_input - 500
internal_error