{"openapi":"3.1.0","info":{"title":"Patcher API","description":"Community catalog of macOS app patching metadata.","version":"0.1.0"},"paths":{"/apps":{"get":{"tags":["apps"],"summary":"List Apps","description":"List apps in the catalog with optional filters and pagination.\n\nAll filters (``vendor``, ``source``, ``exclude_source``) and the\n``limit``/``offset`` pagination push down into a single SQL statement\nso the database does the filtering before paginating. Earlier versions\nof this endpoint filtered ``source``/``exclude_source`` in Python after\nmaterializing every row that matched ``vendor``, which made ``limit``\ndescribe the post-fetch slice rather than the actual page size.\n\nResults are ordered by ``slug`` so pagination is deterministic across\nrequests.\n\n:param vendor: Case-insensitive exact vendor match. None disables.\n:param source: Include only rows whose ``sources`` contains this token.\n:param exclude_source: Drop rows whose ``sources`` contains this token.\n:param limit: Maximum rows to return. Default 100, max 1000.\n:param offset: Number of filtered rows to skip before returning. Default 0.","operationId":"list_apps_apps_get","parameters":[{"name":"vendor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"exclude_source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exclude Source"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/App"},"title":"Response List Apps Apps Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/drift":{"get":{"tags":["apps"],"summary":"List Drift","description":"Scan the catalog for cross-source version drift.\n\nIterates apps with at least two versioned sources, compares their\nreported versions, and returns those whose sources disagree on what\n\"latest\" means. Drift is computed in Python after a single SQL fetch\n(drift cannot be expressed as a SQL filter without materializing the\nJSON payloads). ``total_scanned`` reflects the number of apps with\nenough sources to assess drift; ``total_with_drift`` is the unpaged\ncount of disagreements.\n\n:param vendor: Case-insensitive exact vendor match. None disables.\n:param source: When set, drop entries where this source did not\n    participate in the disagreement.\n:param limit: Max entries on this page. Default 100, max 1000.\n:param offset: Entries to skip before the page. Default 0.","operationId":"list_drift_apps_drift_get","parameters":[{"name":"vendor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DriftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{slug}":{"get":{"tags":["apps"],"summary":"Get App","operationId":"get_app_apps__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{slug}/sources":{"get":{"tags":["apps"],"summary":"Get App Sources","operationId":"get_app_sources_apps__slug__sources_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSources"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{slug}/drift":{"get":{"tags":["apps"],"summary":"Get App Drift","description":"Drift detection for a single app.\n\nReturns ``null`` when the app exists but has no drift (either fewer\nthan two versioned sources, or every source agrees). 404 if the slug\nis unknown.\n\n:param slug: URL-friendly app identifier.","operationId":"get_app_drift_apps__slug__drift_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DriftEntry"},{"type":"null"}],"title":"Response Get App Drift Apps  Slug  Drift Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/apps/{slug}/generate-label":{"post":{"tags":["apps"],"summary":"Generate Label","description":"Generate an Installomator label for ``slug``.\n\nProjects the app's Homebrew Cask + Installomator source payloads into\nan Installomator label fragment that consumers can drop into their\nInstallomator deployments. Returns the label plus provenance metadata\n(which sources contributed) and any warnings about fields that couldn't\nbe resolved (most commonly ``expectedTeamID`` for Cask-only apps).\n\n:param slug: URL-friendly app identifier.\n:type slug: str\n:param session: Async SQLAlchemy session (injected).\n:type session: sqlalchemy.ext.asyncio.AsyncSession\n:raises HTTPException: 404 if ``slug`` doesn't exist; 422 if the app has\n    no source detail attached (rare — usually a leftover seed record).\n:return: The generated label content + metadata.\n:rtype: :class:`patcher_api.schemas.labels.GenerateLabelResponse`","operationId":"generate_label_apps__slug__generate_label_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateLabelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/stats":{"get":{"tags":["stats"],"summary":"Get Stats","operationId":"get_stats_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogStats"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Health Health Get"}}}}}}}},"components":{"schemas":{"App":{"properties":{"slug":{"type":"string","title":"Slug"},"bundle_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bundle Id"},"name":{"type":"string","title":"Name"},"vendor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"},"current_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Version"},"latest_release_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Latest Release Date"},"download_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Download Url"},"install_method":{"anyOf":[{"$ref":"#/components/schemas/InstallMethod"},{"type":"null"}]},"sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sha256"},"sources":{"items":{"type":"string"},"type":"array","title":"Sources"}},"type":"object","required":["slug","name","sources"],"title":"App"},"AppSources":{"properties":{"installomator":{"anyOf":[{"$ref":"#/components/schemas/InstallomatorSource"},{"type":"null"}]},"homebrew_cask":{"anyOf":[{"$ref":"#/components/schemas/HomebrewCaskSource"},{"type":"null"}]},"autopkg":{"anyOf":[{"$ref":"#/components/schemas/AutopkgSource"},{"type":"null"}]},"mas":{"anyOf":[{"$ref":"#/components/schemas/MasSource"},{"type":"null"}]},"jamf_app_installer":{"anyOf":[{"$ref":"#/components/schemas/JamfAppInstallerSource"},{"type":"null"}]}},"type":"object","title":"AppSources"},"AutopkgRecipeEntry":{"properties":{"identifier":{"type":"string","title":"Identifier"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"shortname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shortname"},"repo":{"type":"string","title":"Repo"},"path":{"type":"string","title":"Path"},"parent_identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Identifier"},"inferred_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Inferred Type"},"recipe_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Recipe Url"}},"type":"object","required":["identifier","repo","path"],"title":"AutopkgRecipeEntry","description":"Single recipe attached to an app via the AutoPkg index.\n\n``name`` and ``shortname`` are optional, mirroring the upstream index\n(and the :class:`~patcher_api.schemas.autopkg.AutopkgIndexEntry` ingest\nschema): shared-processor recipes carry ``name: null`` and some app\nrecipes have no clean ``shortname``. The response must tolerate the\n``None`` that stitch faithfully stored, or ``/apps/{slug}/sources``\n500s for any app whose matched recipes lack one."},"AutopkgSource":{"properties":{"recipes":{"items":{"$ref":"#/components/schemas/AutopkgRecipeEntry"},"type":"array","title":"Recipes"}},"type":"object","required":["recipes"],"title":"AutopkgSource","description":"All AutoPkg recipes matched to an app via the recipe index.\n\nAutoPkg coverage is multi-recipe by nature: a single app like Firefox\ntypically has download, munki, pkg, jamf, and intune variants across\nmultiple maintainer repos. Each match is preserved as a separate\n:class:`AutopkgRecipeEntry`."},"CatalogStats":{"properties":{"total_apps":{"type":"integer","title":"Total Apps"},"sources":{"$ref":"#/components/schemas/SourceCoverage"},"last_refresh":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Refresh"},"catalog_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Catalog Sha"}},"type":"object","required":["total_apps","sources","last_refresh","catalog_sha"],"title":"CatalogStats","description":"Top-line catalog statistics returned by ``GET /stats``."},"DriftEntry":{"properties":{"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"vendor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor"},"versions":{"items":{"$ref":"#/components/schemas/SourceVersion"},"type":"array","title":"Versions"},"leader":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Leader"},"laggard":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Laggard"}},"type":"object","required":["slug","name","versions"],"title":"DriftEntry","description":"Drift detected on a single app.\n\n``leader`` and ``laggard`` are the source names with the highest and\nlowest parsed versions; both are ``None`` when at least one version\nstring couldn't be parsed (e.g. Cask's ``2025-04-15`` date-style\nversions). In that case ``versions`` is still complete and consumers\ncan render the disagreement without ordering it."},"DriftResponse":{"properties":{"total_scanned":{"type":"integer","title":"Total Scanned"},"total_with_drift":{"type":"integer","title":"Total With Drift"},"entries":{"items":{"$ref":"#/components/schemas/DriftEntry"},"type":"array","title":"Entries"}},"type":"object","required":["total_scanned","total_with_drift","entries"],"title":"DriftResponse","description":"Paginated drift results across the catalog.\n\n``total_scanned`` is the number of apps inspected (those with ≥2\nversioned sources); ``total_with_drift`` is the subset where the\nsources disagreed. ``entries`` is the page of disagreements."},"GenerateLabelResponse":{"properties":{"label_name":{"type":"string","title":"Label Name"},"content":{"additionalProperties":true,"type":"object","title":"Content"},"sources_used":{"items":{"type":"string"},"type":"array","title":"Sources Used"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"}},"additionalProperties":false,"type":"object","required":["label_name","content","sources_used","warnings"],"title":"GenerateLabelResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HomebrewCaskSource":{"properties":{"token":{"type":"string","title":"Token"},"cask_json":{"additionalProperties":true,"type":"object","title":"Cask Json"}},"type":"object","required":["token","cask_json"],"title":"HomebrewCaskSource"},"InstallMethod":{"type":"string","enum":["dmg","pkg","zip","tbz","pkgInDmg","pkgInZip","appInDmgInZip"],"title":"InstallMethod","description":"Mirrors Installomator's `type` variable.\n\nSee https://github.com/Installomator/Installomator/wiki/Label-Variables-Reference\nfor the upstream definition."},"InstallomatorSource":{"properties":{"label_name":{"type":"string","title":"Label Name"},"label_url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Label Url"},"raw":{"additionalProperties":true,"type":"object","title":"Raw"}},"type":"object","required":["label_name","label_url","raw"],"title":"InstallomatorSource"},"JamfAppInstallerSource":{"properties":{"title":{"type":"string","title":"Title"},"source":{"type":"string","title":"Source"},"host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host"},"bundle_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bundle Id"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"jamf_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Jamf Id"},"download_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Download Url"},"architecture":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Architecture"}},"type":"object","required":["title","source"],"title":"JamfAppInstallerSource","description":"Jamf App Installers catalog coverage for an app.\n\n``title``/``source``/``host`` come from the public HTML catalog; the rest\nis enrichment from the App Installers titles API (absent on HTML-only\nrows, hence optional)."},"MasSource":{"properties":{"bundle_id":{"type":"string","title":"Bundle Id"},"store_url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Store Url"},"raw":{"additionalProperties":true,"type":"object","title":"Raw"}},"type":"object","required":["bundle_id","raw"],"title":"MasSource"},"SourceCoverage":{"properties":{"installomator":{"type":"integer","title":"Installomator"},"homebrew_cask":{"type":"integer","title":"Homebrew Cask"},"jamf_app_installer":{"type":"integer","title":"Jamf App Installer"},"autopkg":{"type":"integer","title":"Autopkg"}},"type":"object","required":["installomator","homebrew_cask","jamf_app_installer","autopkg"],"title":"SourceCoverage","description":"How many apps carry each upstream source's data."},"SourceVersion":{"properties":{"source":{"type":"string","title":"Source"},"version":{"type":"string","title":"Version"},"parsed_ok":{"type":"boolean","title":"Parsed Ok"}},"type":"object","required":["source","version","parsed_ok"],"title":"SourceVersion","description":"One source's reported version for an app."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}