Catalog
The tracking plan: catalog entries for event and trait names, each in a governance state (allowed, denied, quarantined, where denied always wins). Ingestion is accept-by-default; rejection has exactly two sources, the deny-list and canonical-trait format validations. With the org’s ingestionPolicy set to strict, unclassified names are held in the quarantine queue instead of applied; promoting a name allow-lists it and backfills its held values. Property and trait types: string, number, boolean, object, array<any>, array<string>, array<number>, array<boolean>, array<object>. Observation infers an array’s item type when its members agree, and array<any> when they do not or the array is empty. All mutations require org:admin.
POST /v1/catalog/events
Section titled “POST /v1/catalog/events”Register an event name
Auth: Clerk session token.
Request body
{ "data": { "name": "string", "properties": {} }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.name |
string | yes | length 1–200 |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | no | default {} |
Response 201
{ "data": { "orgId": "string", "name": "string", "properties": { "key": "string" }, "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
GET /v1/catalog/events
Section titled “GET /v1/catalog/events”List catalog events
Auth: Clerk session token.
Query parameters
{ "cursor": "string", "limit": 50, "q": "string", "state": "allowed"}| Field | Type | Required | Constraints |
|---|---|---|---|
cursor |
string | no | |
limit |
integer | no | default 50; 1 ≤ x ≤ 100 |
q |
string | no | length 0–200 |
state |
“allowed” | “denied” | no |
Response 200
{ "data": [ { "orgId": "string", "name": "string", "properties": { "key": "string" }, "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" } ], "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object[] | yes | |
data[] |
object | yes | |
data[].orgId |
string | yes | |
data[].name |
string | yes | |
data[].properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes | |
data[].state |
“allowed” | “denied” | yes | |
data[].createdAt |
string (date-time) | yes | |
data[].updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
GET /v1/catalog/events/{name}
Section titled “GET /v1/catalog/events/{name}”Fetch one catalog event
Auth: Clerk session token.
Response 200
{ "data": { "orgId": "string", "name": "string", "properties": { "key": "string" }, "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
PATCH /v1/catalog/events/{name}
Section titled “PATCH /v1/catalog/events/{name}”Update a catalog event
Auth: Clerk session token.
Request body
{ "data": { "properties": { "key": "string" } }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes |
Response 200
{ "data": { "orgId": "string", "name": "string", "properties": { "key": "string" }, "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/catalog/events/{name}/state
Section titled “POST /v1/catalog/events/{name}/state”Allow or deny an event name
Auth: Clerk session token.
Request body
{ "data": { "state": "allowed" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.state |
“allowed” | “denied” | yes |
Response 200
{ "data": { "orgId": "string", "name": "string", "properties": { "key": "string" }, "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.properties |
record<string, “string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>”> | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/catalog/traits
Section titled “POST /v1/catalog/traits”Register a trait
Auth: Clerk session token.
Request body
{ "data": { "name": "string", "type": "string" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.name |
string | yes | length 1–200 |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes |
Response 201
{ "data": { "orgId": "string", "name": "string", "type": "string", "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
GET /v1/catalog/traits
Section titled “GET /v1/catalog/traits”List catalog traits
Auth: Clerk session token.
Query parameters
{ "cursor": "string", "limit": 50, "q": "string", "state": "allowed"}| Field | Type | Required | Constraints |
|---|---|---|---|
cursor |
string | no | |
limit |
integer | no | default 50; 1 ≤ x ≤ 100 |
q |
string | no | length 0–200 |
state |
“allowed” | “denied” | no |
Response 200
{ "data": [ { "orgId": "string", "name": "string", "type": "string", "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" } ], "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object[] | yes | |
data[] |
object | yes | |
data[].orgId |
string | yes | |
data[].name |
string | yes | |
data[].type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes | |
data[].state |
“allowed” | “denied” | yes | |
data[].createdAt |
string (date-time) | yes | |
data[].updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
GET /v1/catalog/traits/{name}
Section titled “GET /v1/catalog/traits/{name}”Fetch one catalog trait
Auth: Clerk session token.
Response 200
{ "data": { "orgId": "string", "name": "string", "type": "string", "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
PATCH /v1/catalog/traits/{name}
Section titled “PATCH /v1/catalog/traits/{name}”Update a catalog trait
Auth: Clerk session token.
Request body
{ "data": { "type": "string" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes |
Response 200
{ "data": { "orgId": "string", "name": "string", "type": "string", "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/catalog/traits/{name}/state
Section titled “POST /v1/catalog/traits/{name}/state”Allow or deny a trait name
Auth: Clerk session token.
Request body
{ "data": { "state": "allowed" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.state |
“allowed” | “denied” | yes |
Response 200
{ "data": { "orgId": "string", "name": "string", "type": "string", "state": "allowed", "createdAt": "2026-01-15T09:30:00.000Z", "updatedAt": "2026-01-15T09:30:00.000Z" }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.orgId |
string | yes | |
data.name |
string | yes | |
data.type |
“string” | “number” | “boolean” | “object” | “array<any>” | “array<string>” | “array<number>” | “array<boolean>” | “array<object>” | yes | |
data.state |
“allowed” | “denied” | yes | |
data.createdAt |
string (date-time) | yes | |
data.updatedAt |
string (date-time) | yes | |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/catalog/quarantine/promote
Section titled “POST /v1/catalog/quarantine/promote”Promote a quarantined name into the catalog
Auth: Clerk session token.
Request body
{ "data": { "kind": "event", "name": "string" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.kind |
“event” | “trait” | yes | |
data.name |
string | yes | length 1–∞ |
Response 200
{ "data": { "promoted": 42 }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.promoted |
integer | yes | 0 ≤ x ≤ ∞ |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.
POST /v1/catalog/quarantine/deny
Section titled “POST /v1/catalog/quarantine/deny”Deny a quarantined name
Auth: Clerk session token.
Request body
{ "data": { "kind": "event", "name": "string" }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.kind |
“event” | “trait” | yes | |
data.name |
string | yes | length 1–∞ |
Response 200
{ "data": { "dropped": 42 }, "meta": { "requestId": "string", "nextCursor": "string", "hasMore": true }}| Field | Type | Required | Constraints |
|---|---|---|---|
data |
object | yes | |
data.dropped |
integer | yes | 0 ≤ x ≤ ∞ |
meta |
object | no | |
meta.requestId |
string | no | |
meta.nextCursor |
string | null | no | |
meta.hasMore |
boolean | no |
Errors follow the shared envelope and code table.