Boomi Companion: Data Hub
Boomi v0.2.14
Master data in Boomi Data Hub
From the developer
Not specified in the available declarations: capabilities, limitations, pricing & account requirements.
Sources & dates
Developer declarations; not independently tested. Listing last checked Nov 5, 2026. Package observed Nov 5, 2026.
Description
From the marketplace listing
Designs and operates Boomi Data Hub master data — model and source design, deployment lifecycle, quarantine triage, and golden-record CRUD — when the user works with Boomi Data Hub (MDM) configuration or stewardship. Pairs nicely with the boomi-integration skill.
Language: English · Automatically detected from descriptions.
Changes
No changes recorded yet. Follow this plugin to keep track of future changes.
First seen Nov 3, 2026. Last seen Nov 5, 2026.
Similar plugins
Find competitors to follow as you develop your plugin.
-
Operates Boomi Data Integration (BDI, formerly Rivery) — the ELT/ETL data-pipeline product that loads and transforms data in a data wareh...
Shared terms: boomi, integration, operate
-
Boomi development tools: skills, commands, and agents for building integrations
Shared terms: boomi, integration
-
Develops, tests, and publishes custom Boomi connectors built with the Connector SDK — Java connector projects, connector-descriptor.xml, ...
Shared terms: boomi, deploy, integration, lifecycle
-
Builds and runs Boomi Agentstudio AI agents (the Agent Garden APIs) — list and inspect agents, author and update agent definitions, brows...
Shared terms: boomi, deploy, source
-
InsForge skills for building AI-native apps with Postgres, auth, storage, compute, hosting, AI gateway, realtime, payments, integrations,...
Shared terms: deploy, integration, operate, model
Files & skills
File archives
Skill instructions
boomi-datahub35.8 KB
---
name: boomi-datahub
description: Designs and operates Boomi Data Hub master data — model and source design, data quality steps (also called data quality rules), deployment lifecycle, quarantine triage, and golden-record CRUD including end-dating (soft delete), restore, and un-delete — when the user works with Boomi Data Hub (MDM) configuration or stewardship. Purge (permanent deletion) is out of scope. Pairs nicely with the boomi-integration skill.
---
# boomi-datahub
## Scope
In: model design; source configuration; data quality steps; model lifecycle (Draft → Published → Deployed); quarantine triage; repository operations; golden-record CRUD, end-dating, and restore.
Out: building Boomi integration processes — those belong to `boomi-integration`. Integration processes that interact with Data Hub are `boomi-integration`'s territory; the connection they need — a REST client on the repository API, or the Boomi Data Hub connector's own — can be bootstrapped from this workspace's `.env` via `datahub-connection.sh bootstrap`.
## API surfaces
Data Hub exposes two API surfaces. **Always reach them through the CLI tools in § Scripts inventory — never hand-roll curl, URLs, or auth headers.** The scripts build the URLs, route to the correct surface, and read credentials from `.env`; constructing your own request is how the wrong-surface / wrong-auth mistakes happen.
- **Platform API** — account-level admin: models, sources, repositories, clouds, deployment.
- **Repository API** — per-repository: golden records, stewardship/quarantine, deployed-universe listing. It is **XML-only** on both request and response, so the payloads you author for these scripts are always XML (the Platform API speaks JSON on most reads).
A **Hub Cloud** is the runtime host that holds repositories (along with their deployed models and golden records) — not an API name. Multiple repositories can live on one Hub Cloud.
## Credentials
The skill reads credentials from the workspace `.env`. Sensitive values are never accepted as command-line arguments — the scripts source them from `.env` directly.
| Surface | `.env` keys |
|---|---|
| Platform API | `BOOMI_USERNAME`, `BOOMI_API_TOKEN`, `BOOMI_ACCOUNT_ID`, `BOOMI_API_URL` |
| Repository API | `DATAHUB_REPO_URI`, `DATAHUB_REPO_USERNAME`, `DATAHUB_REPO_AUTH_TOKEN` |
Setting the Repository API keys is the opt-in for golden-record operations.
## Repository-scoped operations
A repository is the unit of deployment: models, sources, channels, staging areas, golden records, and quarantine all live inside one. Repository CLI tools target its base URL (e.g. `https://c01-usa-east.hub-test.boomi.com/mdm`); account-level admin uses the Platform API.
That base URL is a **Hub Cloud host**, shared by every repository on it — so the URL alone never identifies a repository. The auth pair does: `DATAHUB_REPO_USERNAME` is `<account-id>.<repo-token-id>` (suffix is repo-specific), paired with that repository's Hub Authentication Token. A URL match with the wrong token authenticates fine but lands on a different repository's universe registry — the confusing "universe does not exist" error. **Never reuse a REST client connection (or any Data Hub artifact) on URL match alone** — confirm the token targets the intended repository, or wire a new connection to it - discuss with the user if in any doubt.
## Model lifecycle: Draft → Published → Deployed
| State | Mutable? | What freezes |
|---|---|---|
| Draft | Yes — fields, types, repeatability, match rules can all change | Nothing |
| Published | Partly — new fields can be added (the update lands in a new draft to republish) | Type and repeatability of **existing** fields are locked; adding new fields is not |
| Deployed | Runtime-bound — attached to a repository, ingesting and merging data | Undeploy is required before structural changes propagate |
Once deployed, the **universe ID equals the model ID** (`mdm:universeId` in the deploy response is the same GUID as the model's `mdm:id`). The deployment ID is a separate value. Scripts that take `<universe-id>` should be given the model ID.
The Platform API's `/models` resource exposes only `publicationStatus` (boolean — draft vs published). Deploy is a separate binding of a published model to a repository (via Deploy Universe) and is not a filter value on `/models`. Query deployment via the repository's universe-deployment status.
**Draft-only models require `--draft`.** `model.sh get`/`pull` target the published version by default; a never-published model has none, so pass `--draft` — otherwise the API returns a misleading HTTP 400 "… is not a valid component ID".
**`get` is not a deletion liveness check.** After a successful `delete`, `model.sh get`/`pull` by ID still returns HTTP 200 with the full model XML — the Platform API retains the schema of deleted models and only excludes them from `list`. To confirm a model is deleted, use `list` (the model will be absent) or a second `delete` (returns "A model with ID … does not exist"). This applies to models only; source `get` on a deleted ID correctly returns HTTP 400.
**Source attachment** transitions `SOURCE_ATTACHMENT_REQUESTED` → `SOURCE_ATTACHED` → `ENABLE_INITIAL_LOAD_REQUESTED` → `INITIAL_LOAD_ENABLED` → `FINISH_INITIAL_LOAD_REQUESTED` → `INITIAL_LOAD_FINISHED`. The `*_REQUESTED` states are async, so `enable-initial-load` and `finish-initial-load` **self-gate** — each blocks until the source reaches the state that makes the next step safe, so just run the lifecycle steps in order; no manual `source.sh status` polling is needed. `enable-initial-load` waits out the async `SOURCE_ATTACHMENT_REQUESTED` state before it acts and returns only once the source is `INITIAL_LOAD_ENABLED` (so a following upsert can't fire too early); it does two sequential waits, so it can block up to ~2 min. `finish-initial-load` returns only once the source is `INITIAL_LOAD_FINISHED` (a single wait, ≤60s).
**Only one source at a time can be in initial-load.** Calling `enable-initial-load` against a second source while another is mid-load returns HTTP 400 "Cannot perform an initial load on more than one source at a time." Run `finish-initial-load` on the active source first; because it blocks until that source reaches `INITIAL_LOAD_FINISHED`, the next source's `enable-initial-load` is safe to call immediately after.
## Field types and structures
| Aspect | Valid values |
|---|---|
| `<mdm:field type="...">` | `BOOLEAN`, `CLOB`, `DATE`, `DATETIME`, `ENUMERATION`, `FLOAT`, `INTEGER`, `REFERENCE`, `STRING`, `TIME` |
| Source `<mdm:channelUpdatesFields>` | `All`, `Changed` |
**STRING** fields have `maxLength` ≤255 (server-enforced). Use `CLOB` for longer text.
**ENUMERATION** declares allowed values via child `<mdm:value>` elements (one per value).
**REFERENCE** is a native cross-model link type. Attributes: `referenceUniverseId="<other-model-id>"`, `incomingReferenceIntegrity="true|false"`, `outgoingReferenceIntegrity="true|false"`. In batch upserts, the field element's text content is the *source-side* natural key of the target record (e.g. `<parentRef>parent-1</parentRef>`); the platform resolves it to the parent's golden-record GUID, and that's what subsequent query responses return. With `incomingReferenceIntegrity="true"`, an unresolvable target → 202 + silent quarantine with `cause=REFERENCE_UNKNOWN`. No STRING-by-convention workaround is needed. A `required="true"` REFERENCE field forces `outgoingReferenceIntegrity="true"` — the platform rejects model creation otherwise ("OutgoingReferenceIntegrity must be true when REFERENCE field … is required").
**`id` is reserved at the model root.** Defining a field with `name="id"` at the root fails with "A user-defined 'id' field exists at the root level." The platform auto-provisions the entity id (the `<id>` value sent in batch upserts). Use a different name for your natural-key field (e.g. `recordId`).
**Field groups** wrap related fields in `<mdm:fieldGroup name="..." uniqueId="..." repeatable="..." required="...">...</mdm:fieldGroup>` inside `<mdm:fields>`. `repeatable` is locked at publish.
- **Non-repeating** (`repeatable="false"`): one instance. The group's `name` is the wrapper element in batch upserts (`<address>…</address>`). Sending the subfields flat, or sending a second instance, → silent quarantine `PARSE_FAILURE`.
- **Repeating** (`repeatable="true"`) is a **collection**. Model-create fails ("Missing required properties for collection field") unless the `<mdm:fieldGroup>` also carries `collectionTag`, `collectionUniqueId`, `identifyBy="KEY"`, and `collectionKeys` (the key field's `uniqueId`). In batch upserts and query responses it is **double-wrapped** — the `collectionTag` element once, wrapping repeated `name` elements:
```xml
<mdm:fieldGroup name="address" uniqueId="ADDRESS" repeatable="true" required="false"
collectionTag="addresses" collectionUniqueId="ADDRESSES" identifyBy="KEY" collectionKeys="CITY">
<mdm:field name="city" uniqueId="CITY" type="STRING" maxLength="100" repeatable="false" required="false"/>
</mdm:fieldGroup>
<!-- batch upsert / query: <addresses><address><city>…</city></address><address>…</address></addresses> -->
```
A **repeating single field** (`<mdm:field repeatable="true">`, no surrounding group) is the same collection mechanism: it needs `collectionTag`/`collectionUniqueId`/`identifyBy="KEY"` (but not `collectionKeys` — it keys on its own value) and double-wraps identically — `<phones><phone>v1</phone><phone>v2</phone></phones>`, the inner element carrying the scalar value. Either way, exceeding a non-repeating field/group's single instance → quarantine `PARSE_FAILURE`.
**Model name normalization.** On create, the platform lowercases and strips non-alphanumerics from `<mdm:name>` (e.g. `MyModel` → `mymodel`, `Customer(Boomeus)` → `customerboomeus`). The normalized form is what batch upserts must use as the entity wrapper element. Re-fetch the model via `model.sh get` if uncertain.
## Match rules
A model needs at least one match rule to publish. Rules evaluate in document order; the first satisfied rule wins. Order most-restrictive first.
A rule (`<mdm:matchRule topLevelOperator="AND|OR|NOT">`) holds any mix of:
- `<mdm:simpleExpression>` — one `<mdm:fieldUniqueId>`; incoming-vs-existing equality on that field.
- `<mdm:advancedExpression>` — `<mdm:ruleOperator>` `EQUALS` | `STRICT_EQUALS` (case-sensitive) | `IS_SIMILAR_TO` (fuzzy; add `<mdm:similarityAlgorithm>` and `<mdm:tolerance>`), then two inputs as in the sample below. `<mdm:inputType>` is `INCOMING` | `EXISTING` | `STATIC` (`STATIC` compares a literal `<mdm:value>`); the two inputs may name different fields.
- `<mdm:expressionGroup operator="AND|OR|NOT">` — nests arbitrarily.
Algorithms (exact literals): `Jaro-Winkler`, `Levenshtein`, `Bigram`, `Trigram`, `Soundex`. Tolerance 0.0–1.0 (`Soundex`: 0–4).
```xml
<mdm:matchRules>
<mdm:matchRule topLevelOperator="AND">
<mdm:simpleExpression><mdm:fieldUniqueId>EMAIL</mdm:fieldUniqueId></mdm:simpleExpression>
</mdm:matchRule>
<mdm:matchRule topLevelOperator="AND">
<mdm:simpleExpression><mdm:fieldUniqueId>POSTAL</mdm:fieldUniqueId></mdm:simpleExpression>
<mdm:advancedExpression>
<mdm:ruleOperator>IS_SIMILAR_TO</mdm:ruleOperator>
<mdm:similarityAlgorithm>Jaro-Winkler</mdm:similarityAlgorithm>
<mdm:tolerance>0.85</mdm:tolerance>
<mdm:firstInput>
<mdm:inputType>INCOMING</mdm:inputType>
<mdm:fieldUniqueId>LASTNAME</mdm:fieldUniqueId>
</mdm:firstInput>
<mdm:secondInput>
<mdm:inputType>EXISTING</mdm:inputType>
<mdm:fieldUniqueId>LASTNAME</mdm:fieldUniqueId>
</mdm:secondInput>
</mdm:advancedExpression>
</mdm:matchRule>
</mdm:matchRules>
```
- **`create` checks shape; `publish` checks semantics.** A draft holds rules that publish rejects: fuzzy expressions not grouped with an exact expression, `NOT` with more than one child, fuzzy on a non-text field.
- **`NOT` inverts its child** — a standalone NOT rule matches *dissimilar* records; only use it inside a group alongside positive expressions.
- **Pulled rules are canonicalized** — same-field EQUALS collapses to a simpleExpression, omitted tolerance becomes `0.0`, `4` → `4.0`. Re-pull after create before editing.
- **Tune with `datahub-golden-record.sh match`** — see § Match request for its diagnostic payload.
## Tags
A tag classifies golden records by a business rule. A model's tags are optional (`<mdm:tags/>` is valid); the source `sendFilter` below can reference a tag by name to gate a source's outbound channel. Each `<mdm:tag>` in the top-level `<mdm:tags>` block wraps a `<mdm:businessRule>` of keyed inputs + nested conditions:
```xml
<mdm:tags>
<mdm:tag name="Customer">
<mdm:businessRule name="Customer">
<mdm:inputs>
<mdm:input key="1" alias="Stage" fieldUniqueId="STAGE" type="Field"/>
<mdm:input key="2" alias="Category" fieldUniqueId="CATEGORY" type="Field"/>
</mdm:inputs>
<mdm:conditions topLevelOperator="OR">
<mdm:conditionGroup operator="AND">
<mdm:condition operator="EQUAL">
<mdm:firstInput type="Field" key="1"/>
<mdm:secondInput type="Static" value="Active" key="0"/>
</mdm:condition>
<mdm:condition operator="IS_NOT_EMPTY">
<mdm:firstInput type="Field" key="2"/>
</mdm:condition>
</mdm:conditionGroup>
</mdm:conditions>
</mdm:businessRule>
</mdm:tag>
</mdm:tags>
```
- **Omit `id` on create** (platform generates a GUID; `pull` to capture it); a supplied `id` is honored on update. Empty `<mdm:tags/>` removes all tags.
- Conditions reference inputs by `key` (need not be contiguous); `<mdm:conditionGroup operator="AND|OR">` nests arbitrarily under `<mdm:conditions topLevelOperator="AND|OR">`. Binary operators need `<mdm:secondInput>` (`type="Field" key="N"` or `type="Static" value="..." key="0"`); empty-checks take `firstInput` only.
- Operators: `EQUAL`, `NOT_EQUAL`, `LESS_THAN`, `GREATER_THAN`, `LESS_OR_EQUAL`, `GREATER_OR_EQUAL`, `CONTAINS`, `NOT_CONTAINS`, `STARTS_WITH`, `IS_EMPTY`, `IS_NOT_EMPTY`. Note the short `*_OR_EQUAL` (not `*_THAN_OR_EQUAL`) and `NOT_CONTAINS` (not `DOES_NOT_CONTAIN`).
### Outbound gating — `sendFilter`
A source's `<mdm:outbound>` references a tag by **`name`** to gate which records publish on its channel:
```xml
<mdm:sendFilter scope="All">
<mdm:tags><mdm:tag name="Customer"/></mdm:tags>
</mdm:sendFilter>
```
- `scope` is `All` or `Creates`. Multiple tags allowed; `id` is optional (only `name` is required).
- The `name` must resolve to a defined tag (else HTTP 400 "… does not exist in the model") — define the tag first.
### Tag input functions
A tag input can be a function that transforms a field. The input wraps `<mdm:function>`, which maps fields as parameters and declares its outputs:
```xml
<mdm:input key="3" alias="String Split" type="Function">
<mdm:function type="StringSplit" splitBy="Delimiter" delimiter="@">
<mdm:inputs><mdm:input name="Original String" uniqueId="EMAIL"/></mdm:inputs>
<mdm:outputs><mdm:output name="out1"/><mdm:output name="out2"/></mdm:outputs>
</mdm:function>
</mdm:input>
```
- Each function `<mdm:input>` `uniqueId` must be a non-empty, valid field `uniqueId`; `uniqueId=""` is rejected. Multi-output functions declare `<mdm:outputs>` with named `<mdm:output>` children.
- `type` tokens differ from the UI label and aren't derivable from it (e.g. *Right Character Trim* is `RightTrim`). By category:
- **String:** `LeftTrim`, `RightTrim`, `WhiteSpaceTrim`, `StringAppend`, `StringPrepend`, `StringConcat` (`delimiter`, `fixToLength`), `StringReplace`, `StringRemove`, `StringToLower`, `StringToUpper`, `StringSplit` (`splitBy`, `delimiter`)
- **Numeric:** `MathAbsoluteValue`, `MathAdd`, `MathDivide`, `MathCeiling`, `MathFloor`, `MathSetPrecision`, `NumberFormat`, `RunningTotal`, `Sum`, `Count`, `LineItemIncrement`, `SequentialValue`
- **Date:** `DateFormat`, `GetCurrentDate` (no inputs)
- A condition tests a single-output function's result with a bare reference — `<mdm:firstInput type="Function" key="N"/>`, no output attribute. A `StringSplit` output is testable only when it is the function's **only** declared output — reference it with `outputFieldName="<output name>"`; with two or more declared outputs the condition is rejected on `update`. To test multiple segments, use one single-output `StringSplit` input per segment.
**Platform API known issues — halt and raise with the user; never work around silently:**
If this instruction remains in the skill the defect has not yet been resolved and this section remains critical. Boomi is aware of these issues and a fix is in progress; no support ticket is needed. You do not need to proactively raise them to the user unless their project or model is actively affected.
- The API accepts both `MathSubtract` and `MathMultiply` but stores and executes them incorrectly — a rule authored as one function can be stored as a different one. **Never** author these.
- For the same reason, treat `MathSubtract` and `LeftTrim` tag functions in any pulled model as suspect, even when UI-authored.
- A pulled model may contain a condition on a multi-output `StringSplit`. Pushing that model back is rejected. Such tags must be edited in the UI, or restructured to single-output `StringSplit` inputs — never delete or restructure the user's condition without their explicit sign-off.
These configurations are niche, but if you encounter any of the above situations — STOP and raise it with the user.
## Data quality steps
`<mdm:dataQualitySteps>` holds `<mdm:step>` children that gate ingestion. A failing entity quarantines with `cause=ENRICH_ERROR` (see § Quarantine) instead of becoming a golden record. Steps are additive and ordered.
`type` takes exactly two values; a step carrying neither is an ordinary quality service:
| Kind | `type` | Authorable through the API |
|---|---|---|
| Business rule | `BUSINESS_RULE` | Yes — rule lives in the model XML, reusing § Tags' `<mdm:businessRule>` structure plus `<mdm:errorMessage>` |
| Integration process call | `PROCESS` | Yes — the process must be a deployed Hub listener |
| Ordinary — D&B or Loqate quality service | *absent* | No — UI only |
**A model carrying an ordinary step cannot be updated through the API at all.** Any update submitting that step is rejected (`Missing required properties for data quality step <name>`), so every edit to such a model — fields, sources, match rules, tags — has to happen in the UI. Run `quality-steps` before planning a model edit; a `<mdm:step>` with no `type` means stop and route the user to the UI.
**Deploy activates a step, not publish.** A step has no effect until its model version is republished *and* redeployed.
`datahub-model.sh quality-steps` prints the block; `add-quality-step` and `remove-quality-step` edit it. Writes always produce a draft, so every edit after the first needs `--draft`.
Full shapes and per-kind failure modes: `references/data_quality_step.md`.
## End-dating and restore
Soft deletion: the record leaves the active set but keeps fields, history, and source links, and can be restored. Purge (permanent) is out of scope.
**`query` returns active records only** — an end-dated record yields `<RecordQueryResponse resultCount="0" totalCount="0"/>`, identical to a filter matching nothing. `totalCount` counts active only. Use `query-enddated` (same `RecordQueryRequest` body). `get`, `meta`, `history`, `get-by-source` still return end-dated records in full.
| Mechanism | Command | `history` `enddatesource` |
|---|---|---|
| Direct | `enddate` / `enddate-bulk` | `*MDM*` |
| Source-driven | `update` batch, `op="DELETE"` on entity root | contributing source id |
`enddate-bulk` takes `<RecordEndDateRequest>` in two forms that behave differently:
| Form | Status | Body |
|---|---|---|
| ≤100 `<recordId>` | 200 | one `<result>` per record; `<message>` only when `<success>false</success>` |
| `<filter>` | 202 | **empty** — applied asynchronously, seconds later |
Over 100 ids → HTTP 400 `Cannot end-date more than 100 records at one time.`; the count is checked before the ids are validated, and unknown ids fail per-record rather than failing the batch. **The filter form's 202 is acceptance, not completion** — it returns nothing to confirm against, and acceptance does not guarantee application. Always verify with `query` / `query-enddated`.
**Source-driven end-dating is one-way for the source.** Resending the entity returns HTTP 202 and quarantines `RECORD_ALREADY_ENDDATED` (`approve` → HTTP 400). A pipeline that doesn't poll quarantine looks healthy while dropping every later record for that entity. `restore` reverses the end-date but does not resolve the entry — it stays ACTIVE until the source resends, and that resend then incorporates normally (`<resolution>INCORPORATE_SUCCESS</resolution>`).
`op="DELETE"` is positional — on the **entity root** it end-dates the record and skips field validation (`<id>` alone is accepted despite `required="true"` fields); on a collection **item** element (the `fieldGroup` name, e.g. `<phone>`) it removes just that item; on the **`collectionTag`** wrapper (e.g. `<phones>`) it is rejected — `PARSE_FAILURE`, `Attribute 'op' is not allowed to appear in element '…'`.
**Restore is keyed on the source entity, not the record id** — `restore --source <source-id> <entity-id>`, both from `meta`'s `<link>`. Empty body on success. Record id, `createdDate`, and `establishedDate` survive; restore adds a history version rather than rewriting the end-dated one.
### End-date timestamp: two formats
| Surface | Attribute | Example |
|---|---|---|
| `meta` | `endDate` | `2026-08-18T17:17:17Z` (ISO-8601 UTC) |
| `get`, `history`, `get-by-source` | `enddate` | `08-18-2026T12:17:17.000-0500` (MM-DD-YYYY, local offset) |
Names differ by casing alone. The `get` form has a `T` but is month-first, not ISO-8601 — misparses silently when the day is ≤12. Absence of the attribute is the state flag; restore removes it, never blanks it. Origin timestamp is `startdate` in `history`, `createddate` in `get`, `createdDate` in `query`/`meta`.
**Repository summary counters never recover.** `datahub-repository.sh get <repository-id> --universe <id>` decrements on an end-date but does not increment on a restore, so the error accumulates one record per cycle and does not self-correct. `query` + `query-enddated` are authoritative.
## Quarantine
Failed ingests do not enter golden state. They land in quarantine with one of these causes:
| Cause | When it fires | Resolution |
|---|---|---|
| `PARSE_FAILURE` | XSD schema mismatch — wrong field names (`uniqueId` instead of field `name`), missing field-group wrapper, unknown elements | `delete`; fix payload, resubmit |
| `FIELD_FORMAT_ERROR` | Value not in an `ENUMERATION`'s list, malformed `DATETIME`/`FLOAT`/`INTEGER`, etc. | `delete`; fix payload, resubmit |
| `REQUIRED_FIELD` | Missing `required="true"` fields | `delete`; fix payload, resubmit |
| `REFERENCE_UNKNOWN` | A `REFERENCE` value doesn't resolve (when `incomingReferenceIntegrity="true"`) | `delete`; fix payload, resubmit |
| `POSSIBLE_DUPLICATE` | Match rules flagged a possible duplicate of an existing golden record | `reject` — discards the record, no merge |
| `ENRICH_ERROR` | An entity failed a data quality step (see § Data quality steps) | `delete`; fix the payload or rule, resend from source. Not approvable or rejectable. UI-only Retry / Ignore exist while the deployed version is unchanged |
| `RECORD_ALREADY_ENDDATED` | A source re-sent an entity whose golden record is end-dated | `datahub-golden-record.sh restore` (see § End-dating), then resend — restore alone leaves the entry ACTIVE; or `delete` to keep the record end-dated |
| `REQUIRES_END_DATE_APPROVAL` | A source with `endDateApproval required="true"` sent `op="DELETE"` | `approve` — end-dates the record, attributed to that source |
`ENRICH_ERROR`'s `<reason>` is `At data quality step '<step name>': <errorMessage>`.
`approve` only works on entries quarantined by a source's manual-approval settings; against any other cause it returns HTTP 400. Excessive quarantine entries mean the match rules need work, not the runtime.
**`delete` resolves, does not remove.** Returns `<mdm:success>true</mdm:success>` and drops from `type="ACTIVE"`, but `type="ALL"` still lists it as `<resolution>USER_IGNORE</resolution>`. A second `delete` → HTTP 404. Resolution is permanent.
**Newer entries supersede older ones per `sourceEntityId`** (`<resolution>SUPERSEDED</resolution>`); only the newest stays ACTIVE.
**`endDate` on a quarantine entry means "resolved at"**, not soft-deleted.
## Size limits
A model has two byte budgets, both deployment-blocking (not warnings) and both excluding the id field, reference fields, and repeatable (collection) fields:
- **Total model size**: ~64 KB
- **Single row size**: ~8 KB
UTF8MB4 encoding makes STRING fields expensive (4 bytes/char), and each ENUMERATION field costs ~1 KB toward the model budget. Repeatable fields move to child tables, so they don't count toward either budget. Plan for these before publishing.
## `<skill-path>` resolution
- Take the absolute path of this SKILL.md and drop `/SKILL.md`. That is `<skill-path>`.
- Verify by running `bash <skill-path>/scripts/datahub-env-check.sh` from a workspace with `.env`.
- Treat `<skill-path>` as a fixed value for the session.
## References
```
references/
└── data_quality_step.md # Use when: authoring, inspecting, or troubleshooting a model's data quality steps — business rule shape, quality-service and Integration process kinds, activation, ENRICH_ERROR triage
```
## Scripts inventory
All scripts support `--help` (or run with no args) for usage. They emit text (JSON or XML) to stdout and errors to stderr.
**Pipeline discipline.** Pipe script output only to standard text filters (`head`, `tail`, `wc`, `grep`). Do **not** pipe to `python3 -c`, `jq -r`, `awk`, or other interpreters with inline code — some agent harnesses treat each piped executable as a separate trust boundary and prompt for approval per pipe, defeating the point of allowlisting the scripts. If you need a transformation an existing script doesn't provide, surface the request rather than work around it inline — it'll be added to the appropriate CLI tool.
**Temp files stay in the project tree.** When you need a scratch file (XML query body, batch payload, etc.) to pass to a script, write it under `active-development/feedback/` (following bc-integration's working-files paradigm) — not `/tmp/`. The harness prompts for approval on writes outside the project tree, including `/tmp/`.
**Use the Write tool, not bash heredocs.** Create the file with a separate **Write** tool call, then invoke the script with a separate **Bash** call. Do not combine `cat > file <<EOF ... EOF; bash <script> file` into one compound bash command — the harness's static analysis flags heredoc patterns (unquoted delimiters that allow shell expansion, multi-line compound blocks the parser can't fully reason about) and will prompt for approval. Two separate tool calls avoid the prompt entirely.
- `scripts/datahub-common.sh` — sourced helper library.
- `scripts/datahub-env-check.sh` — verify `.env` and reach the Platform API.
- `scripts/datahub-model.sh` — `list | get | pull | quality-steps | add-quality-step | remove-quality-step | create | update | delete | publish`
- `scripts/datahub-source.sh` — `list | get | pull | status | enable-initial-load | finish-initial-load | create | update | delete`
- `scripts/datahub-repository.sh` — `list | get <repository-id> [--universe <id>] | status | clouds | create` (`--universe` scopes the response to one universe's summary within the repo)
- `scripts/datahub-deployment.sh` — `deploy | undeploy | status | list`
- `scripts/datahub-quarantine.sh` — `query | get | approve | reject | delete`
- `scripts/datahub-golden-record.sh` — `query | query-enddated | get | history | meta | match | update | enddate | enddate-bulk | restore | unlink | get-by-source`
- `scripts/datahub-connection.sh` — `bootstrap <rest|connector>` (creates a connection wired to this workspace's Data Hub creds, for use in integration processes). The two kinds are not interchangeable; `--help` covers which to pick and the `/mdm/` path prefix the `rest` kind needs.
Repository API sub-commands take `--universe <id>` and read `DATAHUB_REPO_*` from `.env`.
## Sample payloads
These are illustrative shapes. For existing artifacts (model, source) use `pull` to fetch the current XML and edit surgically; samples below are starting points for net-new.
### `CreateModelRequest` — `datahub-model.sh create`
For updates, rename the wrapper to `<mdm:UpdateModelRequest>` (same inner content) and use `datahub-model.sh update <id>`.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mdm:CreateModelRequest xmlns:mdm="http://mdm.api.platform.boomi.com/">
<mdm:name>myModelName</mdm:name>
<mdm:fields>
<mdm:field name="recordId" repeatable="false" required="true" type="STRING" uniqueId="RECORDID" maxLength="50"/>
<mdm:field name="recordValue" repeatable="false" required="false" type="STRING" uniqueId="RECORDVAL" maxLength="100"/>
</mdm:fields>
<mdm:sources>
<mdm:source id="Manual" type="Both" allowMultipleLinks="false" default="true">
<mdm:inbound>
<mdm:createApproval required="false"/>
<mdm:updateApproval required="false"/>
<mdm:updateApprovalWithBaseValue>false</mdm:updateApprovalWithBaseValue>
<mdm:endDateApproval required="false"/>
<mdm:earlyChangeDetectionEnabled>false</mdm:earlyChangeDetectionEnabled>
</mdm:inbound>
<mdm:outbound>
<mdm:channelUpdatesFields>All</mdm:channelUpdatesFields>
<mdm:sendCreates>true</mdm:sendCreates>
</mdm:outbound>
</mdm:source>
</mdm:sources>
<mdm:dataQualitySteps/>
<mdm:recordTitle>
<mdm:titleParameters>
<mdm:parameter uniqueId="RECORDID"/>
</mdm:titleParameters>
</mdm:recordTitle>
<mdm:matchRules>
<mdm:matchRule topLevelOperator="AND">
<mdm:simpleExpression>
<mdm:fieldUniqueId>RECORDID</mdm:fieldUniqueId>
</mdm:simpleExpression>
</mdm:matchRule>
</mdm:matchRules>
<mdm:tags/>
</mdm:CreateModelRequest>
```
### `CreateSourceRequest` — `datahub-source.sh create`
Three required: `<mdm:name>` (max 255 chars), `<mdm:sourceId>` (max 50 chars; `A-Z`, `a-z`, `0-9`, `_`, `-`), `<mdm:entityIdUrl>` (UI link template; use `{id}` placeholder, or empty string if none). For updates, rename wrapper to `<mdm:UpdateSourceRequest>`.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mdm:CreateSourceRequest xmlns:mdm="http://mdm.api.platform.boomi.com/">
<mdm:name>Salesforce Production</mdm:name>
<mdm:sourceId>SF</mdm:sourceId>
<mdm:entityIdUrl>https://example.my.salesforce.com/{id}</mdm:entityIdUrl>
</mdm:CreateSourceRequest>
```
### `RecordQueryRequest` — `datahub-golden-record.sh query`
Drop `<filter>` for "return all (up to limit)"; swap `EQUALS` for `CONTAINS` / `GREATER_THAN` / etc.; change `<filter op="AND">` to `OR` for disjunction. Set `includeSourceLinks="true"` on root for per-record source metadata.
Do NOT include `xmlns="..."` on `<RecordQueryRequest>` (HTTP 400 "unable to read message body"). For the first page, omit `offsetToken` entirely — `offsetToken="0"` returns HTTP 400 "unable to parse the provided offset token". Use the token from the previous response's `<offsetToken>` for subsequent pages.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RecordQueryRequest limit="10">
<filter op="AND">
<fieldValue>
<fieldId>RECORDID</fieldId>
<operator>EQUALS</operator>
<value>example-value</value>
</fieldValue>
</filter>
</RecordQueryRequest>
```
### Batch upsert — `datahub-golden-record.sh update`
The `src` attribute is a configured contributing source. `<id>` is the source-side natural key — NOT the Data Hub record ID. Response is `202 Accepted` with `Location` ending in the batch ID. The source must already be update-capable: it completes the initial-load lifecycle (enable → upsert → finish; see § Model lifecycle) before its first upsert, or the batch returns HTTP 400 "not yet marked as one that can send updates".
**Element naming rules:**
- The entity wrapper element (e.g. `<contact>` below) must match the model's normalized `<mdm:name>` (see § Field types — lowercase, non-alphanumerics stripped). Wrong case fails synchronously with HTTP 400 "entity of unknown type".
- Field elements use the field's `name` attribute (camelCase), NOT its `uniqueId` (UPPERCASE).
- Field-group subfields must be wrapped in the group's element (e.g. `<address>...</address>`), not flat.
- `<id>` values are NOT restricted to `A-Z, a-z, 0-9, _, -` ≤50 chars — that constraint applies to source `<mdm:sourceId>` at source-create time. Per-batch `<id>` values are preserved verbatim (`@`, length >50, etc. are accepted).
**Failure modes — HTTP 202 does NOT mean success.**
- HTTP 400 (synchronous): only on wrong entity-wrapper case.
- HTTP 202 + silent quarantine: schema mismatches (wrong field name, missing group wrapper) land as `cause=PARSE_FAILURE`. Missing required fields land as `cause=REQUIRED_FIELD`. Always run `datahub-quarantine.sh query` after a batch to confirm records actually materialized as golden records.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<batch src="MANUAL">
<contact>
<id>1</id>
<name>Bob Smith</name>
<city>Berwyn</city>
</contact>
<contact>
<id>2</id>
<name>Carol Jones</name>
<city>Boston</city>
</contact>
</batch>
```
### `RecordEndDateRequest` — `datahub-golden-record.sh enddate-bulk`
≤100 `<recordId>`, or a `<filter>` using `RecordQueryRequest`'s children — the two forms return differently, see § End-dating and restore. Single record: use `enddate <record-id>` (no body).
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RecordEndDateRequest>
<recordId>aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee</recordId>
</RecordEndDateRequest>
```
### Source-driven end-date — `datahub-golden-record.sh update`
`op="DELETE"` on the entity root end-dates the linked golden record. `<id>` is the source-side entity id (`meta`'s `<link entityId="...">`) and the only required element. HTTP 202. One-way — see § End-dating and restore.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<batch src="MANUAL">
<contact op="DELETE">
<id>1</id>
</contact>
</batch>
```
### Match request — `datahub-golden-record.sh match`
Same `<batch>` body shape as upsert; hits `POST /match` instead of `POST /records`. Preview-only — no writes. Per candidate, a hit returns the matched golden record as `<duplicate>` plus a `matchRule` attribute naming the rule logic that fired; a miss returns only the `<entity>`. Fuzzy hits include `<fuzzyMatchDetails>` with the computed `matchStrength` vs the configured `threshold` — use it to tune tolerance against real data.
### `QuarantineQueryRequest` — `datahub-quarantine.sh query`
Root attributes (all optional): `limit`, `offsetToken`, `includeData` (default `true`), `type` (`ACTIVE` default | `RESOLVED` | `ALL`). Filter children: `<sourceId>`, `<sourceEntityId>`, `<createdDate>`, `<endDate>`, `<cause>`, `<resolution>`, `<field name="" value=""/>`. Empty `<QuarantineQueryRequest/>` returns all ACTIVE entries.
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<QuarantineQueryRequest limit="50" type="ACTIVE">
<filter op="AND">
<cause>POSSIBLE_DUPLICATE</cause>
</filter>
</QuarantineQueryRequest>
```
Referenced files: 14 — file references are metadata, not package contents.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package license
- BSD-2-Clause
- Package author
- Boomi
Package observed Nov 5, 2026.
Technical details
- First seen
- Nov 3, 2026 · 18:00 UTC
- Last seen
- Nov 5, 2026 · 00:00 UTC
- Collection status
- Collected
plugins_6ae9fc38af0481918725c125cf7c1ebd
Download plugin data (JSON)