Enrichment & review

Confirm metadata matches, re-run enrichment, decide AI suggestions and approve translations.

When a title is created or imported, Ingenee enriches it automatically: it matches the title to a metadata provider, fills in missing fields, and adds AI-derived themes, moods, keywords and an embedding for search. Anything the AI produces is a proposal. These endpoints let your system confirm matches, trigger a re-run, and accept or reject what the AI proposed.

Rules the server enforces

  • AI proposes, a person decides. Long-form prose such as a synopsis is never published automatically, whatever the confidence.
  • Some fields are always decided one at a time. Age rating, content advisories, territory, license and availability are never accepted in bulk.
  • Locked and person-set fields are immutable to enrichment. A re-run keeps them, and a pending change to a locked field can't be accepted.
  • Your API key is the person. A decision made through the API is recorded in the audit history under the key's creator, together with the key.

Confirm a metadata match

Enrichment auto-attaches a provider match only when it is confident. When it isn't, the title waits for a decision with ranked candidates.

curl https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/match \
  -H "Authorization: Bearer $INGENEE_API_KEY"
200 OK (trimmed)
{
  "decision": "refused",
  "confidence": "mid",
  "reason": "year differs",
  "candidates": [
    { "tmdbId": "603", "title": "The Matrix", "year": 1999, "confidence": "mid", "kind": "movie", "why": "year differs" }
  ],
  "rejectedIds": []
}

Then decide. To accept, send one of the listed candidates' ids; to reject, send reject (optionally with the tmdbId you reject).

curl -X POST https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/match \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "action": "accept", "tmdbId": "603" }'

Accepting an id that isn't one of the candidates returns 422, and deciding a title that has no refused match returns 409 CONFLICT. Only films and TV titles take a TMDB match.

Name the title yourself

If the right title isn't among the candidates, say which film or series this is. Ingenee checks the id against TMDB before it writes anything, so a typo can't enrich a title from the wrong film.

curl -X POST https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/identity \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "tmdbId": "603", "imdbId": "tt0133093" }'
200 OK
{ "id": "7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11", "tmdbId": "603", "imdbId": "tt0133093", "verifiedAs": "The Matrix", "verifiedYear": 1999 }

Send null to clear an id, which also undoes what that id had written. Replacing an id does the same for the old one. Songs and web titles don't take a TMDB id (409 CONFLICT). An id TMDB doesn't know returns 422.

Re-run enrichment

POST /v1/titles/{id}/reembed runs enrichment again for one title: metadata providers, AI themes, moods and keywords, and the embedding. Locked and person-set fields are kept. It runs while you wait and can take several seconds.

curl -X POST https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/reembed \
  -H "Authorization: Bearer $INGENEE_API_KEY"
200 OK
{ "id": "7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11", "reembedded": true }

reembedded: true means the pass ran, not that every step succeeded. Read the title afterwards and check status (enriched, partial or error) and lastEnrichedAt. For many titles, use POST /v1/titles/bulk with "action": "reembed" and up to 200 ids; the response lists each id with ok or an error.

Decide AI suggestions

GET /v1/review groups pending AI suggestions by field and value, across all titles:

200 OK (trimmed)
{
  "summary": { "proposals": 42, "titles": 18, "groups": 9, "auto": 12, "high": 4, "mid": 3, "low": 2, "stuck": 1 },
  "groups": [
    { "field": "mood", "fieldLabel": "Mood", "value": "tense", "titles": 11, "confidence": 0.93, "bucket": "high", "policy": "auto" },
    { "field": "content_rating", "fieldLabel": "Age rating", "value": "TV-14", "titles": 1, "confidence": 0.81, "bucket": "mid", "policy": "always_human" }
  ],
  "stuckTitles": 1
}

Add ?catalog={catalogId} to count only one catalog's titles. GET /v1/review/{titleId} is the per-title view: pending suggestions grouped by field with ranked alternates and confidence, pending changes, locked fields, and the title's place in the queue.

Every suggestion has a policy, which is what your integration should branch on rather than the raw confidence:

