LLM-readable documentation index

Entity Curation

Review, validate, and correct the entities bem extracts, and assign reviewers per type

Hand off to an LLM

bem builds an entity memory automatically as it parses documents — the nodes and edges you read through the Knowledge Graph API, shaped by the types and synonyms you define in the Customer Ontology. Curation is the human-in-the-loop layer over that memory: a reviewer works through the entities Parse extracted, approves the ones that are right, rejects the noise, and fixes types and synonyms along the way.

GET    /v3/review-queue
PATCH  /v3/entities/{id}
POST   /v3/entities/bulk-validate
POST   /v3/entity-types/{typeID}/reviewers
x-api-key: <your API key>

Concepts

The entity lifecycle

Every entity carries a curation status. It begins pre-terminal and a reviewer moves it to a terminal state:

statusMeaning
extractedbem inferred the entity while parsing a document. Awaiting review.
proposedQueued for a reviewer's attention. Awaiting review.
approvedA reviewer confirmed the entity. Terminal.
rejectedA reviewer discarded the entity. Terminal.

Approving or rejecting is only allowed from extracted or proposed. Any other transition — re-approving a terminal entity, for example — is rejected with 409. Once an entity is validated, validatedAt and validatedByUserID record who closed it out and when.

Reviewers

Curation is scoped by entity type. You assign reviewers to a type (POST /v3/entity-types/{typeID}/reviewers), and the review queue can then be filtered to "the entities I'm responsible for" with assignedTo=me. A reviewer is a user (usr_…) with an account role such as operator or admin.

Alias resolution

Curation endpoints honor merges. If you hold an entity id that was later merged away, PATCH /v3/entities/{id} resolves it to the surviving canonical entity and operates on that — you never act on a dead id by accident.

The review queue

GET /v3/review-queue is the reviewer-facing read surface. It returns a cursor-paginated page of entities awaiting curation, each with a small preview (up to 2) of its first mentions — page number, section label, and the exact surface string Parse saw — so a reviewer can triage without opening every entity.

The review queue is a dashboard surface: it is authenticated with your dashboard session (JWT), not an API key. The curation write endpoints below (PATCH / bulk-validate) work with your API key.

ParamDefaultNotes
statusextracted + proposedRepeatable. Restrict to specific lifecycle states.
typeall typesRepeatable, ety_… IDs. Matches the entity's effective type (assigned type, else inferred).
assignedTome or a usr_… ID. Entities whose effective type that user reviews.
sinceRFC3339. Entities created at or after this time.
bucketall bucketsbkt_…; absent → all buckets in the account + environment.
cursornextCursor from a previous response.
limit50Max 200.

All filters AND together. Pagination is cursor-based on entityID ascending; a missing or empty nextCursor (with hasMore: false) means you have reached the last page.

{
  "entities": [
    {
      "entityID": "ent_acme",
      "canonical": "Acme Corporation",
      "type": "organization",
      "status": "extracted",
      "mentionCount": 12,
      "surfaceForms": ["Acme Corp", "ACME"],
      "previewMentions": [
        {
          "mentionID": "emn_1a2b",
          "referenceID": "invoice-04812.pdf",
          "page": 1,
          "sectionLabel": "Bill To",
          "surface": "Acme Corp."
        }
      ]
    }
  ],
  "hasMore": true,
  "nextCursor": "ent_acme"
}

Curating entities

Approve, reject, or correct one entity

PATCH /v3/entities/{id} updates a single entity. Every field is optional, but at least one must be present.

FieldNotes
statusapproved or rejected — only from extracted / proposed, else 409.
assignedTypeIDety_… to override the inferred type. The empty string clears the assignment.
canonicalReplace the canonical surface form (re-derives its normalized form).
addSynonymsstring[] — surface forms to attach as customer_defined synonyms.
removeSynonymIDsesn_… IDs to soft-delete. Only customer_defined / sme_approved synonyms; removing an extracted one is 409.
localeOptional BCP 47 tag stamped on any added synonyms.
# approve an entity and pin its type in one call
curl -X PATCH "https://api.bem.ai/v3/entities/ent_acme" \
  -H "x-api-key: $BEM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "approved", "assignedTypeID": "ety_manufacturer", "addSynonyms": ["Acme Inc."] }'

Approving emits an entity_validated webhook; rejecting emits entity_rejected. The response is the full updated entity record, including its new status, validatedAt, and validatedByUserID.

Validate in bulk

POST /v3/entities/bulk-validate applies one terminal status to many entities at once — the workhorse behind "approve all" in a reviewer's session.

curl -X POST "https://api.bem.ai/v3/entities/bulk-validate" \
  -H "x-api-key: $BEM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "entityIDs": ["ent_acme", "ent_globex"], "status": "approved" }'

The response reports a per-row outcome (in request order) plus an aggregate summary, so a partially-valid batch still tells you exactly what happened:

{
  "results": [
    { "entityID": "ent_acme", "outcome": "validated" },
    { "entityID": "ent_globex", "outcome": "rejected-row", "reason": "already terminal" },
    { "entityID": "ent_missing", "outcome": "skipped", "reason": "not found" }
  ],
  "summary": { "validated": 1, "skipped": 1, "rejectedRow": 1 }
}
outcomeMeaning
validatedThe transition was applied.
skippedEntity not found, or not authorized for the caller.
rejected-rowThe transition itself was illegal (e.g. already terminal).

Assigning reviewers

Reviewers are attached to an entity type, so each subject-matter expert owns the entities in their domain. Manage assignments under /v3/entity-types/{typeID}/reviewers:

# assign a reviewer to a type
curl -X POST "https://api.bem.ai/v3/entity-types/ety_manufacturer/reviewers" \
  -H "x-api-key: $BEM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userID": "usr_2xyz" }'

# list a type's reviewers
curl "https://api.bem.ai/v3/entity-types/ety_manufacturer/reviewers" \
  -H "x-api-key: $BEM_API_KEY"

# remove a reviewer
curl -X DELETE "https://api.bem.ai/v3/entity-types/ety_manufacturer/reviewers/usr_2xyz" \
  -H "x-api-key: $BEM_API_KEY"

To go the other way — every type a given user reviews — use GET /v3/users/{userID}/reviewer-assignments. That mapping is what assignedTo=me resolves against when a reviewer opens their queue.

See also

On this page