Entity Curation
Review, validate, and correct the entities bem extracts, and assign reviewers per type
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:
status | Meaning |
|---|---|
extracted | bem inferred the entity while parsing a document. Awaiting review. |
proposed | Queued for a reviewer's attention. Awaiting review. |
approved | A reviewer confirmed the entity. Terminal. |
rejected | A 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.
| Param | Default | Notes |
|---|---|---|
status | extracted + proposed | Repeatable. Restrict to specific lifecycle states. |
type | all types | Repeatable, ety_… IDs. Matches the entity's effective type (assigned type, else inferred). |
assignedTo | — | me or a usr_… ID. Entities whose effective type that user reviews. |
since | — | RFC3339. Entities created at or after this time. |
bucket | all buckets | bkt_…; absent → all buckets in the account + environment. |
cursor | — | nextCursor from a previous response. |
limit | 50 | Max 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.
| Field | Notes |
|---|---|
status | approved or rejected — only from extracted / proposed, else 409. |
assignedTypeID | ety_… to override the inferred type. The empty string clears the assignment. |
canonical | Replace the canonical surface form (re-derives its normalized form). |
addSynonyms | string[] — surface forms to attach as customer_defined synonyms. |
removeSynonymIDs | esn_… IDs to soft-delete. Only customer_defined / sme_approved synonyms; removing an extracted one is 409. |
locale | Optional 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 }
}outcome | Meaning |
|---|---|
validated | The transition was applied. |
skipped | Entity not found, or not authorized for the caller. |
rejected-row | The 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
Knowledge Graph API
Read back the entities and relations your reviewers curate.
Customer Ontology
Define the entity types and synonyms that curation operates over.
Entity Curation reference
The PATCH and bulk-validate endpoints in full.
Review Queue reference
The GET /v3/review-queue endpoint and its filters.