> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/features/management/namespaces.md).

# Namespaces

A **Namespace** is a logical scoping unit applied across the catalog's taxonomy and resource graph. Every Term and Tag carries an optional namespace; every Data Source and Collector binds to one; Data Entity Groups can inherit a namespace through their parent. Namespaces are the catalog-side analogue of a team-or-domain label — operators use them to keep two teams' "customer" terms from colliding, to keep two teams' data sources visually separated in the catalog, and to scope collector ingestion to a known taxonomy.

This page covers the create / update / soft-delete / list lifecycle, the three permission gates, the cascade-on-delete contract, the case-sensitivity rule, the audit-silence shape Namespace CRUD shares with Owners, and the **four-sister-service auto-create side-door** that mints namespace rows without `NAMESPACE_CREATE`.

## Where to find it

Open the [Management](/features/management.md) page → **Namespaces** tab. The page lists every namespace with a pagination header and a free-text filter; the right-side panel renders the create form. Operators with `NAMESPACE_CREATE` see the **Add namespace** affordance; operators with `NAMESPACE_UPDATE` see an in-place edit on each row; operators with `NAMESPACE_DELETE` see a remove affordance.

## Lifecycle

Five operations exist on the Namespace surface:

| Operation                                                      | Endpoint                      | Permission                                       |
| -------------------------------------------------------------- | ----------------------------- | ------------------------------------------------ |
| List namespaces (paginated, optional `query` substring filter) | `GET /api/namespaces`         | Any authenticated user; anonymous under DISABLED |
| Get a single namespace                                         | `GET /api/namespaces/{id}`    | Any authenticated user; anonymous under DISABLED |
| Create a namespace                                             | `POST /api/namespaces`        | `NAMESPACE_CREATE`                               |
| Update a namespace                                             | `PUT /api/namespaces/{id}`    | `NAMESPACE_UPDATE`                               |
| Soft-delete a namespace                                        | `DELETE /api/namespaces/{id}` | `NAMESPACE_DELETE`                               |

The read endpoints are not gated by a custom-permission entry — the platform's security wiring leaves them at the default `.authenticated()` level. Under `auth.type=DISABLED` they are reachable anonymously (see [DISABLED authentication](/configuration-and-deployment/enable-security/authentication/disabled-authentication.md)).

## Case sensitivity

Namespace names are **case-sensitive**. `finance` and `Finance` are two distinct rows in the `namespace` table — an operator who creates `Finance` and a teammate who creates `finance` end up with two parallel namespaces that share no relations. The list endpoint's `query` parameter applies a case-insensitive substring match for the suggestion list, but resolution against the canonical row is by exact-string match.

Treat namespace names as a controlled vocabulary that benefits from a documented naming convention — `snake_case` and `lowercase-with-hyphens` are both common in deployments we have seen. Drift between case-variants is one of the most common Namespace-directory pollution shapes.

## Soft-delete and reincarnation

The Namespace delete path is a **soft delete** — the `DELETE /api/namespaces/{id}` endpoint sets `deleted_at = NOW()` on the row rather than removing it from the table. The `namespace` table carries a partial-unique index on `name` that scopes uniqueness to rows where `deleted_at IS NULL`; after a soft-delete, the name slot is free and an operator can create a new namespace with the same name. The previously-deleted row is preserved with its original `id`; downstream auditing tables that reference the namespace by id retain their integrity.

The reincarnation pattern is operationally useful for "reset this namespace" workflows — an operator who wants to clear a namespace's history can soft-delete it and immediately create a new namespace with the same name. The new namespace has a different `id`; any prior relations (terms, data sources, collectors) remain attached to the soft-deleted row and do not migrate.

## Cascade-on-delete guard

Namespace delete is blocked when any of four referent tables still reference the namespace. The platform's service layer zips four existence-predicates and rejects the operation if any returns true:

| Referent table                   | Operator-visible referent                        | Where to clear it                                                                                                                    |
| -------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `data_source` (live rows)        | A Data Source registered under this namespace    | Re-assign the source to a different namespace via [Management → Datasources](/features/management.md), or remove the source entirely |
| `collector` (live rows)          | A Collector registered under this namespace      | Re-assign the collector or remove it via [Management → Collectors](/features/management.md)                                          |
| `term` (live rows)               | A Glossary Term scoped to this namespace         | Re-assign or delete the term via the [Business Glossary](/features/data-glossary/business-glossary.md)                               |
| `data_entity` (non-deleted rows) | A live data entity ingested under this namespace | Re-configure the collector's `namespace_name` and re-ingest, or soft-delete the entities                                             |

When any predicate returns true, the platform returns `CascadeDeleteException` (HTTP 400, error code `USR004`) and the namespace remains live. The error message reads "Namespace cannot be deleted: there are still resources attached" — it does not enumerate which of the four classes is blocking the delete. The operator has to walk each referent class themselves.

{% hint style="warning" %}
**The cascade guard is best-effort, not transactional.** The delete path is not wrapped in a transaction, so the four existence-checks and the soft-delete that follows run as separate statements at `READ COMMITTED` with no lock held on the namespace. If a new referent — a data source, collector, term, or ingested data entity scoped to this namespace — is created in the brief window *after* the checks pass and *before* the soft-delete commits, the delete still succeeds and the new referent is left pointing at a soft-deleted namespace. This is a race only under concurrent writes (an operator deleting a namespace at the same moment another operator or a collector ingest attaches something to it); for the common case of a single operator clearing referents by hand it behaves exactly as the table above describes. If you need a hard guarantee, quiesce writes to the namespace before deleting it.
{% endhint %}

