M3: Fix identity projection (discover → media → request) #21
@@ -1,80 +0,0 @@
|
||||
# Assessment of the two existing designs
|
||||
|
||||
## Verdict
|
||||
|
||||
`schema-swe` is the stronger **API inventory starting point**: it surveys administration, integrations, discovery and auxiliary routes much more broadly. `schema-swe-med` is the stronger **default product scope starting point**: it keeps most instance administration out of everyday media workflows and separates moderation, structural changes and subscriptions. Neither is a complete, internally consistent implementation contract. Both need routing corrections, complete schemas, explicit result contracts and a firmer distinction between documented facts and inferred semantics.
|
||||
|
||||
The assessment below is relative to the checked-in RAML, not an assertion about every Ombi release. No live credentials, source controller implementations or deployed behaviour were inspected.
|
||||
|
||||
## Comparable criteria
|
||||
|
||||
| Criterion | schema-swe | schema-swe-med |
|
||||
|---|---|---|
|
||||
| Declared scope | 22 tools, 12 reads and 10 writes; broad administration included | 19 tools, 13 reads and 6 writes; administration mostly excluded |
|
||||
| Coverage accounting | Calls 321 paths “endpoints”; actual total is 377 method/path operations. Its exactly-once coverage claim is not demonstrated by a ledger | Approximate “~180 endpoints” without an enumerated covered set; cannot substantiate the number |
|
||||
| Consolidation | Good use of media/action discriminators; several tools combine unrelated privileges and very different inputs | Better separation of request moderation and subscriptions; still several ambiguous parameter bags |
|
||||
| Routing fidelity | Generally broader and more accurate, but several generalized routes imply nonexistent variants | Multiple definite method/path/body errors in core workflows |
|
||||
| Schema completeness | Three input schemas for 22 tools; no output schemas or annotations | Three input schemas for 19 tools; no output schemas or annotations |
|
||||
| Input validation | Some conditional requirements; open objects and a conditional-default bug | Closed objects are an improvement; claimed conditional/exclusive schemas are not actually present |
|
||||
| Authentication | Useful token caching, expiry margin and single-flight design; changes project API-key-first premise | Similar lifecycle proposal; contradictory required credentials and unsupported refresh-token discussion |
|
||||
| Secret handling | Redaction is recognized, but some tool inputs/operations contradict the no-credentials promise | Smaller surface reduces exposure, but forwarding arbitrary ProblemDetails and user entities remains unsafe |
|
||||
| Result usability | Mostly raw entities; inconsistent variants, recursive graphs and secret-bearing nested users | `{total, items}` normalization is a good direction; totals and cross-version equivalence are overpromised |
|
||||
| Implementation readiness | Design sketch with substantial useful inventory | Design sketch with useful scope decisions but more core correctness fixes needed |
|
||||
|
||||
These are qualitative findings, not arbitrary numerical scores. A smaller advertised tool count does not measure coverage or usability: the size and ambiguity of each tool's argument space matter too.
|
||||
|
||||
## What schema-swe gets right
|
||||
|
||||
It recognizes that HTTP method alone does not determine side effects: the newsletter unsubscribe GET mutates state, while metadata lookup POSTs may be read operations. It includes useful details easily missed in a media-only inventory: the `availble` spelling, integration options, per-user preferences, auxiliary images, jobs, features and administrative settings. The TV request schema distinguishes the v1 TVDB and v2 TMDB request bodies correctly. Keeping authentication internal and deduplicating token acquisition are sensible ideas if JWT support is later verified and enabled.
|
||||
|
||||
## What schema-swe should change
|
||||
|
||||
1. **Correct the denominator and coverage claim.** `api.raml` has 321 resource keys but 377 operations. Broad brace/wildcard tables obscure whether each operation really has a usable branch. `read_requests` includes count, totals and `userhasrequest`, but its schema has no distinct action or user argument to select all of them. `write_request_manage` also has action-name drift between its table and schema.
|
||||
2. **Remove invalid Cartesian products.** There is no `/api/v2/Requests/album/unavailable/...`. Album status options must differ from movie/TV. Nonpaged seasonal/requested movie discovery routes are also absent. Expanding braces mechanically would invent routes.
|
||||
3. **Fix TV IDs.** Its lifecycle schema describes `requestId` as a TV parent ID for all actions, while moderation requires an explicitly established child target. The RAML v2 TV listing response is `RequestsViewModel<ChildRequests>`, whereas v1 TV lists return parents. This is a material distinction, not a presentation detail. The child interpretation of moderation `id` still needs controller verification: its RAML model only calls the field `id`.
|
||||
4. **Do not send unsupported request properties.** `MovieRequestViewModel` supports `is4kRequest`; TV creation models do not. Album creation contains only `foreignAlbumId` and `requestedByAlias`; shared on-behalf/root/quality arguments cannot be forwarded to albums. Collection creation has no documented body at all. `requestOnBehalf` is a string without a documented username-versus-ID interpretation, so the assertion “Username” is unproven.
|
||||
5. **Fix JSON Schema semantics.** In `read_requests`, `scope` is optional and defaults to `list`, but the `if` clauses test `properties.scope` without requiring its presence. With `scope` absent, all conditions match; `requestId`, `query` and `mediaType` become required. JSON Schema `default` does not insert a value. `tvSelection.mode` is not required, contradictory identifiers pass `anyOf`, extra fields are accepted, and `update` does not schema-require `payload`. These are enforceable constraints, not limitations of JSON Schema.
|
||||
6. **Reconcile secret promises with the catalogue.** `write_user.user.password`, integration connection overrides, CouchPotato API-key acquisition and Plex sign-in conflict with the promise that the model never handles credentials. Settings read-redact-write is not safe unless omitted secrets are preserved internally; sending redaction placeholders back could corrupt configuration. Nested `OmbiUser` has `userAccessToken` and `mediaServerToken`, so redacting settings alone is insufficient.
|
||||
7. **Avoid unrestricted full-entity writes.** `payload: object` exposes server-owned identity, status and recursive relationships. Bind mutations to a small typed target and a defined set of editable fields. Do not combine `deleteAll` with routine subscriptions under one tool; description-only confirmation is not authorization.
|
||||
8. **Do not invent enum semantics.** RAML documents numeric sets for `RequestType`, `IssueStatus`, `RequestSource` and `VoteType`, not their labels. Calling issue value 3 “reserved” is no better evidenced than calling it “closed”. Preserve numeric evidence and verify labels.
|
||||
9. **Do not reject album reprocessing as an absent route.** The generic `/api/v2/Requests/reprocess/{type}/{requestId}/{is4K}` exists with a three-value RequestType. Whether every media kind is operationally supported is a separate verification question.
|
||||
10. **Specify actual return variants.** Deletes sometimes return `RequestEngineResult`, others document only success. Tester routes differ between Boolean and `TesterResultModel` or unspecified bodies. Image routes do not uniformly document URL responses. A generic promise of one upstream shape is inaccurate.
|
||||
|
||||
There are also smaller completeness issues: discovery is overloaded into a search tool whose query is always required; keyword and provider searches do not clearly map their `searchTerm` query; Stats omits `from`/`to`; and the settings notes say 12 notification sections while listing 13. These matter less than identifier/body correctness, but reinforce why the exact-operation ledger and one authoritative schema per tool are necessary.
|
||||
|
||||
## What schema-swe-med gets right
|
||||
|
||||
The explicit exclusion of most administration is a defensible product choice, not a coverage defect by itself. Splitting request creation, moderation, management and subscriptions is easier to authorize and explain. It calls out multi-search TMDB IDs for TV and the need for TV child IDs. Closed nested objects reduce accidental parameters. Compact TV results and a normalized list envelope are useful goals. Avoiding unbounded delete-all is reasonable.
|
||||
|
||||
## What schema-swe-med should change
|
||||
|
||||
| Finding | Existing claim | RAML evidence / correction |
|
||||
|---|---|---|
|
||||
| Similar movies | `GET /api/v2/Search/movie/similar` | **POST**, with `SimilarMoviesRefineModel` |
|
||||
| Actor search | `GET /api/v1/Search/movie/actor` | **POST**, with `SearchActorModel` |
|
||||
| Album denial | POST music deny | **PUT** `/api/v1/request/music/deny` |
|
||||
| User detail | `GET /api/v1/Identity/{userId}` | GET `/api/v1/Identity/User/{id}`; the other path is DELETE only |
|
||||
| Retry trigger | `POST /api/v1/RequestRetry` | Absent; only GET queue and DELETE queue entry are documented |
|
||||
| Lidarr metadata | `GET /api/v1/Lidarr/Metadata` | **POST** with Lidarr settings; saved configuration must be loaded internally |
|
||||
| TV creation | Required `tv.tvDbId` sent to v2 | v2 body uses `theMovieDbId`; `tvDbId` belongs to v1 |
|
||||
| Album request modifiers | Shared on-behalf/root/profile fields | Absent from `MusicAlbumRequestViewModel` |
|
||||
| Single album request | Music list endpoint presented as single lookup | No dedicated single-album-request GET; do not pretend a list is a detail route |
|
||||
| Album unavailable list | Generic v2 status expansion | Absent; reject or provide explicitly verified local filtering, never silently drop filter |
|
||||
| Extras contract | `id` plus extras allegedly covers ratings/cast/keywords | Ratings needs name/year; keyword detail needs keyword ID; no standalone cast route is mapped |
|
||||
| Discovery contract | recentlyAdded and collection in mapping | Not in the advertised category list; collection ID absent from parameter list |
|
||||
| Image contract | TVDB and TMDB image paths | No `idType` to choose them; banner supported only for movies |
|
||||
| Request management | Parent delete and child delete/update | No discriminator to select parent versus child |
|
||||
| Issue status | Four labels in mapping, three in schema | Numeric meanings unverified; `closed` omitted from schema despite mapping claim |
|
||||
| Search sorting | `requestedDate`/other enum values declared valid | v2 description only gives `requestDate` as example; translation is unspecified |
|
||||
|
||||
Its schemas state that exclusivity is encoded using `allOf`/`if`/`then`, but contain none of those constraints. For example, `{mediaType:"movie"}` and `{action:"create"}` satisfy their schemas without the necessary bodies. The prose does promise server-side checks, which is better than no validation, but it is misleading to call these exact, fully constrained schemas. Precedence rules for `requestId` versus `search` silently discard user intent when both are supplied.
|
||||
|
||||
The claim that all v1/v2 request responses can be normalized to a truthful `{total, items}` ignores arrays with no total, TV parent/child differences and missing filter mappings. The suggested startup probe cannot establish every route and semantic equivalence. Auth docs list username/password as required while also promising key-only operation; the token model has no refresh-token field.
|
||||
|
||||
## Shared gaps and the alternative
|
||||
|
||||
Both need the project's `ombi_` prefix. Their `read_`/`write_` names are legal MCP names, but names are not MCP annotations or permission checks. Neither specifies full output schemas, tool-level side-effect hints, token budgets, partial failures, bounded pagination, capabilities, unknown fields, resource handling or errors inside HTTP 200 responses.
|
||||
|
||||
The alternative uses closed, disjoint action branches; explicit media/provider/request namespaces; documented method/path routing; typed response projections; and one inventory row per HTTP operation. It keeps the narrower design's default focus and the broader design's inventory breadth, while placing administration behind a separate deployment policy. It records unsupported operations honestly instead of forcing them into misleading catch-all tools.
|
||||
|
||||
MCP allows explicit draft-07 schemas; the first draft's use of draft-07 is **not itself a standards violation**. This proposal chooses 2020-12 for consistency. Output schemas and annotations are protocol options that this design adopts for usability, not retroactive mandatory requirements the drafts violated. See the [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
|
||||
@@ -1,35 +0,0 @@
|
||||
# Authentication and authorization
|
||||
|
||||
## Upstream configuration
|
||||
|
||||
`OMBI_URL` and `OMBI_API_KEY` are required in the baseline deployment. Send the key in the `ApiKey` header as documented by [ApiKeyAuth](../api/raml/securitySchemes/api_key.raml). Do not read a runtime base URL from the RAML. Preserve a configured base-path prefix when appending API routes; simply resolving an absolute `/api/...` URL against a prefixed base can discard that prefix.
|
||||
|
||||
Only deployment configuration chooses upstream origins. Reject URLs with embedded credentials, query strings or fragments. Encode every path segment and query argument independently. Use TLS where configured; permit intentionally configured local HTTP instances without silently weakening certificate checks. Do not follow cross-origin redirects with credentials. Saved integration destinations are separately controlled administrator configuration; tool callers cannot override a host, port or arbitrary URL.
|
||||
|
||||
The global RAML security declaration does not describe the effective Ombi identity, per-operation permissions or anonymous exceptions. API-key possession must not be equated with a human user, unlimited admin privilege or a particular quota principal. Resolve and verify the configured principal and each supported workflow; fail a user-scoped operation if a usable upstream identity cannot be established. Do not invent user impersonation headers.
|
||||
|
||||
## Optional JWT adapter
|
||||
|
||||
JWT is a possible extension, not a prerequisite replacing project configuration. If introduced, explicitly select `api_key` or `user_jwt` in deployment configuration. A selected JWT mode requires username/password from a secret store or environment; partial or ambiguous credentials fail startup. Never switch from rejected JWT credentials to a potentially more privileged API key automatically.
|
||||
|
||||
The token endpoint and `UserAuthModel`/`Token` types are documented; accepting Bearer tokens on every route, bypassing the global ApiKey security declaration for login, and permission semantics require verification. The token response has `access_token` and `expiration`, not a refresh token. `/Token/refresh` uses `token` and the spelling `userename`; do not silently correct that field or assume a refresh-token protocol.
|
||||
|
||||
Once verified: cache tokens in memory; use the earliest valid expiry with a clock-skew margin; deduplicate refresh; avoid indefinite caching when expiry is missing; never log token contents. Decoding JWT `exp` for scheduling is not signature validation. Do not assume `rememberMe: true` changes lifetime without evidence.
|
||||
|
||||
Retry a read at most once after a definite authentication rejection and successful credential refresh. A timed-out or disconnected mutation has an unknown outcome and must not be replayed automatically. A write may be retried after 401 only when the adapter establishes the request was rejected before execution. Never rotate credentials, submit requests, send notifications or run jobs during capability probing.
|
||||
|
||||
## MCP client authorization is separate
|
||||
|
||||
For a local stdio deployment, use process/environment isolation. For a protected HTTP deployment, implement MCP transport authorization independently from Ombi credentials, including token audience validation and protected-resource discovery. Never accept an arbitrary client's Ombi token as the MCP server's bearer token or pass MCP access tokens through to Ombi. These are different trust boundaries. See [MCP authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) and [security guidance](https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices).
|
||||
|
||||
A single configured Ombi principal means all authorized clients share that principal's upstream power. Multi-user hosting needs an explicit client-to-principal binding and isolated credentials/caches. Do not claim user isolation merely because `requestOnBehalf` exists.
|
||||
|
||||
## Policy and secret boundaries
|
||||
|
||||
Use three proposed deployment bundles: `core`, `moderation`, and `administration`. These are server policy names, not Ombi claims or MCP-standard scopes. Authorize every call and branch even if it was advertised earlier. Remove unavailable branches from advertised schemas where practical; otherwise return a precise capability error. Denial is never an invitation to retry with administrator credentials.
|
||||
|
||||
Credentials and authentication endpoints are not tools. Administrative settings mutations accept typed non-secret patches only; credential replacement and destination changes remain in the administrator's UI. `settings_read` returns an allowlisted projection and an opaque revision token, not a redacted full object intended for blind round-tripping. The server preserves private fields internally while merging a patch. If the server cannot read and preserve the original full section safely, saving that section is unsupported.
|
||||
|
||||
No global “confirm” Boolean is treated as authority. Clients can show an operation for approval using their normal MCP interaction flow; server policy enforces the operation's actual scope. Large collection requests, email recipients, destructive deletes and job effects must be visible before execution. This is a product interaction rule, not a requirement to interrupt this documentation task.
|
||||
|
||||
Apply output allowlists recursively: user entities, issue comments, requests, stats, integration responses and exception bodies can contain credentials or private data. Never forward raw headers, stack traces, arbitrary ProblemDetails extensions, signed query strings, webhook URLs or token-bearing media links. Audit sanitized operation identity, target, outcome and correlation ID; do not audit credentials or full raw bodies.
|
||||
@@ -1,149 +0,0 @@
|
||||
# Tool catalogue and routing
|
||||
|
||||
## Grouping strategy
|
||||
|
||||
There are **31 defined tools: 18 core, 5 moderation, 8 administration**. Core is the ordinary media workflow; it is not a guarantee that every core branch is authorized for every Ombi user. User listing, retry queue inspection, saved integration server/user details, cron validation and another user's preferences require additional branch authorization. The administration and moderation bundles are opt-in. A read-only deployment can advertise just the 12 core read tools, with privileged branches removed.
|
||||
|
||||
Grouping follows a task and side-effect boundary: searching, browsing, reading details, submitting, moderating, deleting and subscribing are distinct. Movie/TV/album variants belong together when their intention is the same. Settings sections share one schema-driven administration tool; credentials, raw HTTP, arbitrary URLs, full entity edits and unbounded bulk deletes are not tools. This balances discoverability against overly large action enums.
|
||||
|
||||
The [input catalogue](03-input-schemas.md) defines every parameter, required field, enum and structural constraint. The [operation ledger](05-endpoint-coverage.md) supplies exact method/path pairs, wire parameters and upstream body/result types. Together with the rules below, these form the routing contract. There is no undocumented fallback precedence or generic endpoint passthrough.
|
||||
|
||||
## Catalogue
|
||||
|
||||
| Tool | Task | Bundle | Result family |
|
||||
|---|---|---|---|
|
||||
| `ombi_search` | Text, multi, movie refinement, actor search | core | media page |
|
||||
| `ombi_discover` | Curated lists, similar movies, collection, credits, artist albums, advanced movie filters | core | media page |
|
||||
| `ombi_media` | Details by explicit provider or request; ratings; streaming | core | media/details page |
|
||||
| `ombi_reference` | Genres, languages, keywords, watch-provider catalogue, countries, issue categories | core | reference page |
|
||||
| `ombi_requests` | List/get/search, TV children, recent requests, privileged retry queue | core | request page / retry page |
|
||||
| `ombi_request_stats` | Counts, totals, per-media quota, user-has-requests | core | metrics |
|
||||
| `ombi_issues` | Issues, grouped summaries, comments and counts | core | issue/comment/group page or metrics |
|
||||
| `ombi_votes` | Global vote list or votes on a request | core | vote page |
|
||||
| `ombi_users` | Self, authorized user lookup, claims, online users, preference read | core | user/reference page |
|
||||
| `ombi_library` | Recent additions, calendar, artwork | core | media/calendar/artwork page |
|
||||
| `ombi_server` | Status, version, features, news, stats, cron validation | core | metrics/reference page |
|
||||
| `ombi_integration_read` | Saved ARR options and authorized media-server metadata | core | reference/user page |
|
||||
| `ombi_request_create` | One media request or explicit collection request | core | mutation |
|
||||
| `ombi_request_subscribe` | Subscribe/unsubscribe | core | mutation |
|
||||
| `ombi_issue_create` | Report an issue | core | mutation |
|
||||
| `ombi_issue_comment` | Add a comment | core | mutation |
|
||||
| `ombi_vote` | Up/down vote | core | mutation |
|
||||
| `ombi_user_preferences` | Language, streaming country, newsletter opt-out | core | mutation |
|
||||
| `ombi_request_moderate` | Approval, denial, availability | moderation | mutation |
|
||||
| `ombi_request_delete` | Explicit single movie/album/TV-parent/TV-child deletion | moderation | mutation |
|
||||
| `ombi_request_options` | Advanced routing overrides and TV root/quality | moderation | mutation |
|
||||
| `ombi_request_reprocess` | Reprocess an existing request | moderation | mutation |
|
||||
| `ombi_issue_manage` | State, deletes, category management | moderation | mutation |
|
||||
| `ombi_settings_read` | Safe configuration projection and revision | administration | settings |
|
||||
| `ombi_settings_write` | Typed non-secret patch or feature flag | administration | mutation |
|
||||
| `ombi_user_manage` | Delete user or send welcome email | administration | mutation |
|
||||
| `ombi_integration_test` | Test a saved profile; can send notifications | administration | mutation |
|
||||
| `ombi_job_run` | Trigger permitted jobs / watchlist revalidation | administration | mutation |
|
||||
| `ombi_notification_send` | Email explicit recipients | administration | mutation |
|
||||
| `ombi_retry_remove` | Remove one queue entry | administration | mutation |
|
||||
| `ombi_logs` | Bounded, sanitized diagnostic reads | administration | log page |
|
||||
|
||||
## Shared routing and input rules
|
||||
|
||||
1. Reject unknown properties and irrelevant branch arguments. Required `action`, `media`, `provider`, `kind` and IDs cannot be supplied by silent defaults. Optional page values default server-side to offset 0 / limit 25; maximum limit is 100. Schema defaults are documentation, not mutation of the input.
|
||||
2. Upstream spelling and case are literal. `request/music`, `Request/movie`, `Requests/album`, `NotificationPreferences` and `notificationpreferences` are distinct contract spellings. Encode values; do not change literal paths to match public naming conventions.
|
||||
3. The public media names are movie, TV, artist and album. “Music” is an upstream route segment or the multi-search filter, not a universal alias. No artist-request operation exists. External IDs are never interchangeable with Ombi request IDs. Music IDs are opaque nonempty provider strings until a stronger MusicBrainz format is verified.
|
||||
4. `RequestType`, `IssueStatus` and other integer enums have no labels in the supplied RAML. Numeric-code inputs are deliberately retained only where routing requires them and semantics are otherwise unproven. Publish a verified label map later rather than inventing it now. Search response IDs must retain their source/provider namespace, including multi-search TV TMDB IDs.
|
||||
5. A TV parent is a show record; a TV child is an individual request under it. `ombi_requests.list(media=tv)` uses v2 child pages. Parent detail and children enumeration use v1. A compact projection of v2 children is preferable to secretly swapping in `tvlite` parent results. Return `parent_request_id` alongside child IDs when present.
|
||||
6. Parameter names such as `currentPosition`, `position`, `skip`, `count`, `take` and `amountToLoad` are mapped exactly per ledger. Offsets are zero-based by this MCP contract; the adapter must verify ambiguous upstream paging behaviour. Requests use count then position; issue summary uses position then take; issue list uses take then skip. Do not reverse these pairs.
|
||||
7. Array responses without server pagination are sliced locally only within a bounded fetched response. Mark pagination as local and total unknown unless the complete collection was obtained. A result-size cap is a truncation warning, not a fabricated server total or a promise that the next page exists.
|
||||
8. `format: date-time` and cross-field comparisons must be enforced by the server, not assumed from a client's validator. Stats requires `from <= to` when both are supplied. Strings must contain non-whitespace text where used as queries/comments. Reject duplicate season numbers and duplicate episode numbers; impose a maximum of 2,000 selected episodes per call in addition to per-array limits.
|
||||
9. Never infer permissions solely from the fact that a route is in RAML. Verify capability/role at call time, enforce local policy and retain upstream denial. Per-tool annotations are conservative: all mutations are non-idempotent until side effects are verified; all tools have `openWorldHint: true` because they interact with a configured external service.
|
||||
|
||||
## Search, discovery and details
|
||||
|
||||
`ombi_search.text` routes to the matching v1 `Search/movie`, `Search/tv`, `Search/music/artist`, or `Search/music/album` search-term route. It does not fan out implicitly. `multi` POSTs to v2 multi search; map `tv_shows` to `tvShows` and include all four Boolean properties explicitly, false for categories not selected. `movie_refine` POSTs `{searchTerm, year?, languageCode?}`. `actor` POSTs `{searchTerm, languageCode?}` to v1 movie/actor. Local `page` is never forwarded as an undocumented query parameter.
|
||||
|
||||
`ombi_discover.browse` uses paged v2 routes. Map `now_playing→nowplaying`, `top_rated→toprated`, `most_watched→mostwatched`. Movie allows popular/now-playing/top-rated/upcoming/seasonal/requested; TV allows popular/anticipated/most-watched/trending/requested. No other cross-product is valid. Nonpaged v1 and v2 equivalents are compatibility routes only.
|
||||
|
||||
`similar` is POST v2 movie/similar with `{theMovieDbId, languageCode?}`. A verified v1 POST equivalent can preserve language; a v1 GET cannot preserve a language argument. `collection` returns the collection's members/basic metadata, without creating requests. `credits` chooses actor/{actorId}/movie or /tv. `artist_albums` uses v1 music/artist/album/{foreignArtistId}.
|
||||
|
||||
`advanced_movie` POSTs the exact DiscoverModel property names: `release_year→releaseYear`, `genre_ids→genreIds`, `keyword_ids→keywordIds`, `watch_provider_ids→watchProviders`, `company_ids→companies`, and decade unchanged. Do not invent a `query` requirement for discovery. The optional upstream `type` field is omitted until its semantics are verified; release year and decade must agree if both supplied.
|
||||
|
||||
`ombi_media.details` chooses v2 movie TMDB/IMDb, TV TVDB/TMDB, artist, or artist/album routes. IMDb path placeholder spelling differs from its parameter declaration; substitute the actual path placeholder. `by_request` chooses movie/request, tv/request or artist/request; the TV namespace of this particular upstream route requires adapter verification and must not be guessed from list results. `movie_localized` uses POST v1 movie/info with `{theMovieDbId, languageCode}`. `ratings` uses title and year, not a numeric media ID. `streaming` uses TMDB even for TV. Cast/crew are projections of detailed metadata where present, not invented standalone endpoints.
|
||||
|
||||
`ombi_reference.keywords` passes query `searchTerm`; keyword detail uses its own keyword ID. Watch-provider catalogue search also has optional `searchTerm`; it is distinct from streaming availability for a particular title. Other branches have no request body. Reference values are good optional cached resources, but remain available through tools for tool-only clients.
|
||||
|
||||
## Requests and quotas
|
||||
|
||||
Use v2 list/status routes, always with sort and page segments. Public `sort.field=request_date` maps to the documented example `requestDate`; no speculative sort fields are published. `all` means the base route, not an `/all/` segment. Album lacks an unavailable-status route, so that combination fails schema validation. The misspelled movie `availble` route is an explicitly gated compatibility alias, not the primary path.
|
||||
|
||||
`get` supports movie and TV parent only; no album single-request endpoint is advertised. `children` returns children for a parent. Request `search` uses the appropriate v1 route and rejects list-only status/sort arguments. `recent` uses v2 recentlyRequested. `retry_queue` is a privileged GET and returns queue IDs separately from underlying request IDs.
|
||||
|
||||
`ombi_request_stats.counts` uses Request/count; `total` uses the media's total endpoint; `quota` uses its remaining endpoint. Quota belongs to the actual upstream principal. `has_requests` requires an explicit `user_id` by MCP policy and sends it as the optional upstream `userId` query; viewing another user is subject to authorization. Do not combine instance totals with a per-user quota under an unlabeled “total”.
|
||||
|
||||
### Creating requests
|
||||
|
||||
| Branch | Route | Exact body construction |
|
||||
|---|---|---|
|
||||
| movie | POST `/api/v1/Request/movie` | `tmdb_id→theMovieDbId`, `is_4k→is4kRequest`, `language→languageCode`; authorized optional on-behalf/overrides |
|
||||
| TV, TMDB | POST `/api/v2/Requests/tv` | `id→theMovieDbId`, optional `languageCode`, selection, language profile and overrides |
|
||||
| TV, TVDB | POST `/api/v1/Request/tv` | `id→tvDbId`, selection, language profile and overrides; no languageCode or 4K field |
|
||||
| album | POST `/api/v1/request/music` | `musicbrainz_id→foreignAlbumId`, optional `requested_by_alias→requestedByAlias`; no shared overrides |
|
||||
| collection | POST `/api/v2/Requests/movie/collection/{collectionId}` | No documented body. No 4K, language or on-behalf overrides accepted |
|
||||
|
||||
`on_behalf_user_id` is an MCP user-ID contract. Enable only after verifying whether upstream `requestOnBehalf` expects that ID or a username; if username, resolve ID to username internally and explicitly. Translate root/quality overrides to `rootFolderOverride`/`qualityPathOverride`. Never silently ignore unavailable options.
|
||||
|
||||
TV selection `all`, `first_season`, `latest_season` sets exactly one of `requestAll`, `firstSeason`, `latestSeason` true and the other two false. `episodes` sets all false and maps seasons to `{seasonNumber, episodes:[{episodeNumber}]}`. Require explicit nonempty episode lists. RAML does not establish that an empty list means a whole season; a caller wanting a full season must read details and select its actual episodes. If a verified adapter later adds a whole-season shortcut, add an explicit branch. Do not fabricate `source` enum labels or send a Plex-watchlist origin from this MCP client.
|
||||
|
||||
Collection requests may have partial effects; RAML specifies a single `RequestEngineResult`, not a per-movie result list. Read/display collection membership for review, but do not imply transactional consistency between that read and the later creation. Return accepted/unknown detail where upstream offers no per-item results. Do not simulate collections by silently issuing many movie POSTs.
|
||||
|
||||
### Existing request writes
|
||||
|
||||
Moderation approve/available/unavailable is POST; deny is PUT for all three media families. Movie body is `{id, is4K}` and deny adds reason. TV/album body is `{id}` and deny adds reason. Require a nonempty denial reason by MCP policy. Movie creation's `is4kRequest` and moderation's `is4K` are deliberately different wire names.
|
||||
|
||||
TV moderation IDs are treated as child IDs by the proposed contract, but RAML's bare `id` does not prove controller semantics. Verify before enabling, including subscription and advanced-options target semantics. Do not send a parent ID merely because an integer validates.
|
||||
|
||||
Delete routes distinguish movie, album, TV parent and TV child. Parent deletion may affect all associated children and must be described that way. No delete-all branch exists. Subscription has separate movie/TV subscribe/unsubscribe endpoints; album subscriptions are absent.
|
||||
|
||||
Advanced options body is `{requestId, rootPathOverride?, qualityOverride?, languageProfile?}`. The schema accepts only these editable fields. Direct TV root/quality PUTs put both IDs in the URL and have no body. Reprocess uses numeric RequestType, request ID and `is4K` path Boolean; enable supported type/ID/variant combinations only after verification. In particular, do not claim that album reprocessing is absent simply because album lacks its own named route.
|
||||
|
||||
Full-entity movie/TV/child PUTs are intentionally excluded: their server-owned and recursive fields are not safe public patch contracts. This does not prevent typed routing changes through the documented advanced-options routes.
|
||||
|
||||
## Issues, votes and users
|
||||
|
||||
Issue `list` uses v1 paged issues and returns individual records. `summary` uses v2 and returns provider-grouped summaries; these are not interchangeable pagination units. Provider details v2 is a summary route; the v1 provider/request routes have unspecified result schemas and must be verified before publishing their projections. Category reads are owned by `ombi_reference`, avoiding a second overlapping tool branch.
|
||||
|
||||
Issue creation maps only title, subject, description, `issueCategoryId`, numeric `requestType`, and optional `requestId`/`providerId`. Require at least one association by design; RAML marks these properties optional and does not establish a server requirement. If both are provided, verify they refer to the same media. The server supplies author, timestamps and state; never accept `userReported`, comments, resolved date or persistence ID from the model. Comment POST uses `{comment, issueId}`. Status POST uses `{issueId, status}`. Category POST uses `{value}`. Delete IDs are in the path; no speculative update-category endpoint exists.
|
||||
|
||||
Vote reads use `Vote/music/{requestId}` for albums; writes use `Vote/{up|down}/album/{requestId}`. Do not derive both directions from one generic segment rule. Vote toggling/repeated-call behaviour is unverified, hence no idempotence promise.
|
||||
|
||||
User GET-by-ID is `/Identity/User/{id}`. `self`, all users, dropdown, claims, online and notification preferences each have their exact separate routes. Respect hide-user settings and upstream visibility; do not disclose hidden requester IDs simply to populate a normalized field.
|
||||
|
||||
Language writes send `{lang}`; country writes send `{code}`. Newsletter opt-out is the mutating GET `/Identity/newsletter/unsubscribe/{userId}` and is always a write tool. An explicit user ID prevents accidental interpretation as a query; enforce self-or-authorized-admin access. Preference values can be delivery credentials, so generic notification-preference mutation remains outside the tool surface.
|
||||
|
||||
User creation/update/local-profile mutation are intentionally excluded because their RAML bodies mix credentials, claims and internal state. Administrative deletion and welcome email are supported separately. Welcome email loads the existing user's required view-model fields internally; it never asks the model to construct a UserViewModel or password.
|
||||
|
||||
## Library, server, settings and integrations
|
||||
|
||||
Recent TV grouped and ungrouped routes are separate branches. Calendar has no documented date-range query: local bounding is labeled, not sent as an invented parameter. Movie images use TMDB; TV accepts explicit TVDB or TMDB. TV banners are not documented. Album art uses releasegroupart. The generic poster and background routes take no documented title ID; never describe them as a selected title's poster.
|
||||
|
||||
Images may be binary, redirects or URLs depending on the route; RAML leaves several response bodies unspecified. Return a safe resource reference only after validating actual content type/shape and destination. Do not invent a signed URL or expose an `ApiKey` query. Random backgrounds are read-only but their outputs are not deterministic; idempotent read annotations describe side effects, not identical results.
|
||||
|
||||
Stats passes optional `from` and `to` query values. `update_check` is GET Job/update; `update_info` is GET Update. Running updates is an explicit administrative job. `cron_validate` POSTs `{expression}` to Settings/testcron and is an administrator-gated read calculation, not a scheduled-job mutation.
|
||||
|
||||
`ombi_integration_read` prefers saved-settings GETs. Radarr 4K only applies to profiles/root folders, not tags. Sonarr language profiles uses `/v3/LanguageProfiles`. Lidarr Metadata is POST-only, so load saved Lidarr settings privately and construct its request internally. CouchPotato profile is singular and POST-only. Credential acquisition `/CouchPotato/apikey` is never a tool. RPC POST counterparts accepting settings are compatibility adapters to the same read intent; they do not add caller connection overrides.
|
||||
|
||||
Plex library lookup uses a known saved `machine_id`; Emby/Jellyfin info and Library POSTs use an authorized saved `server_id` to resolve the server settings privately. No arbitrary server object or connection destination is accepted. Routes that acquire Plex/Emby/Jellyfin access or provision accounts remain internal/manual because their authentication/side effects are not adequately specified here.
|
||||
|
||||
Settings section names and wire types are fully listed in [the registry](07-settings-types.md). Read-only flags and customization content are not accidentally accepted as writable sections. Before saving, privately read the original section, verify the revision, merge a typed patch preserving secrets and omitted fields, and POST the full correct wire model. An incomplete original object means save is unsupported. Redacted strings must never become stored credentials. Feature writes are enable/disable POSTs with `{name, enabled}`.
|
||||
|
||||
Integration testers take an administrator-provisioned profile ID. The server selects the exact tester body type, resolves credentials internally and forbids destination overrides. `profile_id` must match the service and authorized instance; it is not an arbitrary filesystem path. Some tests really send messages; completion means the tester returned, not necessarily that a human received a notification.
|
||||
|
||||
Job run names exactly match the 14 POST Job routes in the ledger, including the case-sensitive `arrAvailability`. `revalidate_watchlist` separately POSTs Plex/WatchlistUsers/revalidate. Update, clear-media-server-data, auto-delete and newsletter jobs have significant effects; deployments must opt in to each. No synthetic job-status polling API or MCP task handle is claimed.
|
||||
|
||||
Mass email maps subject/body/bcc and resolves explicit unique user IDs to the upstream `users` entity list privately. Verify the minimal accepted user fields before enabling; never accept arbitrary OmbiUser JSON. Recipient count is capped at 100; an empty list cannot mean “everyone”. Retry removal only DELETEs its queue ID, and never calls a nonexistent POST RequestRetry.
|
||||
|
||||
Logs list returns opaque file IDs bound to a vetted upstream basename. Read accepts that ID, with bounded local line slicing. Reject unknown IDs and traversal; sanitize before caching or returning. If robust sanitization is unavailable, omit logs tools. Raw log downloads and UI logging submission remain excluded.
|
||||
|
||||
## Compatibility policy
|
||||
|
||||
The ledger's alternative routes are accounted for, not all promised working fallbacks. Use only an adapter verified for the instance/version. A global 404 probe cannot distinguish unsupported routes from hidden or missing resources. Never fall back on 401/403, arbitrary 404, validation failures or ambiguous mutation outcomes. Do not transform TVDB to TMDB by copying an integer; any conversion requires an authoritative lookup and an unambiguous match.
|
||||
|
||||
Legacy request filters are integers without documented mappings. No passthrough filter integers are exposed and no “partial availability” mapping is invented. Where v2 is unavailable, a verified unpaged v1 list may be projected and locally bounded with truthful metadata; unavailable filtered semantics produce `UNSUPPORTED_CAPABILITY`. API version selection must not silently change parent/child granularity, counts, language, filters or target identity.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,395 +0,0 @@
|
||||
# Complete endpoint coverage ledger
|
||||
|
||||
This inventory enumerates every method directly declared under every resource in the supplied RAML: **377 HTTP operations across 321 paths**. IDs follow source order and are audit references, not API identifiers. There is one disposition per operation, including intentional exclusions. Links point to the resource start; multiple methods can share a resource line.
|
||||
|
||||
| Disposition | Meaning | Operations |
|
||||
|---|---|---|
|
||||
| D | Direct tool contract; verification/authorization gates still apply | 257 |
|
||||
| A | Alternative/legacy route accounted for; enabled only by a verified compatibility adapter | 37 |
|
||||
| P | Partial exposure: typed, sanitized subset or server-owned body construction | 59 |
|
||||
| I | Internal authentication/provisioning, never a model-callable tool | 12 |
|
||||
| X | Deliberately excluded; manual administration or a narrower supported operation | 12 |
|
||||
|
||||
The body/response columns describe the **upstream** schema, not a promise to pass that object through MCP. “Body unspecified” does not assert an empty response. “Type unspecified” can reflect a RAML generation defect: several `type:` declarations were folded into description text. The mapping contract supplies conservative public types and records semantic gaps. No inferred labels for numeric enums are treated as facts.
|
||||
|
||||
## Operations
|
||||
|
||||
| ID | Method and literal path | Disposition | Owner / branch | Wire inputs | Success response | Notes |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 001 | [GET `/api/v2/Calendar`](../api/raml/api.raml#L11) | D | `ombi_library / calendar` | none documented | 200: array<[CalendarViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.CalendarViewModel.raml)> | — |
|
||||
| 002 | [POST `/api/v1/CouchPotato/profile`](../api/raml/api.raml#L27) | D | `ombi_integration_read / options:couchpotato/profiles` | body [CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) | 200: [CouchPotatoProfiles](../api/raml/types/Ombi.Api.External.ExternalApis.CouchPotato.Models.CouchPotatoProfiles.raml) | Saved settings constructed privately; singular profile path. |
|
||||
| 003 | [POST `/api/v1/CouchPotato/apikey`](../api/raml/api.raml#L45) | I | `administrator credential provisioning` | body [CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) | 200: [CouchPotatoApiKey](../api/raml/types/Ombi.Api.External.ExternalApis.CouchPotato.Models.CouchPotatoApiKey.raml) | Acquires a credential; never return API keys. |
|
||||
| 004 | [GET `/api/v1/CustomPage`](../api/raml/api.raml#L63) | D | `ombi_settings_read / custom_page` | none documented | 200: [CustomPageSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomPageSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 005 | [POST `/api/v1/CustomPage`](../api/raml/api.raml#L63) | P | `ombi_settings_write / patch:custom_page` | body [CustomPageSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomPageSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 006 | [POST `/api/v1/Emby`](../api/raml/api.raml#L93) | I | `media-server provisioning` | body [EmbySettings](../api/raml/types/Ombi.Core.Settings.Models.External.EmbySettings.raml) | 200: [EmbySettings](../api/raml/types/Ombi.Core.Settings.Models.External.EmbySettings.raml) | Credential-bearing settings request/response; behaviour not established by RAML. |
|
||||
| 007 | [POST `/api/v1/Emby/info`](../api/raml/api.raml#L113) | D | `ombi_integration_read / media_server:emby/info` | body [EmbyServers](../api/raml/types/Ombi.Core.Settings.Models.External.EmbyServers.raml) | 200: [PublicInfo](../api/raml/types/Ombi.Api.External.MediaServers.Emby.Models.PublicInfo.raml) | POST resolves configured server privately. |
|
||||
| 008 | [GET `/api/v1/Emby/users`](../api/raml/api.raml#L131) | D | `ombi_integration_read / media_server:emby/users` | none documented | 200: array<[UsersViewModel](../api/raml/types/Ombi.Models.External.UsersViewModel.raml)> | — |
|
||||
| 009 | [POST `/api/v1/Emby/Library`](../api/raml/api.raml#L143) | D | `ombi_integration_read / media_server:emby/libraries` | body [EmbyServers](../api/raml/types/Ombi.Core.Settings.Models.External.EmbyServers.raml) | 200: [EmbyItemContainer](../api/raml/types/Ombi.Api.External.MediaServers.Emby.Models.EmbyItemContainer_1__Ombi.Api.External.MediaServers.Emby.Models.Media.MediaFolders__Ombi.Api.External__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | POST resolves configured server privately. |
|
||||
| 010 | [GET `/api/v2/Features`](../api/raml/api.raml#L161) | D | `ombi_server / features` | none documented | 200: array<[FeatureEnablement](../api/raml/types/Ombi.Settings.Settings.Models.FeatureEnablement.raml)> | — |
|
||||
| 011 | [POST `/api/v2/Features/enable`](../api/raml/api.raml#L177) | P | `ombi_settings_write / feature` | body [FeatureEnablement](../api/raml/types/Ombi.Settings.Settings.Models.FeatureEnablement.raml) | 200: array<[FeatureEnablement](../api/raml/types/Ombi.Settings.Settings.Models.FeatureEnablement.raml)> | Select enable/disable from enabled Boolean. |
|
||||
| 012 | [POST `/api/v2/Features/disable`](../api/raml/api.raml#L202) | P | `ombi_settings_write / feature` | body [FeatureEnablement](../api/raml/types/Ombi.Settings.Settings.Models.FeatureEnablement.raml) | 200: array<[FeatureEnablement](../api/raml/types/Ombi.Settings.Settings.Models.FeatureEnablement.raml)> | Select enable/disable from enabled Boolean. |
|
||||
| 013 | [GET `/api/v2/Hub/Users`](../api/raml/api.raml#L227) | D | `ombi_users / online` | none documented | 200: array<[ConnectedUsersViewModel](../api/raml/types/Ombi.Models.ConnectedUsersViewModel.raml)> | Privileged visibility rules apply. |
|
||||
| 014 | [GET `/api/v1/Identity/Users`](../api/raml/api.raml#L245) | D | `ombi_users / list` | none documented | 200: array<[UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml)> | — |
|
||||
| 015 | [GET `/api/v1/Identity/dropdown/Users`](../api/raml/api.raml#L257) | D | `ombi_users / dropdown` | none documented | 200: array<[UserViewModelDropdown](../api/raml/types/Ombi.Core.Models.UI.UserViewModelDropdown.raml)> | — |
|
||||
| 016 | [GET `/api/v1/Identity`](../api/raml/api.raml#L269) | D | `ombi_users / self` | none documented | 200: [UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml) | — |
|
||||
| 017 | [POST `/api/v1/Identity`](../api/raml/api.raml#L269) | X | `manual user/profile administration` | body [UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml) | 200: [IdentityResult](../api/raml/types/Ombi.Models.Identity.IdentityResult.raml) | Mixed credentials/claims/full entities or delivery-secret preference values are not tool inputs. |
|
||||
| 018 | [PUT `/api/v1/Identity`](../api/raml/api.raml#L269) | X | `manual user/profile administration` | body [UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml) | 200: [IdentityResult](../api/raml/types/Ombi.Models.Identity.IdentityResult.raml) | Mixed credentials/claims/full entities or delivery-secret preference values are not tool inputs. |
|
||||
| 019 | [POST `/api/v1/Identity/language`](../api/raml/api.raml#L318) | D | `ombi_user_preferences / language` | body [UserLanguage](../api/raml/types/Ombi.Models.Identity.UserLanguage.raml) | 200: body unspecified | Body lang, not language. |
|
||||
| 020 | [GET `/api/v1/Identity/streamingcountry`](../api/raml/api.raml#L335) | D | `ombi_reference / streaming_countries` | none documented | 200: body unspecified | — |
|
||||
| 021 | [POST `/api/v1/Identity/streamingcountry`](../api/raml/api.raml#L335) | D | `ombi_user_preferences / streaming_country` | body [CountryStreamingPreference](../api/raml/types/Ombi.Models.Identity.CountryStreamingPreference.raml) | 200: body unspecified | Body code. |
|
||||
| 022 | [GET `/api/v1/Identity/User/{id}`](../api/raml/api.raml#L359) | D | `ombi_users / get` | path `id`:string required | 200: [UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml) | — |
|
||||
| 023 | [PUT `/api/v1/Identity/local`](../api/raml/api.raml#L374) | X | `manual user/profile administration` | body [UpdateLocalUserModel](../api/raml/types/Ombi.Models.Identity.UpdateLocalUserModel.raml) | 200: [IdentityResult](../api/raml/types/Ombi.Models.Identity.IdentityResult.raml) | Mixed credentials/claims/full entities or delivery-secret preference values are not tool inputs. |
|
||||
| 024 | [DELETE `/api/v1/Identity/{userId}`](../api/raml/api.raml#L394) | D | `ombi_user_manage / delete` | path `userId`:unspecified required | 200: [IdentityResult](../api/raml/types/Ombi.Models.Identity.IdentityResult.raml) | — |
|
||||
| 025 | [GET `/api/v1/Identity/claims`](../api/raml/api.raml#L410) | D | `ombi_users / claims` | none documented | 200: array<[ClaimCheckboxes](../api/raml/types/Ombi.Core.Models.UI.ClaimCheckboxes.raml)> | — |
|
||||
| 026 | [POST `/api/v1/Identity/welcomeEmail`](../api/raml/api.raml#L422) | P | `ombi_user_manage / welcome_email` | body [UserViewModel](../api/raml/types/Ombi.Core.Models.UI.UserViewModel.raml) | 200: body unspecified | Resolve user view model internally. |
|
||||
| 027 | [GET `/api/v1/Identity/notificationpreferences`](../api/raml/api.raml#L437) | D | `ombi_users / notification_preferences` | none documented | 200: array<[UserNotificationPreferences](../api/raml/types/Ombi.Store.Entities.UserNotificationPreferences.raml)> | — |
|
||||
| 028 | [GET `/api/v1/Identity/notificationpreferences/{userId}`](../api/raml/api.raml#L447) | D | `ombi_users / notification_preferences` | path `userId`:string required | 200: array<[UserNotificationPreferences](../api/raml/types/Ombi.Store.Entities.UserNotificationPreferences.raml)> | — |
|
||||
| 029 | [POST `/api/v1/Identity/NotificationPreferences`](../api/raml/api.raml#L461) | X | `manual user/profile administration` | body array<[AddNotificationPreference](../api/raml/types/Ombi.Models.Identity.AddNotificationPreference.raml)> | success unspecified | Mixed credentials/claims/full entities or delivery-secret preference values are not tool inputs. |
|
||||
| 030 | [GET `/api/v1/Identity/newsletter/unsubscribe/{userId}`](../api/raml/api.raml#L489) | D | `ombi_user_preferences / unsubscribe_newsletter` | path `userId`:string required | 200: body unspecified | Mutating GET; never a read tool. |
|
||||
| 031 | [GET `/api/v1/Images/tv/{tvdbid}`](../api/raml/api.raml#L499) | D | `ombi_library / tv_images` | path `tvdbid`:integer required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 032 | [GET `/api/v1/Images/poster`](../api/raml/api.raml#L512) | D | `ombi_library / default_poster` | none documented | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 033 | [GET `/api/v1/Images/poster/movie/{movieDbId}`](../api/raml/api.raml#L521) | D | `ombi_library / image` | path `movieDbId`:string required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 034 | [GET `/api/v1/Images/poster/tv/{tvdbid}`](../api/raml/api.raml#L534) | D | `ombi_library / image` | path `tvdbid`:integer required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 035 | [GET `/api/v1/Images/poster/tv/tmdb/{tmdbId}`](../api/raml/api.raml#L547) | D | `ombi_library / image` | path `tmdbId`:string required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 036 | [GET `/api/v1/Images/background/movie/{movieDbId}`](../api/raml/api.raml#L560) | D | `ombi_library / image` | path `movieDbId`:string required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 037 | [GET `/api/v1/Images/banner/movie/{movieDbId}`](../api/raml/api.raml#L573) | D | `ombi_library / image` | path `movieDbId`:string required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 038 | [GET `/api/v1/Images/background/tv/{tvdbid}`](../api/raml/api.raml#L586) | D | `ombi_library / image` | path `tvdbid`:integer required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 039 | [GET `/api/v1/Images/background/tv/tmdb/{id}`](../api/raml/api.raml#L599) | D | `ombi_library / image` | path `id`:string required | 200: string | Response representation must be verified; no assumed public URL. |
|
||||
| 040 | [GET `/api/v1/Images/background`](../api/raml/api.raml#L612) | D | `ombi_library / random_background` | none documented | 200: body unspecified | Response representation must be verified; no assumed public URL. |
|
||||
| 041 | [GET `/api/v1/Images/background/info`](../api/raml/api.raml#L618) | D | `ombi_library / background_info` | none documented | 200: body unspecified | Response representation must be verified; no assumed public URL. |
|
||||
| 042 | [GET `/api/v2/Issues/{position}/{take}/{status}`](../api/raml/api.raml#L624) | D | `ombi_issues / summary` | path `position`:integer required; path `take`:integer required; path `status`:[IssueStatus](../api/raml/types/Ombi.Store.Entities.Requests.IssueStatus.raml) required | 200: array<[IssuesSummaryModel](../api/raml/types/Ombi.Core.Engine.V2.IssuesSummaryModel.raml)> | — |
|
||||
| 043 | [GET `/api/v2/Issues/details/{providerId}`](../api/raml/api.raml#L650) | D | `ombi_issues / provider_summary` | path `providerId`:string required | 200: [IssuesSummaryModel](../api/raml/types/Ombi.Core.Engine.V2.IssuesSummaryModel.raml) | — |
|
||||
| 044 | [GET `/api/v1/Issues/categories`](../api/raml/api.raml#L667) | D | `ombi_reference / issue_categories` | none documented | 200: array<[IssueCategory](../api/raml/types/Ombi.Store.Entities.Requests.IssueCategory.raml)> | — |
|
||||
| 045 | [POST `/api/v1/Issues/categories`](../api/raml/api.raml#L667) | D | `ombi_issue_manage / create_category` | body [IssueCategory](../api/raml/types/Ombi.Store.Entities.Requests.IssueCategory.raml) | 200: boolean | — |
|
||||
| 046 | [DELETE `/api/v1/Issues/categories/{catId}`](../api/raml/api.raml#L698) | D | `ombi_issue_manage / delete_category` | path `catId`:integer required | 200: boolean | — |
|
||||
| 047 | [GET `/api/v1/Issues`](../api/raml/api.raml#L713) | A | `ombi_issues / list` | none documented | 200: array<[Issues](../api/raml/types/Ombi.Store.Entities.Requests.Issues.raml)> | Unpaged list is compatibility only. |
|
||||
| 048 | [POST `/api/v1/Issues`](../api/raml/api.raml#L713) | P | `ombi_issue_create` | body [Issues](../api/raml/types/Ombi.Store.Entities.Requests.Issues.raml) | 200: integer | Minimal issue creation projection. |
|
||||
| 049 | [GET `/api/v1/Issues/{take}/{skip}/{status}`](../api/raml/api.raml#L744) | D | `ombi_issues / list` | path `take`:integer required; path `skip`:integer required; path `status`:[IssueStatus](../api/raml/types/Ombi.Store.Entities.Requests.IssueStatus.raml) required | 200: array<[Issues](../api/raml/types/Ombi.Store.Entities.Requests.Issues.raml)> | — |
|
||||
| 050 | [GET `/api/v1/Issues/count`](../api/raml/api.raml#L766) | D | `ombi_issues / counts` | none documented | 200: [IssueCountModel](../api/raml/types/Ombi.Models.IssueCountModel.raml) | — |
|
||||
| 051 | [GET `/api/v1/Issues/{id}`](../api/raml/api.raml#L777) | D | `ombi_issues / get` | path `id`:integer required | 200: [Issues](../api/raml/types/Ombi.Store.Entities.Requests.Issues.raml) | — |
|
||||
| 052 | [DELETE `/api/v1/Issues/{id}`](../api/raml/api.raml#L777) | D | `ombi_issue_manage / delete` | path `id`:integer required | 200: boolean | — |
|
||||
| 053 | [GET `/api/v1/Issues/request/{id}`](../api/raml/api.raml#L804) | D | `ombi_issues / by_request` | path `id`:integer required | 200: body unspecified | — |
|
||||
| 054 | [GET `/api/v1/Issues/provider/{id}`](../api/raml/api.raml#L814) | D | `ombi_issues / by_provider` | path `id`:string required | 200: body unspecified | — |
|
||||
| 055 | [GET `/api/v1/Issues/{id}/comments`](../api/raml/api.raml#L824) | D | `ombi_issues / comments` | path `id`:integer required | 200: array<[IssueCommentChatViewModel](../api/raml/types/Ombi.Models.IssueCommentChatViewModel.raml)> | — |
|
||||
| 056 | [POST `/api/v1/Issues/comments`](../api/raml/api.raml#L840) | D | `ombi_issue_comment` | body [NewIssueCommentViewModel](../api/raml/types/Ombi.Models.NewIssueCommentViewModel.raml) | 200: [IssueComments](../api/raml/types/Ombi.Store.Entities.Requests.IssueComments.raml) | — |
|
||||
| 057 | [DELETE `/api/v1/Issues/comments/{id}`](../api/raml/api.raml#L860) | D | `ombi_issue_manage / delete_comment` | path `id`:integer required | 200: boolean | — |
|
||||
| 058 | [POST `/api/v1/Issues/status`](../api/raml/api.raml#L875) | D | `ombi_issue_manage / set_status` | body [IssueStateViewModel](../api/raml/types/Ombi.Models.IssueStateViewModel.raml) | 200: boolean | — |
|
||||
| 059 | [POST `/api/v1/Jellyfin`](../api/raml/api.raml#L893) | I | `media-server provisioning` | body [JellyfinSettings](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinSettings.raml) | 200: [JellyfinSettings](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinSettings.raml) | Credential-bearing settings request/response; behaviour not established by RAML. |
|
||||
| 060 | [POST `/api/v1/Jellyfin/info`](../api/raml/api.raml#L913) | D | `ombi_integration_read / media_server:jellyfin/info` | body [JellyfinServers](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinServers.raml) | 200: [PublicInfo](../api/raml/types/Ombi.Api.External.MediaServers.Jellyfin.Models.PublicInfo.raml) | POST resolves configured server privately. |
|
||||
| 061 | [POST `/api/v1/Jellyfin/Library`](../api/raml/api.raml#L931) | D | `ombi_integration_read / media_server:jellyfin/libraries` | body [JellyfinServers](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinServers.raml) | 200: [JellyfinItemContainer](../api/raml/types/Ombi.Api.External.MediaServers.Jellyfin.Models.JellyfinItemContainer_1__Ombi.Api.External.MediaServers.Jellyfin.Models.MediaFolders__Ombi.Api.External__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | POST resolves configured server privately. |
|
||||
| 062 | [GET `/api/v1/Jellyfin/users`](../api/raml/api.raml#L949) | D | `ombi_integration_read / media_server:jellyfin/users` | none documented | 200: array<[UsersViewModel](../api/raml/types/Ombi.Models.External.UsersViewModel.raml)> | — |
|
||||
| 063 | [POST `/api/v1/Job/update`](../api/raml/api.raml#L961) | D | `ombi_job_run / run:update` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 064 | [GET `/api/v1/Job/update`](../api/raml/api.raml#L961) | D | `ombi_server / update_check` | none documented | 200: boolean | — |
|
||||
| 065 | [POST `/api/v1/Job/plexuserimporter`](../api/raml/api.raml#L982) | D | `ombi_job_run / run:plexuserimporter` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 066 | [POST `/api/v1/Job/plexwatchlist`](../api/raml/api.raml#L993) | D | `ombi_job_run / run:plexwatchlist` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 067 | [POST `/api/v1/Job/embyuserimporter`](../api/raml/api.raml#L1004) | D | `ombi_job_run / run:embyuserimporter` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 068 | [POST `/api/v1/Job/jellyfinuserimporter`](../api/raml/api.raml#L1015) | D | `ombi_job_run / run:jellyfinuserimporter` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 069 | [POST `/api/v1/Job/plexcontentcacher`](../api/raml/api.raml#L1026) | D | `ombi_job_run / run:plexcontentcacher` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 070 | [POST `/api/v1/Job/clearmediaserverdata`](../api/raml/api.raml#L1037) | D | `ombi_job_run / run:clearmediaserverdata` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 071 | [POST `/api/v1/Job/plexrecentlyadded`](../api/raml/api.raml#L1048) | D | `ombi_job_run / run:plexrecentlyadded` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 072 | [POST `/api/v1/Job/embycontentcacher`](../api/raml/api.raml#L1059) | D | `ombi_job_run / run:embycontentcacher` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 073 | [POST `/api/v1/Job/embyrecentlyadded`](../api/raml/api.raml#L1070) | D | `ombi_job_run / run:embyrecentlyadded` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 074 | [POST `/api/v1/Job/jellyfincontentcacher`](../api/raml/api.raml#L1081) | D | `ombi_job_run / run:jellyfincontentcacher` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 075 | [POST `/api/v1/Job/arrAvailability`](../api/raml/api.raml#L1092) | D | `ombi_job_run / run:arrAvailability` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 076 | [POST `/api/v1/Job/autodeleterequests`](../api/raml/api.raml#L1103) | D | `ombi_job_run / run:autodeleterequests` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 077 | [POST `/api/v1/Job/newsletter`](../api/raml/api.raml#L1112) | D | `ombi_job_run / run:newsletter` | none documented | 200: boolean | Explicit per-job policy; job accepted is not job completed. |
|
||||
| 078 | [GET `/api/v1/LandingPage`](../api/raml/api.raml#L1123) | D | `ombi_server / landing` | none documented | 200: [MediaSeverAvailibilityViewModel](../api/raml/types/Ombi.Models.MediaSeverAvailibilityViewModel.raml) | — |
|
||||
| 079 | [GET `/api/v1/Lidarr/enabled`](../api/raml/api.raml#L1132) | D | `ombi_integration_read / options:lidarr/enabled` | none documented | 200: boolean | — |
|
||||
| 080 | [POST `/api/v1/Lidarr/Profiles`](../api/raml/api.raml#L1141) | A | `ombi_integration_read / options:lidarr/profiles` | body [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | 200: array<[LidarrProfile](../api/raml/types/Ombi.Api.External.ExternalApis.Lidarr.Models.LidarrProfile.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 081 | [GET `/api/v1/Lidarr/Profiles`](../api/raml/api.raml#L1141) | D | `ombi_integration_read / options:lidarr/profiles` | none documented | 200: array<[LidarrProfile](../api/raml/types/Ombi.Api.External.ExternalApis.Lidarr.Models.LidarrProfile.raml)> | — |
|
||||
| 082 | [POST `/api/v1/Lidarr/RootFolders`](../api/raml/api.raml#L1174) | A | `ombi_integration_read / options:lidarr/root_folders` | body [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | 200: array<[LidarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Lidarr.Models.LidarrRootFolder.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 083 | [GET `/api/v1/Lidarr/RootFolders`](../api/raml/api.raml#L1174) | D | `ombi_integration_read / options:lidarr/root_folders` | none documented | 200: array<[LidarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Lidarr.Models.LidarrRootFolder.raml)> | — |
|
||||
| 084 | [POST `/api/v1/Lidarr/Metadata`](../api/raml/api.raml#L1207) | D | `ombi_integration_read / options:lidarr/metadata` | body [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | 200: array<[MetadataProfile](../api/raml/types/Ombi.Api.External.ExternalApis.Lidarr.Models.MetadataProfile.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 085 | [POST `/api/v1/Logging`](../api/raml/api.raml#L1228) | X | `UI-client telemetry` | body [UiLoggingModel](../api/raml/types/Ombi.Models.UiLoggingModel.raml) | 200: body unspecified | No agent-facing log injection tool; MCP logging is a separate protocol facility. |
|
||||
| 086 | [GET `/api/v1/request/music/{count}/{position}/{orderType}/{statusType}/{availabilityType}`](../api/raml/api.raml#L1243) | A | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `orderType`:unspecified required; path `statusType`:integer required; path `availabilityType`:integer required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 087 | [GET `/api/v1/request/music/total`](../api/raml/api.raml#L1273) | D | `ombi_request_stats / total` | none documented | 200: integer | — |
|
||||
| 088 | [GET `/api/v1/request/music`](../api/raml/api.raml#L1284) | A | `ombi_requests / list` | none documented | 200: array<[AlbumRequest](../api/raml/types/Ombi.Store.Entities.Requests.AlbumRequest.raml)> | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 089 | [POST `/api/v1/request/music`](../api/raml/api.raml#L1284) | D | `ombi_request_create / album` | body [MusicAlbumRequestViewModel](../api/raml/types/Ombi.Core.Models.Requests.MusicAlbumRequestViewModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 090 | [GET `/api/v1/request/music/search/{searchTerm}`](../api/raml/api.raml#L1315) | D | `ombi_requests / search` | path `searchTerm`:unspecified required | 200: array<[AlbumRequest](../api/raml/types/Ombi.Store.Entities.Requests.AlbumRequest.raml)> | — |
|
||||
| 091 | [DELETE `/api/v1/request/music/{requestId}`](../api/raml/api.raml#L1332) | D | `ombi_request_delete` | path `requestId`:unspecified required | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 092 | [POST `/api/v1/request/music/approve`](../api/raml/api.raml#L1348) | D | `ombi_request_moderate / approve` | body [AlbumUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.AlbumUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 093 | [POST `/api/v1/request/music/available`](../api/raml/api.raml#L1368) | D | `ombi_request_moderate / mark_available` | body [AlbumUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.AlbumUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 094 | [POST `/api/v1/request/music/unavailable`](../api/raml/api.raml#L1388) | D | `ombi_request_moderate / mark_unavailable` | body [AlbumUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.AlbumUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 095 | [PUT `/api/v1/request/music/deny`](../api/raml/api.raml#L1408) | D | `ombi_request_moderate / deny` | body [DenyAlbumModel](../api/raml/types/Ombi.Core.Models.Requests.DenyAlbumModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 096 | [GET `/api/v1/request/music/remaining`](../api/raml/api.raml#L1428) | D | `ombi_request_stats / quota` | none documented | 200: [RequestQuotaCountModel](../api/raml/types/Ombi.Core.Models.RequestQuotaCountModel.raml) | — |
|
||||
| 097 | [POST `/api/v1/Notifications/massemail`](../api/raml/api.raml#L1439) | P | `ombi_notification_send` | body [MassEmailModel](../api/raml/types/Ombi.Core.Models.MassEmailModel.raml) | 200: boolean | Resolve explicit user IDs into minimum verified entity body. |
|
||||
| 098 | [POST `/api/v1/Plex`](../api/raml/api.raml#L1457) | I | `Plex authentication/provisioning` | body [UserRequest](../api/raml/types/Ombi.Api.External.MediaServers.Plex.Models.UserRequest.raml) | 200: [PlexAuthentication](../api/raml/types/Ombi.Api.External.MediaServers.Plex.Models.PlexAuthentication.raml) | Credential-bearing account/OAuth workflow stays outside model tools. |
|
||||
| 099 | [POST `/api/v1/Plex/Libraries`](../api/raml/api.raml#L1477) | A | `ombi_integration_read / plex_libraries` | body [PlexServers](../api/raml/types/Ombi.Core.Settings.Models.External.PlexServers.raml) | 200: [PlexLibrariesResponse](../api/raml/types/Ombi.Models.External.PlexLibrariesResponse.raml) | Private saved-server POST alternative. |
|
||||
| 100 | [GET `/api/v1/Plex/Libraries/{machineId}`](../api/raml/api.raml#L1497) | D | `ombi_integration_read / plex_libraries` | path `machineId`:string required | 200: [PlexLibrariesLiteResponse](../api/raml/types/Ombi.Models.External.PlexLibrariesLiteResponse.raml) | — |
|
||||
| 101 | [POST `/api/v1/Plex/user`](../api/raml/api.raml#L1510) | X | `Plex account provisioning` | body [PlexUserViewModel](../api/raml/types/Ombi.Models.External.PlexUserViewModel.raml) | 200: body unspecified | Side effects are insufficiently specified; no guessed read lookup. |
|
||||
| 102 | [GET `/api/v1/Plex/servers`](../api/raml/api.raml#L1525) | D | `ombi_integration_read / plex:servers` | none documented | 200: body unspecified | — |
|
||||
| 103 | [POST `/api/v1/Plex/servers`](../api/raml/api.raml#L1525) | I | `Plex authentication/provisioning` | body [UserRequest](../api/raml/types/Ombi.Api.External.MediaServers.Plex.Models.UserRequest.raml) | 200: [PlexServersViewModel](../api/raml/types/Ombi.Models.External.PlexServersViewModel.raml) | Credential-bearing account/OAuth workflow stays outside model tools. |
|
||||
| 104 | [GET `/api/v1/Plex/friends`](../api/raml/api.raml#L1552) | D | `ombi_integration_read / plex:friends` | none documented | 200: array<[UsersViewModel](../api/raml/types/Ombi.Models.External.UsersViewModel.raml)> | — |
|
||||
| 105 | [POST `/api/v1/Plex/oauth`](../api/raml/api.raml#L1564) | I | `Plex authentication/provisioning` | body [PlexOAuthViewModel](../api/raml/types/Ombi.Models.PlexOAuthViewModel.raml) | 200: body unspecified | Credential-bearing account/OAuth workflow stays outside model tools. |
|
||||
| 106 | [GET `/api/v1/Plex/WatchlistUsers`](../api/raml/api.raml#L1579) | D | `ombi_integration_read / plex:watchlist_users` | none documented | 200: array<[PlexUserWatchlistModel](../api/raml/types/Ombi.Core.Models.PlexUserWatchlistModel.raml)> | — |
|
||||
| 107 | [POST `/api/v1/Plex/WatchlistUsers/revalidate`](../api/raml/api.raml#L1589) | D | `ombi_job_run / revalidate_watchlist` | none documented | 200: body unspecified | — |
|
||||
| 108 | [POST `/api/v1/Radarr/Profiles`](../api/raml/api.raml#L1595) | A | `ombi_integration_read / options:radarr/profiles` | body [RadarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrSettings.raml) | 200: body unspecified | Private saved settings only; no connection overrides. |
|
||||
| 109 | [GET `/api/v1/Radarr/Profiles`](../api/raml/api.raml#L1595) | D | `ombi_integration_read / options:radarr/profiles` | none documented | 200: body unspecified | — |
|
||||
| 110 | [GET `/api/v1/Radarr/enabled`](../api/raml/api.raml#L1620) | D | `ombi_integration_read / options:radarr/enabled` | none documented | 200: boolean | — |
|
||||
| 111 | [POST `/api/v1/Radarr/RootFolders`](../api/raml/api.raml#L1629) | A | `ombi_integration_read / options:radarr/root_folders` | body [RadarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrSettings.raml) | 200: array<[RadarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Radarr.Models.RadarrRootFolder.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 112 | [GET `/api/v1/Radarr/RootFolders`](../api/raml/api.raml#L1629) | D | `ombi_integration_read / options:radarr/root_folders` | none documented | 200: array<[RadarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Radarr.Models.RadarrRootFolder.raml)> | — |
|
||||
| 113 | [GET `/api/v1/Radarr/Profiles/4k`](../api/raml/api.raml#L1662) | D | `ombi_integration_read / options:radarr/profiles/4k` | none documented | 200: body unspecified | — |
|
||||
| 114 | [GET `/api/v1/Radarr/RootFolders/4k`](../api/raml/api.raml#L1671) | D | `ombi_integration_read / options:radarr/root_folders/4k` | none documented | 200: array<[RadarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Radarr.Models.RadarrRootFolder.raml)> | — |
|
||||
| 115 | [POST `/api/v1/Radarr/tags`](../api/raml/api.raml#L1684) | A | `ombi_integration_read / options:radarr/tags` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: array<[Tag](../api/raml/types/Ombi.Api.External.ExternalApis.Radarr.Models.Tag.raml)> | Private saved settings only; no connection overrides. RAML body unexpectedly references SonarrSettings; verify. |
|
||||
| 116 | [GET `/api/v1/Radarr/tags`](../api/raml/api.raml#L1684) | D | `ombi_integration_read / options:radarr/tags` | none documented | 200: array<[Tag](../api/raml/types/Ombi.Api.External.ExternalApis.Radarr.Models.Tag.raml)> | — |
|
||||
| 117 | [GET `/api/v1/RecentlyAdded/movies`](../api/raml/api.raml#L1716) | D | `ombi_library / recent` | none documented | 200: array<[RecentlyAddedMovieModel](../api/raml/types/Ombi.Core.Models.RecentlyAddedMovieModel.raml)> | grouped=true only for tv/grouped. |
|
||||
| 118 | [GET `/api/v1/RecentlyAdded/tv`](../api/raml/api.raml#L1728) | D | `ombi_library / recent` | none documented | 200: array<[RecentlyAddedMovieModel](../api/raml/types/Ombi.Core.Models.RecentlyAddedMovieModel.raml)> | grouped=true only for tv/grouped. |
|
||||
| 119 | [GET `/api/v1/RecentlyAdded/tv/grouped`](../api/raml/api.raml#L1740) | D | `ombi_library / recent` | none documented | 200: array<[RecentlyAddedMovieModel](../api/raml/types/Ombi.Core.Models.RecentlyAddedMovieModel.raml)> | grouped=true only for tv/grouped. |
|
||||
| 120 | [GET `/api/v1/Request/movie/{count}/{position}/{orderType}/{statusType}/{availabilityType}`](../api/raml/api.raml#L1752) | A | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `orderType`:unspecified required; path `statusType`:integer required; path `availabilityType`:integer required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 121 | [GET `/api/v1/Request/movie/info/{requestId}`](../api/raml/api.raml#L1782) | D | `ombi_requests / get:movie` | path `requestId`:unspecified required | 200: [MovieRequests](../api/raml/types/Ombi.Store.Entities.Requests.MovieRequests.raml) | — |
|
||||
| 122 | [GET `/api/v1/Request/movie/total`](../api/raml/api.raml#L1798) | D | `ombi_request_stats / total` | none documented | 200: integer | — |
|
||||
| 123 | [GET `/api/v1/Request/movie`](../api/raml/api.raml#L1809) | A | `ombi_requests / list` | none documented | 200: array<[MovieRequests](../api/raml/types/Ombi.Store.Entities.Requests.MovieRequests.raml)> | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 124 | [POST `/api/v1/Request/movie`](../api/raml/api.raml#L1809) | D | `ombi_request_create / movie` | body [MovieRequestViewModel](../api/raml/types/Ombi.Core.Models.Requests.MovieRequestViewModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 125 | [PUT `/api/v1/Request/movie`](../api/raml/api.raml#L1809) | X | `full entity replacement` | body [MovieRequests](../api/raml/types/Ombi.Store.Entities.Requests.MovieRequests.raml) | 200: [MovieRequests](../api/raml/types/Ombi.Store.Entities.Requests.MovieRequests.raml) | Use typed options/moderation instead; do not expose recursive server-owned state. |
|
||||
| 126 | [GET `/api/v1/Request/movie/search/{searchTerm}`](../api/raml/api.raml#L1859) | D | `ombi_requests / search` | path `searchTerm`:unspecified required | 200: array<[MovieRequests](../api/raml/types/Ombi.Store.Entities.Requests.MovieRequests.raml)> | — |
|
||||
| 127 | [DELETE `/api/v1/Request/movie/{requestId}`](../api/raml/api.raml#L1876) | D | `ombi_request_delete` | path `requestId`:unspecified required | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 128 | [DELETE `/api/v1/Request/movie/all`](../api/raml/api.raml#L1892) | X | `manual bulk deletion` | none documented | 200: body unspecified | Unbounded delete-all deliberately absent; selected single deletions remain possible. |
|
||||
| 129 | [POST `/api/v1/Request/movie/approve`](../api/raml/api.raml#L1900) | D | `ombi_request_moderate / approve` | body [MovieUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.MovieUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 130 | [POST `/api/v1/Request/movie/available`](../api/raml/api.raml#L1920) | D | `ombi_request_moderate / mark_available` | body [MovieUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.MovieUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 131 | [POST `/api/v1/Request/movie/unavailable`](../api/raml/api.raml#L1940) | D | `ombi_request_moderate / mark_unavailable` | body [MovieUpdateModel](../api/raml/types/Ombi.Core.Models.Requests.MovieUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 132 | [PUT `/api/v1/Request/movie/deny`](../api/raml/api.raml#L1960) | D | `ombi_request_moderate / deny` | body [DenyMovieModel](../api/raml/types/Ombi.Core.Models.Requests.DenyMovieModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 133 | [GET `/api/v1/Request/tv/total`](../api/raml/api.raml#L1980) | D | `ombi_request_stats / total` | none documented | 200: integer | — |
|
||||
| 134 | [GET `/api/v1/Request/tv/{count}/{position}/{orderType}/{statusFilterType}/{availabilityFilterType}`](../api/raml/api.raml#L1991) | A | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `orderType`:integer required; path `statusFilterType`:string required; path `availabilityFilterType`:string required; query `statusType`:integer optional; query `availabilityType`:integer optional | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.TvRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 135 | [GET `/api/v1/Request/tvlite/{count}/{position}/{orderType}/{statusFilterType}/{availabilityFilterType}`](../api/raml/api.raml#L2027) | A | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `orderType`:integer required; path `statusFilterType`:string required; path `availabilityFilterType`:string required; query `statusType`:integer optional; query `availabilityType`:integer optional | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.TvRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 136 | [GET `/api/v1/Request/tv`](../api/raml/api.raml#L2063) | A | `ombi_requests / list` | none documented | 200: array<[TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml)> | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 137 | [POST `/api/v1/Request/tv`](../api/raml/api.raml#L2063) | D | `ombi_request_create / tv:tvdb` | body [TvRequestViewModel](../api/raml/types/Ombi.Core.Models.Requests.TvRequestViewModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 138 | [PUT `/api/v1/Request/tv`](../api/raml/api.raml#L2063) | X | `full entity replacement` | body [TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml) | 200: [TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml) | Use typed options/moderation instead; do not expose recursive server-owned state. |
|
||||
| 139 | [GET `/api/v1/Request/tvlite`](../api/raml/api.raml#L2113) | A | `ombi_requests / list` | none documented | 200: array<[TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml)> | Legacy parent/array/filter semantics need verified adapter; not equivalent to v2 TV child pages. |
|
||||
| 140 | [GET `/api/v1/Request/tv/{requestId}`](../api/raml/api.raml#L2125) | D | `ombi_requests / get:tv_parent` | path `requestId`:integer required | 200: [TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml) | — |
|
||||
| 141 | [DELETE `/api/v1/Request/tv/{requestId}`](../api/raml/api.raml#L2125) | D | `ombi_request_delete` | path `requestId`:unspecified required | 200: body unspecified | — |
|
||||
| 142 | [GET `/api/v1/Request/tv/search/{searchTerm}`](../api/raml/api.raml#L2152) | D | `ombi_requests / search` | path `searchTerm`:unspecified required | 200: array<[TvRequests](../api/raml/types/Ombi.Store.Entities.Requests.TvRequests.raml)> | — |
|
||||
| 143 | [PUT `/api/v1/Request/tv/root/{requestId}/{rootFolderId}`](../api/raml/api.raml#L2169) | D | `ombi_request_options / tv_root` | path `requestId`:integer required; path `rootFolderId`:integer required | 200: boolean | — |
|
||||
| 144 | [PUT `/api/v1/Request/tv/quality/{requestId}/{qualityId}`](../api/raml/api.raml#L2187) | D | `ombi_request_options / tv_quality` | path `requestId`:integer required; path `qualityId`:integer required | 200: boolean | — |
|
||||
| 145 | [PUT `/api/v1/Request/tv/child`](../api/raml/api.raml#L2205) | X | `full entity replacement` | body [ChildRequests](../api/raml/types/Ombi.Store.Entities.Requests.ChildRequests.raml) | 200: [ChildRequests](../api/raml/types/Ombi.Store.Entities.Requests.ChildRequests.raml) | Use typed options/moderation instead; do not expose recursive server-owned state. |
|
||||
| 146 | [PUT `/api/v1/Request/tv/deny`](../api/raml/api.raml#L2225) | D | `ombi_request_moderate / deny` | body [DenyTvModel](../api/raml/types/Ombi.Models.DenyTvModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | TV child-ID semantics must be verified. |
|
||||
| 147 | [POST `/api/v1/Request/tv/available`](../api/raml/api.raml#L2245) | D | `ombi_request_moderate / mark_available` | body [TvUpdateModel](../api/raml/types/Ombi.Models.TvUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | TV child-ID semantics must be verified. |
|
||||
| 148 | [POST `/api/v1/Request/tv/unavailable`](../api/raml/api.raml#L2265) | D | `ombi_request_moderate / mark_unavailable` | body [TvUpdateModel](../api/raml/types/Ombi.Models.TvUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | TV child-ID semantics must be verified. |
|
||||
| 149 | [POST `/api/v1/Request/tv/approve`](../api/raml/api.raml#L2285) | D | `ombi_request_moderate / approve` | body [TvUpdateModel](../api/raml/types/Ombi.Models.TvUpdateModel.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | TV child-ID semantics must be verified. |
|
||||
| 150 | [DELETE `/api/v1/Request/tv/child/{requestId}`](../api/raml/api.raml#L2305) | D | `ombi_request_delete` | path `requestId`:unspecified required | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 151 | [GET `/api/v1/Request/tv/{requestId}/child`](../api/raml/api.raml#L2321) | D | `ombi_requests / children` | path `requestId`:unspecified required | 200: array<[ChildRequests](../api/raml/types/Ombi.Store.Entities.Requests.ChildRequests.raml)> | — |
|
||||
| 152 | [GET `/api/v1/Request/count`](../api/raml/api.raml#L2338) | D | `ombi_request_stats / counts` | none documented | 200: [RequestCountModel](../api/raml/types/Ombi.Core.Models.Requests.RequestCountModel.raml) | — |
|
||||
| 153 | [GET `/api/v1/Request/userhasrequest`](../api/raml/api.raml#L2349) | D | `ombi_request_stats / has_requests` | query `userId`:string optional | 200: boolean | — |
|
||||
| 154 | [POST `/api/v1/Request/movie/subscribe/{requestId}`](../api/raml/api.raml#L2364) | D | `ombi_request_subscribe / subscribe` | path `requestId`:integer required | 200: boolean | — |
|
||||
| 155 | [POST `/api/v1/Request/tv/subscribe/{requestId}`](../api/raml/api.raml#L2379) | D | `ombi_request_subscribe / subscribe` | path `requestId`:integer required | 200: boolean | — |
|
||||
| 156 | [POST `/api/v1/Request/movie/unsubscribe/{requestId}`](../api/raml/api.raml#L2394) | D | `ombi_request_subscribe / unsubscribe` | path `requestId`:integer required | 200: boolean | — |
|
||||
| 157 | [POST `/api/v1/Request/tv/unsubscribe/{requestId}`](../api/raml/api.raml#L2409) | D | `ombi_request_subscribe / unsubscribe` | path `requestId`:integer required | 200: boolean | — |
|
||||
| 158 | [GET `/api/v1/Request/movie/remaining`](../api/raml/api.raml#L2424) | D | `ombi_request_stats / quota` | none documented | 200: [RequestQuotaCountModel](../api/raml/types/Ombi.Core.Models.RequestQuotaCountModel.raml) | — |
|
||||
| 159 | [GET `/api/v1/Request/tv/remaining`](../api/raml/api.raml#L2435) | D | `ombi_request_stats / quota` | none documented | 200: [RequestQuotaCountModel](../api/raml/types/Ombi.Core.Models.RequestQuotaCountModel.raml) | — |
|
||||
| 160 | [GET `/api/v1/RequestRetry`](../api/raml/api.raml#L2446) | D | `ombi_requests / retry_queue` | none documented | 200: array<[FailedRequestViewModel](../api/raml/types/Ombi.Models.FailedRequestViewModel.raml)> | Privileged queue read. |
|
||||
| 161 | [DELETE `/api/v1/RequestRetry/{queueId}`](../api/raml/api.raml#L2458) | D | `ombi_retry_remove` | path `queueId`:integer required | 200: body unspecified | — |
|
||||
| 162 | [GET `/api/v2/Requests/movie/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2468) | D | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `sort`:unspecified required; path `sortOrder`:unspecified required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 163 | [GET `/api/v2/Requests/movie/availble/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2500) | A | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | Misspelled alias; prefer available. |
|
||||
| 164 | [GET `/api/v2/Requests/movie/available/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2526) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 165 | [GET `/api/v2/Requests/movie/processing/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2552) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 166 | [GET `/api/v2/Requests/movie/pending/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2578) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 167 | [GET `/api/v2/Requests/movie/denied/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2604) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 168 | [GET `/api/v2/Requests/movie/unavailable/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2630) | D | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `sort`:unspecified required; path `sortOrder`:unspecified required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.MovieRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 169 | [GET `/api/v2/Requests/tv/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2662) | D | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `sort`:unspecified required; path `sortOrder`:unspecified required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 170 | [GET `/api/v2/Requests/tv/pending/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2694) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 171 | [GET `/api/v2/Requests/tv/processing/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2720) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 172 | [GET `/api/v2/Requests/tv/available/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2746) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 173 | [GET `/api/v2/Requests/tv/denied/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2772) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 174 | [GET `/api/v2/Requests/tv/unavailable/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2798) | D | `ombi_requests / list` | path `count`:unspecified required; path `position`:unspecified required; path `sort`:unspecified required; path `sortOrder`:unspecified required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.ChildRequests__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 175 | [POST `/api/v2/Requests/movie/advancedoptions`](../api/raml/api.raml#L2830) | D | `ombi_request_options / advanced` | body [MediaAdvancedOptions](../api/raml/types/Ombi.Core.Models.Requests.MediaAdvancedOptions.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 176 | [POST `/api/v2/Requests/tv/advancedoptions`](../api/raml/api.raml#L2852) | D | `ombi_request_options / advanced` | body [MediaAdvancedOptions](../api/raml/types/Ombi.Core.Models.Requests.MediaAdvancedOptions.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 177 | [GET `/api/v2/Requests/album/available/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2874) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 178 | [GET `/api/v2/Requests/album/processing/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2900) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 179 | [GET `/api/v2/Requests/album/pending/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2926) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 180 | [GET `/api/v2/Requests/album/denied/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2952) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 181 | [GET `/api/v2/Requests/album/{count}/{position}/{sort}/{sortOrder}`](../api/raml/api.raml#L2978) | D | `ombi_requests / list` | path `count`:integer required; path `position`:integer required; path `sort`:string required; path `sortOrder`:string required | 200: [RequestsViewModel](../api/raml/types/Ombi.Core.Models.UI.RequestsViewModel_1__Ombi.Store.Entities.Requests.AlbumRequest__Ombi.Store__Version_3.0.0.0__Culture_neutral__PublicKeyToken_null__.raml) | — |
|
||||
| 182 | [POST `/api/v2/Requests/tv`](../api/raml/api.raml#L3004) | D | `ombi_request_create / tv:tmdb` | body [TvRequestViewModelV2](../api/raml/types/Ombi.Core.Models.Requests.TvRequestViewModelV2.raml) | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | — |
|
||||
| 183 | [POST `/api/v2/Requests/reprocess/{type}/{requestId}/{is4K}`](../api/raml/api.raml#L3028) | D | `ombi_request_reprocess` | path `type`:[RequestType](../api/raml/types/Ombi.Store.Entities.RequestType.raml) required; path `requestId`:integer required; path `is4K`:boolean required | 200: body unspecified | Verify enum/target/4K combinations; do not infer album exclusion. |
|
||||
| 184 | [POST `/api/v2/Requests/movie/collection/{collectionId}`](../api/raml/api.raml#L3044) | D | `ombi_request_create / collection` | path `collectionId`:integer required | 200: [RequestEngineResult](../api/raml/types/Ombi.Core.Engine.RequestEngineResult.raml) | No request body documented; success is RequestEngineResult. |
|
||||
| 185 | [GET `/api/v2/Requests/recentlyRequested`](../api/raml/api.raml#L3061) | D | `ombi_requests / recent` | none documented | 200: array<[RecentlyRequestedModel](../api/raml/types/Ombi.Core.Models.Requests.RecentlyRequestedModel.raml)> | — |
|
||||
| 186 | [POST `/api/v2/Search/multi/{searchTerm}`](../api/raml/api.raml#L3077) | D | `ombi_search / multi` | path `searchTerm`:unspecified required; body [MultiSearchFilter](../api/raml/types/Ombi.Core.Models.Search.V2.MultiSearchFilter.raml) | 200: array<[MultiSearchResult](../api/raml/types/Ombi.Core.Models.Search.V2.MultiSearchResult.raml)> | — |
|
||||
| 187 | [GET `/api/v2/Search/Genres/{media}`](../api/raml/api.raml#L3111) | D | `ombi_reference / genres` | path `media`:unspecified required | 200: array<[Genre](../api/raml/types/Ombi.TheMovieDbApi.Models.Genre.raml)> | — |
|
||||
| 188 | [GET `/api/v2/Search/Languages`](../api/raml/api.raml#L3134) | D | `ombi_reference / languages` | none documented | 200: array<[Language](../api/raml/types/Ombi.TheMovieDbApi.Models.Language.raml)> | — |
|
||||
| 189 | [GET `/api/v2/Search/movie/{movieDbId}`](../api/raml/api.raml#L3150) | D | `ombi_media / details` | path `movieDbId`:unspecified required | 200: [MovieFullInfoViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.MovieFullInfoViewModel.raml) | — |
|
||||
| 190 | [GET `/api/v2/Search/movie/imdb/{imdbid}`](../api/raml/api.raml#L3170) | D | `ombi_media / details` | path `imdbId`:string required | 200: [MovieFullInfoViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.MovieFullInfoViewModel.raml) | — |
|
||||
| 191 | [GET `/api/v2/Search/movie/request/{requestId}`](../api/raml/api.raml#L3187) | D | `ombi_media / by_request` | path `requestId`:integer required | 200: [MovieFullInfoViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.MovieFullInfoViewModel.raml) | Request-ID namespace requires verification. |
|
||||
| 192 | [GET `/api/v2/Search/movie/collection/{collectionId}`](../api/raml/api.raml#L3206) | D | `ombi_discover / collection` | path `collectionId`:unspecified required | 200: [MovieCollectionsViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.MovieCollectionsViewModel.raml) | — |
|
||||
| 193 | [GET `/api/v2/Search/tv/{tvdbId}`](../api/raml/api.raml#L3226) | D | `ombi_media / details` | path `tvdbid`:unspecified required | 200: [SearchFullInfoTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.SearchFullInfoTvShowViewModel.raml) | — |
|
||||
| 194 | [GET `/api/v2/Search/tv/request/{requestId}`](../api/raml/api.raml#L3246) | D | `ombi_media / by_request` | path `requestId`:integer required | 200: [SearchFullInfoTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.SearchFullInfoTvShowViewModel.raml) | Request-ID namespace requires verification. |
|
||||
| 195 | [GET `/api/v2/Search/tv/moviedb/{moviedbid}`](../api/raml/api.raml#L3265) | D | `ombi_media / details` | path `moviedbid`:string required | 200: [SearchFullInfoTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.SearchFullInfoTvShowViewModel.raml) | — |
|
||||
| 196 | [POST `/api/v2/Search/movie/similar`](../api/raml/api.raml#L3284) | D | `ombi_discover / similar` | body [SimilarMoviesRefineModel](../api/raml/types/Ombi.Models.SimilarMoviesRefineModel.raml) | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 197 | [GET `/api/v2/Search/movie/popular`](../api/raml/api.raml#L3321) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 198 | [GET `/api/v2/Search/movie/popular/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3349) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 199 | [POST `/api/v2/Search/advancedSearch/movie/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3384) | D | `ombi_discover / advanced_movie` | path `currentPosition`:integer required; path `amountToLoad`:integer required; body [DiscoverModel](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.DiscoverModel.raml) | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 200 | [GET `/api/v2/Search/movie/seasonal/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3428) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 201 | [GET `/api/v2/Search/movie/requested/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3463) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 202 | [GET `/api/v2/Search/tv/requested/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3498) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchFullInfoTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.V2.SearchFullInfoTvShowViewModel.raml)> | — |
|
||||
| 203 | [GET `/api/v2/Search/movie/nowplaying`](../api/raml/api.raml#L3533) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 204 | [GET `/api/v2/Search/movie/nowplaying/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3561) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 205 | [GET `/api/v2/Search/movie/toprated`](../api/raml/api.raml#L3596) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 206 | [GET `/api/v2/Search/movie/toprated/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3624) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 207 | [GET `/api/v2/Search/movie/upcoming`](../api/raml/api.raml#L3659) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 208 | [GET `/api/v2/Search/movie/upcoming/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3687) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 209 | [GET `/api/v2/Search/tv/popular/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3722) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | — |
|
||||
| 210 | [GET `/api/v2/Search/tv/anticipated/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3757) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | — |
|
||||
| 211 | [GET `/api/v2/Search/tv/mostwatched/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3792) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | — |
|
||||
| 212 | [GET `/api/v2/Search/tv/trending/{currentPosition}/{amountToLoad}`](../api/raml/api.raml#L3827) | D | `ombi_discover / browse` | path `currentPosition`:integer required; path `amountToLoad`:integer required | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | — |
|
||||
| 213 | [GET `/api/v2/Search/actor/{actorId}/movie`](../api/raml/api.raml#L3862) | D | `ombi_discover / credits` | path `actorId`:unspecified required | 200: [ActorCredits](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.ActorCredits.raml) | — |
|
||||
| 214 | [GET `/api/v2/Search/actor/{actorId}/tv`](../api/raml/api.raml#L3892) | D | `ombi_discover / credits` | path `actorId`:unspecified required | 200: [ActorCredits](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.ActorCredits.raml) | — |
|
||||
| 215 | [GET `/api/v2/Search/artist/{artistId}`](../api/raml/api.raml#L3922) | D | `ombi_media / details` | path `artistId`:string required | 200: [ArtistInformation](../api/raml/types/Ombi.Core.Models.Search.V2.Music.ArtistInformation.raml) | — |
|
||||
| 216 | [GET `/api/v2/Search/artist/request/{requestId}`](../api/raml/api.raml#L3949) | D | `ombi_media / by_request` | path `requestId`:integer required | 200: [ArtistInformation](../api/raml/types/Ombi.Core.Models.Search.V2.Music.ArtistInformation.raml) | Request-ID namespace requires verification. |
|
||||
| 217 | [GET `/api/v2/Search/artist/album/{albumId}`](../api/raml/api.raml#L3976) | D | `ombi_media / details` | path `albumId`:string required | 200: [ReleaseGroup](../api/raml/types/Ombi.Core.Models.Search.V2.Music.ReleaseGroup.raml) | — |
|
||||
| 218 | [GET `/api/v2/Search/releasegroupart/{musicBrainzId}`](../api/raml/api.raml#L4003) | D | `ombi_library / album_art` | path `musicBrainzId`:string required | 200: [AlbumArt](../api/raml/types/Ombi.Core.Models.Search.V2.Music.AlbumArt.raml) | — |
|
||||
| 219 | [GET `/api/v2/Search/ratings/movie/{name}/{year}`](../api/raml/api.raml#L4030) | D | `ombi_media / ratings` | path `name`:string required; path `year`:integer required | 200: [MovieRatings](../api/raml/types/Ombi.Api.External.ExternalApis.RottenTomatoes.Models.MovieRatings.raml) | — |
|
||||
| 220 | [GET `/api/v2/Search/ratings/tv/{name}/{year}`](../api/raml/api.raml#L4060) | D | `ombi_media / ratings` | path `name`:string required; path `year`:integer required | 200: [TvRatings](../api/raml/types/Ombi.Api.External.ExternalApis.RottenTomatoes.Models.TvRatings.raml) | — |
|
||||
| 221 | [GET `/api/v2/Search/stream/movie/{movieDbId}`](../api/raml/api.raml#L4090) | D | `ombi_media / streaming` | path `movieDBId`:integer required | 200: array<[StreamingData](../api/raml/types/Ombi.Core.Models.Search.V2.StreamingData.raml)> | — |
|
||||
| 222 | [GET `/api/v2/Search/stream/tv/{movieDbId}`](../api/raml/api.raml#L4120) | D | `ombi_media / streaming` | path `movieDbId`:integer required | 200: array<[StreamingData](../api/raml/types/Ombi.Core.Models.Search.V2.StreamingData.raml)> | — |
|
||||
| 223 | [GET `/api/v1/Search/movie/{searchTerm}`](../api/raml/api.raml#L4150) | D | `ombi_search / text` | path `searchTerm`:unspecified required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | — |
|
||||
| 224 | [POST `/api/v1/Search/movie/actor`](../api/raml/api.raml#L4173) | D | `ombi_search / actor` | body [SearchActorModel](../api/raml/types/Ombi.Models.SearchActorModel.raml) | 200: body unspecified | — |
|
||||
| 225 | [POST `/api/v1/Search/movie`](../api/raml/api.raml#L4202) | D | `ombi_search / movie_refine` | body [SearchMovieRefineModel](../api/raml/types/Ombi.Models.SearchMovieRefineModel.raml) | 200: body unspecified | — |
|
||||
| 226 | [GET `/api/v1/Search/movie/info/{theMovieDbId}`](../api/raml/api.raml#L4231) | A | `ombi_media / details` | path `theMovieDbId`:unspecified required | 200: [SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml) | Legacy details alternative. |
|
||||
| 227 | [POST `/api/v1/Search/movie/info`](../api/raml/api.raml#L4253) | D | `ombi_media / movie_localized` | body [SearchMovieExtraInfoRefineModel](../api/raml/types/Ombi.Models.SearchMovieExtraInfoRefineModel.raml) | 200: body unspecified | — |
|
||||
| 228 | [POST `/api/v1/Search/movie/similar`](../api/raml/api.raml#L4282) | A | `ombi_discover / similar` | body [SimilarMoviesRefineModel](../api/raml/types/Ombi.Models.SimilarMoviesRefineModel.raml) | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Preserve language semantics; no automatic fallback. |
|
||||
| 229 | [GET `/api/v1/Search/movie/{theMovieDbId}/similar`](../api/raml/api.raml#L4309) | A | `ombi_discover / similar` | path `theMovieDbId`:unspecified required | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Preserve language semantics; no automatic fallback. |
|
||||
| 230 | [GET `/api/v1/Search/movie/popular`](../api/raml/api.raml#L4332) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 231 | [GET `/api/v1/Search/movie/nowplaying`](../api/raml/api.raml#L4350) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 232 | [GET `/api/v1/Search/movie/toprated`](../api/raml/api.raml#L4368) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 233 | [GET `/api/v1/Search/movie/upcoming`](../api/raml/api.raml#L4386) | A | `ombi_discover / browse` | none documented | 200: array<[SearchMovieViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchMovieViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 234 | [GET `/api/v1/Search/tv/{searchTerm}`](../api/raml/api.raml#L4404) | D | `ombi_search / text` | path `searchTerm`:unspecified required | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | — |
|
||||
| 235 | [GET `/api/v1/Search/tv/info/{tvdbId}`](../api/raml/api.raml#L4427) | A | `ombi_media / details` | path `tvdbId`:unspecified required | 200: [SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml) | Legacy details alternative. |
|
||||
| 236 | [GET `/api/v1/Search/tv/popular`](../api/raml/api.raml#L4449) | A | `ombi_discover / browse` | none documented | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 237 | [GET `/api/v1/Search/tv/anticipated`](../api/raml/api.raml#L4467) | A | `ombi_discover / browse` | none documented | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 238 | [GET `/api/v1/Search/tv/mostwatched`](../api/raml/api.raml#L4485) | A | `ombi_discover / browse` | none documented | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 239 | [GET `/api/v1/Search/tv/trending`](../api/raml/api.raml#L4503) | A | `ombi_discover / browse` | none documented | 200: array<[SearchTvShowViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchTvShowViewModel.raml)> | Nonpaged/legacy alternate only. |
|
||||
| 240 | [GET `/api/v1/Search/music/artist/{searchTerm}`](../api/raml/api.raml#L4521) | D | `ombi_search / text` | path `searchTerm`:string required | 200: array<[SearchArtistViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchArtistViewModel.raml)> | — |
|
||||
| 241 | [GET `/api/v1/Search/music/album/{searchTerm}`](../api/raml/api.raml#L4543) | D | `ombi_search / text` | path `searchTerm`:string required | 200: array<[SearchAlbumViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchAlbumViewModel.raml)> | — |
|
||||
| 242 | [GET `/api/v1/Search/music/album/info/{foreignAlbumId}`](../api/raml/api.raml#L4565) | A | `ombi_media / details` | path `foreignAlbumId`:string required | 200: [SearchAlbumViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchAlbumViewModel.raml) | Legacy details alternative. |
|
||||
| 243 | [GET `/api/v1/Search/music/artist/album/{foreignArtistId}`](../api/raml/api.raml#L4586) | D | `ombi_discover / artist_albums` | path `foreignArtistId`:string required | 200: array<[SearchAlbumViewModel](../api/raml/types/Ombi.Core.Models.Search.SearchAlbumViewModel.raml)> | — |
|
||||
| 244 | [GET `/api/v1/Settings/ombi`](../api/raml/api.raml#L4608) | D | `ombi_settings_read / ombi` | none documented | 200: [OmbiSettings](../api/raml/types/Ombi.Settings.Settings.Models.OmbiSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 245 | [POST `/api/v1/Settings/ombi`](../api/raml/api.raml#L4608) | P | `ombi_settings_write / patch:ombi` | body [OmbiSettings](../api/raml/types/Ombi.Settings.Settings.Models.OmbiSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 246 | [GET `/api/v1/Settings/baseurl`](../api/raml/api.raml#L4638) | D | `ombi_settings_read / base_url` | none documented | 200: string | Allowlisted projection; no raw secrets. |
|
||||
| 247 | [GET `/api/v1/Settings/about`](../api/raml/api.raml#L4649) | D | `ombi_server / about` | none documented | 200: [AboutViewModel](../api/raml/types/Ombi.Models.AboutViewModel.raml) | — |
|
||||
| 248 | [POST `/api/v1/Settings/ombi/resetApi`](../api/raml/api.raml#L4658) | X | `manual administration` | none documented | 200: string | Key rotation would invalidate configured credentials; no tool. |
|
||||
| 249 | [GET `/api/v1/Settings/plex`](../api/raml/api.raml#L4667) | D | `ombi_settings_read / plex` | none documented | 200: [PlexSettings](../api/raml/types/Ombi.Core.Settings.Models.External.PlexSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 250 | [POST `/api/v1/Settings/plex`](../api/raml/api.raml#L4667) | P | `ombi_settings_write / patch:plex` | body [PlexSettings](../api/raml/types/Ombi.Core.Settings.Models.External.PlexSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 251 | [GET `/api/v1/Settings/clientid`](../api/raml/api.raml#L4697) | D | `ombi_settings_read / client_id` | none documented | 200: string | Allowlisted projection; no raw secrets. |
|
||||
| 252 | [GET `/api/v1/Settings/emby`](../api/raml/api.raml#L4706) | D | `ombi_settings_read / emby` | none documented | 200: [EmbySettings](../api/raml/types/Ombi.Core.Settings.Models.External.EmbySettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 253 | [POST `/api/v1/Settings/emby`](../api/raml/api.raml#L4706) | P | `ombi_settings_write / patch:emby` | body [EmbySettings](../api/raml/types/Ombi.Core.Settings.Models.External.EmbySettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 254 | [GET `/api/v1/Settings/jellyfin`](../api/raml/api.raml#L4736) | D | `ombi_settings_read / jellyfin` | none documented | 200: [JellyfinSettings](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 255 | [POST `/api/v1/Settings/jellyfin`](../api/raml/api.raml#L4736) | P | `ombi_settings_write / patch:jellyfin` | body [JellyfinSettings](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 256 | [GET `/api/v1/Settings/landingpage`](../api/raml/api.raml#L4766) | D | `ombi_settings_read / landingpage` | none documented | 200: [LandingPageSettings](../api/raml/types/Ombi.Core.Settings.Models.LandingPageSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 257 | [POST `/api/v1/Settings/landingpage`](../api/raml/api.raml#L4766) | P | `ombi_settings_write / patch:landingpage` | body [LandingPageSettings](../api/raml/types/Ombi.Core.Settings.Models.LandingPageSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 258 | [GET `/api/v1/Settings/customization`](../api/raml/api.raml#L4796) | D | `ombi_settings_read / customization` | none documented | 200: [CustomizationSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomizationSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 259 | [POST `/api/v1/Settings/customization`](../api/raml/api.raml#L4796) | P | `ombi_settings_write / patch:customization` | body [CustomizationSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomizationSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 260 | [GET `/api/v1/Settings/defaultlanguage`](../api/raml/api.raml#L4826) | D | `ombi_settings_read / default_language` | none documented | 200: string | Allowlisted projection; no raw secrets. |
|
||||
| 261 | [GET `/api/v1/Settings/themes`](../api/raml/api.raml#L4837) | D | `ombi_settings_read / themes` | none documented | 200: array<[PresetThemeViewModel](../api/raml/types/Ombi.Models.PresetThemeViewModel.raml)> | Allowlisted projection; no raw secrets. |
|
||||
| 262 | [GET `/api/v1/Settings/sonarr`](../api/raml/api.raml#L4849) | D | `ombi_settings_read / sonarr` | none documented | 200: [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 263 | [POST `/api/v1/Settings/sonarr`](../api/raml/api.raml#L4849) | P | `ombi_settings_write / patch:sonarr` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 264 | [GET `/api/v1/Settings/radarr`](../api/raml/api.raml#L4879) | D | `ombi_settings_read / radarr` | none documented | 200: [RadarrCombinedModel](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrCombinedModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 265 | [POST `/api/v1/Settings/radarr`](../api/raml/api.raml#L4879) | P | `ombi_settings_write / patch:radarr` | body [RadarrCombinedModel](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrCombinedModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 266 | [GET `/api/v1/Settings/lidarr`](../api/raml/api.raml#L4909) | D | `ombi_settings_read / lidarr` | none documented | 200: [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 267 | [POST `/api/v1/Settings/lidarr`](../api/raml/api.raml#L4909) | P | `ombi_settings_write / patch:lidarr` | body [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 268 | [GET `/api/v1/Settings/lidarrenabled`](../api/raml/api.raml#L4939) | D | `ombi_settings_read / lidarrenabled` | none documented | 200: boolean | Allowlisted projection; no raw secrets. |
|
||||
| 269 | [POST `/api/v1/Settings/authentication`](../api/raml/api.raml#L4950) | P | `ombi_settings_write / patch:authentication` | body [AuthenticationSettings](../api/raml/types/Ombi.Settings.Settings.Models.AuthenticationSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 270 | [GET `/api/v1/Settings/authentication`](../api/raml/api.raml#L4950) | D | `ombi_settings_read / authentication` | none documented | 200: [AuthenticationSettings](../api/raml/types/Ombi.Settings.Settings.Models.AuthenticationSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 271 | [POST `/api/v1/Settings/Update`](../api/raml/api.raml#L4980) | P | `ombi_settings_write / patch:update` | body [UpdateSettings](../api/raml/types/Ombi.Settings.Settings.Models.UpdateSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 272 | [GET `/api/v1/Settings/Update`](../api/raml/api.raml#L4980) | D | `ombi_settings_read / update` | none documented | 200: [UpdateSettings](../api/raml/types/Ombi.Settings.Settings.Models.UpdateSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 273 | [GET `/api/v1/Settings/UserManagement`](../api/raml/api.raml#L5010) | D | `ombi_settings_read / user_management` | none documented | 200: [UserManagementSettings](../api/raml/types/Ombi.Settings.Settings.Models.UserManagementSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 274 | [POST `/api/v1/Settings/UserManagement`](../api/raml/api.raml#L5010) | P | `ombi_settings_write / patch:user_management` | body [UserManagementSettings](../api/raml/types/Ombi.Settings.Settings.Models.UserManagementSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 275 | [GET `/api/v1/Settings/CouchPotato`](../api/raml/api.raml#L5040) | D | `ombi_settings_read / couchpotato` | none documented | 200: [CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 276 | [POST `/api/v1/Settings/CouchPotato`](../api/raml/api.raml#L5040) | P | `ombi_settings_write / patch:couchpotato` | body [CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 277 | [GET `/api/v1/Settings/DogNzb`](../api/raml/api.raml#L5070) | D | `ombi_settings_read / dognzb` | none documented | 200: [DogNzbSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.DogNzbSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 278 | [POST `/api/v1/Settings/DogNzb`](../api/raml/api.raml#L5070) | P | `ombi_settings_write / patch:dognzb` | body [DogNzbSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.DogNzbSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 279 | [POST `/api/v1/Settings/SickRage`](../api/raml/api.raml#L5100) | P | `ombi_settings_write / patch:sickrage` | body [SickRageSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SickRageSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 280 | [GET `/api/v1/Settings/SickRage`](../api/raml/api.raml#L5100) | D | `ombi_settings_read / sickrage` | none documented | 200: [SickRageSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SickRageSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 281 | [GET `/api/v1/Settings/jobs`](../api/raml/api.raml#L5130) | D | `ombi_settings_read / jobs` | none documented | 200: [JobSettings](../api/raml/types/Ombi.Settings.Settings.Models.JobSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 282 | [POST `/api/v1/Settings/jobs`](../api/raml/api.raml#L5130) | P | `ombi_settings_write / patch:jobs` | body [JobSettings](../api/raml/types/Ombi.Settings.Settings.Models.JobSettings.raml) | 200: [JobSettingsViewModel](../api/raml/types/Ombi.Models.JobSettingsViewModel.raml) | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 283 | [POST `/api/v1/Settings/testcron`](../api/raml/api.raml#L5160) | D | `ombi_server / cron_validate` | body [CronViewModelBody](../api/raml/types/Ombi.Models.CronViewModelBody.raml) | 200: [CronTestModel](../api/raml/types/Ombi.Models.CronTestModel.raml) | Administrator-gated read calculation. |
|
||||
| 284 | [POST `/api/v1/Settings/Issues`](../api/raml/api.raml#L5178) | P | `ombi_settings_write / patch:issues` | body [IssueSettings](../api/raml/types/Ombi.Settings.Settings.Models.IssueSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 285 | [GET `/api/v1/Settings/Issues`](../api/raml/api.raml#L5178) | D | `ombi_settings_read / issues` | none documented | 200: [IssueSettings](../api/raml/types/Ombi.Settings.Settings.Models.IssueSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 286 | [GET `/api/v1/Settings/issuesenabled`](../api/raml/api.raml#L5208) | D | `ombi_settings_read / issuesenabled` | none documented | 200: boolean | Allowlisted projection; no raw secrets. |
|
||||
| 287 | [POST `/api/v1/Settings/vote`](../api/raml/api.raml#L5217) | P | `ombi_settings_write / patch:vote` | body [VoteSettings](../api/raml/types/Ombi.Settings.Settings.Models.VoteSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 288 | [GET `/api/v1/Settings/vote`](../api/raml/api.raml#L5217) | D | `ombi_settings_read / vote` | none documented | 200: [VoteSettings](../api/raml/types/Ombi.Settings.Settings.Models.VoteSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 289 | [GET `/api/v1/Settings/voteenabled`](../api/raml/api.raml#L5247) | D | `ombi_settings_read / voteenabled` | none documented | 200: boolean | Allowlisted projection; no raw secrets. |
|
||||
| 290 | [POST `/api/v1/Settings/themoviedb`](../api/raml/api.raml#L5256) | P | `ombi_settings_write / patch:themoviedb` | body [TheMovieDbSettings](../api/raml/types/Ombi.Core.Settings.Models.External.TheMovieDbSettings.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 291 | [GET `/api/v1/Settings/themoviedb`](../api/raml/api.raml#L5256) | D | `ombi_settings_read / themoviedb` | none documented | 200: [TheMovieDbSettings](../api/raml/types/Ombi.Core.Settings.Models.External.TheMovieDbSettings.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 292 | [POST `/api/v1/Settings/notifications/email`](../api/raml/api.raml#L5286) | P | `ombi_settings_write / patch:notifications.email` | body [EmailNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.EmailNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 293 | [GET `/api/v1/Settings/notifications/email`](../api/raml/api.raml#L5286) | D | `ombi_settings_read / notifications.email` | none documented | 200: [EmailNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.EmailNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 294 | [GET `/api/v1/Settings/notifications/email/enabled`](../api/raml/api.raml#L5316) | D | `ombi_settings_read / notifications.email.enabled` | none documented | 200: boolean | Allowlisted projection; no raw secrets. |
|
||||
| 295 | [POST `/api/v1/Settings/notifications/discord`](../api/raml/api.raml#L5327) | P | `ombi_settings_write / patch:notifications.discord` | body [DiscordNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.DiscordNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 296 | [GET `/api/v1/Settings/notifications/discord`](../api/raml/api.raml#L5327) | D | `ombi_settings_read / notifications.discord` | none documented | 200: [DiscordNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.DiscordNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 297 | [POST `/api/v1/Settings/notifications/telegram`](../api/raml/api.raml#L5357) | P | `ombi_settings_write / patch:notifications.telegram` | body [TelegramNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.TelegramNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 298 | [GET `/api/v1/Settings/notifications/telegram`](../api/raml/api.raml#L5357) | D | `ombi_settings_read / notifications.telegram` | none documented | 200: [TelegramNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.TelegramNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 299 | [POST `/api/v1/Settings/notifications/pushbullet`](../api/raml/api.raml#L5387) | P | `ombi_settings_write / patch:notifications.pushbullet` | body [PushbulletNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushbulletNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 300 | [GET `/api/v1/Settings/notifications/pushbullet`](../api/raml/api.raml#L5387) | D | `ombi_settings_read / notifications.pushbullet` | none documented | 200: [PushbulletNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushbulletNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 301 | [POST `/api/v1/Settings/notifications/pushover`](../api/raml/api.raml#L5417) | P | `ombi_settings_write / patch:notifications.pushover` | body [PushoverNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushoverNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 302 | [GET `/api/v1/Settings/notifications/pushover`](../api/raml/api.raml#L5417) | D | `ombi_settings_read / notifications.pushover` | none documented | 200: [PushoverNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushoverNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 303 | [POST `/api/v1/Settings/notifications/slack`](../api/raml/api.raml#L5447) | P | `ombi_settings_write / patch:notifications.slack` | body [SlackNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.SlackNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 304 | [GET `/api/v1/Settings/notifications/slack`](../api/raml/api.raml#L5447) | D | `ombi_settings_read / notifications.slack` | none documented | 200: [SlackNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.SlackNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 305 | [POST `/api/v1/Settings/notifications/mattermost`](../api/raml/api.raml#L5477) | P | `ombi_settings_write / patch:notifications.mattermost` | body [MattermostNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MattermostNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 306 | [GET `/api/v1/Settings/notifications/mattermost`](../api/raml/api.raml#L5477) | D | `ombi_settings_read / notifications.mattermost` | none documented | 200: [MattermostNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MattermostNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 307 | [GET `/api/v1/Settings/notifications/twilio`](../api/raml/api.raml#L5507) | D | `ombi_settings_read / notifications.twilio` | none documented | 200: [TwilioSettingsViewModel](../api/raml/types/Ombi.Core.Models.UI.TwilioSettingsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 308 | [POST `/api/v1/Settings/notifications/twilio`](../api/raml/api.raml#L5507) | P | `ombi_settings_write / patch:notifications.twilio` | body [TwilioSettingsViewModel](../api/raml/types/Ombi.Core.Models.UI.TwilioSettingsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 309 | [POST `/api/v1/Settings/notifications/mobile`](../api/raml/api.raml#L5537) | P | `ombi_settings_write / patch:notifications.mobile` | body [MobileNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MobileNotificationsViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 310 | [GET `/api/v1/Settings/notifications/mobile`](../api/raml/api.raml#L5537) | D | `ombi_settings_read / notifications.mobile` | none documented | 200: [MobileNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MobileNotificationsViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 311 | [POST `/api/v1/Settings/notifications/gotify`](../api/raml/api.raml#L5567) | P | `ombi_settings_write / patch:notifications.gotify` | body [GotifyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.GotifyNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 312 | [GET `/api/v1/Settings/notifications/gotify`](../api/raml/api.raml#L5567) | D | `ombi_settings_read / notifications.gotify` | none documented | 200: [GotifyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.GotifyNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 313 | [POST `/api/v1/Settings/notifications/ntfy`](../api/raml/api.raml#L5597) | P | `ombi_settings_write / patch:notifications.ntfy` | body [NtfyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NtfyNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 314 | [GET `/api/v1/Settings/notifications/ntfy`](../api/raml/api.raml#L5597) | D | `ombi_settings_read / notifications.ntfy` | none documented | 200: [NtfyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NtfyNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 315 | [POST `/api/v1/Settings/notifications/webhook`](../api/raml/api.raml#L5627) | P | `ombi_settings_write / patch:notifications.webhook` | body [WebhookNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.WebhookNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 316 | [GET `/api/v1/Settings/notifications/webhook`](../api/raml/api.raml#L5627) | D | `ombi_settings_read / notifications.webhook` | none documented | 200: [WebhookNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.WebhookNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 317 | [POST `/api/v1/Settings/notifications/newsletter`](../api/raml/api.raml#L5657) | P | `ombi_settings_write / patch:notifications.newsletter` | body [NewsletterNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NewsletterNotificationViewModel.raml) | 200: boolean | Only typed non-secret patch; preserve omitted/private fields. |
|
||||
| 318 | [GET `/api/v1/Settings/notifications/newsletter`](../api/raml/api.raml#L5657) | D | `ombi_settings_read / notifications.newsletter` | none documented | 200: [NewsletterNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NewsletterNotificationViewModel.raml) | Allowlisted projection; no raw secrets. |
|
||||
| 319 | [POST `/api/v1/Sonarr/Profiles`](../api/raml/api.raml#L5687) | A | `ombi_integration_read / options:sonarr/profiles` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: array<[SonarrProfile](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.SonarrProfile.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 320 | [GET `/api/v1/Sonarr/Profiles`](../api/raml/api.raml#L5687) | D | `ombi_integration_read / options:sonarr/profiles` | none documented | 200: array<[SonarrProfile](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.SonarrProfile.raml)> | — |
|
||||
| 321 | [POST `/api/v1/Sonarr/RootFolders`](../api/raml/api.raml#L5719) | A | `ombi_integration_read / options:sonarr/root_folders` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: array<[SonarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.SonarrRootFolder.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 322 | [GET `/api/v1/Sonarr/RootFolders`](../api/raml/api.raml#L5719) | D | `ombi_integration_read / options:sonarr/root_folders` | none documented | 200: array<[SonarrRootFolder](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.SonarrRootFolder.raml)> | — |
|
||||
| 323 | [GET `/api/v1/Sonarr/v3/LanguageProfiles`](../api/raml/api.raml#L5751) | D | `ombi_integration_read / options:sonarr/language_profiles` | none documented | 200: array<[LanguageProfiles](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.V3.LanguageProfiles.raml)> | — |
|
||||
| 324 | [POST `/api/v1/Sonarr/v3/LanguageProfiles`](../api/raml/api.raml#L5751) | A | `ombi_integration_read / options:sonarr/language_profiles` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: array<[LanguageProfiles](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.V3.LanguageProfiles.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 325 | [POST `/api/v1/Sonarr/tags`](../api/raml/api.raml#L5783) | A | `ombi_integration_read / options:sonarr/tags` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: array<[Tag](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.Tag.raml)> | Private saved settings only; no connection overrides. |
|
||||
| 326 | [GET `/api/v1/Sonarr/tags`](../api/raml/api.raml#L5783) | D | `ombi_integration_read / options:sonarr/tags` | none documented | 200: array<[Tag](../api/raml/types/Ombi.Api.External.ExternalApis.Sonarr.Models.Tag.raml)> | — |
|
||||
| 327 | [GET `/api/v1/Sonarr/enabled`](../api/raml/api.raml#L5815) | D | `ombi_integration_read / options:sonarr/enabled` | none documented | 200: boolean | — |
|
||||
| 328 | [GET `/api/v1/Sonarr/version`](../api/raml/api.raml#L5824) | D | `ombi_integration_read / options:sonarr/version` | none documented | 200: string | — |
|
||||
| 329 | [GET `/api/v1/Stats`](../api/raml/api.raml#L5833) | D | `ombi_server / stats` | query `from`:string optional; query `to`:string optional | 200: [UserStatsSummary](../api/raml/types/Ombi.Core.Engine.UserStatsSummary.raml) | from/to query parameters are supported. |
|
||||
| 330 | [GET `/api/v1/Status`](../api/raml/api.raml#L5849) | D | `ombi_server / status` | none documented | 200: [HttpStatusCode](../api/raml/types/System.Net.HttpStatusCode.raml) | — |
|
||||
| 331 | [GET `/api/v1/Status/info`](../api/raml/api.raml#L5860) | D | `ombi_server / status_info` | none documented | 200: string | — |
|
||||
| 332 | [GET `/api/v2/System/news`](../api/raml/api.raml#L5871) | D | `ombi_server / news` | none documented | 200: body unspecified | — |
|
||||
| 333 | [GET `/api/v2/System/logs`](../api/raml/api.raml#L5877) | D | `ombi_logs / list` | none documented | 200: body unspecified | — |
|
||||
| 334 | [GET `/api/v2/System/logs/{logFileName}`](../api/raml/api.raml#L5883) | D | `ombi_logs / read` | path `logFileName`:string required | 200: body unspecified | Vetted opaque ID maps to filename; sanitized bounded local slicing. |
|
||||
| 335 | [GET `/api/v2/System/logs/download/{logFileName}`](../api/raml/api.raml#L5893) | X | `raw diagnostic download` | path `logFileName`:string required | 200: body unspecified | May expose secrets; sanitized logs tool is the supported alternative. |
|
||||
| 336 | [POST `/api/v1/Tester/discord`](../api/raml/api.raml#L5903) | P | `ombi_integration_test / discord` | body [DiscordNotificationSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.DiscordNotificationSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 337 | [POST `/api/v1/Tester/pushbullet`](../api/raml/api.raml#L5923) | P | `ombi_integration_test / pushbullet` | body [PushbulletSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.PushbulletSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 338 | [POST `/api/v1/Tester/pushover`](../api/raml/api.raml#L5943) | P | `ombi_integration_test / pushover` | body [PushoverSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.PushoverSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 339 | [POST `/api/v1/Tester/gotify`](../api/raml/api.raml#L5963) | P | `ombi_integration_test / gotify` | body [GotifySettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.GotifySettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 340 | [POST `/api/v1/Tester/ntfy`](../api/raml/api.raml#L5983) | P | `ombi_integration_test / ntfy` | body [NtfySettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.NtfySettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 341 | [POST `/api/v1/Tester/webhook`](../api/raml/api.raml#L6003) | P | `ombi_integration_test / webhook` | body [WebhookSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.WebhookSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 342 | [POST `/api/v1/Tester/mattermost`](../api/raml/api.raml#L6023) | P | `ombi_integration_test / mattermost` | body [MattermostNotificationSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.MattermostNotificationSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 343 | [POST `/api/v1/Tester/slack`](../api/raml/api.raml#L6043) | P | `ombi_integration_test / slack` | body [SlackNotificationSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.SlackNotificationSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 344 | [POST `/api/v1/Tester/email`](../api/raml/api.raml#L6063) | P | `ombi_integration_test / email` | body [EmailNotificationSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.EmailNotificationSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 345 | [POST `/api/v1/Tester/plex`](../api/raml/api.raml#L6083) | P | `ombi_integration_test / plex` | body [PlexServers](../api/raml/types/Ombi.Core.Settings.Models.External.PlexServers.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 346 | [POST `/api/v1/Tester/emby`](../api/raml/api.raml#L6103) | P | `ombi_integration_test / emby` | body [EmbyServers](../api/raml/types/Ombi.Core.Settings.Models.External.EmbyServers.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 347 | [POST `/api/v1/Tester/jellyfin`](../api/raml/api.raml#L6123) | P | `ombi_integration_test / jellyfin` | body [JellyfinServers](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinServers.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 348 | [POST `/api/v1/Tester/radarr`](../api/raml/api.raml#L6143) | P | `ombi_integration_test / radarr` | body [RadarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrSettings.raml) | 200: [TesterResultModel](../api/raml/types/Ombi.Core.Models.TesterResultModel.raml) | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 349 | [POST `/api/v1/Tester/sonarr`](../api/raml/api.raml#L6163) | P | `ombi_integration_test / sonarr` | body [SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) | 200: [TesterResultModel](../api/raml/types/Ombi.Core.Models.TesterResultModel.raml) | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 350 | [POST `/api/v1/Tester/couchpotato`](../api/raml/api.raml#L6183) | P | `ombi_integration_test / couchpotato` | body [CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 351 | [POST `/api/v1/Tester/telegram`](../api/raml/api.raml#L6203) | P | `ombi_integration_test / telegram` | body [TelegramSettings](../api/raml/types/Ombi.Settings.Settings.Models.Notifications.TelegramSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 352 | [POST `/api/v1/Tester/sickrage`](../api/raml/api.raml#L6223) | P | `ombi_integration_test / sickrage` | body [SickRageSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SickRageSettings.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 353 | [POST `/api/v1/Tester/newsletter`](../api/raml/api.raml#L6243) | P | `ombi_integration_test / newsletter` | body [NewsletterNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NewsletterNotificationViewModel.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 354 | [POST `/api/v1/Tester/mobile`](../api/raml/api.raml#L6261) | P | `ombi_integration_test / mobile` | body [MobileNotificationTestViewModel](../api/raml/types/Ombi.Models.MobileNotificationTestViewModel.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 355 | [POST `/api/v1/Tester/lidarr`](../api/raml/api.raml#L6279) | P | `ombi_integration_test / lidarr` | body [LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) | 200: [TesterResultModel](../api/raml/types/Ombi.Core.Models.TesterResultModel.raml) | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 356 | [POST `/api/v1/Tester/whatsapp`](../api/raml/api.raml#L6297) | P | `ombi_integration_test / whatsapp` | body [WhatsAppSettingsViewModel](../api/raml/types/Ombi.Core.Models.UI.WhatsAppSettingsViewModel.raml) | 200: boolean | Saved authorized profile only; exact tester body differs by service. |
|
||||
| 357 | [GET `/api/v1/TheMovieDb/Keywords`](../api/raml/api.raml#L6315) | D | `ombi_reference / keywords` | query `searchTerm`:unspecified optional | 200: array<[TheMovidDbKeyValue](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.TheMovidDbKeyValue.raml)> | — |
|
||||
| 358 | [GET `/api/v1/TheMovieDb/Keywords/{keywordId}`](../api/raml/api.raml#L6332) | D | `ombi_reference / keyword` | path `keywordId`:unspecified required | 200: body unspecified | — |
|
||||
| 359 | [GET `/api/v1/TheMovieDb/WatchProviders/movie`](../api/raml/api.raml#L6345) | D | `ombi_reference / watch_providers` | query `searchTerm`:unspecified optional | 200: array<[WatchProvidersResults](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.WatchProvidersResults.raml)> | — |
|
||||
| 360 | [GET `/api/v1/TheMovieDb/WatchProviders/tv`](../api/raml/api.raml#L6362) | D | `ombi_reference / watch_providers` | query `searchTerm`:unspecified optional | 200: array<[WatchProvidersResults](../api/raml/types/Ombi.Api.External.ExternalApis.TheMovieDb.Models.WatchProvidersResults.raml)> | — |
|
||||
| 361 | [POST `/api/v1/Token`](../api/raml/api.raml#L6379) | I | `upstream authentication adapter` | body [UserAuthModel](../api/raml/types/Ombi.Models.UserAuthModel.raml) | 200: [Token](../api/raml/types/Ombi.Controllers.V1.Token.raml) | Not model-callable; optional auth flow must be verified. |
|
||||
| 362 | [POST `/api/v1/Token/plextoken`](../api/raml/api.raml#L6405) | I | `upstream authentication adapter` | body [PlexTokenAuthentication](../api/raml/types/Ombi.Models.External.PlexTokenAuthentication.raml) | success unspecified | Not model-callable; optional auth flow must be verified. |
|
||||
| 363 | [GET `/api/v1/Token/{pinId}`](../api/raml/api.raml#L6431) | I | `upstream authentication adapter` | path `pinId`:integer required | success unspecified | Not model-callable; optional auth flow must be verified. |
|
||||
| 364 | [POST `/api/v1/Token/refresh`](../api/raml/api.raml#L6444) | I | `upstream authentication adapter` | body [TokenController_TokenRefresh](../api/raml/types/Ombi.Controllers.V1.TokenController_TokenRefresh.raml) | success unspecified | Not model-callable; optional auth flow must be verified. |
|
||||
| 365 | [POST `/api/v1/Token/requirePassword`](../api/raml/api.raml#L6464) | I | `upstream authentication adapter` | body [UserAuthModel](../api/raml/types/Ombi.Models.UserAuthModel.raml) | 200: boolean | Not model-callable; optional auth flow must be verified. |
|
||||
| 366 | [POST `/api/v1/Token/header_auth`](../api/raml/api.raml#L6482) | I | `upstream authentication adapter` | none documented | 200: body unspecified | Not model-callable; optional auth flow must be verified. |
|
||||
| 367 | [GET `/api/v1/Update`](../api/raml/api.raml#L6494) | D | `ombi_server / update_info` | none documented | 200: [UpdateModel](../api/raml/types/Ombi.Core.Processor.UpdateModel.raml) | — |
|
||||
| 368 | [GET `/api/v1/Vote`](../api/raml/api.raml#L6503) | D | `ombi_votes / list` | none documented | 200: array<[VoteViewModel](../api/raml/types/Ombi.Core.Models.UI.VoteViewModel.raml)> | — |
|
||||
| 369 | [POST `/api/v1/Vote/up/movie/{requestId}`](../api/raml/api.raml#L6515) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 370 | [POST `/api/v1/Vote/up/tv/{requestId}`](../api/raml/api.raml#L6530) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 371 | [POST `/api/v1/Vote/up/album/{requestId}`](../api/raml/api.raml#L6545) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 372 | [POST `/api/v1/Vote/down/movie/{requestId}`](../api/raml/api.raml#L6560) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 373 | [POST `/api/v1/Vote/down/tv/{requestId}`](../api/raml/api.raml#L6575) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 374 | [POST `/api/v1/Vote/down/album/{requestId}`](../api/raml/api.raml#L6590) | D | `ombi_vote` | path `requestId`:integer required | 200: [VoteEngineResult](../api/raml/types/Ombi.Core.Models.VoteEngineResult.raml) | — |
|
||||
| 375 | [GET `/api/v1/Vote/movie/{requestId}`](../api/raml/api.raml#L6605) | D | `ombi_votes / get` | path `requestId`:integer required | 200: array<[Votes](../api/raml/types/Ombi.Store.Entities.Votes.raml)> | — |
|
||||
| 376 | [GET `/api/v1/Vote/music/{requestId}`](../api/raml/api.raml#L6621) | D | `ombi_votes / get` | path `requestId`:integer required | 200: array<[Votes](../api/raml/types/Ombi.Store.Entities.Votes.raml)> | — |
|
||||
| 377 | [GET `/api/v1/Vote/tv/{requestId}`](../api/raml/api.raml#L6637) | D | `ombi_votes / get` | path `requestId`:integer required | 200: array<[Votes](../api/raml/types/Ombi.Store.Entities.Votes.raml)> | — |
|
||||
@@ -1,221 +0,0 @@
|
||||
# Evidence gaps and acceptance criteria
|
||||
|
||||
## What this review establishes
|
||||
|
||||
The review reads all six earlier design documents, inventories every resource/method in `api.raml`, follows the request/response type references relevant to the tool contracts and inspects the authentication security scheme. The complete inventory is **377 operations, 321 paths**. The replacement accounts for 257 direct mappings, 37 alternative routes, 59 partial mappings, 12 internal operations and 12 exclusions. These are design dispositions, not live integration-test results.
|
||||
|
||||
The input catalogue contains all 31 proposed tool schemas; it does not stop at the three most complex tools. The output contract binds each of those 31 operations to specific result families. Administrative patch schemas reference 33 writable settings sections; settings reads cover 41 sections. Feature writes are separate actions and not counted as a settings section.
|
||||
|
||||
No application code, runtime dependency declaration, credentials, existing draft or RAML file is changed. No live Ombi mutation is performed. Source-specific verification remains necessary before implementing or enabling gated branches.
|
||||
|
||||
## Checks completed on these documents
|
||||
|
||||
- Parsed the embedded JSON and validated all 31 input schemas plus the common output schema using `jsonschema` 4.25.1's Draft202012Validator. The validator was installed in a temporary directory, not added to this project.
|
||||
- Validated 217 positive branch/result examples, including one minimal input per branch and success/error results for every tool. Rejected 568 negative cases covering missing required fields, unexpected properties and representative invalid media/action combinations. These checks establish schema consistency, not upstream runtime semantics.
|
||||
- Compared the ledger's unique method/path set directly with the parsed RAML: exact equality, 377 operations, 321 distinct paths, no omissions or duplicates. Every published tool has an operation owner in the ledger.
|
||||
- Checked local Markdown links and all local `$ref` targets, and confirmed the new documents contain no environment-specific instance URL.
|
||||
- Reproduced the earlier drafts' schema issues with a validator: SWE `read_requests` with only `mediaType=movie` unexpectedly requires requestId and query; SWE accepts TV with both external IDs and an empty selection; MED accepts movie creation without its body and issue creation without its body.
|
||||
|
||||
The schemas deliberately retain runtime checks for semantics JSON Schema cannot establish: upstream identity, permissions, available profiles, provider-ID provenance, duplicate season-number keys, no-op patches, array identity preservation and request outcome reconciliation.
|
||||
|
||||
## Source defects and semantic gaps
|
||||
|
||||
| Gap | Evidence | Required decision / verification |
|
||||
|---|---|---|
|
||||
| Authentication model | ApiKey scheme is global; Token routes also appear beneath it | Establish login exceptions, Bearer support and permission model separately; keep API-key baseline |
|
||||
| API-key principal | Security scheme only names a header | Determine effective user, quotas, on-behalf rights and required permissions; do not infer admin or anonymous identity |
|
||||
| Enum labels | IssueStatus `[0,1,2,3]`, RequestType `[0,1,2]`, VoteType/RequestSource numeric only | Verify symbolic names from authoritative controller/enum source for supported versions; preserve raw codes meanwhile |
|
||||
| TV request identity | v1 request body has tvDbId; v2 has theMovieDbId | Keep provider-specific create branches; never substitute one ID namespace for another |
|
||||
| TV result granularity | v2 TV list wraps ChildRequests; v1 wraps TvRequests | Preserve target kind and parent ID; no silent fallback between units |
|
||||
| TV moderation/options/subscriptions/details | Several models/routes only say `id` or `requestId` | Verify each controller's accepted parent/child namespace, independently per operation |
|
||||
| On behalf | requestOnBehalf is just string | Confirm ID versus username and permissions; resolve the public user ID internally if necessary |
|
||||
| Whole-season semantics | Season/episode properties optional; no empty-list contract | Use explicit episodes; do not infer empty means all |
|
||||
| Request-type filtering | Legacy order/status/availability parameters are unlabeled integers | Establish finite maps before any legacy filtered adapter; reject unsupported combinations |
|
||||
| Path/parameter mismatches | IMDb imdbid/imdbId; TV tvdbId/tvdbid; streaming movieDbId/movieDBId | Substitute literal path placeholders while preserving parameter meaning |
|
||||
| Legacy TV filter mismatch | Path names statusFilterType/availabilityFilterType plus separate statusType/availabilityType parameters | Verify the actual wire behaviour; do not assume these duplicate-looking parameters are interchangeable |
|
||||
| Folded type declarations | Some descriptions contain text such as `type: integer` instead of a YAML type field | Treat human text as evidence of intent, not valid machine typing; adapter/public schema must state its choice |
|
||||
| Radarr tags POST | Body is declared SonarrSettings | Prefer existing GET; verify the POST rather than silently correcting RAML |
|
||||
| Response omissions | Images, user-country list, provider issues, status info, server logs and some integration methods omit schemas | Inspect actual supported-version output and use an allowlisted projection; unknown structures fail closed |
|
||||
| Integration side effects | Some POST credential/setup endpoints lack descriptions | Keep outside read tools until behaviour is established; no claim that every POST lookup is harmless |
|
||||
| Collection creation | No body, single RequestEngineResult response | No unsupported modifiers; verify partial success/retry semantics and do not fabricate per-item results |
|
||||
| Refresh model | token/userename, no refresh-token field | Verify typo and semantics; no invented refresh token |
|
||||
| Pagination | Ambiguous prose and plain arrays mixed with paged wrappers | Validate units/offset semantics/total fields per route; no invented totals |
|
||||
| Update and test results | Boolean, model and unspecified responses differ | Interpret per operation, not one universal tester or mutation shape |
|
||||
| Request retry | GET queue and DELETE entry only | Reprocess existing request via v2 when supported; no synthetic POST queue route |
|
||||
| Settings replacement | POST section bodies, no documented PATCH/ETag | Private merge + revision check; report residual race with external writers |
|
||||
| Deployment paths | RAML contains a fixed baseUri | Never use it as a distributable default or public fixture; preserve configured reverse-proxy path prefixes |
|
||||
|
||||
## Acceptance criteria for an implementation
|
||||
|
||||
These are future checks, not claims that an implementation was written or tested here.
|
||||
|
||||
### Schema and routing
|
||||
|
||||
- Validate every published schema with JSON Schema 2020-12 and exercise each branch with both a valid object and common invalid combinations.
|
||||
- An empty object must not satisfy a request-creation, issue-creation, moderation or delete schema. Optional defaults must not trigger unrelated conditional requirements.
|
||||
- Movie, TVDB TV, TMDB TV, album and collection creation must send the exact wire body and method. TV has no is4kRequest, album has no requestOnBehalf, and collection has no invented body.
|
||||
- Denial uses PUT for all media. Similar and actor searches use POST. Lidarr Metadata uses POST. User detail uses Identity/User/{id}. No POST RequestRetry is emitted.
|
||||
- Request list rejects album+unavailable, preserves TV child identity, maps request_date to requestDate and verifies local versus upstream pagination metadata.
|
||||
- Reject empty explicit episode lists, duplicate seasons/episodes, ambiguous providers, irrelevant action properties and overflowing request budgets before upstream calls.
|
||||
- Check every method/path pair against the ledger, including spelling/case and request/response types. Generated brace expansion must never add routes.
|
||||
|
||||
### Error and output behaviour
|
||||
|
||||
- Handle HTTP 200 business failures, Boolean tester failures, empty-success bodies, missing optional fields and unknown response structures independently.
|
||||
- Confirm structuredContent validates, text fallback contains the same bounded projection and MCP isError agrees with ok.
|
||||
- Do not infer success from result ID presence or a timeout; expose UNKNOWN_OUTCOME where execution may already have occurred.
|
||||
- Verify safe errors for upstream 401/403/404/429/5xx, network errors, invalid JSON and malformed/oversized bodies.
|
||||
- Check list pages with zero items, unknown totals, truncated arrays and concurrent insertion/deletion. Do not offer a false continuation offset.
|
||||
- Test nested secrets in requests, issues, stats, user records and error bodies, not just settings.
|
||||
|
||||
### Authorization and effects
|
||||
|
||||
- Apply core/moderation/administration and branch policy both at tools/list and call time. Annotation values cannot grant permissions.
|
||||
- Confirm the effective upstream principal and another-user access explicitly. API key mode must not accidentally promise per-client user isolation.
|
||||
- Keep one shared HTTP client, encode path/query values, preserve configured prefixes and suppress cross-origin credential forwarding.
|
||||
- Ensure authentication recovery cannot replay ambiguous writes; capability probes must never trigger jobs or writes.
|
||||
- Preserve settings secrets and omitted values, reject stale revisions, reject unsafe array replacements and disclose external-writer race limits.
|
||||
- Verify email recipients, welcome-email target, collection scope, TV parent-delete effects and each enabled job's scope before mutation.
|
||||
- Never expose credential acquisition, key rotation, arbitrary HTTP, raw entity replacement or raw logs through an accidental fallback branch.
|
||||
|
||||
## Review examples
|
||||
|
||||
These are illustrative arguments with placeholder IDs, not commands executed against a server. Actual IDs and revision/profile references must come from authorized reads. Numeric examples do not assert real media identities. The TV moderation example requires a verified adapter that establishes ID 456 as a child request.
|
||||
|
||||
### Mixed search
|
||||
|
||||
Tool: `ombi_search`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "multi",
|
||||
"query": "Example title",
|
||||
"include": [
|
||||
"movies",
|
||||
"tv_shows"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### TMDB TV details
|
||||
|
||||
Tool: `ombi_media`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "details",
|
||||
"target": {
|
||||
"media": "tv",
|
||||
"provider": "tmdb",
|
||||
"id": 123
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Explicit TV episode request
|
||||
|
||||
Tool: `ombi_request_create`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "tv",
|
||||
"provider": "tmdb",
|
||||
"id": 123,
|
||||
"selection": {
|
||||
"mode": "episodes",
|
||||
"seasons": [
|
||||
{
|
||||
"season_number": 1,
|
||||
"episodes": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TV child moderation
|
||||
|
||||
Tool: `ombi_request_moderate`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "approve",
|
||||
"media": "tv",
|
||||
"request_id": 456
|
||||
}
|
||||
```
|
||||
|
||||
### Movie 4K denial
|
||||
|
||||
Tool: `ombi_request_moderate`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "deny",
|
||||
"media": "movie",
|
||||
"request_id": 789,
|
||||
"is_4k": true,
|
||||
"reason": "Not currently accepting 4K requests."
|
||||
}
|
||||
```
|
||||
|
||||
### Album request
|
||||
|
||||
Tool: `ombi_request_create`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "album",
|
||||
"musicbrainz_id": "example-release-group-id"
|
||||
}
|
||||
```
|
||||
|
||||
### Issue comment
|
||||
|
||||
Tool: `ombi_issue_comment`.
|
||||
|
||||
```json
|
||||
{
|
||||
"issue_id": 123,
|
||||
"comment": "Playback fails at the same point on a second device."
|
||||
}
|
||||
```
|
||||
|
||||
### Change a setting
|
||||
|
||||
Tool: `ombi_settings_write`.
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "patch",
|
||||
"section": "ombi",
|
||||
"revision": "example-revision-from-settings-read",
|
||||
"changes": {
|
||||
"hideRequestsUsers": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Saved notification test
|
||||
|
||||
Tool: `ombi_integration_test`.
|
||||
|
||||
```json
|
||||
{
|
||||
"service": "discord",
|
||||
"profile_id": "example-authorized-saved-profile"
|
||||
}
|
||||
```
|
||||
|
||||
## Primary references
|
||||
|
||||
- [Local RAML API](../api/raml/api.raml) and [API-key security scheme](../api/raml/securitySchemes/api_key.raml) define the upstream evidence boundary.
|
||||
- [MCP tools, 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) defines registration and tool result behaviour.
|
||||
- [MCP schema reference](https://modelcontextprotocol.io/specification/2025-11-25/schema) defines annotation and message types.
|
||||
- [MCP authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) distinguishes transport authorization from upstream credentials.
|
||||
- [MCP resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) supports optional bounded reference/artwork resources.
|
||||
|
||||
The protocol baseline is intentionally pinned; consult the applicable version when choosing a newer transport implementation. RAML corrections or live-server observations should be recorded as versioned adapter evidence rather than silently editing the meaning of this snapshot.
|
||||
@@ -1,68 +0,0 @@
|
||||
# Administrative settings type registry
|
||||
|
||||
`ombi_settings_write` uses the exact closed projected types embedded in its input schema. This table binds each section to the upstream body type. A patch is merged into an internally loaded full section, never posted as a partial upstream replacement. All nested object patches merge by property; arrays replace the complete array and are validated as complete elements against the saved section/type. Omitted fields preserve their values; null is rejected. An empty nested object is a no-op and must not be treated as deletion. Reject patches that make no effective change.
|
||||
|
||||
`revision` is a server-issued opaque digest bound to principal, instance and section. Recheck immediately before save, serialize local saves and reject stale revisions. RAML has no ETag or compare-and-swap guarantee, so external writers can still race; report this limit. Any array element needing hidden fields or identity values must be matched unambiguously to a saved element by a documented safe key; if impossible, reject that array edit. Never guess indexes after concurrent changes.
|
||||
|
||||
These types deliberately exclude credentials, connection destinations, internal persistence IDs, migration flags and script/service execution configuration. Such changes remain manual. Enums retain RAML numeric values because labels are not supplied. Fields like `enabled`, schedules, HTML/CSS, templates, default roles and automatic deletion can still have major effects; administrator authorization is required, and job/configuration consequences must be described.
|
||||
|
||||
| Section | Upstream POST path | Upstream body type |
|
||||
|---|---|---|
|
||||
| `custom_page` | `/api/v1/CustomPage` | [Ombi.Settings.Settings.Models.CustomPageSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomPageSettings.raml) |
|
||||
| `ombi` | `/api/v1/Settings/ombi` | [Ombi.Settings.Settings.Models.OmbiSettings](../api/raml/types/Ombi.Settings.Settings.Models.OmbiSettings.raml) |
|
||||
| `plex` | `/api/v1/Settings/plex` | [Ombi.Core.Settings.Models.External.PlexSettings](../api/raml/types/Ombi.Core.Settings.Models.External.PlexSettings.raml) |
|
||||
| `emby` | `/api/v1/Settings/emby` | [Ombi.Core.Settings.Models.External.EmbySettings](../api/raml/types/Ombi.Core.Settings.Models.External.EmbySettings.raml) |
|
||||
| `jellyfin` | `/api/v1/Settings/jellyfin` | [Ombi.Core.Settings.Models.External.JellyfinSettings](../api/raml/types/Ombi.Core.Settings.Models.External.JellyfinSettings.raml) |
|
||||
| `landingpage` | `/api/v1/Settings/landingpage` | [Ombi.Core.Settings.Models.LandingPageSettings](../api/raml/types/Ombi.Core.Settings.Models.LandingPageSettings.raml) |
|
||||
| `customization` | `/api/v1/Settings/customization` | [Ombi.Settings.Settings.Models.CustomizationSettings](../api/raml/types/Ombi.Settings.Settings.Models.CustomizationSettings.raml) |
|
||||
| `sonarr` | `/api/v1/Settings/sonarr` | [Ombi.Settings.Settings.Models.External.SonarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SonarrSettings.raml) |
|
||||
| `radarr` | `/api/v1/Settings/radarr` | [Ombi.Settings.Settings.Models.External.RadarrCombinedModel](../api/raml/types/Ombi.Settings.Settings.Models.External.RadarrCombinedModel.raml) |
|
||||
| `lidarr` | `/api/v1/Settings/lidarr` | [Ombi.Settings.Settings.Models.External.LidarrSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.LidarrSettings.raml) |
|
||||
| `authentication` | `/api/v1/Settings/authentication` | [Ombi.Settings.Settings.Models.AuthenticationSettings](../api/raml/types/Ombi.Settings.Settings.Models.AuthenticationSettings.raml) |
|
||||
| `update` | `/api/v1/Settings/Update` | [Ombi.Settings.Settings.Models.UpdateSettings](../api/raml/types/Ombi.Settings.Settings.Models.UpdateSettings.raml) |
|
||||
| `user_management` | `/api/v1/Settings/UserManagement` | [Ombi.Settings.Settings.Models.UserManagementSettings](../api/raml/types/Ombi.Settings.Settings.Models.UserManagementSettings.raml) |
|
||||
| `couchpotato` | `/api/v1/Settings/CouchPotato` | [Ombi.Settings.Settings.Models.External.CouchPotatoSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.CouchPotatoSettings.raml) |
|
||||
| `dognzb` | `/api/v1/Settings/DogNzb` | [Ombi.Settings.Settings.Models.External.DogNzbSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.DogNzbSettings.raml) |
|
||||
| `sickrage` | `/api/v1/Settings/SickRage` | [Ombi.Settings.Settings.Models.External.SickRageSettings](../api/raml/types/Ombi.Settings.Settings.Models.External.SickRageSettings.raml) |
|
||||
| `jobs` | `/api/v1/Settings/jobs` | [Ombi.Settings.Settings.Models.JobSettings](../api/raml/types/Ombi.Settings.Settings.Models.JobSettings.raml) |
|
||||
| `issues` | `/api/v1/Settings/Issues` | [Ombi.Settings.Settings.Models.IssueSettings](../api/raml/types/Ombi.Settings.Settings.Models.IssueSettings.raml) |
|
||||
| `vote` | `/api/v1/Settings/vote` | [Ombi.Settings.Settings.Models.VoteSettings](../api/raml/types/Ombi.Settings.Settings.Models.VoteSettings.raml) |
|
||||
| `themoviedb` | `/api/v1/Settings/themoviedb` | [Ombi.Core.Settings.Models.External.TheMovieDbSettings](../api/raml/types/Ombi.Core.Settings.Models.External.TheMovieDbSettings.raml) |
|
||||
| `notifications.email` | `/api/v1/Settings/notifications/email` | [Ombi.Core.Models.UI.EmailNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.EmailNotificationsViewModel.raml) |
|
||||
| `notifications.discord` | `/api/v1/Settings/notifications/discord` | [Ombi.Core.Models.UI.DiscordNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.DiscordNotificationsViewModel.raml) |
|
||||
| `notifications.telegram` | `/api/v1/Settings/notifications/telegram` | [Ombi.Core.Models.UI.TelegramNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.TelegramNotificationsViewModel.raml) |
|
||||
| `notifications.pushbullet` | `/api/v1/Settings/notifications/pushbullet` | [Ombi.Core.Models.UI.PushbulletNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushbulletNotificationViewModel.raml) |
|
||||
| `notifications.pushover` | `/api/v1/Settings/notifications/pushover` | [Ombi.Core.Models.UI.PushoverNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.PushoverNotificationViewModel.raml) |
|
||||
| `notifications.slack` | `/api/v1/Settings/notifications/slack` | [Ombi.Core.Models.UI.SlackNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.SlackNotificationsViewModel.raml) |
|
||||
| `notifications.mattermost` | `/api/v1/Settings/notifications/mattermost` | [Ombi.Core.Models.UI.MattermostNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MattermostNotificationsViewModel.raml) |
|
||||
| `notifications.twilio` | `/api/v1/Settings/notifications/twilio` | [Ombi.Core.Models.UI.TwilioSettingsViewModel](../api/raml/types/Ombi.Core.Models.UI.TwilioSettingsViewModel.raml) |
|
||||
| `notifications.mobile` | `/api/v1/Settings/notifications/mobile` | [Ombi.Core.Models.UI.MobileNotificationsViewModel](../api/raml/types/Ombi.Core.Models.UI.MobileNotificationsViewModel.raml) |
|
||||
| `notifications.gotify` | `/api/v1/Settings/notifications/gotify` | [Ombi.Core.Models.UI.GotifyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.GotifyNotificationViewModel.raml) |
|
||||
| `notifications.ntfy` | `/api/v1/Settings/notifications/ntfy` | [Ombi.Core.Models.UI.NtfyNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NtfyNotificationViewModel.raml) |
|
||||
| `notifications.webhook` | `/api/v1/Settings/notifications/webhook` | [Ombi.Core.Models.UI.WebhookNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.WebhookNotificationViewModel.raml) |
|
||||
| `notifications.newsletter` | `/api/v1/Settings/notifications/newsletter` | [Ombi.Core.Models.UI.NewsletterNotificationViewModel](../api/raml/types/Ombi.Core.Models.UI.NewsletterNotificationViewModel.raml) |
|
||||
|
||||
## Read-only sections
|
||||
|
||||
| Section | GET path |
|
||||
|---|---|
|
||||
| `base_url` | `/api/v1/Settings/baseurl` |
|
||||
| `client_id` | `/api/v1/Settings/clientid` |
|
||||
| `default_language` | `/api/v1/Settings/defaultlanguage` |
|
||||
| `themes` | `/api/v1/Settings/themes` |
|
||||
| `lidarrenabled` | `/api/v1/Settings/lidarrenabled` |
|
||||
| `issuesenabled` | `/api/v1/Settings/issuesenabled` |
|
||||
| `voteenabled` | `/api/v1/Settings/voteenabled` |
|
||||
| `notifications.email.enabled` | `/api/v1/Settings/notifications/email/enabled` |
|
||||
|
||||
## Fields excluded from patches
|
||||
|
||||
The following exact field names are excluded recursively wherever encountered; `key` in a selected-library record is an identifier, not automatically a secret. Read projections also exclude private values and use an allowlist, not merely this name list.
|
||||
|
||||
`accessToken`, `accountSid`, `administratorId`, `apiKey`, `applicationToken`, `applicationUrl`, `authToken`, `authorizationHeader`, `baseUrl`, `botApi`, `customDonationUrl`, `disableCertificateChecking`, `disableTLS`, `favicon`, `hasMigratedOldTvDbData`, `host`, `iconUrl`, `id`, `installId`, `ip`, `logo`, `machineIdentifier`, `password`, `plexAuthToken`, `port`, `processName`, `scriptLocation`, `serverHostname`, `serverId`, `set`, `ssl`, `subDir`, `useScript`, `userToken`, `webhookUrl`, `windowsService`, `windowsServiceName`, `wizard`.
|
||||
|
||||
`notifications.mobile` may have only template fields left after projection. Notification tester bodies differ from settings UI view models: choose the tester body type from the operation ledger and construct it internally; do not forward a settings view model blindly. Profiles for testers without a complete saved-settings route (for example mobile) must be provisioned outside the model.
|
||||
|
||||
## Feature writes
|
||||
|
||||
`action=feature` sends `{name, enabled}` to `/api/v2/Features/enable` when true and `/disable` when false. The feature name must have been returned by the configured instance; do not invent a universal feature enum. Feature writes are not an arbitrary settings section.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Ombi MCP schema — Astra high
|
||||
|
||||
This is a documentation-only design and audit of the supplied RAML snapshot. It does not implement a server, contact an Ombi instance, or certify undocumented server behaviour.
|
||||
|
||||
The API contains **321 distinct paths and 377 HTTP operations**. Coverage means accounting for every method/path pair, including compatibility routes, internal authentication and deliberate exclusions. It does not mean publishing 377 tools or exposing every administrative capability by default.
|
||||
|
||||
Read in this order:
|
||||
|
||||
1. [Assessment of both earlier attempts](00-assessment.md).
|
||||
2. [Authentication, authorization and transport](01-authentication.md).
|
||||
3. [Tool catalogue and exact routing rules](02-tool-mapping.md).
|
||||
4. [Complete input schema catalogue](03-input-schemas.md).
|
||||
5. [Output contracts and MCP behaviour](04-results.md).
|
||||
6. [All 377 operations and their disposition](05-endpoint-coverage.md).
|
||||
7. [Uncertainties and acceptance criteria](06-verification.md).
|
||||
8. [Administrative settings types](07-settings-types.md).
|
||||
|
||||
The proposed catalogue separates ordinary media workflows, moderation and optional administration. Only authorized, configured tools and branches should be advertised. Tool count is a consequence of coherent tasks and permission boundaries, not an optimization target by itself.
|
||||
|
||||
The catalogue defines **31 tools: 18 core, 5 moderation and 8 administration**. All input schemas and the shared output schema have been validated; the ledger matches all 377 RAML operations exactly. Runtime assumptions are explicitly tracked in the verification document.
|
||||
|
||||
MCP interoperability baseline: **2025-11-25**, with explicit JSON Schema 2020-12. This is a deliberate compatibility target, not a claim that it is the newest published revision. A later protocol version requires its own transport/versioning review; the domain design here does not assume later extensions.
|
||||
|
||||
Evidence labels used throughout:
|
||||
|
||||
- **Documented:** directly supported by a path, description or type in this RAML snapshot.
|
||||
- **Design:** an intentional MCP contract, stricter validation rule or normalization proposed here.
|
||||
- **Verify:** semantics absent or ambiguous in RAML; must be confirmed before that branch is enabled.
|
||||
|
||||
All URLs come from `OMBI_URL`; the source RAML's environment-specific `baseUri` is never a runtime default. `OMBI_API_KEY` remains the standard upstream credential.
|
||||
@@ -113,7 +113,7 @@ The [input catalogue](03-input-schemas.md) defines every parameter, required fie
|
||||
|
||||
`read_media.details` chooses v2 movie TMDB/IMDb, TV TVDB/TMDB, artist, or artist/album routes. TV `tmdb` uses v2 `Search/tv/moviedb/{id}`; TV `tvdb` uses the legacy v1 `Search/tv/info/{tvdbId}` route — the v2 `Search/tv/{tvdbId}` route is a TMDB-keyed alias of moviedb despite its parameter name and must never serve a TVDB lookup. IMDb path placeholder spelling differs from its parameter declaration; substitute the actual path placeholder. `by_request` chooses movie/request, tv/request or artist/request; the TV namespace of this particular upstream route requires adapter verification and must not be guessed from list results. `movie_localized` uses POST v1 movie/info with `{theMovieDbId, languageCode}`. `ratings` uses title and year, not a numeric media ID. `streaming` uses TMDB even for TV. Cast/crew are projections of detailed metadata where present, not invented standalone endpoints.
|
||||
|
||||
TV identifier labels follow the origin route, not the upstream field name. The v1 TVMaze-backed routes (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) place the TVDB id in the misleadingly-named `theMovieDbId` field and the TVMaze id in `seriesId`; emit `tvdb` and `tvmaze` there. The v2 engine routes (browse, `moviedb`, `by_request`, multi-search) are TMDB-keyed; emit `tmdb`. The v1 `RecentlyAdded` TV model's id namespace is unverified — keep `tmdb` pending live confirmation.
|
||||
TV identifier labels follow the origin route, not the upstream field name. The v1 TVMaze-backed routes (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) place the TVDB id in the misleadingly-named `theMovieDbId` field and the TVMaze id in `seriesId`; emit `tvdb` and `tvmaze` there. The v2 engine routes (browse, `moviedb`, `by_request`, multi-search) are TMDB-keyed; emit `tmdb` and do not treat `seriesId` as TVMaze. When `theMovieDbId` is absent, `id` is labelled with that origin namespace so browse and collection members still carry a usable identifier. Multi-search `mediaType` is case-insensitive (`Artist`→artist). The v1 `RecentlyAdded` TV model's id namespace is unverified — keep `tmdb` pending live confirmation.
|
||||
|
||||
`read_reference.keywords` passes query `searchTerm`; keyword detail uses its own keyword ID. Watch-provider catalogue search also has optional `searchTerm`; it is distinct from streaming availability for a particular title. Other branches have no request body. Reference values are good optional cached resources, but remain available through tools for tool-only clients.
|
||||
|
||||
@@ -121,7 +121,7 @@ TV identifier labels follow the origin route, not the upstream field name. The v
|
||||
|
||||
Use v2 list/status routes, always with sort and page segments. `status` defaults `all`; `sort_direction` maps to the `{sortOrder}` segment (`asc`/`desc`). Public `sort.field=request_date` maps to the documented example `requestDate`; no speculative sort fields are published. `all` means the base route, not an `/all/` segment. Album lacks an unavailable-status route, so that combination fails schema validation. The misspelled movie `availble` route is an explicitly gated compatibility alias, not the primary path.
|
||||
|
||||
`get` supports movie and TV parent only; no album single-request endpoint is advertised. `children` returns children for a parent. Request `search` uses the appropriate v1 route and rejects list-only status/sort arguments. `recent` uses v2 recentlyRequested. `retry_queue` is a privileged GET and returns queue IDs separately from underlying request IDs.
|
||||
`get` supports movie and TV parent only; no album single-request endpoint is advertised. `children` returns children for a parent. Request `search` uses the appropriate v1 route and rejects list-only status/sort arguments. `recent` uses v2 recentlyRequested; `target.id` is `requestId` (never a provider id left in `id`). `retry_queue` is a privileged GET and returns queue IDs separately from underlying request IDs.
|
||||
|
||||
`read_request_stats.counts` uses Request/count; `total` uses the media's total endpoint; `quota` uses its remaining endpoint. Quota belongs to the actual upstream principal. `has_requests` requires an explicit `user_id` by MCP policy and sends it as the optional upstream `userId` query; viewing another user is subject to authorization. Do not combine instance totals with a per-user quota under an unlabeled “total”.
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ Each per-tool output schema is intentionally a bounded projection, not the recur
|
||||
|
||||
| Family | Projection rules |
|
||||
|---|---|
|
||||
| media_page | Search/discovery/details return zero or more normalized media records. Details usually has one item. Keep every known ID namespace. On TV the upstream `theMovieDbId` field name lies: TVMaze-backed v1 routes (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) carry the TVDB id there (emit `tvdb`, plus `seriesId`→`tvmaze`); TMDB-keyed v2 routes carry the TMDB id (emit `tmdb`). The namespace label is per origin route, never per field name. Map cast/crew into credits; title-specific streaming into providers; rating fields into named rating references. Never claim a global provider catalogue is a title's availability. Collections keep their own collection identity and returned members. |
|
||||
| request_page | Map source entity `id` to target kind and ID. v2 TV items are children and `parentRequestId` is preserved; v1 parent records stay parents. Include standard and 4K state separately. Never infer one combined lifecycle status when booleans disagree. |
|
||||
| media_page | Search/discovery/details return zero or more normalized media records. Details usually has one item. Keep every known ID namespace; never emit the same namespace+value twice. On TV the upstream `theMovieDbId` field name lies: TVMaze-backed v1 routes (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) carry the TVDB id there (emit `tvdb`, plus `seriesId`→`tvmaze`); TMDB-keyed v2 routes carry the TMDB id (emit `tmdb`) and must not label `seriesId` as `tvmaze` — on those routes it echoes the TMDB id. When `theMovieDbId` is absent, `id` is labelled with the same origin namespace (v2 browse/collection members and MovieFullInfoViewModel). `belongsToCollection.id` is the collection's TMDB id, not the movie's. Multi-search `mediaType` is matched case-insensitively (`Artist`→`artist`/`musicbrainz`). The namespace label is per origin route, never per field name. Map cast/crew into credits; title-specific streaming into providers; rating fields into named rating references. Never claim a global provider catalogue is a title's availability. Collections keep their own collection identity and returned members. |
|
||||
| request_page | Map the Ombi request id to target kind and ID: prefer `requestId` over `id`. v2 TV list items are children and `parentRequestId` is preserved; v1 parent records stay parents. On `recent`, `RecentlyRequestedModel.requestId` is the target id for every kind; a provider-shaped `id` (common on TV) is never the target — provider values go in `identifiers` (`mediaId`, `tvDbId`, `externalProviderId`). Include standard and 4K state separately. Never infer one combined lifecycle status when booleans disagree. |
|
||||
| issue_page | Project writable/display fields plus IDs/timestamps. Wire `resovledDate` maps to `resolved_date` without changing upstream spelling. Omit nested user objects and comments unless requested separately. |
|
||||
| group_page | v2 issue summaries are provider groups. Count and page units describe groups, not individual issues. Truncate nested issues with a warning. |
|
||||
| comment_page | Preserve comment text and authorized author identifier, omit full user graph. |
|
||||
|
||||
@@ -26,7 +26,7 @@ The schemas deliberately retain runtime checks for semantics JSON Schema cannot
|
||||
| API-key principal | Security scheme only names a header | Determine effective user, quotas, on-behalf rights and required permissions; do not infer admin or anonymous identity |
|
||||
| Enum labels | `RequestType` (T1), `IssueStatus` (T2), `NotificationAgent` (T3), `NotificationType` (T4) label maps **published** (routing rule #4); `VoteType`, `RequestSource`, `RequestLimitType` and legacy `orderType`/`statusType`/`availabilityType` filters remain numeric-only gaps | Per-instance verify published maps for version drift and `closed`→3 (not frontend-confirmed); verify symbolic names for the remaining enums from authoritative controller/enum source; preserve raw `*_code` codes meanwhile |
|
||||
| TV request identity | v1 request body has tvDbId; v2 has theMovieDbId | Keep provider-specific create branches; never substitute one ID namespace for another |
|
||||
| TV search/details id provenance | `theMovieDbId` carries a different namespace per origin route: TVMaze-backed v1 (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) places the TVDB id there and the TVMaze id in `seriesId`; v2 routes are TMDB-keyed. The v2 `Search/tv/{tvdbId}` route is a TMDB alias | Label `theMovieDbId` per origin (`tvdb` on v1 TV, `tmdb` on v2) and emit `seriesId` as `tvmaze`; live-confirm the v1 `tv/info/{tvdbId}` route shape, the `RecentlyAdded` TV id namespace, the multi-search TV `id` namespace, and the `by_request` `externalProviderId` namespace |
|
||||
| TV search/details id provenance | `theMovieDbId` carries a different namespace per origin route: TVMaze-backed v1 (`Search/tv/{term}`, `Search/tv/info/{tvdbId}`) places the TVDB id there and the TVMaze id in `seriesId`; v2 routes are TMDB-keyed. The v2 `Search/tv/{tvdbId}` route is a TMDB alias. v2 `seriesId` echoes the TMDB id. Browse/collection members often omit `theMovieDbId` and only populate `id`. | Label `theMovieDbId` (and, when absent, `id`) per origin (`tvdb` on v1 TV, `tmdb` on v2). Emit `seriesId` as `tvmaze` only on v1 TVMaze-backed routes. Live-confirm the v1 `tv/info/{tvdbId}` route shape, the `RecentlyAdded` TV id namespace, the multi-search TV `id` namespace, and the `by_request` `externalProviderId` namespace |
|
||||
| TV result granularity | v2 TV list wraps ChildRequests; v1 wraps TvRequests | Preserve target kind and parent ID; no silent fallback between units |
|
||||
| TV moderation/options/subscriptions/details | Several models/routes only say `id` or `requestId` | Verify each controller's accepted parent/child namespace, independently per operation |
|
||||
| On behalf | requestOnBehalf is just string | Confirm ID versus username and permissions; resolve the public user ID internally if necessary |
|
||||
|
||||
@@ -108,12 +108,22 @@ func TestReadMediaDetailsProjectsUndocumentedFields(t *testing.T) {
|
||||
t.Errorf("genres = %v", it.Genres)
|
||||
}
|
||||
ids := map[string]string{}
|
||||
imdbCount := 0
|
||||
for _, id := range it.Identifiers {
|
||||
ids[id.Namespace] = id.Value
|
||||
if id.Namespace == "imdb" {
|
||||
imdbCount++
|
||||
}
|
||||
}
|
||||
if ids["tmdb"] != "27205" || ids["imdb"] != "tt1375666" {
|
||||
t.Errorf("identifiers = %v", ids)
|
||||
}
|
||||
if imdbCount != 1 {
|
||||
t.Errorf("imdb emitted %d times, want 1", imdbCount)
|
||||
}
|
||||
if ids["tmdb"] == "8091" {
|
||||
t.Errorf("collection TMDB id leaked as movie identifier")
|
||||
}
|
||||
// Undocumented upstream fields must not appear in the projection.
|
||||
for _, leaked := range []string{
|
||||
"productionCompanies", "undocumentedFuture", "runtimeNewField",
|
||||
@@ -308,6 +318,240 @@ func TestReadSearchTVEmitsTVDBAndTVMaze(t *testing.T) {
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestReadMediaDetailsTVTMDBDoesNotEmitSeriesIdAsTVMaze(t *testing.T) {
|
||||
mock := newMockOmbi(t, "jwt")
|
||||
c := spawnServer(t, mock.env())
|
||||
c.handshake(t)
|
||||
|
||||
out := c.callTool(t, "read_media", map[string]any{
|
||||
"action": "details",
|
||||
"target": map[string]any{"media": "tv", "provider": "tmdb", "id": 1396},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("media_page decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) != 1 || page.Items[0].Title != "Breaking Bad" {
|
||||
t.Fatalf("unexpected projection: %s", data)
|
||||
}
|
||||
ids := map[string]string{}
|
||||
imdbCount := 0
|
||||
for _, id := range page.Items[0].Identifiers {
|
||||
ids[id.Namespace] = id.Value
|
||||
if id.Namespace == "imdb" {
|
||||
imdbCount++
|
||||
}
|
||||
}
|
||||
if ids["tmdb"] != "1396" || ids["tvdb"] != "81189" {
|
||||
t.Errorf("identifiers = %v", ids)
|
||||
}
|
||||
if v, ok := ids["tvmaze"]; ok {
|
||||
t.Errorf("v2 seriesId labelled tvmaze %q (echoed TMDB id)", v)
|
||||
}
|
||||
if imdbCount != 1 {
|
||||
t.Errorf("imdb emitted %d times, want 1", imdbCount)
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestReadDiscoverTVBrowseEmitsTMDBIdentifier(t *testing.T) {
|
||||
mock := newMockOmbi(t, "jwt")
|
||||
c := spawnServer(t, mock.env())
|
||||
c.handshake(t)
|
||||
|
||||
out := c.callTool(t, "read_discover", map[string]any{
|
||||
"action": "browse", "media": "tv", "category": "popular",
|
||||
"page": map[string]any{"limit": 5},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) == 0 || page.Items[0].Title != "Reacher" {
|
||||
t.Fatalf("unexpected browse page: %s", data)
|
||||
}
|
||||
ids := map[string]string{}
|
||||
for _, id := range page.Items[0].Identifiers {
|
||||
ids[id.Namespace] = id.Value
|
||||
}
|
||||
if ids["tmdb"] != "1668" {
|
||||
t.Errorf("browse identifiers = %v, want tmdb 1668", ids)
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestReadDiscoverCollectionMembersEmitTMDBIdentifier(t *testing.T) {
|
||||
mock := newMockOmbi(t, "jwt")
|
||||
c := spawnServer(t, mock.env())
|
||||
c.handshake(t)
|
||||
|
||||
out := c.callTool(t, "read_discover", map[string]any{
|
||||
"action": "collection", "collection_id": 8091,
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Media string `json:"media"`
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) < 2 {
|
||||
t.Fatalf("want collection + members, got %s", data)
|
||||
}
|
||||
found := false
|
||||
for _, it := range page.Items {
|
||||
if it.Media != "movie" {
|
||||
continue
|
||||
}
|
||||
ids := map[string]string{}
|
||||
for _, id := range it.Identifiers {
|
||||
ids[id.Namespace] = id.Value
|
||||
}
|
||||
if it.Title == "Alien" && ids["tmdb"] == "348" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("collection member missing tmdb identifier: %s", data)
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestReadSearchMultiArtistMapsMusicBrainz(t *testing.T) {
|
||||
mock := newMockOmbi(t, "jwt")
|
||||
c := spawnServer(t, mock.env())
|
||||
c.handshake(t)
|
||||
|
||||
out := c.callTool(t, "read_search", map[string]any{
|
||||
"action": "multi", "query": "radiohead", "include": []string{"music"},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Media string `json:"media"`
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) == 0 {
|
||||
t.Fatalf("no multi items: %s", data)
|
||||
}
|
||||
it := page.Items[0]
|
||||
if it.Media != "artist" {
|
||||
t.Errorf("media = %q, want artist", it.Media)
|
||||
}
|
||||
ids := map[string]string{}
|
||||
for _, id := range it.Identifiers {
|
||||
ids[id.Namespace] = id.Value
|
||||
}
|
||||
if ids["musicbrainz"] != "8dc08e7c-41f1-4a04-97dc-eb00d91d1d2f" {
|
||||
t.Errorf("identifiers = %v", ids)
|
||||
}
|
||||
if _, ok := ids["provider_unknown"]; ok {
|
||||
t.Errorf("Artist mapped as provider_unknown: %v", ids)
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestReadRequestsRecentTVParentUsesRequestID(t *testing.T) {
|
||||
mock := newMockOmbi(t, "jwt")
|
||||
c := spawnServer(t, mock.env())
|
||||
c.handshake(t)
|
||||
|
||||
out := c.callTool(t, "read_requests", map[string]any{
|
||||
"action": "recent", "page": map[string]any{"limit": 5},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Target struct {
|
||||
Kind string `json:"kind"`
|
||||
ID int `json:"id"`
|
||||
} `json:"target"`
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
var movie, tv *struct {
|
||||
Target struct {
|
||||
Kind string `json:"kind"`
|
||||
ID int `json:"id"`
|
||||
} `json:"target"`
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
}
|
||||
for i := range page.Items {
|
||||
switch page.Items[i].Title {
|
||||
case "Yummy":
|
||||
movie = &page.Items[i]
|
||||
case "Seven Up!":
|
||||
tv = &page.Items[i]
|
||||
}
|
||||
}
|
||||
if movie == nil || tv == nil {
|
||||
t.Fatalf("missing recent items: %s", data)
|
||||
}
|
||||
if movie.Target.Kind != "movie" || movie.Target.ID != 2207 {
|
||||
t.Errorf("movie target = %+v", movie.Target)
|
||||
}
|
||||
if tv.Target.Kind != "tv_parent" || tv.Target.ID != 88 {
|
||||
t.Errorf("tv_parent target = %+v, want request id 88 not provider 259032", tv.Target)
|
||||
}
|
||||
tvIDs := map[string]string{}
|
||||
for _, id := range tv.Identifiers {
|
||||
tvIDs[id.Namespace] = id.Value
|
||||
}
|
||||
if tvIDs["tmdb"] != "259032" {
|
||||
t.Errorf("tv provider id missing from identifiers: %v", tvIDs)
|
||||
}
|
||||
for _, it := range page.Items {
|
||||
if it.Title == "Provider Only" && it.Target.ID == 111 {
|
||||
t.Errorf("provider id occupied target.id on requestId-less recent item: %+v", it.Target)
|
||||
}
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
// --- enum translation twins (T1 request_type, T2 issue status) ---
|
||||
|
||||
func TestEnumTranslationTwins(t *testing.T) {
|
||||
|
||||
@@ -443,3 +443,69 @@ func TestLiveNotFound(t *testing.T) {
|
||||
t.Errorf("http_status = %v", e.HTTPStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// --- M3 identity projection live ---
|
||||
|
||||
func TestLiveDiscoverTVBrowseHasIdentifiers(t *testing.T) {
|
||||
c := liveServer(t)
|
||||
out := c.callTool(t, "read_discover", map[string]any{
|
||||
"action": "browse", "media": "tv", "category": "popular",
|
||||
"page": map[string]any{"limit": 5},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) == 0 {
|
||||
t.Skip("live TV popular browse returned no items")
|
||||
}
|
||||
for _, it := range page.Items {
|
||||
if len(it.Identifiers) == 0 {
|
||||
t.Fatalf("browse item %q has empty identifiers (issue #2)", it.Title)
|
||||
}
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
func TestLiveSearchMultiArtistMapped(t *testing.T) {
|
||||
c := liveServer(t)
|
||||
out := c.callTool(t, "read_search", map[string]any{
|
||||
"action": "multi", "query": "radiohead", "include": []string{"music"},
|
||||
})
|
||||
data := requireOK(t, out)
|
||||
var page struct {
|
||||
Items []struct {
|
||||
Media string `json:"media"`
|
||||
Title string `json:"title"`
|
||||
Identifiers []struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Value string `json:"value"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &page); err != nil {
|
||||
t.Fatalf("decode: %v\n%s", err, data)
|
||||
}
|
||||
if len(page.Items) == 0 {
|
||||
t.Skip("live multi music search returned no items")
|
||||
}
|
||||
it := page.Items[0]
|
||||
if it.Media == "unknown" {
|
||||
t.Fatalf("music result media=unknown (issue #9): %s", data)
|
||||
}
|
||||
for _, id := range it.Identifiers {
|
||||
if id.Namespace == "provider_unknown" {
|
||||
t.Fatalf("music result labelled provider_unknown (issue #9): %s", data)
|
||||
}
|
||||
}
|
||||
assertNoLeak(t, out.Raw)
|
||||
}
|
||||
|
||||
@@ -62,8 +62,14 @@ func newMockOmbi(t *testing.T, mode string) *mockOmbi {
|
||||
mux.HandleFunc("GET /api/v2/Requests/movie/{amt}/{pos}/requestDate/{order}", m.wrap(m.movieList))
|
||||
mux.HandleFunc("GET /api/v2/Requests/tv/{amt}/{pos}/requestDate/{order}", m.wrap(m.tvList))
|
||||
mux.HandleFunc("GET /api/v2/Search/movie/{id}", m.wrap(m.movieDetails))
|
||||
mux.HandleFunc("GET /api/v2/Search/movie/collection/{id}", m.wrap(m.movieCollection))
|
||||
mux.HandleFunc("GET /api/v2/Search/tv/moviedb/{id}", m.wrap(m.tvDetailsTMDB))
|
||||
mux.HandleFunc("GET /api/v2/Search/tv/{id}", m.wrap(m.tvDetailsTVDB))
|
||||
mux.HandleFunc("GET /api/v2/Search/tv/popular/{pos}/{amt}", m.wrap(m.tvBrowse))
|
||||
mux.HandleFunc("GET /api/v2/Search/tv/anticipated/{pos}/{amt}", m.wrap(m.tvBrowse))
|
||||
mux.HandleFunc("GET /api/v2/Search/tv/trending/{pos}/{amt}", m.wrap(m.tvBrowse))
|
||||
mux.HandleFunc("POST /api/v2/Search/multi/{term}", m.wrap(m.multiSearch))
|
||||
mux.HandleFunc("GET /api/v2/Requests/recentlyRequested", m.wrap(m.recentlyRequested))
|
||||
mux.HandleFunc("GET /api/v1/Search/tv/info/{id}", m.wrap(m.tvInfoTVDB))
|
||||
mux.HandleFunc("GET /api/v1/Search/tv/{term}", m.wrap(m.tvSearch))
|
||||
mux.HandleFunc("GET /api/v1/Request/movie/info/{id}", m.wrap(m.movieInfo))
|
||||
@@ -286,7 +292,9 @@ func mockUser(id, name string) map[string]any {
|
||||
// mockMovieDetail: SearchMovieViewModel-ish with extra fields.
|
||||
func mockMovieDetail() map[string]any {
|
||||
return map[string]any{
|
||||
"id": 27205, "theMovieDbId": 27205, "imdbId": "tt1375666",
|
||||
// Live MovieFullInfoViewModel carries TMDB in `id` and often
|
||||
// omits theMovieDbId — the projection must fall back to id.
|
||||
"id": 27205, "imdbId": "tt1375666",
|
||||
"title": "Inception", "originalTitle": "Inception",
|
||||
"overview": "A thief who steals corporate secrets.",
|
||||
"releaseDate": "2010-07-15T00:00:00", "status": "Released",
|
||||
@@ -304,8 +312,10 @@ func mockMovieDetail() map[string]any {
|
||||
"crew": []any{
|
||||
map[string]any{"id": 525, "name": "Christopher Nolan", "department": "Directing", "job": "Director"},
|
||||
},
|
||||
"externalIds": map[string]any{"imdbId": "tt1375666"},
|
||||
"belongsToCollection": nil,
|
||||
"externalIds": map[string]any{"imdbId": "tt1375666"},
|
||||
"belongsToCollection": map[string]any{
|
||||
"id": 8091, "name": "Alien Collection",
|
||||
},
|
||||
// Undocumented upstream fields:
|
||||
"productionCompanies": []any{map[string]any{"id": 1, "name": "Legendary"}},
|
||||
"videos": map[string]any{"results": []any{}},
|
||||
@@ -319,8 +329,11 @@ func mockMovieDetail() map[string]any {
|
||||
// both the seasons projection and the write expansion path.
|
||||
func mockTVDetail() map[string]any {
|
||||
return map[string]any{
|
||||
// Live v2 moviedb payloads put the TMDB id in seriesId too;
|
||||
// that must not be labelled tvmaze.
|
||||
"id": 1396, "theMovieDbId": 1396, "theTvDbId": 81189,
|
||||
"title": "Breaking Bad", "name": "Breaking Bad",
|
||||
"seriesId": 1396,
|
||||
"title": "Breaking Bad", "name": "Breaking Bad",
|
||||
"overview": "A chemistry teacher turns to cooking meth.",
|
||||
"firstAired": "2008-01-20T00:00:00", "status": "Ended",
|
||||
"available": false, "requested": false, "fullyAvailable": false,
|
||||
@@ -580,6 +593,63 @@ func (m *mockOmbi) tvSearch(w http.ResponseWriter, r *http.Request) {
|
||||
m.json([]any{mockTVSearchHit()})(w, r)
|
||||
}
|
||||
|
||||
// tvBrowse is the v2 TMDB-keyed popular/anticipated/trending list —
|
||||
// members carry the provider id only in `id`.
|
||||
func (m *mockOmbi) tvBrowse(w http.ResponseWriter, r *http.Request) {
|
||||
m.json([]any{
|
||||
map[string]any{
|
||||
"id": 1668, "title": "Reacher",
|
||||
"overview": "A former military policeman.",
|
||||
"posterPath": "/reacher.jpg",
|
||||
},
|
||||
})(w, r)
|
||||
}
|
||||
|
||||
func (m *mockOmbi) movieCollection(w http.ResponseWriter, r *http.Request) {
|
||||
m.json(map[string]any{
|
||||
"id": 8091, "name": "Alien Collection", "overview": "In space…",
|
||||
"collection": []any{
|
||||
map[string]any{
|
||||
"id": 348, "title": "Alien",
|
||||
"releaseDate": "1979-05-25T00:00:00",
|
||||
"posterPath": "/alien.jpg",
|
||||
},
|
||||
},
|
||||
})(w, r)
|
||||
}
|
||||
|
||||
func (m *mockOmbi) multiSearch(w http.ResponseWriter, r *http.Request) {
|
||||
m.json([]any{
|
||||
map[string]any{
|
||||
"id": "8dc08e7c-41f1-4a04-97dc-eb00d91d1d2f",
|
||||
"mediaType": "Artist", "title": "Radiohead",
|
||||
"poster": "/radiohead.jpg",
|
||||
},
|
||||
})(w, r)
|
||||
}
|
||||
|
||||
func (m *mockOmbi) recentlyRequested(w http.ResponseWriter, r *http.Request) {
|
||||
m.json([]any{
|
||||
map[string]any{
|
||||
"requestId": 2207, "type": 1, "title": "Yummy",
|
||||
"mediaId": "12345", "userId": "u-1",
|
||||
"requestDate": "2026-09-01T10:00:00",
|
||||
},
|
||||
map[string]any{
|
||||
// Live TV recent items put the provider id in `id` and
|
||||
// may omit requestId; target.id must not become 259032.
|
||||
"id": 259032, "requestId": 88, "type": 0,
|
||||
"title": "Seven Up!", "mediaId": "259032",
|
||||
"userId": "u-1", "requestDate": "2026-09-02T10:00:00",
|
||||
},
|
||||
map[string]any{
|
||||
"id": 111, "type": 0, "title": "Provider Only",
|
||||
"mediaId": "111", "userId": "u-1",
|
||||
"requestDate": "2026-09-03T10:00:00",
|
||||
},
|
||||
})(w, r)
|
||||
}
|
||||
|
||||
func (m *mockOmbi) createTV(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
|
||||
+50
-12
@@ -95,6 +95,12 @@ func toStr(v any) (string, bool) {
|
||||
return s, true
|
||||
case float64:
|
||||
return strconv.FormatFloat(s, 'f', -1, 64), true
|
||||
case int:
|
||||
return strconv.Itoa(s), true
|
||||
case int64:
|
||||
return strconv.FormatInt(s, 10), true
|
||||
case json.Number:
|
||||
return s.String(), true
|
||||
case bool:
|
||||
return strconv.FormatBool(s), true
|
||||
}
|
||||
@@ -160,9 +166,27 @@ func addID(ids []Identifier, ns string, v any) []Identifier {
|
||||
if !ok || s == "" || s == "0" {
|
||||
return ids
|
||||
}
|
||||
for _, id := range ids {
|
||||
if id.Namespace == ns && id.Value == s {
|
||||
return ids
|
||||
}
|
||||
}
|
||||
return append(ids, Identifier{Namespace: ns, Value: s})
|
||||
}
|
||||
|
||||
// firstID returns the first present, non-zero identifier value among
|
||||
// keys. Used so v2 browse/details payloads that only populate `id`
|
||||
// still emit a labelled identifier, without inventing a namespace.
|
||||
func firstID(m map[string]any, keys ...string) any {
|
||||
for _, k := range keys {
|
||||
s, ok := toStr(m[k])
|
||||
if ok && s != "" && s != "0" {
|
||||
return m[k]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- media projections ---
|
||||
|
||||
// projectSearchMedia projects the shared tail of SearchMovieViewModel,
|
||||
@@ -184,16 +208,26 @@ func projectSearchMedia(m map[string]any, media, tvIDNS string) Media {
|
||||
out.Genres = strList(jarr(m, "genre"))
|
||||
switch media {
|
||||
case "movie":
|
||||
out.Identifiers = addID(out.Identifiers, "tmdb", m["theMovieDbId"])
|
||||
// MovieFullInfoViewModel and v2 browse/collection members often
|
||||
// carry the TMDB id only in `id`; theMovieDbId is the preferred
|
||||
// field when present.
|
||||
out.Identifiers = addID(out.Identifiers, "tmdb", firstID(m, "theMovieDbId", "id"))
|
||||
out.Identifiers = addID(out.Identifiers, "imdb", m["imdbId"])
|
||||
if rid, ok := toInt(m["requestId"]); ok && rid > 0 {
|
||||
out.RequestTargets = []OutTarget{{Kind: "movie", ID: rid}}
|
||||
}
|
||||
case "tv":
|
||||
out.Identifiers = addID(out.Identifiers, tvIDNS, m["theMovieDbId"])
|
||||
// Label `id` with the same origin namespace as theMovieDbId so
|
||||
// v2 browse (id=TMDB, theMovieDbId absent) emits tmdb, while v1
|
||||
// TVMaze (id=TVDB) does not grow a bogus tmdb identifier.
|
||||
out.Identifiers = addID(out.Identifiers, tvIDNS, firstID(m, "theMovieDbId", "id"))
|
||||
out.Identifiers = addID(out.Identifiers, "tvdb", m["theTvDbId"])
|
||||
out.Identifiers = addID(out.Identifiers, "imdb", m["imdbId"])
|
||||
out.Identifiers = addID(out.Identifiers, "tvmaze", m["seriesId"])
|
||||
// seriesId is a TVMaze id only on the v1 TVMaze-backed routes.
|
||||
// On the v2 moviedb route it echoes the TMDB id.
|
||||
if tvIDNS == "tvdb" {
|
||||
out.Identifiers = addID(out.Identifiers, "tvmaze", m["seriesId"])
|
||||
}
|
||||
if rid, ok := toInt(m["requestId"]); ok && rid > 0 {
|
||||
out.RequestTargets = []OutTarget{{Kind: "tv_parent", ID: rid}}
|
||||
}
|
||||
@@ -227,7 +261,9 @@ func strList(a []any) []string {
|
||||
}
|
||||
|
||||
// projectFullMovie enriches a movie projection with credits, genres,
|
||||
// ratings and artwork from MovieFullInfoViewModel.
|
||||
// ratings and artwork from MovieFullInfoViewModel. belongsToCollection.id
|
||||
// is the collection's TMDB id, not the movie's, and is not emitted as
|
||||
// a movie identifier.
|
||||
func projectFullMovie(m map[string]any) Media {
|
||||
out := projectSearchMedia(m, "movie", "tmdb")
|
||||
out.Genres = namesOf(jarr(m, "genres"))
|
||||
@@ -245,11 +281,6 @@ func projectFullMovie(m map[string]any) Media {
|
||||
out.Ratings = append(out.Ratings, Reference{Name: "vote_count", Value: i})
|
||||
}
|
||||
}
|
||||
if c := jobj(m, "belongsToCollection"); c != nil {
|
||||
if id, ok := toInt(c["id"]); ok {
|
||||
out.Identifiers = addID(out.Identifiers, "tmdb", id)
|
||||
}
|
||||
}
|
||||
for _, k := range []string{"backdropPath", "posterPath"} {
|
||||
if u := jstr(m, k); u != "" {
|
||||
out.ArtworkURIs = appendIfMissing(out.ArtworkURIs, u)
|
||||
@@ -380,10 +411,13 @@ func appendIfMissing(s []string, v string) []string {
|
||||
// AlbumRequest / RecentlyRequestedModel into the request family.
|
||||
func (o *op) projectRequest(m map[string]any, kind string) Request {
|
||||
r := Request{Target: OutTarget{Kind: kind}}
|
||||
if id, ok := toInt(m["id"]); ok {
|
||||
// requestId is the Ombi request id on RecentlyRequestedModel and
|
||||
// on some request entities; `id` is the request id on list/get
|
||||
// entities but a provider id on recent TV payloads. Prefer
|
||||
// requestId so a provider id never occupies the target slot.
|
||||
if id, ok := toInt(m["requestId"]); ok && id > 0 {
|
||||
r.Target.ID = id
|
||||
}
|
||||
if id, ok := toInt(m["requestId"]); ok && r.Target.ID == 0 {
|
||||
} else if id, ok := toInt(m["id"]); ok && id > 0 {
|
||||
r.Target.ID = id
|
||||
}
|
||||
r.Title = jstr(m, "title", "artistName")
|
||||
@@ -401,6 +435,7 @@ func (o *op) projectRequest(m map[string]any, kind string) Request {
|
||||
switch kind {
|
||||
case "movie":
|
||||
r.Identifiers = addID(r.Identifiers, "tmdb", m["theMovieDbId"])
|
||||
r.Identifiers = addID(r.Identifiers, "tmdb", m["mediaId"])
|
||||
r.Identifiers = addID(r.Identifiers, "imdb", m["imdbId"])
|
||||
r.Is4K = jbool(m, "is4kRequest")
|
||||
r.Approved4K = jbool(m, "approved4K")
|
||||
@@ -408,10 +443,13 @@ func (o *op) projectRequest(m map[string]any, kind string) Request {
|
||||
r.Denied4K = jbool(m, "denied4K")
|
||||
case "tv_parent", "tv_child":
|
||||
r.Identifiers = addID(r.Identifiers, "tvdb", m["tvDbId"])
|
||||
r.Identifiers = addID(r.Identifiers, "tvdb", m["theTvDbId"])
|
||||
r.Identifiers = addID(r.Identifiers, "tmdb", m["externalProviderId"])
|
||||
r.Identifiers = addID(r.Identifiers, "tmdb", m["mediaId"])
|
||||
r.Identifiers = addID(r.Identifiers, "imdb", m["imdbId"])
|
||||
case "album":
|
||||
r.Identifiers = addID(r.Identifiers, "musicbrainz", m["foreignAlbumId"])
|
||||
r.Identifiers = addID(r.Identifiers, "musicbrainz", m["mediaId"])
|
||||
}
|
||||
if srs := jarr(m, "seasonRequests"); len(srs) > 0 {
|
||||
r.Seasons = o.seasonsOf(srs)
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func idMap(ids []Identifier) map[string]string {
|
||||
out := map[string]string{}
|
||||
for _, id := range ids {
|
||||
out[id.Namespace] = id.Value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func countNS(ids []Identifier, ns string) int {
|
||||
n := 0
|
||||
for _, id := range ids {
|
||||
if id.Namespace == ns {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func TestProjectSearchMediaMovieFallsBackToID(t *testing.T) {
|
||||
m := projectSearchMedia(map[string]any{
|
||||
"id": 348, "title": "Alien", "imdbId": "tt0078748",
|
||||
}, "movie", "tmdb")
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["tmdb"] != "348" || ids["imdb"] != "tt0078748" {
|
||||
t.Fatalf("identifiers = %v", m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectFullMovieDedupsImdbAndSkipsCollectionID(t *testing.T) {
|
||||
m := projectFullMovie(map[string]any{
|
||||
"id": 348, "title": "Alien", "imdbId": "tt0078748",
|
||||
"externalIds": map[string]any{"imdbId": "tt0078748"},
|
||||
"belongsToCollection": map[string]any{"id": 8091, "name": "Alien Collection"},
|
||||
})
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["tmdb"] != "348" {
|
||||
t.Errorf("tmdb = %q, want 348 (not collection 8091)", ids["tmdb"])
|
||||
}
|
||||
if _, ok := ids["tmdb"]; ok && countNS(m.Identifiers, "tmdb") != 1 {
|
||||
t.Errorf("tmdb emitted %d times: %v", countNS(m.Identifiers, "tmdb"), m.Identifiers)
|
||||
}
|
||||
if countNS(m.Identifiers, "imdb") != 1 {
|
||||
t.Errorf("imdb emitted %d times: %v", countNS(m.Identifiers, "imdb"), m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectSearchMediaTVV2IDFallbackIsTMDBNotTVMaze(t *testing.T) {
|
||||
m := projectSearchMedia(map[string]any{
|
||||
"id": 1668, "title": "Reacher", "seriesId": 1668,
|
||||
}, "tv", "tmdb")
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["tmdb"] != "1668" {
|
||||
t.Errorf("tmdb = %q", ids["tmdb"])
|
||||
}
|
||||
if _, ok := ids["tvmaze"]; ok {
|
||||
t.Errorf("v2 seriesId must not be labelled tvmaze: %v", m.Identifiers)
|
||||
}
|
||||
if _, ok := ids["tvdb"]; ok {
|
||||
t.Errorf("unexpected tvdb: %v", m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectSearchMediaTVV1KeepsTVMazeAndDoesNotInventTMDB(t *testing.T) {
|
||||
m := projectSearchMedia(map[string]any{
|
||||
"id": 81189, "theMovieDbId": 81189, "seriesId": 169,
|
||||
"imdbId": "tt0903747", "title": "Breaking Bad",
|
||||
}, "tv", "tvdb")
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["tvdb"] != "81189" || ids["tvmaze"] != "169" || ids["imdb"] != "tt0903747" {
|
||||
t.Errorf("identifiers = %v", m.Identifiers)
|
||||
}
|
||||
if _, ok := ids["tmdb"]; ok {
|
||||
t.Errorf("v1 id fallback must not emit tmdb: %v", m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectFullTVTMDBDoesNotEchoSeriesIdAsTVMaze(t *testing.T) {
|
||||
o := &op{}
|
||||
m := o.projectFullTV(map[string]any{
|
||||
"id": 1206, "theMovieDbId": 1206, "seriesId": 1206,
|
||||
"theTvDbId": 75150, "imdbId": "tt0227882",
|
||||
"externalIds": map[string]any{"imdbId": "tt0227882", "tvdbId": 75150},
|
||||
"title": "Button Moon",
|
||||
}, "tmdb")
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["tmdb"] != "1206" || ids["tvdb"] != "75150" || ids["imdb"] != "tt0227882" {
|
||||
t.Errorf("identifiers = %v", m.Identifiers)
|
||||
}
|
||||
if v, ok := ids["tvmaze"]; ok {
|
||||
t.Errorf("bogus tvmaze %q (echoed TMDB id)", v)
|
||||
}
|
||||
if countNS(m.Identifiers, "imdb") != 1 {
|
||||
t.Errorf("imdb emitted %d times: %v", countNS(m.Identifiers, "imdb"), m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectMultiResultArtistCaseInsensitive(t *testing.T) {
|
||||
o := &op{}
|
||||
m := o.projectMultiResult(map[string]any{
|
||||
"id": "8dc08e7c-41f1-4a04-97dc-eb00d91d1d2f",
|
||||
"mediaType": "Artist", "title": "Radiohead",
|
||||
})
|
||||
if m.Media != "artist" {
|
||||
t.Errorf("media = %q, want artist", m.Media)
|
||||
}
|
||||
ids := idMap(m.Identifiers)
|
||||
if ids["musicbrainz"] != "8dc08e7c-41f1-4a04-97dc-eb00d91d1d2f" {
|
||||
t.Errorf("identifiers = %v", m.Identifiers)
|
||||
}
|
||||
if _, ok := ids["provider_unknown"]; ok {
|
||||
t.Errorf("Artist mapped as provider_unknown: %v", m.Identifiers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectRequestPrefersRequestIDOverProviderID(t *testing.T) {
|
||||
o := &op{}
|
||||
r := o.projectRequest(map[string]any{
|
||||
"id": 259032, "requestId": 88, "title": "Seven Up!",
|
||||
"mediaId": "259032", "tvDbId": 259032,
|
||||
}, "tv_parent")
|
||||
if r.Target.Kind != "tv_parent" || r.Target.ID != 88 {
|
||||
t.Errorf("target = %+v, want id 88", r.Target)
|
||||
}
|
||||
ids := idMap(r.Identifiers)
|
||||
if ids["tvdb"] != "259032" {
|
||||
t.Errorf("tvdb identifier = %q", ids["tvdb"])
|
||||
}
|
||||
if ids["tmdb"] != "259032" {
|
||||
t.Errorf("mediaId tmdb identifier = %q", ids["tmdb"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectRequestListStillUsesEntityID(t *testing.T) {
|
||||
o := &op{}
|
||||
r := o.projectRequest(map[string]any{
|
||||
"id": 10, "theMovieDbId": 27205, "title": "Inception",
|
||||
}, "movie")
|
||||
if r.Target.ID != 10 {
|
||||
t.Errorf("target.id = %d, want 10", r.Target.ID)
|
||||
}
|
||||
}
|
||||
@@ -203,8 +203,13 @@ func (o *op) requestsRecent(a *RequestsListArgs) *ToolResult {
|
||||
kind = "movie"
|
||||
}
|
||||
r := o.projectRequest(m, kind)
|
||||
if id, ok := toInt(m["requestId"]); ok {
|
||||
// RecentlyRequestedModel.RequestId is the Ombi request id.
|
||||
// Extra `id` on a TV payload is a provider id and must not
|
||||
// occupy target.id even when requestId is absent.
|
||||
if id, ok := toInt(m["requestId"]); ok && id > 0 {
|
||||
r.Target.ID = id
|
||||
} else {
|
||||
r.Target.ID = 0
|
||||
}
|
||||
r.RequestedUserID = jstr(m, "userId")
|
||||
items = append(items, r)
|
||||
|
||||
@@ -113,8 +113,10 @@ func (o *op) searchMulti(a *SearchArgs) *ToolResult {
|
||||
|
||||
// projectMultiResult maps MultiSearchResult {id, mediaType, title,
|
||||
// poster, overview} — the id retains its source/provider namespace.
|
||||
// mediaType is matched case-insensitively: upstream music results
|
||||
// emit "Artist".
|
||||
func (o *op) projectMultiResult(m map[string]any) Media {
|
||||
mt := jstr(m, "mediaType")
|
||||
mt := strings.ToLower(jstr(m, "mediaType"))
|
||||
out := Media{Identifiers: []Identifier{},
|
||||
Title: jstr(m, "title"), Overview: jstr(m, "overview")}
|
||||
switch mt {
|
||||
|
||||
Reference in New Issue
Block a user