# Repository

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## POST /repository/trash-restore

**Restore records from trash**

`operationId: RepositoryController_trashRestore`

Brings soft-deleted records back. Only works for records that were soft-deleted — a hard delete or a truncate leaves nothing to restore.

#### Signature

```http
POST /repository/trash-restore (body) -> Restore result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /repository/delete/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

What to restore.

```json
{
  "datatype": "sf_product",
  "ids": [
    "66f1a2b3c4d5e6f708192a3b"
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Restore result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/isunique/{datatype}/{attribute}/{value}/{scopeValue}

**Check whether a value is unique**

`operationId: RepositoryController_isUnique`

Reports whether a value is already taken for an attribute — the check behind "this username is unavailable" on a signup form.

This route is **public**: it can be called without authentication, which means it discloses whether a given email or username exists in the org. Rate-limit any public form built on it.

`scopeValue` narrows the uniqueness check to a subset, for values that need only be unique within a group.

#### Signature

```http
GET /repository/isunique/{datatype}/{attribute}/{value}/{scopeValue} (datatype: string, attribute: string, value: string, scopeValue: string) -> Whether the value is available
```

#### Access

Public — no credentials required.

#### Notes

- Unauthenticated. It confirms the existence of accounts — treat it as an enumeration surface.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /repository/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes |  |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `attribute` | path | string | yes | Field to check. |
| `value` | path | string | yes | Value to test. |
| `scopeValue` | path | string | yes | Restrict uniqueness to a subset. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Whether the value is available |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/collections/fix

**Repair collection definitions**

`operationId: RepositoryController_fixCollection`

Repairs inconsistencies in the org's collection definitions — an administrative maintenance operation, not part of normal use.

It rewrites schema metadata. Run it deliberately, not as part of a routine flow.

#### Signature

```http
POST /repository/collections/fix () -> What was repaired
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Maintenance operation — it modifies schema definitions across the org.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/collections/{name}/{subName}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What was repaired |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/collections/{name}/{subName}

**Get collection definitions**

`operationId: RepositoryController_getCollection`

Returns the collection (datatype) definitions for the org — the schemas that describe what fields each datatype has. Omit `name` to list them all.

This is how a generic client discovers what it can create: read the collection definition, then build a form from its schema.

#### Signature

```http
GET /repository/collections/{name}/{subName} (name: string, subName: string, enriched?: boolean, collectionType?: string) -> Collection definitions
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- `enriched` defaults to `true` here; the `en` flag elsewhere on this controller defaults to false.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/collections/fix`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `name` | path | string | yes | Collection name. Omit to list all. |
| `subName` | path | string | yes | Nested collection name. |
| `enriched` | query | boolean | — | Include resolved schema detail. **Defaults to true**, unlike most enrich flags on this controller. |
| `collectionType` | query | string | — | Filter by collection type. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Collection definitions |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/link/{datatype}/{id}

**A studio link to a record**

`operationId: RepositoryController_recordLink`

A link to a record in the studio, to hand to a person — "here it is" instead of "the id is 6a5b…". Returns `{ url, title, datatype }`; the url opens `/app/collection/{datatype}/{id}`, which shows the record in its own app when it has one, else in the Database app. The title is the person's name, else the record's title, subject, name or email.

#### Signature

```http
GET /repository/link/{datatype}/{id} (datatype: string, id: string) -> `{ url, title, datatype }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Staff only: a customer token gets `403`.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | response not found | No record of that datatype has the given id. | Check the id and datatype. Note the message is the literal string `response not found`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/get/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `id` | path | string | yes | Record `sk`. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ url, title, datatype }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | response not found — No record of that datatype has the given id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/get/{datatype}/{id}

**Get a record by id**

`operationId: RepositoryController_getData`

Fetches one record by its `sk`.

**Mind the overload.** This route and `GET /repository/get/{datatype}/{attribute}/{value}` share the same `get/` prefix and are told apart only by how many segments follow. Three segments (`get/product/abc123`) is a lookup by id; four (`get/product/name/widget`) is a lookup by attribute. Getting the arity wrong silently runs the other query.

#### Signature

```http
GET /repository/get/{datatype}/{id} (datatype: string, id: string) -> The record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- This is one of the few repository reads that raises a real `404` rather than returning null.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | response not found | No record of that datatype has the given id. | Check the id and datatype. Note the message is the literal string `response not found`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/find-any-id/{datatype}/{id}`
- `GET /repository/get/{datatype}/{attribute}/{value}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `id` | path | string | yes | Record `sk`. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | response not found — No record of that datatype has the given id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-any-id/{datatype}/{id}

**Get a record by any identifier**

`operationId: RepositoryController_findOneAnyId`

Resolves a record from whatever identifier you have — the `sk`, the `name`, or another natural key such as a SKU. Use this when the caller holds a slug from a URL rather than a record id.

A record that does not exist comes back as `null` with a `200`, not a `404`.

#### Signature

```http
GET /repository/find-any-id/{datatype}/{id} (datatype: string, id: string, en?: boolean) -> The record, or `null` when nothing matches
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Returns `null` + `200` for a miss — check the body, not the status.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/get/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `id` | path | string | yes | Any identifier — `sk`, `name`, or another natural key. |
| `en` | query | boolean | — | Resolve linked records inline instead of returning bare references. Costs extra queries. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The record, or `null` when nothing matches |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/get/{datatype}/{attribute}/{value}

**Find records by attribute**

`operationId: RepositoryController_findByDatatypeAttribute2`

Lists records of a datatype whose attribute equals a value, paged.

The attribute is resolved **within the data payload**, so pass the bare field name — `name`, not `data.name`. Prefixing it with `data.` produces a double prefix and matches nothing.

Omitting both attribute and value lists the datatype unfiltered. See the note on `GET /repository/get/{datatype}/{id}` about how these two routes are distinguished.

#### Signature

```http
GET /repository/get/{datatype}/{attribute}/{value} (datatype: string, attribute: string, value: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- Attributes are scoped to the data payload automatically — `data.name` will not match.
- Matching is exact equality, not a partial or fuzzy match. Use `search` for that.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/find-by-attribute/{datatype}/{attribute}/{attrValue}`
- `GET /repository/search/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `attribute` | path | string | yes | Field to match, **without** a `data.` prefix. |
| `value` | path | string | yes | Value to match. |
| `l` | query | string | — | Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth. |
| `en` | query | boolean | — | Resolve linked records inline instead of returning bare references. Costs extra queries. |
| `p` | query | integer | — | Page number, 1-based. |
| `ps` | query | integer | — | Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans. |
| `s` | query | string | — | Field to sort by. |
| `st` | query | "asc" \| "desc" | — | Sort direction. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-by-attribute/{datatype}/{attribute}/{attrValue}

**Find records by attribute**

`operationId: RepositoryController_findByDatatypeAttribute`

The explicit form of the attribute lookup, without the arity overload of the `get/` routes — prefer this one in new work.

It additionally accepts an `attributes` map for matching several fields at once, and a `keyword` for a text filter alongside the attribute match.

As with the `get/` form, attributes are resolved inside the data payload: pass `name`, not `data.name`.

#### Signature

```http
GET /repository/find-by-attribute/{datatype}/{attribute}/{attrValue} (datatype: string, attribute: string, attrValue: string, attributes?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- Preferred over `GET /repository/get/{datatype}/{attribute}/{value}` — same behaviour, unambiguous route.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/get/{datatype}/{attribute}/{value}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `attribute` | path | string | yes | Field to match, without a `data.` prefix. |
| `attrValue` | path | string | yes | Value to match. |
| `l` | query | string | — | Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth. |
| `keyword` | query | string | — | Text filter applied alongside the attribute match. |
| `en` | query | boolean | — | Resolve linked records inline instead of returning bare references. Costs extra queries. |
| `attributes` | query | string | — | Additional field/value pairs to match, as a map. |
| `p` | query | integer | — | Page number, 1-based. |
| `ps` | query | integer | — | Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans. |
| `s` | query | string | — | Field to sort by. |
| `st` | query | "asc" \| "desc" | — | Sort direction. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/lookup-code

**Resolve a scanned code**

`operationId: RepositoryController_lookupCode`

Resolves a scanned string — a barcode, receipt number, ticket code — to the record it identifies, returning `{ type, match }`.

It answers *what this code is*, not what to do about it: routing the user to the right screen is the client's decision. Narrow the search with `types` when the scanner's context already limits what a code could be.

This route is **public** — anyone can probe codes against it.

#### Signature

```http
POST /repository/lookup-code (body) -> What the code resolved to
```

#### Access

Public — no credentials required.

#### Notes

- Unauthenticated — do not rely on it to keep record existence private.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `GET /repository/find-any-id/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The scanned code.

```json
{
  "code": "A7K2M9QX4"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | What the code resolved to |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-timed-data/{startDate}/{endDate}

**Find records in a date range**

`operationId: RepositoryController_findTimedData`

Returns records falling inside a date range, across one or more datatypes. The read behind a calendar or an activity feed that spans several collections at once.

Both dates are optional path segments; `datatypes` narrows which collections are searched.

#### Signature

```http
GET /repository/find-timed-data/{startDate}/{endDate} (startDate: string, endDate: string, datatypes?: string, enrich?: boolean) -> Records in the range
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- Omitting both dates returns an unbounded range — always supply at least one on a large org.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/search/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `startDate` | path | string | yes | Start of the range. |
| `endDate` | path | string | yes | End of the range. |
| `enrich` | query | boolean | — | Resolve linked records inline. |
| `datatypes` | query | string | — | Comma-separated collections to search. Omit to search the defaults. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Records in the range |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-page-data/{datatype}/{dataId}

**Query records (alias)**

`operationId: RepositoryController_query`

An alias for `GET /repository/find/{datatype}/{dataId}` — the same handler is mounted at both paths. Prefer `find/`.

#### Signature

```http
GET /repository/find-page-data/{datatype}/{dataId} (datatype: string, dataId: string, attributes?: string, categories?: string, tags?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string) -> A page of matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/find/{datatype}/{dataId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |
| `dataId` | path | string | yes | Restrict to one record. |
| `l` | query | string | — | Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth. |
| `keyword` | query | string | — |  |
| `en` | query | boolean | yes |  |
| `attributes` | query | string | — |  |
| `categories` | query | string | — |  |
| `tags` | query | string | — |  |
| `p` | query | integer | — | Page number, 1-based. |
| `ps` | query | integer | — | Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans. |
| `s` | query | string | — | Field to sort by. |
| `st` | query | "asc" \| "desc" | — | Sort direction. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find/{datatype}/{dataId}

**Query records**

`operationId: RepositoryController_query`

The richest read on the controller: filters by attributes, categories, tags and keyword together, with paging and sorting.

It is mounted at two paths — `find/...` and `find-page-data/...` — which behave identically. `find/` is the one to use; the other name is historical.

#### Signature

```http
GET /repository/find/{datatype}/{dataId} (datatype: string, dataId: string, attributes?: string, categories?: string, tags?: string, keyword?: string, p?: integer, ps?: integer, l?: string, s?: string, st?: string, en?: boolean) -> A page of matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/find-page-data/{datatype}/{dataId}`
- `POST /repository/find/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |
| `dataId` | path | string | yes | Restrict to one record. |
| `l` | query | string | — | Cursor for keyset pagination: the sort value of the last record from the previous page. Pass it to continue past that record instead of skipping with `p`, which stays fast at any depth. |
| `keyword` | query | string | — | Text filter. |
| `en` | query | boolean | — | Resolve linked records inline. |
| `attributes` | query | string | — | Field/value pairs to match. |
| `categories` | query | string | — | Categories to match. Repeat for several. |
| `tags` | query | string | — | Tags to match. Repeat for several. |
| `p` | query | integer | — | Page number, 1-based. |
| `ps` | query | integer | — | Page size — how many records to return. Large values are slower; prefer cursor paging via `l` for deep scans. |
| `s` | query | string | — | Field to sort by. |
| `st` | query | "asc" \| "desc" | — | Sort direction. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/category/{datatype}

**Get categories for a datatype**

`operationId: RepositoryController_getDataTypeCategories`

The categories in use for a datatype. Omit `datatype` for categories across everything.

#### Signature

```http
GET /repository/category/{datatype} (datatype: string) -> Categories
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/tag`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | Collection to read categories for. Omit for all. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Categories |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/tag

**List tags**

`operationId: RepositoryController_getTags`

The tags defined for the org, optionally filtered by keyword — the source for a tag autocomplete.

#### Signature

```http
GET /repository/tag (keyword?: string) -> Tags
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/tag`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `keyword` | query | string | — | Filter tags by text. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Tags |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/tag

**Create a tag**

`operationId: RepositoryController_addTag`

Adds a tag to the org's tag vocabulary so it can be applied to records and offered in autocomplete.

#### Signature

```http
POST /repository/tag (body) -> The created tag
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/tag`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The tag to create.

```json
{
  "name": "summer-sale",
  "title": "Summer Sale"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created tag |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-related/{datatype}/{anyId}

**Find related records**

`operationId: RepositoryController_findRelated`

Returns records linked to a given one, across datatypes. Resolves by any identifier, not just the `sk`.

#### Signature

```http
GET /repository/find-related/{datatype}/{anyId} (datatype: string, anyId: string) -> Related records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/find/{datatype}/{dataId}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |
| `anyId` | path | string | yes | Any identifier for the source record. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Related records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/find/{datatype}

**Find records of any data type — look up a user (staff, colleague) by email or name and get their phone, or customers, orders, anything**

`operationId: RepositoryController_findPost`

Look up any data the caller can read: `datatype` is the collection. The org’s people are `user` (staff — name in `data.firstName`/`data.lastName`, `data.title`, `data.email`, `data.phone`); customers are `customer`. Body `{ query, options }`: `query` is a filter on the record (fields under `data.`), `options` `{ page, pageSize, sort }`. Example — a colleague’s phone number: `POST /repository/find/user` with `{ "query": { "data.email": "someone@company.com" } }`. Also mounted at `POST /repository/query/{datatype}`.

#### Signature

```http
POST /repository/find/{datatype} (datatype: string, body) -> Matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/query/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |

### Request body

The filter and paging.

```json
{
  "query": {
    "data.email": "someone@company.com"
  },
  "options": {
    "page": 1,
    "pageSize": 20
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/query/{datatype}

**Query records with a body (alias)**

`operationId: RepositoryController_findPost`

An alias for `POST /repository/find/{datatype}` — the same handler on both paths.

#### Signature

```http
POST /repository/query/{datatype} (datatype: string, body) -> Matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/find/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |

### Request body

The query to run.

```json
{
  "query": {
    "data.status": "active"
  },
  "options": {
    "page": 1,
    "pageSize": 50
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/find-experts

**Find experts**

`operationId: RepositoryController_findExperts`

Searches for experts by keyword — a domain-specific lookup that happens to live on the repository controller.

#### Signature

```http
GET /repository/find-experts (keyword?: string) -> Matching experts
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `keyword` | query | string | yes | Search text. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Matching experts |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/search/{datatype}

**Search records**

`operationId: RepositoryController_searchGet`

Full-text search across a datatype, with an optional structured `query` for additional filtering. Omit `datatype` to search across everything.

`query` is a JSON string parsed leniently — a malformed value is treated as absent rather than rejected, so a syntax error silently widens the search instead of failing.

#### Signature

```http
GET /repository/search/{datatype} (datatype: string, keyword?: string, query?: string, p?: integer, ps?: integer) -> Matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- A malformed `query` is silently dropped. Check the result count if a filter appears to have no effect.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/search/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `keyword` | query | string | — | Text to search for. |
| `query` | query | string | — | Structured filter as a JSON string. Parsed leniently — malformed JSON is ignored. |
| `datatype` | path | string | yes | Collection to search. Omit to search across all. |
| `p` | query | integer | — |  |
| `ps` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/search/{datatype}

**Search records with a body**

`operationId: RepositoryController_searchPost`

The POST form of search, for queries too large or too structured to sit in a query string. `query` is a real object here rather than a JSON string, so it is not silently dropped when malformed.

#### Signature

```http
POST /repository/search/{datatype} (datatype: string, body) -> Matching records
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- Prefer this over the GET form for anything beyond a simple keyword — the structured query is validated rather than silently ignored.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /repository/search/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |

### Request body

The search to run.

```json
{
  "keyword": "cola",
  "query": {
    "data.status": "active"
  },
  "options": {
    "page": 1,
    "pageSize": 50
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Matching records |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/update/{id}

**Update a record**

`operationId: RepositoryController_updateHandler`

Replaces a record's data. The datatype is inferred from the record itself, so it is not a path parameter here — unlike almost every other write on this controller.

Use `update-partial` to change a few fields without sending the whole payload.

#### Signature

```http
POST /repository/update/{id} (id: string, body) -> The updated record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:update`.

#### Notes

- This writes the payload you send — fields you omit can be lost. Prefer `update-partial` for targeted edits.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/update-partial/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record `sk`. |

### Request body

The record to write.

```json
{
  "data": {
    "sku": "DRK-COLA-330",
    "title": "Cola 330ml",
    "price": 12.5
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/update-partial/{datatype}/{id}

**Partially update a record**

`operationId: RepositoryController_updatePartialHandler`

Writes only the fields you send, leaving the rest of the record untouched. This is the safe way to edit one part of a large record, and the way to avoid two concurrent editors overwriting each other's unrelated fields.

Paths are dotted and absolute within the record — `data.children`, not `children`.

#### Signature

```http
POST /repository/update-partial/{datatype}/{id} (datatype: string, id: string, body) -> The updated record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:update`.

#### Notes

- Paths include the `data.` prefix here, unlike the attribute-lookup endpoints which strip it.
- Send dotted paths, never a nested `data` object. `{"data": {...}}` REPLACES the entire `data` field — every key you did not include, such as images, price or attributes, is dropped. Snapshot the record with `find` before any bulk write.
- Send the `version` you read from `get`. A stale version is rejected instead of silently overwriting a concurrent editor.
- Read with `get/{datatype}/{id}` before editing. `find` returns a partial projection, so a record edited from a `find` result can lose fields you never saw.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/update/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `id` | path | string | yes | Record `sk`. |

### Request body

The fields to write, keyed by dotted path.

```json
{
  "data.price": 12.5
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /repository/create-extended

**Create a record with extensions**

`operationId: RepositoryController_createPageWit`

Creates a record along with its related sub-records in one call — the composite form of create, for a record that is not useful on its own.

#### Signature

```http
PUT /repository/create-extended (body) -> The created record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The record and its extensions.

```json
{
  "datatype": "sf_product",
  "data": {
    "sku": "DRK-COLA-330"
  },
  "extensions": []
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The created record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /repository/create

**Create a record**

`operationId: RepositoryController_createPutHandler`

Identical to `POST /repository/create` — the same handler is mounted on both verbs. Use whichever your client prefers.

#### Signature

```http
PUT /repository/create (body) -> The created record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The record to create.

```json
{
  "datatype": "sf_product",
  "isNew": true,
  "data": {
    "sku": "DRK-COLA-330",
    "title": "Cola 330ml",
    "price": 12
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The created record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/create

**Create a record**

`operationId: RepositoryController_createHandler`

Creates a record of any datatype. `datatype` names the collection and `data` carries the domain fields.

The same handler is mounted on both `POST` and `PUT` — they behave identically.

#### Signature

```http
POST /repository/create (body) -> The created record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.

#### Notes

- `isNew: true` is mandatory. Without it the request is rejected with 400 — the record is only accepted as a creation when it says it is new.
- Author is taken from the authenticated user, not the body.
- Field validation depends on the datatype's schema — an unknown datatype is created without one.
- A top-level `name` is truncated at 100 characters; the full title belongs in `data`.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `PUT /repository/create`
- `POST /repository/update/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The record to create.

```json
{
  "datatype": "sf_product",
  "isNew": true,
  "data": {
    "sku": "DRK-COLA-330",
    "title": "Cola 330ml",
    "price": 12
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The result of the create operation |
| `201` | The created record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /repository/clone

**Clone a record**

`operationId: RepositoryController_cloneHandler`

Builds a copy of a record and **returns it without saving**. The clone exists only in the response — post it to `POST /repository/create` to persist it.

That makes it a template-builder rather than a duplicate operation: adjust the returned copy before creating it.

#### Signature

```http
PUT /repository/clone (body) -> An unsaved copy of the record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:create`.

#### Notes

- **Nothing is persisted.** The clone is discarded unless you create it explicitly.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | CLONE_FAILED | Clone failed | The source record does not exist. | Check `datatype` and `uid`. Note this is a `400`, not a `404`. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Which record to clone.

```json
{
  "datatype": "sf_product",
  "uid": "66f1a2b3c4d5e6f708192a3b"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | An unsaved copy of the record |
| `400` | Clone failed — The source record does not exist. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /repository/delete/{datatype}/{id}

**Delete a record**

`operationId: RepositoryController_deleteData`

Deletes one record.

**`customer` and `user` are refused here.** Deleting an account has to notify the person, unwind their sessions and leave an audit trail, and a generic row delete does none of that — the account holder would find out when they next tried to sign in. The error names the domain endpoint to use instead.

#### Signature

```http
DELETE /repository/delete/{datatype}/{id} (datatype: string, id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |
| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |

Plus the standard platform errors: `401`, `429`, `500`.

#### See also

- `POST /repository/delete/{datatype}`
- `POST /repository/trash-restore`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |
| `id` | path | string | yes | Record `sk`. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `400` | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/delete/{datatype}

**Delete several records**

`operationId: RepositoryController_deleteBulkData`

Deletes many records of one datatype in a single call. The same `customer` / `user` guard applies as for the single delete.

#### Signature

```http
POST /repository/delete/{datatype} (datatype: string, body) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.

#### Notes

- The body is a bare array of ids, not an object wrapping one.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |
| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |

Plus the standard platform errors: `401`, `429`, `500`.

#### See also

- `DELETE /repository/delete/{datatype}/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |

### Request body

The record ids to delete, as a bare array.

```json
[
  "66f1a2b3c4d5e6f708192a3b",
  "66f1a2b3c4d5e6f708192a3c"
]
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Deletion result |
| `400` | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/delete-query/{datatype}

**Delete every record a query matches**

`operationId: RepositoryController_deleteByQuery`

The list screen's "Select all N → Delete": finds every record of the datatype matching `query` (a Mongo filter on the stored shape, e.g. `{ "data.status": "archived" }`) and deletes each one the same way the bulk delete does — cascades, trash, the `customer` / `user` refusal and the root-group guard. Answers with the counts.

**An empty or missing `query` matches every record of the datatype** — it is then equivalent to a truncate.

#### Signature

```http
POST /repository/delete-query/{datatype} (datatype: string, body) -> { matched, deleted, failed }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:delete`.

#### Notes

- Send a real filter. `{}` deletes the whole collection.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | USE_DOMAIN_API | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. | The datatype is `customer` or `user`. | Use the domain endpoint the error names — `DELETE user/customer/profile/{emailOrUsername}` or `DELETE user/user/delete/{emailOrUsername}`. The error body carries `code`, `datatype` and `endpoint`. |
| `403` | ROOT_ORG_REQUIRED | Only the root organization can delete a root group. Blocked: RootAdmin. | A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. | Root groups are managed from the root org only. |

Plus the standard platform errors: `401`, `429`, `500`.

#### See also

- `POST /repository/delete/{datatype}`
- `DELETE /repository/truncate/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |

### Request body

The filter.

```json
{
  "query": {
    "data.status": "archived"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { matched, deleted, failed } |
| `400` | '<datatype>' cannot be deleted through the repository API — use <endpoint>, which notifies the account holder and records the removal. — The datatype is `customer` or `user`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only the root organization can delete a root group. Blocked: RootAdmin. — A `usergroup` or `userrole` record being deleted grants a root role, and the caller's org is not the root org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "matched": 12,
  "deleted": 12,
  "failed": []
}
```

## DELETE /repository/truncate/{datatype}

**Truncate a collection**

`operationId: RepositoryController_truncateCollection`

Deletes **every record** of a datatype in the organization. There is no filter and no undo.

Root administrators only, and API only: the Studio no longer offers it. Datatypes that must be removed through their own domain API (such as `customer` and `user`) are refused with `USE_DOMAIN_API`, the same rule the single and bulk deletes apply. Every call is logged with who made it.

#### Signature

```http
DELETE /repository/truncate/{datatype} (datatype: string) -> Truncation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `RootAdmin`.

#### Notes

- Irreversible and unfiltered — it empties the whole collection for the org.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | USE_DOMAIN_API | This datatype is removed through its own API | The datatype (e.g. customer, user) has a domain delete endpoint. | Use that endpoint instead. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/delete/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Truncation result |
| `400` | This datatype is removed through its own API — The datatype (e.g. customer, user) has a domain delete endpoint. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/aggregate/{datatype}

**Run an aggregation**

`operationId: RepositoryController_aggregate`

Runs an aggregation pipeline over a datatype and returns the result — the escape hatch for reporting queries the list endpoints cannot express.

The body is passed to the storage engine, so it is powerful and unvalidated: an expensive pipeline runs exactly as written. Scope aggregations tightly on large collections.

#### Signature

```http
POST /repository/aggregate/{datatype} (datatype: string, body) -> The aggregation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`). Required role(s): `content:read`.

#### Notes

- Unvalidated and unbounded — an unscoped pipeline can scan an entire collection.
- Results are org-scoped, but the pipeline itself is otherwise passed through as given.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/search/{datatype}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on, e.g. `sf_product`, `customer`, `category`. Determines the shape of `data`. |

### Request body

The aggregation pipeline.

```json
[
  {
    "$match": {
      "data.status": "active"
    }
  },
  {
    "$group": {
      "_id": "$data.brand",
      "count": {
        "$sum": 1
      }
    }
  }
]
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The aggregation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /repository/preview/{orgId}/{site}/{page}

**Preview a page**

`operationId: RepositoryController_previewPage`

Renders a preview of a site page. Note the org is a **path parameter** here rather than the `orgid` header, because a preview link has to carry everything it needs.

#### Signature

```http
GET /repository/preview/{orgId}/{site}/{page} (orgId: string, site: string, page: string) -> The rendered preview
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- The only repository route that takes the org from the path.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes |  |
| `orgId` | path | string | yes | Organization id, in the path rather than the header. |
| `site` | path | string | yes | Site name. |
| `page` | path | string | yes | Page name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rendered preview |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/bulk-create

**Create many records**

`operationId: RepositoryController_bulkCreate`

Creates many records in one call — the import path for a data load. Records are of one datatype and validated as a set.

#### Signature

```http
POST /repository/bulk-create (body) -> The creation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Not idempotent — re-running an import duplicates the records unless the datatype enforces uniqueness.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/migrate`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The records to create.

```json
{
  "datatype": "sf_product",
  "records": [
    {
      "sku": "DRK-COLA-330",
      "title": "Cola 330ml"
    }
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The creation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /repository/export/{datatype}

**Export records**

`operationId: RepositoryController_exportData`

Exports a datatype as CSV or JSON, streamed as a file download.

**A mid-export failure still returns `200`.** Because the row count is unknown until the last page is read, the response is chunked and the headers are sent before the data — so once an export starts, there is no way to signal failure through the status code. Instead the error is written **into the file**: a CSV gains a trailing `# export failed: …` line, and a JSON export is closed off with `]`.

**Always check the tail of the file** before trusting an export to be complete.

CSV output begins with a UTF-8 BOM so Excel reads non-ASCII characters correctly rather than mangling them as the local codepage.

#### Signature

```http
POST /repository/export/{datatype} (datatype: string, body) -> The exported file, streamed as an attachment named `<datatype>-<date>.<format>`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- A failed export returns `200` with a truncated file — check the last line for `# export failed:` (CSV) or a bare `]` (JSON).
- CSV output carries a UTF-8 BOM, which some strict parsers will need told about.
- The response is chunked, so `Content-Length` is absent and progress cannot be measured.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /repository/bulk-create`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The collection to act on. |

### Request body

What to export, and in what format.

```json
{
  "format": "csv",
  "query": {
    "data.status": "active"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The exported file, streamed as an attachment named `<datatype>-<date>.<format>` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