## Auto-create side-door

The Namespace surface carries a load-bearing structural quirk: **four sister services on the platform mint Namespace rows through the side-door `NamespaceServiceImpl.getOrCreate(name)`** when their request body carries a non-empty `namespace_name` field. The four callers are:

| Sister service            | Endpoint                                             | Gating permission for the parent operation |
| ------------------------- | ---------------------------------------------------- | ------------------------------------------ |
| Data Source service       | `POST /api/datasources`, `PUT /api/datasources/{id}` | `DATA_SOURCE_CREATE`, `DATA_SOURCE_UPDATE` |
| Term service              | `POST /api/terms`                                    | `TERM_CREATE`                              |
| Collector service         | `POST /api/collectors`                               | `COLLECTOR_CREATE`                         |
| Data Entity Group service | `POST /api/dataentitygroups`                         | `DATA_ENTITY_GROUP_CREATE`                 |

Each of these calls `getOrCreate(name)`, which does `getByName(name).switchIfEmpty(createByName(name))` — the service silently inserts a new Namespace row if the name does not already exist. The caller does **not** need to hold `NAMESPACE_CREATE`; holding any one of the four parent permissions is sufficient to mint a Namespace row that becomes immediately visible to every authenticated user via the catalogue read.

{% hint style="warning" %}
**The four-sister-service side-door collapses `NAMESPACE_CREATE` with four parent permissions.** Operators expecting `NAMESPACE_CREATE` to be the gate for namespace creation — as the [Permissions page](/configuration-and-deployment/enable-security/authorization/permissions.md) describes it — discover the side-door when their namespace directory proliferates with junk rows (typos, organic operator typing of distinct strings for the same intent through one of the four side-door endpoints).

**Mitigation today.** Treat `DATA_SOURCE_CREATE`, `DATA_SOURCE_UPDATE`, `TERM_CREATE`, `COLLECTOR_CREATE`, and `DATA_ENTITY_GROUP_CREATE` as implicitly granting namespace-creation rights when their request bodies carry `namespace_name`. Audit the Namespaces list growth on a cadence — typo-introduced duplicates and case-variants are the most common operator-visible drift shape (see [Case sensitivity](#case-sensitivity) above). If strict separation between Namespace creation and the four sister-service operations is a deployment requirement, treat the `namespace_name` form field as a closed allowlist at the network-perimeter layer rather than relying on platform RBAC.

The side-door is not a candidate for a one-commit upstream fix — closing it would either break the existing collector-ingestion workflow (which currently expects the side-door for first-time namespace bootstrap), or require a finer-grained `NAMESPACE_CREATE_VIA_RESOURCE` permission that adds UX complexity. Both are product-level decisions; the operator-side caveat is the right immediate ship.
{% endhint %}

A related sibling pattern exists on the [Owner](/configuration-and-deployment/enable-security/authorization/owners.md) surface — Owner CRUD has its own side-door cluster through three resource-creation endpoints. The Namespace cluster is the canonical example because four distinct sister services touch it, but the architectural pattern (auto-create on `getOrCreate(name)`-style helpers) is general.

## Activity trail

**Namespace CRUD emits no Activity Feed event.** Two independent facts of the platform's design both block it:

1. **No event type exists.** The Activity Feed's event-type catalogue has no `NAMESPACE_*` member at all — there is simply no type the platform could emit for a namespace create, update, or delete.
2. **The activity table requires a data-entity anchor.** Even if an event type existed, the activity table is schema-anchored to `data_entity_id NOT NULL` — every emitted event must reference a single data entity (see [Activity Feed → Scope](/features/active-platform-features/activity-feed.md#known-caveats)). Namespace lifecycle operations have no data-entity anchor.

Closing one of these alone would not make Namespace CRUD auditable; both would have to change. The same shape applies to Owner CRUD, Role CRUD, Policy CRUD, and every other RBAC and taxonomy mutation; see [Audit trail scope](/configuration-and-deployment/enable-security/audit-trail-scope.md) for the platform-wide audit-coverage matrix and the compensating-controls catalogue.

For compliance regimes that need who-changed-what-when on Namespaces, instrument the audit externally — an API-gateway access log records every authenticated mutation on `/api/namespaces*`; the PostgreSQL WAL via `pgaudit` records the `namespace` row writes. The audit-of-occurrence lives outside the platform until both the missing event type and the schema-tier `data_entity_id NOT NULL` anchor are addressed.

## Where to next

* [Management](/features/management.md) — the parent page; covers the tab-visibility model and the read-collaborative posture across the eight non-Associations Management areas.
* [Permissions](/configuration-and-deployment/enable-security/authorization/permissions.md) — the canonical home for `NAMESPACE_CREATE`, `NAMESPACE_UPDATE`, `NAMESPACE_DELETE` and the four sister-service permissions the side-door collapses against.
* [Activity Feed → Scope](/features/active-platform-features/activity-feed.md#known-caveats) — the canonical home for the audit-silence pattern Namespace CRUD shares with Owners, Roles, and Policies.
* [Audit trail scope](/configuration-and-deployment/enable-security/audit-trail-scope.md) — the compliance-facing summary of what the platform audits today and the external instrumentation patterns for the silent-mutation surfaces.
* [Business Glossary](/features/data-glossary/business-glossary.md), [Manual Object Tagging](/features/data-discovery/tagging.md) — the two taxonomy surfaces that scope themselves to a Namespace; one canonical place to clear referents when preparing for a Namespace delete.