policyApplies toMeaning
autoConfidence of 0.90 or more on an ordinary field (themes, moods, keywords …)Safe to accept in bulk
reviewLower confidence, or no confidence givenA person should look
read_throughSynopsis, short description, taglineLong-form prose: read it, then decide it on its own
always_humanAge rating, content advisories, audience fit, territory, license, availabilityDecide one at a time; never bulk-accepted

Accept or reject by field and value. Add assetId to decide for one title only; leave it out to decide the whole group.

curl -X POST https://dev.app.ingenee.ai/api/v1/review/accept \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "field": "mood", "value": "tense", "assetId": "7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11" }'
200 OK
{ "decided": 1, "action": "accepted" }

POST /v1/review/reject takes the same body and also removes the value from the title's AI attributes so it stops appearing. The response is { "decided": 0, … } if nothing matched, which usually means it was already decided.

Pending changes to fields

Some proposals are changes to a canonical field, shown as current and proposed value side by side. GET /v1/modifications returns { count, rows } (up to 500 pending, newest first); each row has id, assetId, assetTitle, field, currentValue, proposedValue, source, confidence and proposedAt.

curl -X POST https://dev.app.ingenee.ai/api/v1/modifications/3f6b1c2e-8a4d-4e0b-9c7a-5d1e2f3a4b5c \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "action": "accept" }'

The response is { "id": "…", "status": "…" }. Accepting writes the proposed value to the field, unless the field is locked (refused) or the change was already decided (409 CONFLICT).

Translate a title's metadata

Translation creates proposals per language and field. Nothing reaches the title's localized metadata until you approve it.

curl -X POST https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/localize \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "locales": ["es", "te"], "fields": ["title", "synopsis", "genres"], "skipExisting": true }'
200 OK
{ "created": [ { "locale": "es", "field": "title" }, { "locale": "es", "field": "synopsis" } ],
  "skipped": [ { "locale": "te", "field": "title", "reason": "locked" } ] }

locales takes 1 to 54 language codes. fields can be title, synopsis, short_description, keywords, genres, cast, editorial_summary, themes, moods, audience and tone. A skipped reason is source (the target is the text's own language), locked, or exists (with skipExisting). onlyOutdated regenerates only proposals whose source text changed. A 403 ENTITLEMENT means AI localization isn't enabled for your organization.

GET /v1/titles/{id}/localizations shows, per language, every field's source, proposedValue, status, confidence, locked and whether it is outdated (the source text changed since). Approve what you've reviewed:

curl -X POST https://dev.app.ingenee.ai/api/v1/titles/7c1a3f0e-5d2b-4f6a-9a41-2f0e9d3b8c11/localizations/approve \
  -H "Authorization: Bearer $INGENEE_API_KEY" -H "content-type: application/json" \
  -d '{ "locale": "es" }'
200 OK
{ "approved": 3, "skipped": [], "locale": "es", "scope": "language" }

Leave out locale to approve every pending language (scope: "all-languages"). Locked proposals are skipped and reported. Approving applies to all pending proposals in scope at once, so read them first. Editing or rejecting a single proposal isn't available through the API.

Not yet available

  • A dedicated "re-run metadata matching only" call. reembed re-runs the whole enrichment pass; match and identity change which provider record a title is tied to.
  • Accepting or editing a single translation proposal. Approval is per language or for all.
  • Bulk approving every pending AI proposal in one call (the console's "Approve all").

Endpoints

OperationMethod + pathScope
Get match candidatesGET /v1/titles/{id}/matchlibrary:read
Accept or reject a matchPOST /v1/titles/{id}/matchenrichment:write
Set TMDB / IMDb identityPOST /v1/titles/{id}/identityenrichment:write
Re-run enrichmentPOST /v1/titles/{id}/reembedenrichment:write
Translate metadataPOST /v1/titles/{id}/localizeenrichment:write
List translation proposalsGET /v1/titles/{id}/localizationslibrary:read
Approve translationsPOST /v1/titles/{id}/localizations/approveenrichment:write
List AI suggestionsGET /v1/reviewlibrary:read
Get a title's suggestionsGET /v1/review/{id}library:read
Accept suggestionsPOST /v1/review/acceptenrichment:write
Reject suggestionsPOST /v1/review/rejectenrichment:write
List pending modificationsGET /v1/modificationslibrary:read
Resolve a modificationPOST /v1/modifications/{modId}enrichment:write

On this page