Get an API key

As the owner, open Settings → Server → Integrations and create a named API key. Give each tool its own key and only the scopes it needs:

Task Scope
Libraries and recently added read
Strict path notify and subtitle refresh library, with owner authority
Own-account sessions read and playback
Server-wide sessions and webhook management admin, with the required owner or administrator authority

Scopes do not replace account or library permissions. admin includes read, playback and library; the other scopes are independent. Owners can Reveal and Copy keys later and revoke them immediately. On TV, secrets are never displayed. Keys are recoverable in the server’s local database and backups.

Set the server address and your key in your terminal. Use an address the script can reach; 127.0.0.1 works only on the server itself. Use HTTPS across untrusted networks.

export PORTICO_URL='http://127.0.0.1:32500'
read -r -s -p 'Portico API key: ' PORTICO_API_KEY; echo
export PORTICO_API_KEY

The examples use Bash and curl. Save Python examples as files and run them with Python 3; they need no extra packages. Native requests work without turning on Integrations compatibility or contacting Portico Services.

Notify a file or folder change

Use the absolute path as Portico sees it, including container mounts. Keep it narrower than a library source root.

curl --fail-with-body "$PORTICO_URL/v1/libraries/paths-changed" \
  -H "Authorization: Bearer $PORTICO_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"mode":"strict","paths":[{"path":"/media/Movies/Arrival (2016)","change":"changed"}]}'

Use added, changed or removed. A 202 queues targeted work; it does not mean scanning and matching have finished. Inspect the response’s paths and jobs.

Strict mode never falls back to a full-source scan. Source roots, unresolved paths, symlink-following sources and unsupported remote sources can be refused. Correct the path or source configuration. A busy or paused source can return 503 and Retry-After: 2; wait and retry the same narrow target. Do not replace a refused request with a full-library refresh.

For an import hook, save this as notify.py. It sends one exact file or folder, reports failures and leaves retry scheduling to the caller:

import json
import os
import sys
from urllib.error import HTTPError
from urllib.request import Request, urlopen

if len(sys.argv) not in (2, 3):
    raise SystemExit("Usage: python3 notify.py /absolute/server/path [added|changed|removed]")
path = sys.argv[1]
change = sys.argv[2] if len(sys.argv) == 3 else "changed"
if not path.startswith("/") or change not in {"added", "changed", "removed"}:
    raise SystemExit("Use an absolute server path and added, changed or removed")
body = json.dumps({"mode": "strict", "paths": [{"path": path, "change": change}]}).encode()
request = Request(
    os.environ["PORTICO_URL"].rstrip("/") + "/v1/libraries/paths-changed",
    data=body,
    headers={"Authorization": "Bearer " + os.environ["PORTICO_API_KEY"],
             "Content-Type": "application/json"},
    method="POST",
)
try:
    with urlopen(request, timeout=30) as response:
        print(response.read().decode())
except HTTPError as error:
    print(error.read().decode(), file=sys.stderr)
    print("Retry-After:", error.headers.get("Retry-After", "not supplied"), file=sys.stderr)
    raise SystemExit(1)
python3 notify.py '/media/Movies/Arrival (2016)' changed

List libraries

curl --fail-with-body "$PORTICO_URL/v1/integrations/libraries?limit=20" \
  -H "Authorization: Bearer $PORTICO_API_KEY"

Read the items array. When nextCursor is present, send it back unchanged with the same query and credential. Stop when it is absent or empty. Never infer the end from a short page.

Recently added

Choose a library ID from the previous response:

export PORTICO_LIBRARY_ID='replace-with-library-id'
curl --fail-with-body --get "$PORTICO_URL/v1/integrations/items" \
  -H "Authorization: Bearer $PORTICO_API_KEY" \
  --data-urlencode "libraryId=$PORTICO_LIBRARY_ID" \
  --data-urlencode 'sort=added' --data-urlencode 'order=desc' \
  --data-urlencode 'limit=20'

The newest visible titles come first. Follow nextCursor with the same filters to read more. Ignore unknown optional fields. If a continuation expires, start the query again.

Who’s watching

curl --fail-with-body "$PORTICO_URL/v1/integrations/sessions?limit=20" \
  -H "Authorization: Bearer $PORTICO_API_KEY"

For server-wide monitoring, use an authorized owner or administrator credential with admin scope. A limited key with read and playback sees only permitted own-account controls and activity. A key does not gain global visibility just because its creator is the owner. Follow nextCursor for more sessions.

Receive webhooks

Portico can send events straight to your endpoint. Follow the webhook guide to create one, verify its signature and handle duplicate deliveries.

Refresh subtitles

Use an item ID from the native items response and an owner key with library scope:

export PORTICO_ITEM_ID='replace-with-item-id'
curl --fail-with-body -X POST \
  "$PORTICO_URL/v1/integrations/items/$PORTICO_ITEM_ID/subtitles/refresh" \
  -H "Authorization: Bearer $PORTICO_API_KEY" \
  -H 'Content-Type: application/json' --data '{}'

This rereads eligible exact subtitle sidecars using stored media evidence. It does not probe media, enrich metadata or replace artwork. Missing or changed evidence is an explicit refusal. Fix the underlying path or evidence instead of broadening the operation.

For parameters, models and error codes, use the native Integrations reference.