Download OpenAPI specification:Download
Self-hostable data-quality platform: connect data sources, profile data assets, add rules, bundle them into validations, run them on a schedule, and review execution runs, results, anomalies, and alerts. Authenticate every request with a Bearer token: a personal API key (dqk_…) or a session access token.
Agent registers one of its locally-configured datasources.
object (Display Metadata) Default: {} | |
| name required | string (Name) |
| type required | string (Type) |
{- "display_metadata": { },
- "name": "string",
- "type": "string"
}nullAgent deregisters one of its datasources.
| datasource_id required | string <uuid> (Datasource Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Agent updates display_metadata on its datasource.
| datasource_id required | string <uuid> (Datasource Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Atomic claim. Returns the dispatch envelope on success; 409 if another agent already won the job (the same agent re-calling will also see 409, on purpose — claim must be idempotent against self).
| run_queue_id required | string <uuid> (Run Queue Id) |
nullAccept the redacted metadata payload, write a
ValidationResult row, mark the queue row success/failed, fire
alert evaluation, audit the call.
Three layers of defence on the payload:
_MAX_COMPLETE_PAYLOAD_BYTES) — even a healthy
redacted result is well under 8 KiB; anything bigger is
suspicious.validate_redacted_payload — rejects forbidden keys
(unexpected_rows, kwargs, value_set, …)._truncate_payload_strings — caps the user-controlled
string fields so description-style exfiltration can't fit
more than a few hundred chars of customer data.Then the host-side belt-and-suspenders: the queue row's checkpoint
must target one of this agent's own datasources in this principal's
workspace (:func:_run_queue_targets_agent).
| run_queue_id required | string <uuid> (Run Queue Id) |
required | object (Data) | ||
| |||
{- "data": { }
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Mark the queue row failed with the agent-provided error (scrubbed to 2000 chars). Used when the agent can't reach its DB, GX raises, etc.
| run_queue_id required | string <uuid> (Run Queue Id) |
required | object (Data) | ||
| |||
{- "data": { }
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Roll claimed_at forward so a long-running job doesn't get
reclaimed.
| run_queue_id required | string <uuid> (Run Queue Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Stream the rendered export file.
Authenticated by the HMAC signature in the query string (see
:func:services.export_storage.verify_download_token). Deliberately
not gated by Depends(get_principal) — the signed URL is the
credential, designed to be hand-able to an auditor without forcing
them to log into DQ Cloud. The signature expires per the URL's own
exp claim; past the expiry, every request returns 410 Gone.
| export_id required | string <uuid> (Export Id) |
| sig required | string (Sig) [ 10 .. 128 ] characters |
| exp required | integer (Exp) >= 0 |
nullList the org's most recent exports.
Hard-capped at 30 rows — the UI surface is "what's pending and what
can I still download", not an audit-trail of every export ever
requested. A future GET ?cursor=... would extend this; not in
scope here.
| org_id required | string <uuid> (Org Id) |
| limit | integer (Limit) [ 1 .. 30 ] Default: 30 |
nullRequest a new audit-log export.
Inserts an AuditExport row in status='pending' and enqueues
run_audit_export on ARQ. Returns 202 with the row + a
poll_url (the single-row GET) so the UI can wire up the polling
loop without having to construct the URL itself.
Date-range cap is :data:settings.audit_export_max_range_days
(default 90). Years-long exports are out of scope; an operator
wanting one requests it in 90-day slices.
| org_id required | string <uuid> (Org Id) |
| end_date required | string <date> (End Date) |
| format required | string (Format) |
| start_date required | string <date> (Start Date) |
{- "end_date": "2019-08-24",
- "format": "string",
- "start_date": "2019-08-24"
}nullSingle-row status check — used by the UI's poll loop.
Returns the row plus, when status='complete', a freshly-signed
download URL. The URL is regenerated on every read (rather than
being persisted) so the underlying signing key can be rotated
without invalidating already-issued export rows the moment the
config flips.
| org_id required | string <uuid> (Org Id) |
| export_id required | string <uuid> (Export Id) |
nullPublic, unauthenticated deployment metadata.
Returned fields:
region — operator-declared region label (DEPLOYMENT_REGION).version — the API package version (matches app.version).sso_enabled — whether SSO is available at this deployment.llm_provider — the default LLM provider, or None when AI is
not configured.{- "property1": { },
- "property2": { }
}Public, unauthenticated build marker (C-84-1).
Returns the commit sha the running API image was built from (stamped per
deploy from image_refs.hcl), its short form, the commit timestamp and
the package version. The UI shows the same sha in the account menu. A commit
sha of an open-source-shaped codebase is not sensitive; nothing else is
returned.
{- "data": {
- "build_time": "string",
- "sha": "string",
- "short_sha": "string",
- "version": "string"
}
}Return the static expectation gallery.
Wrapped in {"data": [...]} to match the convention used by every
other V1 endpoint — the UI's apiFetch helper unwraps .data
uniformly so adding a new endpoint that returns a bare list would
be a paper cut.
{- "property1": [
- { }
], - "property2": [
- { }
]
}| org_id required | string <uuid> (Org Id) |
Audit Retention Days (integer) or Audit Retention Days (null) (Audit Retention Days) | |
Any of >= 0 integer (Audit Retention Days) >= 0 | |
{- "audit_retention_days": 0
}null| org_id required | string <uuid> (Org Id) |
| name required | string (Name) |
Workspace Id (string) or Workspace Id (null) (Workspace Id) |
{- "name": "string",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}nullWhich AI features auto-run for this org (asks U4).
Readable by EVERY authenticated org member — unlike ai-policy (LLM
managers only) — because every AI panel reads it to decide whether to
generate on mount. It exposes no secret and no spend figure, just the
per-feature switches: the org's own choice (source="org") or, until an
admin saves one, the deployment default (source="deployment").
| org_id required | string <uuid> (Org Id) |
{- "data": {
- "asset_summary": true,
- "editable": true,
- "explain": true,
- "failures_review": true,
- "rca": true,
- "rule_suggestions": true,
- "source": "deployment"
}
}Save the org's AI auto-run switches (asks U4) — admin-gated.
Same gate as ai-policy (CAN_MANAGE_LLM_CONFIGS): the switches decide
whether LLM spend happens without a click, so only the people who manage
the org's AI engine may flip them. Not fresh_auth — it flips routing
flags, not a credential. Partial: an omitted flag keeps its current value.
Audited.
| org_id required | string <uuid> (Org Id) |
Asset Summary (boolean) or Asset Summary (null) (Asset Summary) | |
Explain (boolean) or Explain (null) (Explain) | |
Failures Review (boolean) or Failures Review (null) (Failures Review) | |
Rca (boolean) or Rca (null) (Rca) | |
Rule Suggestions (boolean) or Rule Suggestions (null) (Rule Suggestions) |
{- "asset_summary": true,
- "explain": true,
- "failures_review": true,
- "rca": true,
- "rule_suggestions": true
}{- "data": {
- "asset_summary": true,
- "editable": true,
- "explain": true,
- "failures_review": true,
- "rca": true,
- "rule_suggestions": true,
- "source": "deployment"
}
}Return the org's stored ai_mode plus deployment native-availability.
native_available lets the UI decide whether to even offer the native
toggle (a self-host without Vertex access shows BYOLLM only), and
effective_mode is the engine the resolver would pick right now.
| org_id required | string <uuid> (Org Id) |
{- "property1": {
- "ai_mode": "native",
- "effective_mode": "native",
- "native_available": false
}, - "property2": {
- "ai_mode": "native",
- "effective_mode": "native",
- "native_available": false
}
}Set (or clear, with ai_mode: null) the org's native-vs-BYOLLM override.
| org_id required | string <uuid> (Org Id) |
Ai Mode (string) or Ai Mode (null) (Ai Mode) | |
Any of string (Ai Mode) Enum: "native" "byollm" | |
{- "ai_mode": "native"
}{- "property1": {
- "ai_mode": "native",
- "effective_mode": "native",
- "native_available": false
}, - "property2": {
- "ai_mode": "native",
- "effective_mode": "native",
- "native_available": false
}
}| org_id required | string <uuid> (Org Id) |
[- {
- "base_url": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "is_default": true,
- "model": "string",
- "name": "string",
- "provider": "openai"
}
]| org_id required | string <uuid> (Org Id) |
Api Key (string) or Api Key (null) (Api Key) | |
Base Url (string) or Base Url (null) (Base Url) | |
| is_default | boolean (Is Default) Default: false |
| model required | string (Model) |
| name required | string (Name) |
| provider required | string (Provider) Enum: "openai" "anthropic" "azure_openai" "ollama" "custom" "gemini" "google_ai" |
{- "api_key": "string",
- "base_url": "string",
- "is_default": false,
- "model": "string",
- "name": "string",
- "provider": "openai"
}{- "base_url": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "is_default": true,
- "model": "string",
- "name": "string",
- "provider": "openai"
}| org_id required | string <uuid> (Org Id) |
| config_id required | string <uuid> (Config Id) |
Api Key (string) or Api Key (null) (Api Key) | |
Base Url (string) or Base Url (null) (Base Url) | |
Is Default (boolean) or Is Default (null) (Is Default) | |
Model (string) or Model (null) (Model) | |
Name (string) or Name (null) (Name) |
{- "api_key": "string",
- "base_url": "string",
- "is_default": true,
- "model": "string",
- "name": "string"
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Return this org's AI usage aggregated by day + a totals block.
Wire shape::
{
"data": {
"window": {"days": 30, "since": "2026-07-18T00:00:00Z"},
"totals": {
"requests": 12, "prompt_tokens": 3400, "output_tokens": 900,
"est_cost_usd": 0.0031,
"native": {"requests": 10, "prompt_tokens": 3000, "output_tokens": 800, "est_cost_usd": 0.0028},
"byo": {"requests": 2, "prompt_tokens": 400, "output_tokens": 100, "est_cost_usd": 0.0003}
},
"by_day": [
{"day": "2026-08-16", "requests": 5, "prompt_tokens": 1200,
"output_tokens": 300, "est_cost_usd": 0.0011}
],
"by_model": [
{"model": "gemini-3.6-flash", "is_native": true, "requests": 10,
"prompt_tokens": 3000, "output_tokens": 800, "est_cost_usd": 0.0028}
]
}
}
by_day is ascending by date and includes only days that had usage.
Estimated cost is derived at read time from :data:_PRICE_PER_1M (so a price
correction re-prices history) and rolled up per day from per-MODEL rates —
NOT from an org-wide blended rate — so a day mixing Flash and Pro is priced
correctly.
| org_id required | string <uuid> (Org Id) |
| days | integer (Days) [ 1 .. 365 ] Default: 30 Window size in days (1..365). The range is [now - days, now]. |
{ }required | object (AlertRuleCreate) | ||||||||||||||||||||||||
| |||||||||||||||||||||||||
{- "data": {
- "channel": "create_incident",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "destination": { },
- "enabled": true,
- "mentions": [ ],
- "min_severity": "info",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "scope_type": "specific",
- "severities": [
- "info"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "trigger": "on_failure",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}nullLimit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{- "data": [
- {
- "alert_id": "c8b0bcf7-1a95-4165-9b1e-665b02ceda9f",
- "alert_rule_id": "a87b6b3b-8bd5-4bdb-9f21-206bb29a1fe6",
- "channel": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "error": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "external_ticket_id": "string",
- "external_ticket_status": "string",
- "external_ticket_url": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "rule_channel": "string",
- "rule_checkpoint_id": "e7445876-e7ec-4a7a-8cbe-a6aa53a4d3dd",
- "rule_rule_group_id": "cb20fba2-0817-4419-9059-b9ec9c7f5f97",
- "rule_scope_type": "string",
- "rule_trigger": "string",
- "rule_validation_id": "bb821ffb-2735-4e48-b390-3f1265e99599",
- "rule_validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_validation_names": [
- "string"
], - "sent_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc",
- "validation_name": "string",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
], - "next_cursor": "string"
}| resource_id required | string <uuid> (Resource Id) |
required | object (AlertRuleUpdate) | ||||||||||||||||||||||
| |||||||||||||||||||||||
{- "data": {
- "channel": "create_incident",
- "destination": { },
- "enabled": true,
- "mentions": [
- "string"
], - "min_severity": "info",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "scope_type": "specific",
- "severities": [
- "info"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "trigger": "on_failure",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (AlertRuleCreate) | ||||||||||||||||||||||||
| |||||||||||||||||||||||||
{- "data": {
- "channel": "create_incident",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "destination": { },
- "enabled": true,
- "mentions": [ ],
- "min_severity": "info",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "scope_type": "specific",
- "severities": [
- "info"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "trigger": "on_failure",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{- "data": [
- {
- "alert_id": "c8b0bcf7-1a95-4165-9b1e-665b02ceda9f",
- "alert_rule_id": "a87b6b3b-8bd5-4bdb-9f21-206bb29a1fe6",
- "channel": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "error": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "external_ticket_id": "string",
- "external_ticket_status": "string",
- "external_ticket_url": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "rule_channel": "string",
- "rule_checkpoint_id": "e7445876-e7ec-4a7a-8cbe-a6aa53a4d3dd",
- "rule_rule_group_id": "cb20fba2-0817-4419-9059-b9ec9c7f5f97",
- "rule_scope_type": "string",
- "rule_trigger": "string",
- "rule_validation_id": "bb821ffb-2735-4e48-b390-3f1265e99599",
- "rule_validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_validation_names": [
- "string"
], - "sent_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc",
- "validation_name": "string",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
], - "next_cursor": "string"
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (AlertRuleUpdate) | ||||||||||||||||||||||
| |||||||||||||||||||||||
{- "data": {
- "channel": "create_incident",
- "destination": { },
- "enabled": true,
- "mentions": [
- "string"
], - "min_severity": "info",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "scope_type": "specific",
- "severities": [
- "info"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "trigger": "on_failure",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
nullList audit events for one org, newest-first.
Owner + admin only — editor/viewer/agent tokens all get 403. Returns
{"data": [...], "next_cursor": "..."}. next_cursor is null when
the current page is the last one.
Issue #196: admin was previously denied here, but the per-resource
history modal is wired into every detail page and admins are the
primary day-to-day audience for "who changed this, when?". The role
set matches the rest of the admin-readable surface
(agent_tokens, llm_configs, the bulk AI routes, etc.) which
all gate on ("admin", "owner").
FEATURE-8: pass resource_type=<kind>&resource_id=<uuid> to scope the
list to a single resource (e.g. one datasource, one rule suite, one
checkpoint). Both filters must be supplied together; one without the
other is rejected as 400 — resource_type alone would scan a hot
prefix of the index, and resource_id alone is ambiguous because
UUIDs are not globally unique across resource types in this table.
| org_id required | string <uuid> (Org Id) |
Cursor (string) or Cursor (null) (Cursor) | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 |
Resource Type (string) or Resource Type (null) (Resource Type) | |
Resource Id (string) or Resource Id (null) (Resource Id) |
null| org_id required | string <uuid> (Org Id) |
Event Filter (object) or Event Filter (null) (Event Filter) | |
| name required | string (Name) [ 1 .. 255 ] characters |
Signing Secret (string) or Signing Secret (null) (Signing Secret) | |
| url required | string (Url) |
{- "event_filter": { },
- "name": "string",
- "signing_secret": "string",
- "url": "string"
}null| org_id required | string <uuid> (Org Id) |
| webhook_id required | string <uuid> (Webhook Id) |
Enabled (boolean) or Enabled (null) (Enabled) | |
Event Filter (object) or Event Filter (null) (Event Filter) | |
Name (string) or Name (null) (Name) | |
Signing Secret (string) or Signing Secret (null) (Signing Secret) | |
Url (string) or Url (null) (Url) |
{- "enabled": true,
- "event_filter": { },
- "name": "string",
- "signing_secret": "string",
- "url": "string"
}nullAUDIT-3.4: force-clear the previous-secret grace window.
Recovery path for the case where the operator successfully rotated
but lost the new plaintext in flight (terminal closed before paste,
CI step crashed mid-copy, browser hung). The 24h grace window guard
on :func:rotate_audit_siem_webhook_secret would otherwise lock
them out for a day, with a verifier-side secret nobody on the team
knows.
Semantics:
previous_secret_encrypted and
previous_secret_expires_at to NULL. After this returns the
rotate endpoint accepts a fresh call immediately.PATCH signing_secret=<value>,
not this endpoint. We 422 with a directive detail so the UI can
surface the actual next step instead of a generic error.audit_siem_webhook.previous_secret_cleared so the
compliance trail records the escape-hatch invocation distinctly
from the rotate that preceded it. Receivers reading the audit
feed see rotate -> previous_secret_cleared -> rotate and can
reason about the sequence.| org_id required | string <uuid> (Org Id) |
| webhook_id required | string <uuid> (Webhook Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}AUDIT-3.2: rotate the HMAC signing secret with a 24h grace window.
The flow:
secret_encrypted (the current secret's ciphertext) to
previous_secret_encrypted so the dispatcher can keep signing
in-flight bodies with it during the grace window.previous_secret_expires_at = now() + 24h.secret_encrypted.{secret, expires_old_at} so the operator can paste
the new secret into their receiver and knows the cutoff after
which the receiver MUST be verifying the new secret only.Single-use guard: if a previous rotation is still inside its grace window, we refuse with 409. Without this, a double-rotation would silently lose the original secret and any receiver still verifying against it would start dropping events. Operators who really need to force a second rotation can wait out the window or PATCH the row to clear the previous-secret columns (an emergency lever — the docs warn against it).
| org_id required | string <uuid> (Org Id) |
| webhook_id required | string <uuid> (Webhook Id) |
nullEnqueue a synthetic dispatch_audit_siem so the operator can
verify their receiver wiring without waiting for a real audit
event. The worker's job accepts event_id=None and builds a
payload with action="audit_siem_webhook.test" so receivers can
distinguish a probe from a real fire.
| org_id required | string <uuid> (Org Id) |
| webhook_id required | string <uuid> (Webhook Id) |
nullOrg-wide per-asset BI feed across every accessible workspace (#37).
Org-admin-tier ONLY (mirrors org_dashboard_metrics): a caller below
super_admin / the legacy compat spellings gets a 403. No ?team_id=
(team is a within-workspace concept). Isolation-respecting.
| org_id required | string <uuid> (Org Id) |
Cursor (string) or Cursor (null) (Cursor) | |
Limit (integer) or Limit (null) (Limit) Opt-in page size. Omit for the full row set in one response (next_cursor null); supply it to page via next_cursor. |
{- "data": [
- {
- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "asset_name": "string",
- "asset_type": "string",
- "datasource_id": "11f86609-0961-4dd0-a490-1432bc861567",
- "datasource_name": "string",
- "is_restricted": false,
- "last_result_id": "b1ee649c-4fa3-47ca-86d8-bdcd03f4dd17",
- "last_run_time": "2019-08-24T14:15:22Z",
- "owner_email": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "status": "string",
- "tags": [
- "string"
], - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string"
}Org-wide per-run BI feed across every accessible workspace (#37).
Org-admin-tier ONLY (mirrors org_dashboard_metrics): a caller below
super_admin / the legacy compat spellings gets a 403. No ?team_id=
(team is a within-workspace concept). Isolation-respecting.
| org_id required | string <uuid> (Org Id) |
Cursor (string) or Cursor (null) (Cursor) | |
Limit (integer) or Limit (null) (Limit) Opt-in page size. Omit for the full window in one response (next_cursor null); supply it to page via next_cursor. | |
Since (string) or Since (null) (Since) Inclusive run_time lower bound (ISO 8601). Naive timestamps are interpreted as UTC. | |
Until (string) or Until (null) (Until) Inclusive run_time upper bound (ISO 8601). Naive timestamps are interpreted as UTC. |
{- "data": [
- {
- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "asset_name": "string",
- "batch_kind": "string",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "checkpoint_name": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "partition_value": "string",
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_group_name": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "run_time": "2019-08-24T14:15:22Z",
- "status": "string",
- "success": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string"
}Per-asset latest-result BI feed, workspace-scoped (#37).
One flat row per data asset — name, datasource, owner, tags, current
passing/failing/never_run status, last run — for a BI tool to
pull into a table. Viewer-readable (workspace_principal + CAN_VIEW_RESULTS);
dqk_ personal keys and dqsa_ service accounts pass, agent / SCIM
tokens are rejected. Isolation-respecting.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Cursor (string) or Cursor (null) (Cursor) | |
Limit (integer) or Limit (null) (Limit) Opt-in page size. Omit for the full row set in one response (next_cursor null); supply it to page via next_cursor. | |
Team Id (string) or Team Id (null) (Team Id) Narrow the rows to one team's slice — must be a live team of this workspace, else 404. Assets carry no team, so a team filter narrows the results feeding each asset's status, not the asset set itself. |
{- "data": [
- {
- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "asset_name": "string",
- "asset_type": "string",
- "datasource_id": "11f86609-0961-4dd0-a490-1432bc861567",
- "datasource_name": "string",
- "is_restricted": false,
- "last_result_id": "b1ee649c-4fa3-47ca-86d8-bdcd03f4dd17",
- "last_run_time": "2019-08-24T14:15:22Z",
- "owner_email": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "status": "string",
- "tags": [
- "string"
], - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string"
}Per-run BI fact-table feed, workspace-scoped (#37).
One flat row per validation run over an optional ?since=/?until=
window, newest-first — the time-series table a BI tool trends. Run-level
columns only in v1 (per-expectation pass-rate / severity are a follow-up).
Viewer-readable; dqk_ / dqsa_ pass, agent / SCIM rejected.
Isolation-respecting.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Cursor (string) or Cursor (null) (Cursor) | |
Limit (integer) or Limit (null) (Limit) Opt-in page size. Omit for the full window in one response (next_cursor null); supply it to page via next_cursor. | |
Since (string) or Since (null) (Since) Inclusive run_time lower bound (ISO 8601). Naive timestamps are interpreted as UTC. | |
Until (string) or Until (null) (Until) Inclusive run_time upper bound (ISO 8601). Naive timestamps are interpreted as UTC. | |
Team Id (string) or Team Id (null) (Team Id) Narrow the runs to one team's slice — must be a live team of this workspace, else 404 (team ownership is derived via the parent checkpoint). |
{- "data": [
- {
- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "asset_name": "string",
- "batch_kind": "string",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "checkpoint_name": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "partition_value": "string",
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_group_name": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "run_time": "2019-08-24T14:15:22Z",
- "status": "string",
- "success": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string"
}Return the org's branding metadata + inline logo data URIs.
Readable by any org member. When no logo is set the variants are
null and the UI falls back to the MeasuredCloud wordmark.
| org_id required | string <uuid> (Org Id) |
nullReset the org logo to default by dropping the branding row. Org-admin gated. Idempotent — resetting an org with no logo is a 204 no-op.
| org_id required | string <uuid> (Org Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Stream the raw logo bytes for variant with its own content type.
Member-readable. 404 when the org has no row or the requested variant is unset (the caller then falls back to the wordmark / the other variant).
Every response carries X-Content-Type-Options: nosniff (the browser
must honour the declared type, not sniff bytes) and an explicit
Content-Disposition. For image/svg+xml we additionally lock the
document down with a no-resources, sandboxed CSP so an SVG opened directly
in the API origin can't execute embedded script — without that, an
admin-uploaded <script> would run against the dqr_ cookie origin
(stored XSS). Raster variants stay inline and render normally.
| org_id required | string <uuid> (Org Id) |
| variant | string (Variant) Default: "light" Enum: "light" "dark" |
nullSet / replace the org logo. Org-admin gated.
Body carries base64 logo_light and/or logo_dark plus a
content_type (with optional per-variant logo_light_type /
logo_dark_type overrides). At least one variant is required. Each
variant is validated for base64-decodability and the MAX_LOGO_BYTES
size cap; every content type is validated against ALLOWED_CONTENT_TYPES.
Only the variant(s) this PUT addresses are touched — both the bytes and that variant's type move together, and the other variant's bytes/type are left intact (so a single-variant update can't desync the row).
| org_id required | string <uuid> (Org Id) |
| content_type required | string (Content Type) |
Logo Dark (string) or Logo Dark (null) (Logo Dark) | |
Logo Dark Type (string) or Logo Dark Type (null) (Logo Dark Type) | |
Logo Light (string) or Logo Light (null) (Logo Light) | |
Logo Light Type (string) or Logo Light Type (null) (Logo Light Type) |
{- "content_type": "string",
- "logo_dark": "string",
- "logo_dark_type": "string",
- "logo_light": "string",
- "logo_light_type": "string"
}null| org_id required | string <uuid> (Org Id) |
Auth Token (string) or Auth Token (null) (Auth Token) | |
| enabled | boolean (Enabled) Default: true |
| endpoint required | string (Endpoint) |
| kind required | string (Kind) |
{- "auth_token": "string",
- "enabled": true,
- "endpoint": "string",
- "kind": "string"
}null| org_id required | string <uuid> (Org Id) |
| integration_id required | string <uuid> (Integration Id) |
Auth Token (string) or Auth Token (null) (Auth Token) | |
Enabled (boolean) or Enabled (null) (Enabled) | |
Endpoint (string) or Endpoint (null) (Endpoint) |
{- "auth_token": "string",
- "enabled": true,
- "endpoint": "string"
}nullEnqueue an out-of-cycle inbound walk for this one integration.
PR-#102 review blocker: the original cut enqueued the cron
entry-point (catalog_sync_inbound), which walked every enabled
integration across every org — at best wasteful, at worst a
cross-tenant correctness bug (an operator hitting "Sync now" for
org A bumped last_synced_at on org B's rows). We now enqueue
the per-integration job so the walk is scoped to (org_id,
integration_id).
| org_id required | string <uuid> (Org Id) |
| integration_id required | string <uuid> (Integration Id) |
null| org_id required | string <uuid> (Org Id) |
Signing Secret (string) or Signing Secret (null) (Signing Secret) | |
Webhook Url (string) or Webhook Url (null) (Webhook Url) |
{- "signing_secret": "string",
- "webhook_url": "string"
}nullEnqueue a synthetic notify_catalog_sync fire so the operator can
verify the receiver is wired up correctly without waiting for a real
checkpoint to land.
Returns 202 — the worker handles the POST asynchronously. The operator
polls last_fired_at (or watches their receiver) to observe arrival.
| org_id required | string <uuid> (Org Id) |
nullDEPRECATED: the legacy checkpoints, each with its mapped validation_id.
| org_id required | string <uuid> (Org Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }DEPRECATED: the legacy checkpoints, each with its mapped validation_id.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }DEPRECATED: one legacy checkpoint with its mapped validation_id.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| checkpoint_id required | string <uuid> (Checkpoint Id) |
{ }DEPRECATED: runs the checkpoint's mapped validation (an execution run).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| checkpoint_id required | string <uuid> (Checkpoint Id) |
Rule Parameters (object) or Rule Parameters (null) (Rule Parameters) No longer supported: a validation run executes its rules as saved. A non-empty value is rejected (422). | |||
Any of
| |||
{- "rule_parameters": { }
}{ }DEPRECATED: runs the mapped validation once per partition value.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| checkpoint_id required | string <uuid> (Checkpoint Id) |
| batch_definition_id required | string <uuid> (Batch Definition Id) |
required | Array of strings or integers (Partition Values) [ 1 .. 100 ] items |
{- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "partition_values": [
- "string"
]
}{ }Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
nullName (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
nullName (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Rule Group Id (string) or Rule Group Id (null) (Rule Group Id) | |
Parent Result Id (string) or Parent Result Id (null) (Parent Result Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Limit (integer) or Limit (null) (Limit) | |
| offset | integer (Offset) >= 0 Default: 0 |
Sort (string) or Sort (null) (Sort) | |
Asset (string) or Asset (null) (Asset) | |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) |
nullName (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Rule Group Id (string) or Rule Group Id (null) (Rule Group Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Asset (string) or Asset (null) (Asset) | |
| format | string (Format) ^(csv|xlsx)$ Default: "csv" |
nullBatch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| name required | string (Name) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| name required | string (Name) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Rule Group Id (string) or Rule Group Id (null) (Rule Group Id) | |
Parent Result Id (string) or Parent Result Id (null) (Parent Result Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Limit (integer) or Limit (null) (Limit) | |
| offset | integer (Offset) >= 0 Default: 0 |
Sort (string) or Sort (null) (Sort) | |
Asset (string) or Asset (null) (Asset) | |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Tag (string) or Tag (null) (Tag) | |
Dbt Model (string) or Dbt Model (null) (Dbt Model) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Rule Group Id (string) or Rule Group Id (null) (Rule Group Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Asset (string) or Asset (null) (Asset) | |
| format | string (Format) ^(csv|xlsx)$ Default: "csv" |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Status (string) or Status (null) (Status) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| name required | string (Name) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}List registered custom expectations for the calling principal's org.
Open to every authenticated member of the org — the rule-suite editor is the primary consumer and editor-level users need to see the catalog. Owner-only on this surface would block the editor for every non-owner member.
| org_id required | string <uuid> (Org Id) |
nullList configured plugin packages + import status.
Open to every authenticated member of the org (#113). The Plugins
settings page renders the response for any member who lands on it;
the previous owner-only gate broke the page for editors/viewers
even though the package names are deployment config (already
visible in the worker env) and carry no secrets. Each package is
imported in this process so an operator who pasted a typo into
EXPECTATIONS_PLUGIN_PACKAGES sees the failure in the settings
UI without having to tail the worker logs.
The response carries installed=True for packages that import
cleanly and error=<message> for ones that don't. The import is
cached by Python's import machinery so repeated reads are cheap.
| org_id required | string <uuid> (Org Id) |
nullOrg-wide asset-health roll-up across every accessible workspace (#142).
Org-admin-tier ONLY — mirrors org_dashboard_metrics / results_all's
org endpoint exactly: a caller below super_admin (or the legacy
owner / admin compat spellings) gets a 403, NOT a partial roll-up.
An org-admin-tier role yields accessible = None from
:func:derive_accessible_workspaces, so the listener emits a tautology and
the SAME :func:_compute_asset_health SQL rolls up every workspace's assets.
No path {workspace_id} means the workspace clamp never fires. No
?team_id= here — team is a within-workspace concept.
| org_id required | string <uuid> (Org Id) |
{- "data": {
- "assets_with_results": 0,
- "by_owner": [
- {
- "failing_assets": 0,
- "owner_email": "string"
}
], - "failing": 0,
- "good": 0,
- "never_run": 0,
- "scope": "workspace",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_narrowed": true,
- "total_assets": 0
}
}Org-wide dashboard roll-up across every accessible workspace (#665).
Org-admin-tier ONLY — mirrors results_all's org endpoint exactly: a
caller below super_admin (or the legacy owner / admin compat
spellings) gets a 403, NOT a partial roll-up. An org-admin-tier role yields
accessible = None from :func:derive_accessible_workspaces, so the
listener emits a tautology and the SAME :func:_compute_metrics SQL rolls
up every workspace's rows in the org's tenant schema. No path
{workspace_id} means the #646 workspace clamp never fires, so the
numbers genuinely span the whole org rather than one workspace.
No ?team_id= here — team is a within-workspace concept; the org tier is
intentionally the union across workspaces.
| org_id required | string <uuid> (Org Id) |
| period | integer (Period) Default: 30 Window in days — one of 30, 60, 90, 365 (the 30/60/90/1y selector). |
{- "data": {
- "by_group": [
- {
- "archived": false,
- "failed": 0,
- "key": "string",
- "name": "string",
- "passed": 0
}
], - "by_status": {
- "failed": 0,
- "passed": 0
}, - "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "coverage": {
- "covered": 0,
- "rate": 0,
- "total": 0
}, - "failure_rate": 0,
- "group_by": "team",
- "health_score": 0,
- "pass_rate": 0,
- "period_days": 0,
- "scope": "workspace",
- "severity_counts": {
- "critical": 0,
- "info": 0,
- "warning": 0
}, - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "total_runs": 0
}
}Download the org-wide dashboard roll-up as .xlsx (#130 PR 2).
Gated IDENTICALLY to :func:org_dashboard_metrics — org-admin-tier ONLY (a
caller below super_admin / the legacy compat spellings gets a 403, NOT a
partial roll-up), same rate bucket, same _compute_metrics through the
isolation listener. No {workspace_id} in the path, so the roll-up spans
every accessible workspace. File attachment, not the {"data": ...}
envelope.
| org_id required | string <uuid> (Org Id) |
| format | string (Format) ^(xlsx)$ Default: "xlsx" |
| period | integer (Period) Default: 30 Window in days — one of 30, 60, 90, 365 (the 30/60/90/1y selector). |
nullWorkspace- (or team-) scoped asset-health roll-up (#142).
Viewer-readable: workspace_principal (org match + workspace membership;
org admins bypass per HARDEN-17) + CAN_VIEW_RESULTS. The listener
filters every query to the caller's accessible workspaces, clamped to the
path workspace on this workspace-scoped read.
?team_id= narrows the failing-side counts via the listener's team
predicate (see :func:dashboard_metrics._narrow_to_team); the asset totals
stay workspace-level (assets carry no team) and team_narrowed is False.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Team Id (string) or Team Id (null) (Team Id) Narrow the FAILING-side counts (good/failing/by_owner) to one team's slice (#142). Must be a live team of this workspace, else 404. total_assets/never_run stay workspace-level (assets carry no team); team_narrowed is then False. Omit for the whole-workspace numbers. |
{- "data": {
- "assets_with_results": 0,
- "by_owner": [
- {
- "failing_assets": 0,
- "owner_email": "string"
}
], - "failing": 0,
- "good": 0,
- "never_run": 0,
- "scope": "workspace",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_narrowed": true,
- "total_assets": 0
}
}Workspace- (or team-) scoped dashboard metrics for one period (#657/#665).
Viewer-readable: workspace_principal (org match + workspace membership;
org admins bypass per HARDEN-17) + CAN_VIEW_RESULTS (which the viewer
role holds). The listener filters every query to the caller's accessible
workspaces, clamped to the path workspace on this workspace-scoped read.
?team_id= further narrows to one team via the listener's team predicate
(see :func:_narrow_to_team); coverage.total stays workspace-level
because data assets carry no team (documented in the module docstring).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| period | integer (Period) Default: 30 Window in days — one of 30, 60, 90, 365 (the 30/60/90/1y selector). |
Team Id (string) or Team Id (null) (Team Id) Narrow the metrics to one team's slice (#665). Must be a live team of this workspace, else 404. Omit for the whole-workspace numbers. |
{- "data": {
- "by_group": [
- {
- "archived": false,
- "failed": 0,
- "key": "string",
- "name": "string",
- "passed": 0
}
], - "by_status": {
- "failed": 0,
- "passed": 0
}, - "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "coverage": {
- "covered": 0,
- "rate": 0,
- "total": 0
}, - "failure_rate": 0,
- "group_by": "team",
- "health_score": 0,
- "pass_rate": 0,
- "period_days": 0,
- "scope": "workspace",
- "severity_counts": {
- "critical": 0,
- "info": 0,
- "warning": 0
}, - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "total_runs": 0
}
}Download the workspace- (or team-) scoped dashboard metrics as .xlsx
(#130 PR 2).
Gated IDENTICALLY to :func:dashboard_metrics (workspace_principal +
CAN_VIEW_RESULTS, same rate bucket, same period/team validation and the
same _compute_metrics through the isolation listener), so a user can
export exactly the numbers they can see — no more, no less. ?format= is
xlsx (the only container; a typo 422s rather than silently returning
something else). The response is a file attachment, NOT the {"data": ...}
envelope (file downloads are the documented exception, cf. the results
export).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| format | string (Format) ^(xlsx)$ Default: "xlsx" |
| period | integer (Period) Default: 30 Window in days — one of 30, 60, 90, 365 (the 30/60/90/1y selector). |
Team Id (string) or Team Id (null) (Team Id) Narrow the metrics to one team's slice (#665). Must be a live team of this workspace, else 404. Omit for the whole-workspace numbers. |
null| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
required | object (GxSuiteImport) Body for The "coming from GX" on-ramp: the platform is a thin 1:1 wrapper over GX
The route normalises the suite via
| ||||||
| |||||||
{- "data": {
- "expectation_suite_name": "string",
- "expectations": [
- { }
], - "name": "string"
}
}{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "editable_count": 0,
- "imported_count": 0,
- "suite_id": "3e9c08eb-436b-4d4b-9cfe-ca21d8bdb99b",
- "unknown_types": [
- "string"
]
}
}| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (GxSuiteImport) Body for The "coming from GX" on-ramp: the platform is a thin 1:1 wrapper over GX
The route normalises the suite via
| ||||||
| |||||||
{- "data": {
- "expectation_suite_name": "string",
- "expectations": [
- { }
], - "name": "string"
}
}{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "editable_count": 0,
- "imported_count": 0,
- "suite_id": "3e9c08eb-436b-4d4b-9cfe-ca21d8bdb99b",
- "unknown_types": [
- "string"
]
}
}| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
required | AssetRuleCreate (object) or AssetRulesWrite (object) (Data) | ||||||||||||||
One of
| |||||||||||||||
{- "data": {
- "create_validation": {
- "name": "string"
}, - "description": "string",
- "kwargs": { },
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
required | object (AssetRulesWrite) Body for Mirrors the PUT and POST share this body: both replace the asset's rule list. An
entry that carries the EPIC PR 4 (additive): an entry may also carry | ||||||
| |||||||
{- "data": {
- "rules": [
- { }
]
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
required | object (ApplyTemplateRequest) Body for Applies a reusable, asset-agnostic
| ||||||
| |||||||
{- "data": {
- "column_map": {
- "property1": "string",
- "property2": "string"
}, - "dry_run": false,
- "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc"
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
{- "data": {
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "execution_mode": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_queue_id": "6398e6b9-ada2-4a20-8eca-542784f96b75",
- "status": "pending",
- "trigger": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
required | object (AssetRulePatch) Body for | ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "kwargs": { },
- "name": "string",
- "rule_type": "string"
}
}{- "data": {
- "created_at": "2019-08-24T14:15:22Z",
- "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "description": "string",
- "enabled": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "kwargs": { },
- "memory_flag": "string",
- "memory_note": "string",
- "memory_updated_at": "2019-08-24T14:15:22Z",
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "severity": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
required | object (RuleMemoryWrite) Body for | ||||
| |||||
{- "data": {
- "flag": "positive",
- "note": "string"
}
}{- "data": {
- "flag": "positive",
- "note": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
{- "data": {
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "execution_mode": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_queue_id": "6398e6b9-ada2-4a20-8eca-542784f96b75",
- "status": "pending",
- "trigger": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | AssetRuleCreate (object) or AssetRulesWrite (object) (Data) | ||||||||||||||
One of
| |||||||||||||||
{- "data": {
- "create_validation": {
- "name": "string"
}, - "description": "string",
- "kwargs": { },
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (AssetRulesWrite) Body for Mirrors the PUT and POST share this body: both replace the asset's rule list. An
entry that carries the EPIC PR 4 (additive): an entry may also carry | ||||||
| |||||||
{- "data": {
- "rules": [
- { }
]
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (ApplyTemplateRequest) Body for Applies a reusable, asset-agnostic
| ||||||
| |||||||
{- "data": {
- "column_map": {
- "property1": "string",
- "property2": "string"
}, - "dry_run": false,
- "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc"
}
}{ }| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "execution_mode": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_queue_id": "6398e6b9-ada2-4a20-8eca-542784f96b75",
- "status": "pending",
- "trigger": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (AssetRulePatch) Body for | ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "kwargs": { },
- "name": "string",
- "rule_type": "string"
}
}{- "data": {
- "created_at": "2019-08-24T14:15:22Z",
- "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "description": "string",
- "enabled": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "kwargs": { },
- "memory_flag": "string",
- "memory_note": "string",
- "memory_updated_at": "2019-08-24T14:15:22Z",
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "severity": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleMemoryWrite) Body for | ||||
| |||||
{- "data": {
- "flag": "positive",
- "note": "string"
}
}{- "data": {
- "flag": "positive",
- "note": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "draft": "string",
- "narrative_status": "generated"
}
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "execution_mode": "string",
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_queue_id": "6398e6b9-ada2-4a20-8eca-542784f96b75",
- "status": "pending",
- "trigger": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| org_id required | string <uuid> (Org Id) |
Destination (string) or Destination (null) (Destination) | |
Any of string (Destination) | |
{- "destination": "string"
}nullEnqueue an out-of-cycle build for this org.
The job lives in worker/jobs/build_data_docs.py and is registered
as a regular ARQ function (not only a cron) precisely so the API can
trigger it on demand. Returns 202 — the work happens asynchronously
and the caller polls last_built_at to observe completion.
| org_id required | string <uuid> (Org Id) |
nullInbound dbt Cloud webhook receiver.
Flow:
NULL → 404 (we treat the
endpoint as not-configured-for-this-org rather than 401 because
leaking "this org exists but isn't configured" is worse signal
to a probing attacker than a flat not-found).Authorization: Token <hex> header.dbt:<schema>.<model> for each
model in the payload.services.validation_runs).dbt_webhook_last_received_at so the UI can show feed
health.Authorization: no get_principal dependency — the HMAC signature
IS the authorization. dbt Cloud isn't an org member; it carries a
shared secret.
| org_id required | string <uuid> (Org Id) |
{ }| org_id required | string <uuid> (Org Id) |
Signing Secret (string) or Signing Secret (null) (Signing Secret) | |
Any of string (Signing Secret) | |
{- "signing_secret": "string"
}nullThe deleted (archived) workspaces the caller may see, newest first.
A super admin sees every one in the org (they can restore them). Any other user sees the ones they belonged to — directly, or through a team the delete retired — so the Settings card can show them with Restore disabled rather than hiding them. Non-user tokens see none. Nothing here opens the workspace: it stays 404 on every workspace-scoped route until restored.
| org_id required | string <uuid> (Org Id) |
{- "data": [
- {
- "deleted_at": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "slug": "string"
}
]
}List the workspaces the calling principal can access (#446, #382).
Powers the UI's workspace switcher. Wire shape::
{ "data": [ { "id": "...", "name": "...", "slug": "...",
"role": "super_admin|technical_admin|workspace_admin|..." } ] }
Visibility mirrors the TENANCY-1 accessible-workspace resolution (see
:func:db.workspace_isolation.derive_accessible_workspaces and
:func:auth.dependencies.workspace_principal):
role echoes their org role
(super_admin), matching the workspace_principal bypass
that hands org admins the org role for any workspace.technical_admin: they navigate the org tree to manage each
workspace's data-source connections (and nothing else).workspace_memberships row
or via a team they belong to whose workspace_id is that workspace
(Phase-1 Unit 3). role is the effective (highest) role across
those paths, the same merge
:func:auth.effective_role.resolve_workspace_role applies
per-workspace.dqk_ / dqa_) without org owner / admin
authority carry no workspace membership, so they see an empty list —
consistent with workspace_principal rejecting them. (An org-wide
programmatic token still authenticates; it just has no membership
rows to surface here.)Any authenticated org member may call this; org_id must match the
principal's org (403 otherwise). The list is empty rather than 404 when
the caller has no accessible workspaces.
| org_id required | string <uuid> (Org Id) |
nullCreate a new workspace under org_id.
Returns the new workspace shape (id / name / slug /
org_id / timestamps). A slug collision against another workspace
in the same org returns 409 (the (org_id, slug) unique constraint
backstops the race). The QUOTA-1 gate (see dependencies=) rejects
creates that would exceed the org's plan max_workspaces with HTTP
402 + structured quota_exceeded body.
| org_id required | string <uuid> (Org Id) |
| name required | string (Name) [ 1 .. 255 ] characters |
| slug required | string (Slug) [ 2 .. 100 ] characters |
{- "name": "string",
- "slug": "string"
}nullDelete (archive) a workspace. See the policy block above.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| confirm_name required | string (Confirm Name) The workspace's exact name, typed back. |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Fetch a workspace's display fields (name, slug, timestamps).
Any org member can read — restricting reads to workspace-admins would make the Settings landing card unrenderable for editors / viewers, who are the bulk of users.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
nullRename and/or re-slug a workspace.
Wire shape:
.. code-block:: json
{ "name": "Analytics — Prod", "slug": "analytics-prod" }
Either or both fields may be present. Sending neither is 422.
Authorization is owner / workspace_admin (see
:func:_require_workspace_admin). A slug collision against another
workspace in the same org returns 409 (the (org_id, slug) unique
constraint backstops the race). Step-up auth is intentionally NOT
required: a rename is reversible and doesn't bridge credentials —
unlike invite-create / IdP CRUD which do gate on fresh_auth.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Slug (string) or Slug (null) (Slug) |
{- "name": "string",
- "slug": "string"
}null#279: the inventory the Danger-zone dialog lists before a delete, plus
is_last — true when this is the org's only live workspace, so the dialog
can warn that the org will be left without one (#432: it no longer blocks).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
nullRestore a deleted workspace. See the policy block above.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "created_at": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "schedules_resumed": 0,
- "slug": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| org_id required | string <uuid> (Org Id) |
required | object (Config) |
| default_role | string (Default Role) Default: "viewer" Enum: "super_admin" "technical_admin" "editor" "viewer" |
| enforced | boolean (Enforced) Default: false |
| jit_provisioning_enabled | boolean (Jit Provisioning Enabled) Default: false |
Jit Team Prefix (string) or Jit Team Prefix (null) (Jit Team Prefix) | |
| kind required | string (Kind) Enum: "oidc" "saml" |
| name required | string (Name) |
{- "config": { },
- "default_role": "super_admin",
- "enforced": false,
- "jit_provisioning_enabled": false,
- "jit_team_prefix": "string",
- "kind": "oidc",
- "name": "string"
}nullSSO-2: preview an OIDC IdP's discovery document by issuer URL.
Owners and admins paste the IdP's issuer URL (e.g.
https://your-tenant.okta.com) into the "Add identity provider"
modal and click Discover. The UI POSTs here, we GET
{issuer}/.well-known/openid-configuration server-side, and return
the trimmed fields the form needs. The endpoint is read-only: no
IdP row is created or modified.
Two layers of SSRF protection apply (defence in depth, per the SSO-2 spec):
_validate_issuer_url enforces https://, parses out a
hostname, runs :func:validate_destination_host to reject RFC-1918,
loopback, link-local, and cloud-metadata addresses before any
socket is opened. Any failure here surfaces as 400 with a
sanitised reason — the raw exception is never propagated to the
client because it could embed an internal hostname / port.~dq_cloud_api.auth.sso.fetch_discovery then runs
:func:~dq_cloud_api.auth.sso._ssrf_guard on both the issuer URL
and every URL returned in the discovery document (token_endpoint,
jwks_uri). That guard catches the case where the IdP is
publicly resolvable but tries to direct us at an internal endpoint
in its discovery response.On upstream failure (non-200 from the IdP, malformed JSON, missing
required field), fetch_discovery raises HTTPException(502)
— we catch that and re-raise as 400 so the UI can render it inline
next to the issuer field rather than as a "Bad Gateway" toast.
The response shape carries the four endpoints the create form needs
(authorization_endpoint, token_endpoint, userinfo_endpoint,
jwks_uri) plus two informational arrays
(scopes_supported, claims_supported) the UI shows as
chips so the admin can confirm the IdP supports what they expect.
| org_id required | string <uuid> (Org Id) |
| issuer_url required | string (Issuer Url) |
{- "issuer_url": "string"
}null| org_id required | string <uuid> (Org Id) |
| idp_id required | string <uuid> (Idp Id) |
Config (object) or Config (null) (Config) | |
Default Role (string) or Default Role (null) (Default Role) | |
Enforced (boolean) or Enforced (null) (Enforced) | |
Jit Provisioning Enabled (boolean) or Jit Provisioning Enabled (null) (Jit Provisioning Enabled) | |
Jit Team Prefix (string) or Jit Team Prefix (null) (Jit Team Prefix) | |
Name (string) or Name (null) (Name) |
{- "config": { },
- "default_role": "super_admin",
- "enforced": true,
- "jit_provisioning_enabled": true,
- "jit_team_prefix": "string",
- "name": "string"
}nullReview of PR #391: EXPLICITLY link the signed-in user's OWN account to one of their org's identity providers.
The SSO callback never AUTO-links an IdP identity by email to an
org-admin-tier account (super_admin / technical_admin): whoever configures
an IdP controls the email it asserts. An admin who wants to sign in with
SSO proves their password here first (fresh_auth step-up); the
returned redirect_url carries a one-shot state naming THEIR user id,
and the callback links the IdP identity to that account only. Any member
may use it for themselves; interactive user sessions only.
| org_id required | string <uuid> (Org Id) |
| idp_id required | string <uuid> (Idp Id) |
nullrequired | object (IncidentCreate) | ||||||||||||||||||||
| |||||||||||||||||||||
{- "data": {
- "assignee_email": "string",
- "assignee_user_id": "e942d24d-8cee-4fa2-bf68-164509e32e2f",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "root_cause": "string",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "severity": "info",
- "title": "string",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
}null#156 PR-2: upsert the rotation for a (scope, scope_ref_id).
Validates scope/ref coherence, that the scope target is VISIBLE in this
workspace (team belongs to this workspace; asset resolves through the
isolation-filtered DataAsset select — invisible → 404, same clamp
posture as create_incident), and that every member is an assignable
member of the workspace (non-member → 422, reusing PR-1's validator).
The round-robin cursor is preserved by user-id across an edit when the
previous next-assignee is still in the list, else clamped to 0 (product
fork FE).
required | object (IncidentRotationWrite) #156 PR-2: upsert body for a rotation config (keyed on scope + ref).
| ||||||
| |||||||
{- "data": {
- "member_user_ids": [ ],
- "scope": "workspace",
- "scope_ref_id": "55f1c626-5f44-4204-8b3c-1419f5efdb80"
}
}null| org_id required | string <uuid> (Org Id) |
Team Id (string) or Team Id (null) (Team Id) Narrow the summary to one team's slice (#141). Workspace prefix only — must be a live team of this workspace, else 404. Omit for the whole-workspace (or, on the org prefix, org-wide) numbers. |
null| resource_id required | string <uuid> (Resource Id) |
required | object (IncidentUpdate) | ||||||||||||||||||
| |||||||||||||||||||
{- "data": {
- "assignee_email": "string",
- "assignee_user_id": "e942d24d-8cee-4fa2-bf68-164509e32e2f",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "resolution_note": "string",
- "root_cause": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "severity": "info",
- "status": "open",
- "title": "string"
}
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
required | object (_IncidentEventPayload) | ||
| |||
{- "data": {
- "body": "string"
}
}null#155: push this incident to an external ServiceNow / PagerDuty ticket.
Reuses one of the workspace's EXISTING ServiceNow / PagerDuty alert-rule
destinations (its stored, encrypted creds) to open one upstream ticket,
stamps the external_ticket_* columns on the incident, and appends an
external_ticket timeline entry. Push-only: there is no sync-back — a
second push is refused (409) so an incident links at most one ticket.
| resource_id required | string <uuid> (Resource Id) |
required | object (_ExternalTicketPayload) | ||
| |||
{- "data": {
- "alert_rule_id": "a87b6b3b-8bd5-4bdb-9f21-206bb29a1fe6"
}
}null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (IncidentCreate) | ||||||||||||||||||||
| |||||||||||||||||||||
{- "data": {
- "assignee_email": "string",
- "assignee_user_id": "e942d24d-8cee-4fa2-bf68-164509e32e2f",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "root_cause": "string",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "severity": "info",
- "title": "string",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
}null#156 PR-2: upsert the rotation for a (scope, scope_ref_id).
Validates scope/ref coherence, that the scope target is VISIBLE in this
workspace (team belongs to this workspace; asset resolves through the
isolation-filtered DataAsset select — invisible → 404, same clamp
posture as create_incident), and that every member is an assignable
member of the workspace (non-member → 422, reusing PR-1's validator).
The round-robin cursor is preserved by user-id across an edit when the
previous next-assignee is still in the list, else clamped to 0 (product
fork FE).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (IncidentRotationWrite) #156 PR-2: upsert body for a rotation config (keyed on scope + ref).
| ||||||
| |||||||
{- "data": {
- "member_user_ids": [ ],
- "scope": "workspace",
- "scope_ref_id": "55f1c626-5f44-4204-8b3c-1419f5efdb80"
}
}null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Team Id (string) or Team Id (null) (Team Id) Narrow the summary to one team's slice (#141). Workspace prefix only — must be a live team of this workspace, else 404. Omit for the whole-workspace (or, on the org prefix, org-wide) numbers. |
null| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (IncidentUpdate) | ||||||||||||||||||
| |||||||||||||||||||
{- "data": {
- "assignee_email": "string",
- "assignee_user_id": "e942d24d-8cee-4fa2-bf68-164509e32e2f",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "resolution_note": "string",
- "root_cause": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "severity": "info",
- "status": "open",
- "title": "string"
}
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_IncidentEventPayload) | ||
| |||
{- "data": {
- "body": "string"
}
}null#155: push this incident to an external ServiceNow / PagerDuty ticket.
Reuses one of the workspace's EXISTING ServiceNow / PagerDuty alert-rule
destinations (its stored, encrypted creds) to open one upstream ticket,
stamps the external_ticket_* columns on the incident, and appends an
external_ticket timeline entry. Push-only: there is no sync-back — a
second push is refused (409) so an incident links at most one ticket.
| resource_id required | string <uuid> (Resource Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_ExternalTicketPayload) | ||
| |||
{- "data": {
- "alert_rule_id": "a87b6b3b-8bd5-4bdb-9f21-206bb29a1fe6"
}
}null| org_id required | string <uuid> (Org Id) |
| email required | string <email> (Email) |
| role | string (Role) Default: "editor" Enum: "super_admin" "technical_admin" "workspace_admin" "team_admin" "editor" "viewer" |
Team Id (string) or Team Id (null) (Team Id) | |
Workspace Id (string) or Workspace Id (null) (Workspace Id) |
{- "email": "user@example.com",
- "role": "super_admin",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}nullRevoke a pending invite.
We delete the row rather than mark it accepted_at — the row's
purpose is to track a still-usable invite link, and a revoked invite
is no different from one that never existed. The audit row preserves
the trail.
| org_id required | string <uuid> (Org Id) |
| invite_id required | string <uuid> (Invite Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}List every user in the org with all their workspace + team memberships.
Issue #649 Part B: a super admin auditing "who exists where" shouldn't have
to hop between workspaces. This returns one row per OrgMembership user,
each carrying their org role plus the workspace-membership rows and
team-membership rows they hold across the whole org — regardless of which
workspace is currently selected.
Gating: org-admin tier only (super_admin — see
:data:ORG_ADMIN_ROLES). A
workspace_admin / technical_admin / team role gets a 403; they keep
the workspace-scoped /workspaces/{ws}/members view. The data lives in
public-schema tables (org_memberships / workspace_memberships /
team_memberships), which the TENANCY-1 workspace-isolation listener does
NOT touch, so there's no cross-workspace leak to guard against here beyond
the org-id scoping — and the gate keeps non-super-admins from ever reaching
this aggregate.
Each member row's workspaces / teams are sorted by name so the UI
table is stable without a client-side sort. Returns the standard
{"data": [...]} envelope.
| org_id required | string <uuid> (Org Id) |
nullList members of a workspace + their workspace role.
Owner / workspace_admin only. Returns the joined User shape so the
UI doesn't have to do a second roundtrip per row.
Issue #188: org-level super admins are implicitly present in every
workspace of their org — the seed/first-admin flow (and the SSO-JIT
provisioning path) creates an OrgMembership row but doesn't always
seed a matching WorkspaceMembership. The previous query joined only
against workspace_memberships and so reported "No workspace members"
for a freshly-seeded org where the owner is the only user. Merge in the
org-level super admins so the principal viewing this page always sees
themself (and any other super admins) in the list. Workspace-
membership rows win the role tie-break when both exist, since that's
where the per-workspace UI gestures (role change, remove) take effect.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
nullGive an existing org member access to a workspace at role.
The Members page's per-member "Workspace access" dialog uses this to add someone to a workspace they aren't in yet; PATCH changes the role after that and DELETE removes it. Inviting is still how a new person joins the org. Same authority as PATCH: an org super admin, or a workspace admin of this workspace, capped by the grant ceiling. A super admin already reaches every workspace through their org role, so adding one is a 409.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| role required | string (Role) Enum: "workspace_admin" "editor" "viewer" |
| user_id required | string <uuid> (User Id) |
{- "role": "workspace_admin",
- "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
}nullRemove a user from a workspace. The org membership stays put.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| user_id required | string <uuid> (User Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Change a workspace member's role.
Refuses to demote the principal themself — a workspace admin can't
accidentally lock themselves out of the admin surface on the same
request. (Org owners aren't subject to workspace roles, so the org
owner can demote anyone, including their own WorkspaceMembership.)
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| user_id required | string <uuid> (User Id) |
| role required | string (Role) Enum: "workspace_admin" "editor" "viewer" |
{- "role": "workspace_admin"
}nullC-22-4: mark a workspace VIEWER 'restricted' or 'unrestricted'.
A restricted viewer cannot see data assets flagged is_restricted
(PII / PHI / PCI) — nor those assets' results, profiles, anomalies or AI
suggestions — in this workspace (the do_orm_execute listener in
db/workspace_isolation.py enforces it). This is the write path that
activates that filter.
Authority: a workspace-admin-or-higher decision, the same bar as flipping
an asset's is_restricted flag (CAN_MANAGE_RESTRICTED_FLAG) — the
org super admin, or a user whose EFFECTIVE role in this workspace is
workspace admin (direct membership or team grant). Only a viewer
membership carries a level (400 otherwise). Audited as
workspace_membership.viewer_access_updated.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| user_id required | string <uuid> (User Id) |
| viewer_access_level required | string (Viewer Access Level) Enum: "restricted" "unrestricted" |
{- "viewer_access_level": "restricted"
}nullApprove a pending join request. Adds the user as an org editor
member and stamps the row as decided.
| org_id required | string <uuid> (Org Id) |
| request_id required | string <uuid> (Request Id) |
nullClaim a domain. Body {domain}. Returns the verification token
and the TXT record the admin must add.
| org_id required | string <uuid> (Org Id) |
| domain required | string (Domain) |
{- "domain": "string"
}nullUn-claim a domain. Future signups at this address fall back to the default "create new org" flow.
| org_id required | string <uuid> (Org Id) |
| domain_id required | string <uuid> (Domain Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Run a DNS TXT lookup against the claimed domain and flip
verified_at if the record matches.
Returns 200 with {verified_at: ...} on success. 422 if no TXT
record on the domain carries the expected dq-verify=<token>
value, or if the resolver returns NXDOMAIN / no-answer.
DNS work goes through dns.resolver.resolve so tests can
monkeypatch the symbol with a stub. The resolver call is wrapped in
a tight timeout — a slow / dead DNS server shouldn't hang an admin
request indefinitely.
| org_id required | string <uuid> (Org Id) |
| domain_id required | string <uuid> (Domain Id) |
null| org_id required | string <uuid> (Org Id) |
required | object (CreateKeyPayload) | ||||
| |||||
{- "data": {
- "name": "string",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
}nullReturn the org's plan + per-quota usage + limit.
Wire shape::
{
"data": {
"plan": "free",
"upgrade_url": "/settings/billing",
"quotas": {
"workspaces": {"current": 1, "limit": 1},
"users": {"current": 3, "limit": 5},
"datasources": {"current": 2, "limit": 3},
"monthly_runs": {"current": 47, "limit": 1000}
}
}
}
Any authenticated org member can read this — the matrix isn't sensitive and the UI needs it for the dashboard chrome regardless of role.
| org_id required | string <uuid> (Org Id) |
{ }Super Admin "All workspaces" aggregator (design §3.3).
Org-admin-tier only: callers below super_admin (or the legacy
owner / admin compat spellings) get a 403 — they don't have
full-org visibility, so an "all workspaces" roll-up would silently lie
to them. CAN_VIEW_RESULTS + the default token-type gate keep agent /
SCIM tokens out.
| org_id required | string <uuid> (Org Id) |
Cursor (string) or Cursor (null) (Cursor) | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 |
Since (string) or Since (null) (Since) Inclusive run_time lower bound (ISO 8601). Naive timestamps are interpreted as UTC. | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "batch_kind": "string",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "checkpoint_name": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "partition_value": "string",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_group_name": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "run_time": "2019-08-24T14:15:22Z",
- "success": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string",
- "summary": {
- "by_status": {
- "failed": 0,
- "success": 0
}, - "by_team": {
- "property1": {
- "count": 0,
- "failed": 0,
- "passed": 0
}, - "property2": {
- "count": 0,
- "failed": 0,
- "passed": 0
}
}, - "by_workspace": {
- "property1": {
- "count": 0,
- "failed": 0,
- "passed": 0
}, - "property2": {
- "count": 0,
- "failed": 0,
- "passed": 0
}
}, - "team_names": {
- "property1": {
- "archived": false,
- "name": "string"
}, - "property2": {
- "archived": false,
- "name": "string"
}
}, - "total_count": 0,
- "workspace_names": {
- "property1": {
- "archived": false,
- "name": "string"
}, - "property2": {
- "archived": false,
- "name": "string"
}
}
}
}Workspace Admin "All teams" aggregator (design §3.3).
Gating: workspace_principal (org match + workspace membership; org
admins bypass per HARDEN-17) + CAN_VIEW_RESULTS. Any member tier may
call it — the roll-up reflects exactly the slice their pinned workspace +
team contexts admit, so a team-tier caller gets their teams' buckets plus
workspace_shared, never a sibling team's.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Cursor (string) or Cursor (null) (Cursor) | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 |
Since (string) or Since (null) (Since) Inclusive run_time lower bound (ISO 8601). Naive timestamps are interpreted as UTC. | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "batch_kind": "string",
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "checkpoint_name": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",
- "partition_value": "string",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_group_name": "string",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rule_suite_name": "string",
- "run_time": "2019-08-24T14:15:22Z",
- "success": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}
], - "next_cursor": "string",
- "summary": {
- "by_team": {
- "property1": {
- "count": 0,
- "failed": 0,
- "passed": 0
}, - "property2": {
- "count": 0,
- "failed": 0,
- "passed": 0
}
}, - "total_count": 0
}
}| org_id required | string <uuid> (Org Id) |
Name (string) or Name (null) (Name) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }| org_id required | string <uuid> (Org Id) |
Name (string) or Name (null) (Name) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }| org_id required | string <uuid> (Org Id) |
| resource_id required | string <uuid> (Resource Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{ }DEPRECATED: dry-run lint of a legacy suite's rules (nothing is written).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| suite_id required | string <uuid> (Suite Id) |
{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Name (string) or Name (null) (Name) | |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }| org_id required | string <uuid> (Org Id) |
| resource_id required | string <uuid> (Resource Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{ }The zone every new schedule runs in, and how existing schedules compare.
Readable by every org member — it names a timezone, nothing secret — so the schedule form and calendar can show times in it. The schedule counts cover only the schedules the caller can see (workspace / team isolation applies).
| org_id required | string <uuid> (Org Id) |
{- "data": {
- "aligned_schedules": 0,
- "mismatched_schedules": 0,
- "schedule_timezones": [ ],
- "source": "org",
- "timezone": "string"
}
}Set the organization schedule timezone — org admins only (audited).
The zone is validated against the IANA database (zoneinfo); an unknown
one is a 422. Saving it changes the zone NEW schedules run in and the
zone the app shows times in. Existing schedules keep their own zone — and
so their firing times — unless align_schedules is true: then every
schedule whose zone differs is moved onto this one (its cron is read in the
new zone from its next run on) and aligned_schedules says how many.
| org_id required | string <uuid> (Org Id) |
| align_schedules | boolean (Align Schedules) Default: false Also move every existing schedule whose zone differs onto this one, recomputing its next run. Off by default: saving the setting alone never shifts an existing schedule's firing time. |
| timezone required | string (Timezone) [ 1 .. 100 ] characters IANA zone, e.g. 'America/New_York' or 'UTC'. |
{- "align_schedules": false,
- "timezone": "America/New_York"
}{- "data": {
- "aligned_schedules": 0,
- "mismatched_schedules": 0,
- "schedule_timezones": [ ],
- "source": "org",
- "timezone": "string"
}
}DEPRECATED (EPIC PR 8, checkpoints retired): creates a schedule that runs
the checkpoint's mapped validation (checkpoints.validation_id). 409 when
the checkpoint maps to none. Use POST .../schedules instead.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| checkpoint_id required | string <uuid> (Checkpoint Id) |
| cron_expression required | string (Cron Expression) |
| enabled | boolean (Enabled) Default: true |
Team Id (string) or Team Id (null) (Team Id) Team to scope this schedule to. Omit to inherit the checkpoint's team; send null explicitly to keep the schedule workspace-shared. | |
Timezone (string) or Timezone (null) (Timezone) IANA zone the cron is read in. Omit to use the organization schedule timezone (Settings → Scheduling; UTC until an admin sets one). |
{- "cron_expression": "0 6 * * *",
- "enabled": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "timezone": "string"
}null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Validation Id (string) or Validation Id (null) (Validation Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
nullEPIC PR 7: create a schedule that runs one to many validations
(validation_ids, required); each firing starts one execution run per
validation.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| cron_expression required | string (Cron Expression) |
| enabled | boolean (Enabled) Default: true |
Team Id (string) or Team Id (null) (Team Id) Team to scope this schedule to. Omit to inherit the checkpoint's team; send null explicitly to keep the schedule workspace-shared. | |
Timezone (string) or Timezone (null) (Timezone) IANA zone the cron is read in. Omit to use the organization schedule timezone (Settings → Scheduling; UTC until an admin sets one). | |
Array of Validation Ids (strings) or Validation Ids (null) (Validation Ids) The validations this schedule runs (1 to 50, same workspace). On |
{- "cron_expression": "0 6 * * *",
- "enabled": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "timezone": "string",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| schedule_id required | string <uuid> (Schedule Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| schedule_id required | string <uuid> (Schedule Id) |
Cron Expression (string) or Cron Expression (null) (Cron Expression) | |
Enabled (boolean) or Enabled (null) (Enabled) | |
Team Id (string) or Team Id (null) (Team Id) | |
Timezone (string) or Timezone (null) (Timezone) | |
Array of Validation Ids (strings) or Validation Ids (null) (Validation Ids) Replace the validations this schedule runs (1 to 50, same workspace). |
{- "cron_expression": "string",
- "enabled": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "timezone": "string",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}null#712: cron-schedule a validation. EPIC PR 7: validation_ids in the
body adds more validations to the same schedule; each firing starts one
execution run per validation (worker.jobs.enqueue_scheduled).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| rule_group_id required | string <uuid> (Rule Group Id) |
| cron_expression required | string (Cron Expression) |
| enabled | boolean (Enabled) Default: true |
Team Id (string) or Team Id (null) (Team Id) Team to scope this schedule to. Omit to inherit the checkpoint's team; send null explicitly to keep the schedule workspace-shared. | |
Timezone (string) or Timezone (null) (Timezone) IANA zone the cron is read in. Omit to use the organization schedule timezone (Settings → Scheduling; UTC until an admin sets one). | |
Array of Validation Ids (strings) or Validation Ids (null) (Validation Ids) The validations this schedule runs (1 to 50, same workspace). On |
{- "cron_expression": "0 6 * * *",
- "enabled": true,
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "timezone": "string",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}null| org_id required | string <uuid> (Org Id) |
| name required | string (Name) |
{- "name": "string"
}nullMint a service account; returns the raw dqsa_ token exactly once.
| org_id required | string <uuid> (Org Id) |
| name required | string (Name) [ 1 .. 255 ] characters |
| role required | string (Role) Enum: "super_admin" "technical_admin" "workspace_admin" "team_admin" "editor" "viewer" |
Team Id (string) or Team Id (null) (Team Id) | |
Workspace Id (string) or Workspace Id (null) (Workspace Id) |
{- "name": "string",
- "role": "super_admin",
- "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}nullRevoke a service account (soft delete) and bust its auth cache so the
dqsa_ token stops working immediately.
| org_id required | string <uuid> (Org Id) |
| service_account_id required | string <uuid> (Service Account Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Create or ROTATE the org's Slack integration.
Upsert on (org_id, slack_team_id) so re-posting with a new token
rotates it in place rather than colliding on the unique constraint.
| org_id required | string <uuid> (Org Id) |
| bot_token required | string (Bot Token) non-empty |
Slack Team Id (string) or Slack Team Id (null) (Slack Team Id) | |
Team Name (string) or Team Name (null) (Team Name) |
{- "bot_token": "string",
- "slack_team_id": "string",
- "team_name": "string"
}null| org_id required | string <uuid> (Org Id) |
| integration_id required | string <uuid> (Integration Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Resolve a batch of Slack member ids/mentions to display names.
GRACEFULLY DEGRADES: with no integration configured (or on any Slack
error / outage) it returns null for every id plus
token_configured: false — NEVER a 500. Subteams / specials / unknown
shapes (DECISION-3) resolve to null and pass through at the UI.
| org_id required | string <uuid> (Org Id) |
| ids | Array of strings (Ids) <= 100 items |
{- "ids": [
- "string"
]
}nullUX gaps D4: search the org's Slack members by NAME for the mention picker.
Backed by Slack users.list (the users:read scope the bot token
already needs for users.info) through the connected per-org bot token:
the walk follows Slack's pagination cursor, and the reduced
{id, name, real_name, handle} directory is cached in Redis per org on a
short TTL, so each keystroke searches the cache rather than Slack. The rule
still STORES the picked member's id (the delivered message keeps the live
<@id> mention).
GRACEFULLY DEGRADES, never a 500: no integration → token_configured: false (the UI keeps its paste-an-id field); a Slack failure → an empty
list + a sanitised Slack error CODE. The token is never returned or logged.
| org_id required | string <uuid> (Org Id) |
| q | string (Q) <= 100 characters Default: "" Name to search for (case-insensitive). |
| limit | integer (Limit) [ 1 .. 50 ] Default: 20 Max members returned. |
{- "data": [
- {
- "handle": "",
- "id": "string",
- "name": "string",
- "real_name": ""
}
], - "error": "string",
- "token_configured": true
}List teams in the org, newest first, each with its member count.
Optionally filter to a single workspace with the workspace_id query
param (the workspace's Teams settings page passes it). Org-level
listing (no filter) still returns every team — including orphans
(workspace_id IS NULL) so an admin can find + re-parent them.
| org_id required | string <uuid> (Org Id) |
Workspace Id (string) or Workspace Id (null) (Workspace Id) |
nullCreate a new team under a workspace. 409 on a slug collision within that workspace; 404 if the workspace is unknown / in another org.
| org_id required | string <uuid> (Org Id) |
Description (string) or Description (null) (Description) | |
External Group Id (string) or External Group Id (null) (External Group Id) | |
| name required | string (Name) [ 1 .. 80 ] characters |
| slug required | string (Slug) [ 2 .. 80 ] characters |
| workspace_id required | string <uuid> (Workspace Id) |
{- "description": "string",
- "external_group_id": "string",
- "name": "string",
- "slug": "string",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}nullDelete a team. Cascades wipe its memberships via the FK.
| org_id required | string <uuid> (Org Id) |
| team_id required | string <uuid> (Team Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| team_id required | string <uuid> (Team Id) |
Description (string) or Description (null) (Description) | |
External Group Id (string) or External Group Id (null) (External Group Id) | |
Name (string) or Name (null) (Name) | |
Slug (string) or Slug (null) (Slug) | |
Workspace Id (string) or Workspace Id (null) (Workspace Id) |
{- "description": "string",
- "external_group_id": "string",
- "name": "string",
- "slug": "string",
- "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
}nullAdd a user to a team.
Rejects users that are not :class:OrgMembership rows of the same org —
a team is org-scoped by construction. Returns 400 on a cross-org user
id (the request is malformed for this org's surface). Existing
membership is a no-op idempotent path so SCIM clients can replay the
call without surfacing a conflict.
Status code distinguishes the two outcomes so callers (notably SCIM PATCH replays) can tell a fresh insert from a redundant retry:
TeamMembership row was inserted.The decorator deliberately omits status_code so the handler can
return a :class:JSONResponse with the right code on each branch
(FastAPI's status_code= decorator argument is a single fixed
value, which is what produced the original 201-on-no-op bug).
| org_id required | string <uuid> (Org Id) |
| team_id required | string <uuid> (Team Id) |
| added_via | string (Added Via) Default: "manual" Enum: "manual" "invite" |
| role | string (Role) Default: "viewer" Enum: "team_admin" "editor" "viewer" "workspace_admin" |
| user_id required | string <uuid> (User Id) |
{- "added_via": "manual",
- "role": "team_admin",
- "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
}null| org_id required | string <uuid> (Org Id) |
| team_id required | string <uuid> (Team Id) |
| user_id required | string <uuid> (User Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Change a team member's role and/or a team viewer's access level.
Phase-1 Unit 3: a member's role on the team drives their effective
workspace role (the team's workspace_id + this role). A no-op
re-role (same value) returns the existing row without an audit event.
C-22-4: viewer_access_level ('restricted' | 'unrestricted') is only
valid when the resulting role is viewer (400 otherwise) and takes the
workspace-admin bar (:func:ensure_can_set_viewer_access) on the team's
workspace — a team admin manages the roster but not data visibility. A
role change away from viewer clears the level; a change TO viewer starts
at 'unrestricted' unless the body sets one.
| org_id required | string <uuid> (Org Id) |
| team_id required | string <uuid> (Team Id) |
| user_id required | string <uuid> (User Id) |
Role (string) or Role (null) (Role) | |
Viewer Access Level (string) or Viewer Access Level (null) (Viewer Access Level) |
{- "role": "team_admin",
- "viewer_access_level": "restricted"
}nullEvery run in flight in this workspace, newest first. See the module docstring for the three sources, the window, and the scoping.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": [
- {
- "cancel_requested": false,
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "kind": "validation",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_id": "dded282c-8ebd-44cf-8ba5-9a234973d1ec",
- "running_asset_id": "87c938d9-123e-4426-b9d3-8735dfc2a0b5",
- "running_asset_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "schedule_id": "8929bb67-0da1-406c-99d2-5447376a4f58",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "trigger": "string",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc",
- "validation_name": "string"
}
]
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Kind (string) or Kind (null) (Kind) | |
Status (string) or Status (null) (Status) | |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
required | Kind (string) or Kind (string) (Kind) |
Llm Config Id (string) or Llm Config Id (null) (Llm Config Id) | |
Validation Result Id (string) or Validation Result Id (null) (Validation Result Id) |
{- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "kind": "sql_rule",
- "llm_config_id": "6ca9eb9b-0294-4b27-bd8f-f3502be7c77b",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}nullAccept many AISuggestion rows as rules on their assets in a single
request. Per-row errors are reported in the response — a single missing
or incompatible suggestion does not block the rest of the batch.
Response shape:
.. code-block:: json
{
"accepted": ["<uuid>", ...],
"skipped": [{"id": "<uuid>", "reason": "already accepted"}, ...],
"failed": [{"id": "<uuid>", "error": "Suggestion not found"}, ...]
}
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
Rule Suite Id (string) or Rule Suite Id (null) (Rule Suite Id) | |
| suggestion_ids required | Array of strings <uuid> (Suggestion Ids) [ 1 .. 200 ] items [ items <uuid > ] |
{- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "suggestion_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}{ }Mark many AISuggestion rows as rejected/dismissed in one
transaction.
Uses review_status = "rejected" to stay compatible with the
single-row reject path (POST .../review) — the wire-level
terminology is "dismiss" (FEATURE-39), but on disk the value is the
same one the PARITY-8 reject flow writes. Already-reviewed rows are
reported in skipped and left untouched.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Reason (string) or Reason (null) (Reason) | |
| suggestion_ids required | Array of strings <uuid> (Suggestion Ids) [ 1 .. 200 ] items [ items <uuid > ] |
{- "reason": "string",
- "suggestion_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}{ }Add the suggested rules to the target asset and mark accepted.
PARITY-8. The body of the work lives in :func:apply_accept_suggestion
so FEATURE-39's bulk-accept path can call exactly the same routine on
each suggestion id in its batch — see
:mod:dq_cloud_api.routers.ai_bulk.
Authz is per-kind (:func:enforce_accept_gate) and runs AFTER the
suggestion is loaded — see the route decorator note.
HARDEN-21 / API review wave-7 (H1): emits the same per-suggestion audit row the bulk path writes, so the timeline carries the same granularity whether the operator clicked "Accept all" or single-stepped through.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| suggestion_id required | string <uuid> (Suggestion Id) |
NewValidation (object) or null | |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
Rule Suite Id (string) or Rule Suite Id (null) (Rule Suite Id) | |
| validation_ids | Array of strings <uuid> (Validation Ids) [ items <uuid > ] |
{- "create_validation": {
- "name": "string"
}, - "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}nullMark a single AI suggestion as accepted/rejected/dismissed.
HARDEN-21 / API review wave-7 (H2): emits an audit row keyed to the
same action vocabulary as ai_bulk.bulk_dismiss_suggestions so the
audit timeline has matching granularity whether the user took action via
the single-row endpoint or the bulk batch.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| suggestion_id required | string <uuid> (Suggestion Id) |
Notes (string) or Notes (null) (Notes) | |
| status required | string (Status) Enum: "accepted" "rejected" |
{- "notes": "string",
- "status": "accepted"
}{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Enqueue an LLM job that summarises an asset's latest profile.
Phase-2 native AI. The worker (:func:generate_profile_summary) loads the
asset's most recent COMPLETED profile run and writes
{summary, findings} to the suggestion's payload. With no profile yet
it summarises from the asset's metadata instead and marks the payload
profile_based: false + a "run a profile for a better summary" note
(asks U4). Read-only — poll GET /ai-suggestions/{id} for the result;
there's no accept flow.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| asset_id required | string <uuid> (Asset Id) |
| reuse_existing | boolean (Reuse Existing) Default: false |
{- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "reuse_existing": false
}nullAsk the AI to propose a severity for each of an asset's rules.
Returns {"data": {"suggestion_id": ...}}. The worker loads the asset's
rules (and its latest profile, when there is one), asks the LLM for a
relative severity per rule with a one-sentence reason, and writes
{reviews, dropped} to the suggestion's payload. Read-only: no rule is
changed. 404 for an unknown asset, 422 when the asset has no rules, 400
when no AI engine is configured.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| asset_id required | string <uuid> (Asset Id) |
{- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4"
}{- "data": {
- "suggestion_id": "c3383f81-61a5-431f-9e29-b95019c63114"
}
}Enqueue an LLM job that translates a natural-language rule into a custom-SQL expectation. FEATURE-23.
The worker (:func:generate_sql_rule_suggestion) loads the asset's
most recent profile, prompts the configured LLM with the column
metadata + the user's English description, validates the returned SQL
via :func:dq_shared.expectations.validate_custom_sql_query, and
writes {sql, rationale, confidence, natural_language} to the
suggestion's payload column. The UI polls
GET /ai-suggestions/{id} and renders the proposal.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| asset_id required | string <uuid> (Asset Id) |
| natural_language required | string (Natural Language) |
{- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "natural_language": "string"
}nullEnqueue an LLM job that authors validated rule proposals for an asset.
Phase-2 native AI. The worker (:func:generate_rule_suggestions) loads the
asset's most recent COMPLETED profile + existing rules + the expectation
gallery, asks the LLM for {rule_type, kwargs, rationale, confidence}
proposals, validates each against the gallery kwargs schema (dropping the
invalid ones), and writes {suggestions, dropped} to the payload. Accept
the proposals into the asset's suite via
POST /ai-suggestions/{id}/accept.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| asset_id required | string <uuid> (Asset Id) |
Guidance (string) or Guidance (null) (Guidance) | |
| reuse_existing | boolean (Reuse Existing) Default: false |
{- "asset_id": "b4695157-0d1d-4da0-8f9e-5c53149389e4",
- "guidance": "string",
- "reuse_existing": false
}nullStart drafting a validation with AI. Returns {suggestion_id, reused}.
Target assets are optional. Named assets must be visible to the caller and in this workspace, and must not mix two restricted teams (422). An identical request by the same user that is still in flight, or finished within the hour and not yet accepted, is reused rather than paid for twice.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Array of Data Asset Ids (strings) or Data Asset Ids (null) (Data Asset Ids) | |
| guidance required | string (Guidance) [ 3 .. 1000 ] characters |
{- "data_asset_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "guidance": "string"
}{ }Create the validation from the reviewed draft, atomically.
Creates the validation (named and described as the user left it), creates the new rules — reusing an existing rule of the asset that checks the same thing — and puts each asset in the validation with exactly the chosen rules. Any failure (a hidden asset, an invalid rule, mixed restricted teams, a rule that isn't the asset's) writes nothing.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| suggestion_id required | string <uuid> (Suggestion Id) |
Description (string) or Description (null) (Description) | |
required | Array of objects (Items) [ 1 .. 10 ] items |
| name required | string (Name) [ 1 .. 255 ] characters |
{- "description": "string",
- "items": [
- {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "existing_rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "new_rules": [
- {
- "kwargs": { },
- "rule_type": "string"
}
]
}
], - "name": "string"
}{ }List anomaly events for one workspace, newest-first with cursor pagination.
API review wave-7 / M5: previously this endpoint hard-capped at 200 with no
cursor, which was both too small for noisy 24-hour drift windows and
incompatible with incremental UI rendering. Now mirrors the
GET /audit-events pagination shape — opaque base64 cursor of
"<detected_at_iso>|<id>" for stable newest-first iteration through
sub-millisecond-collision rows.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Cursor (string) or Cursor (null) (Cursor) | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
| unacknowledged_only | boolean (Unacknowledged Only) Default: false |
nullDownload the (filtered) anomaly-events list as an .xlsx workbook (#130
PR 3).
Honours the SAME data_asset_id filter the list endpoint accepts, so a
per-asset export (the /anomalies?asset= deep-link) matches the on-screen
feed. Gated by CAN_VIEW_RESULTS ("export what you can see") and by
scope_to_visible_assets (the same parent-asset visibility gate the list
uses — #622 finding 1: anomaly rows leak observed/baseline actuals). The
workspace_principal dep + tenant_session keep workspace isolation in
force. Reuses the shared export helpers + the EXPORT_MAX_ROWS cap: past
it the request 400s and asks the caller to filter by asset. File attachment,
NOT the {"data": ...} envelope (the documented download exception).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
| format | string (Format) ^(xlsx)$ Default: "xlsx" |
null| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| event_id required | string <uuid> (Event Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}High-level AI account review over the period (Phase 1).
Reuses dashboard_metrics._compute_metrics (health score, pass rate,
coverage, severity counts, run counts) as the snapshot, feeds it to
account_review_messages, and generates a plain-English executive review
live. Graceful: no default LLM config → narrative_status='no_llm_config'
(review None), transient provider failure → 'error', both still 200.
Returns {"data": AccountReviewResponse}.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| period | integer (Period) Default: 30 Window in days — one of 30, 60, 90, 365 (the 30/60/90/1y selector). |
{- "data": {
- "completion_tokens": 0,
- "narrative_status": "generated",
- "period_days": 0,
- "prompt_tokens": 0,
- "review": "string"
}
}Synchronous explain call — suitable for UI tooltips and quick explanations of either a validation result or an anomaly event.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Anomaly Event Id (string) or Anomaly Event Id (null) (Anomaly Event Id) | |
Extra Context (string) or Extra Context (null) (Extra Context) | |
Llm Config Id (string) or Llm Config Id (null) (Llm Config Id) | |
| regenerate | boolean (Regenerate) Default: false |
Validation Result Id (string) or Validation Result Id (null) (Validation Result Id) |
{- "anomaly_event_id": "0a7ae1c5-4599-492f-a9c9-4f69bc3af0a6",
- "extra_context": "string",
- "llm_config_id": "6ca9eb9b-0294-4b27-bd8f-f3502be7c77b",
- "regenerate": false,
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}{- "cached": false,
- "completion_tokens": 0,
- "explanation": "string",
- "llm_config_id": "6ca9eb9b-0294-4b27-bd8f-f3502be7c77b",
- "prompt_tokens": 0
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
Status (string) or Status (null) (Status) |
[- {
- "columns": [
- {
- "dtype": "string",
- "is_unique_key": false,
- "max": { },
- "mean": 0,
- "min": { },
- "name": "string",
- "null_rate": 0,
- "sampled": false,
- "source_type": "string",
- "stddev": 0,
- "top_values": [
- null
], - "unique_count": 0
}
], - "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "error": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "row_count": 0,
- "run_at": "2019-08-24T14:15:22Z",
- "sampled_row_count": 0,
- "schema_hash": "string",
- "status": "pending"
}
]| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| data_asset_id required | string <uuid> (Data Asset Id) |
| sample_max_rows | integer (Sample Max Rows) Default: 10000 |
{- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "sample_max_rows": 10000
}{- "columns": [
- {
- "dtype": "string",
- "is_unique_key": false,
- "max": { },
- "mean": 0,
- "min": { },
- "name": "string",
- "null_rate": 0,
- "sampled": false,
- "source_type": "string",
- "stddev": 0,
- "top_values": [
- null
], - "unique_count": 0
}
], - "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "error": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "row_count": 0,
- "run_at": "2019-08-24T14:15:22Z",
- "sampled_row_count": 0,
- "schema_hash": "string",
- "status": "pending"
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| run_id required | string <uuid> (Run Id) |
{- "columns": [
- {
- "dtype": "string",
- "is_unique_key": false,
- "max": { },
- "mean": 0,
- "min": { },
- "name": "string",
- "null_rate": 0,
- "sampled": false,
- "source_type": "string",
- "stddev": 0,
- "top_values": [
- null
], - "unique_count": 0
}
], - "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "error": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "row_count": 0,
- "run_at": "2019-08-24T14:15:22Z",
- "sampled_row_count": 0,
- "schema_hash": "string",
- "status": "pending"
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| run_id required | string <uuid> (Run Id) |
{- "data": {
- "cancelled": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "status": "pending"
}
}Aggregate AI review of the CURRENT failures over a filtered slice (Phase 1).
Honours the SAME filters as the Results list / summary
(start_date / end_date / checkpoint_id / batch_definition_id
and — asks U4, additive — rule_group_id, the Results page's
"Validation" filter) with status=fail forced, reuses that summary
aggregation (failed count, pass rate, per-day trend, worst checkpoints /
rule groups) plus a small sample of recent failing rows, and generates a
plain-English review. Served from the narrative cache when the identical
failures were already reviewed (the worker pre-generates the unfiltered
review after a run with failures), so the auto-run card is usually free;
refresh=true bypasses the cached copy. Same graceful-degradation
contract as the account review. Returns
{"data": FailuresReviewResponse}.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Batch Definition Id (string) or Batch Definition Id (null) (Batch Definition Id) | |
Checkpoint Id (string) or Checkpoint Id (null) (Checkpoint Id) | |
Rule Group Id (string) or Rule Group Id (null) (Rule Group Id) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
| refresh | boolean (Refresh) Default: false When true, skip the narrative cache and generate a fresh review (the card's Regenerate). The fresh review then replaces the cached one. |
{- "data": {
- "completion_tokens": 0,
- "failed_count": 0,
- "narrative_status": "generated",
- "prompt_tokens": 0,
- "review": "string"
}
}A validation result's AI explanation IF one is already cached.
The background auto-run (generate_result_explanation) and every
POST .../explain warm the cache, so the result page can show the
explanation on arrival without spending — even with the explain auto-run
switch off, where the panel otherwise waits for a click. explanation is
null when nothing is cached yet.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| result_id required | string <uuid> (Result Id) |
{- "data": {
- "explanation": "string",
- "generated_at": "string"
}
}Correlate a failed validation result's already-available signals into a
ranked likely-cause summary (FEATURE-410). Returns {"data": RcaResponse}.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| result_id required | string <uuid> (Result Id) |
| include_narrative | boolean (Include Narrative) Default: false When true and an AI engine is available, append a plain-English AI narrative. Served from the narrative cache when the identical analysis was already narrated (e.g. in the background after the run), so repeat page opens don't re-spend. Off by default. |
{- "data": {
- "applicable": true,
- "completion_tokens": 0,
- "contributing_factors": [ ],
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_resolved": false,
- "failing_expectations": [ ],
- "failure_pattern": "new",
- "first_failed_at": "2019-08-24T14:15:22Z",
- "last_passed_at": "2019-08-24T14:15:22Z",
- "llm_config_id": "6ca9eb9b-0294-4b27-bd8f-f3502be7c77b",
- "narrative": "string",
- "narrative_status": "generated",
- "prompt_tokens": 0,
- "stat_diffs": [ ]
}
}Owner / admin only.
Workspace membership is already enforced by workspace_principal;
the extra org-role check restricts mutations to people who can
edit catalog metadata (owners + admins). Editors can view the
graph but can't tear edges down.
| dep_id required | string <uuid> (Dep Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}List direct upstream + downstream edges for one asset.
Returns two lists rather than one combined edge list so the UI can render "Upstream of X" / "Downstream of X" sections without re-classifying each row client-side.
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "property1": [
- { }
], - "property2": [
- { }
]
}Create one upstream edge for asset_id.
The path asset is the downstream end (the consuming asset). The body supplies upstream — either another asset in the same org or a free-form external pointer.
Cross-org dependencies are rejected: when upstream_asset_id is
supplied we verify it resolves inside the current tenant schema. The
RLS policy alone wouldn't catch this — a row with an FK to an asset
that doesn't exist in this tenant's data_assets would fail the
FK at write time, but the error message would be a confusing SQL
state. We pre-check so the 400 is explicit and uniform.
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_DependencyPayload) | ||||||
| |||||||
{- "data": {
- "source": "manual",
- "upstream_asset_id": "ffa31581-640f-4c37-80f2-d0dbf1b44a1e",
- "upstream_external": {
- "model": "string",
- "system": "string",
- "url": "string"
}
}
}{ }Walk the lineage graph up to depth hops in both directions.
Returns {"data": {"nodes": [...], "edges": [...]}} where nodes
carry a kind of either "asset" (resolved via a DataAsset
row, with name/type for label rendering) or "external"
(carrying the system + model + url from upstream_external).
Walk is cycle-safe: a walked set of asset IDs short-circuits
revisits, and we cap the BFS depth even before that to bound
payload size. Both an A→B→A cycle and a fan-out star are handled
in O(N + E) within the cap.
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| depth | integer (Depth) Default: 3 |
{ }Owner / admin only — same authority gradient as the rest of the catalog mutations (asset_dependencies, alert rules, etc.). Editors can list and read but can't tear a batch definition down.
validation_configs.batch_definition_id has ON DELETE SET NULL
so deleting a definition leaves the linked configs pointing at NULL
(the worker interprets that as "whole asset"), avoiding an orphaned
FK error. The UI surfaces a soft warning when a definition still has
configs pointed at it.
| bd_id required | string <uuid> (Bd Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}List every batch definition attached to one asset.
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{ }Create one batch definition for asset_id.
The body is validated through the shared
:func:validate_batch_definition helper — the worker uses the same
function so a row accepted here is guaranteed to be renderable.
Unknown config keys are dropped (the validator returns a freshly
constructed config dict).
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_BatchDefinitionPayload) | ||||||
| |||||||
{- "data": {
- "config": { },
- "kind": "string",
- "name": ""
}
}{ }Synchronously parse a (manifest, run_results) pair and write the
resulting :class:DataAsset / :class:ValidationResult rows.
Returns {"data": {assets_registered, assets_updated, results_imported, rule_suite_id, unmatched_run_results}} — the UI shows the first three
on a flash message after the upload.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| datasource_id required | string <uuid> (Datasource Id) |
required | object (Manifest) |
Run Results (object) or Run Results (null) (Run Results) |
{- "datasource_id": "11f86609-0961-4dd0-a490-1432bc861567",
- "manifest": { },
- "run_results": { }
}{ }Return the RunQueue row, scoped to the caller's org + workspace.
See module docstring for the authorisation rules and the cross-tenant 404 posture.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| run_queue_id required | string <uuid> (Run Queue Id) |
{- "data": {
- "checkpoint_id": "4d0293ba-f0d3-4c7e-8eba-c2cef877c686",
- "claimed_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "error_message": "string",
- "execution_mode": "string",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "scheduled_run_id": "66fe6763-8965-4e68-8b2a-b30f7cc15d06",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
}Request a cooperative cancel of one run-queue row.
Sets cancel_requested = True when the row is still pending / running.
The worker checks it before the run starts and polls it while the runner
works, so a direct run already in progress is interrupted within a few
seconds and ends cancelled with no result (an agent-mode run the on-prem
agent already started finishes there). Idempotent: a terminal
row (completed / failed / cancelled) is a no-op 200 echoing its
current status rather than a 409 — "there is nothing to stop" is not an error,
and a UI polling the run shouldn't have to special-case the race where the run
finished a beat before Stop was clicked.
If the row is a rule-group parent's MEMBER row, this cancels just that member
(the group-level cancel that flags every member is
POST .../validations/{id}/run/cancel).
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| run_queue_id required | string <uuid> (Run Queue Id) |
{- "data": {
- "cancel_requested": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "status": "string"
}
}Return grouped, workspace-scoped search hits across first-class objects.
Blank / whitespace q yields an empty list rather than a full-table
dump. limit is capped per object type so a broad term can't return an
unbounded result set.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| q required | string (Q) Search term; matched as a case-insensitive substring. |
Type (string) or Type (null) (Type) Optional scope filter — restrict to one hit type (datasource / data_asset / rule_group / schedule / alert / result / column). Incidents are not searched while they are hidden (#144). | |
| limit | integer (Limit) [ 1 .. 25 ] Default: 8 Max hits returned per object type. |
{- "data": [
- {
- "description": "string",
- "group": "string",
- "id": "string",
- "label": "string",
- "route": "string",
- "timestamp": "2019-08-24T14:15:22Z",
- "type": "datasource"
}
]
}Create a new comment on a validation result.
API review wave-7 (M2): Pydantic body replaces the previous
await request.json() hand-parse. The model enforces the FEATURE-22
1..8192-codepoint cap up front; the body still goes through
_validate_body to honour the UTF-8-byte cap for multibyte text and
to trim trailing whitespace consistently with the previous behaviour.
| result_id required | string <uuid> (Result Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_ResultCommentPayload) | ||
| |||
{- "data": {
- "body": "string"
}
}{ }| result_id required | string <uuid> (Result Id) |
| comment_id required | string <uuid> (Comment Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Edit a comment. Author + org-owner only (see :func:_can_modify).
API review wave-7 (M2): typed body — same shape as create.
| result_id required | string <uuid> (Result Id) |
| comment_id required | string <uuid> (Comment Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (_ResultCommentPayload) | ||
| |||
{- "data": {
- "body": "string"
}
}{ }Window health for one data asset (404 when it isn't visible).
| asset_id required | string <uuid> (Asset Id) |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, the health score covers. Default 30; 1-365. |
{- "data": {
- "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "failure_rate": 0,
- "health_score": 0,
- "runs": 0,
- "runs_failed": 0,
- "window_days": 0
}
}Window health for every data asset with a run in the window.
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, the health score covers. Default 30; 1-365. |
{- "data": [
- {
- "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "failure_rate": 0,
- "health_score": 0,
- "runs": 0,
- "runs_failed": 0,
- "window_days": 0
}
]
}Window health for one data asset (404 when it isn't visible).
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, the health score covers. Default 30; 1-365. |
{- "data": {
- "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "failure_rate": 0,
- "health_score": 0,
- "runs": 0,
- "runs_failed": 0,
- "window_days": 0
}
}Window health for every data asset with a run in the window.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, the health score covers. Default 30; 1-365. |
{- "data": [
- {
- "checks_evaluated": 0,
- "checks_failed": 0,
- "checks_passed": 0,
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "failure_rate": 0,
- "health_score": 0,
- "runs": 0,
- "runs_failed": 0,
- "window_days": 0
}
]
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
required | object (RuleValidationsWrite) Body for | ||||
| |||||
{- "data": {
- "create_validation": {
- "name": "string"
}, - "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "added": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "removed": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "validations": [
- {
- "is_default": false,
- "source": "default",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc",
- "validation_name": "string"
}
]
}
}| group_id required | string <uuid> (Group Id) |
{- "data": [
- {
- "asset_rule_count": 0,
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "mode": "all",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
]
}| group_id required | string <uuid> (Group Id) |
| asset_id required | string <uuid> (Asset Id) |
required | object (ValidationAssetInclusion) Body for
| ||||||
| |||||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "mode": "all",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "added_rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "asset_rule_count": 0,
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "mode": "all",
- "previous_mode": "all",
- "removed_rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}| group_id required | string <uuid> (Group Id) |
required | object (ValidationRuleAdd) Body for
| ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60"
}
}{ }| group_id required | string <uuid> (Group Id) |
| rule_id required | string <uuid> (Rule Id) |
required | object (ValidationRuleBatchPatch) Body for | ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a"
}
}{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "kwargs": { },
- "name": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "rule_type": "string",
- "severity": "string",
- "source": "default"
}
}| group_id required | string <uuid> (Group Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "batch_definition_kind": "string",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "has_scoping_batches": false,
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rules": [
- {
- "kwargs": { },
- "name": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "rule_type": "string",
- "severity": "string"
}
], - "runnable": true
}
]
}| asset_id required | string <uuid> (Asset Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleValidationsWrite) Body for | ||||
| |||||
{- "data": {
- "create_validation": {
- "name": "string"
}, - "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "added": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "removed": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "validations": [
- {
- "is_default": false,
- "source": "default",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc",
- "validation_name": "string"
}
]
}
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": [
- {
- "asset_rule_count": 0,
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "mode": "all",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
]
}| group_id required | string <uuid> (Group Id) |
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| group_id required | string <uuid> (Group Id) |
| asset_id required | string <uuid> (Asset Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (ValidationAssetInclusion) Body for
| ||||||
| |||||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "mode": "all",
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "added_rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "asset_rule_count": 0,
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "mode": "all",
- "previous_mode": "all",
- "removed_rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (ValidationRuleAdd) Body for
| ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60"
}
}{ }| group_id required | string <uuid> (Group Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| group_id required | string <uuid> (Group Id) |
| rule_id required | string <uuid> (Rule Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (ValidationRuleBatchPatch) Body for | ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a"
}
}{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "kwargs": { },
- "name": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "rule_type": "string",
- "severity": "string",
- "source": "default"
}
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "batch_definition_kind": "string",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "has_scoping_batches": false,
- "member_id": "435a4844-006a-4cfc-a644-e8eb2dd2ca43",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "rules": [
- {
- "kwargs": { },
- "name": "string",
- "rule_id": "728c1541-d6d1-4290-9a53-cdf01dd32d60",
- "rule_type": "string",
- "severity": "string"
}
], - "runnable": true
}
]
}| run_id required | string <uuid> (Run Id) |
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "checks_evaluated": 0,
- "checks_failed": 0,
- "completed_assets": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "health_score": 0,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "partition_value": "string",
- "previous_health_score": 0,
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_filter": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_time": "2019-08-24T14:15:22Z",
- "schedule_id": "8929bb67-0da1-406c-99d2-5447376a4f58",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "total_assets": 0,
- "trigger": "manual",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| run_id required | string <uuid> (Run Id) |
{- "data": {
- "assets": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "execution_mode": "direct",
- "position": 0,
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "state": "queued",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
], - "cancel_requested": false,
- "completed_assets": 0,
- "in_flight": true,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "run_id": "dded282c-8ebd-44cf-8ba5-9a234973d1ec",
- "status": "string",
- "total_assets": 0,
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| group_id required | string <uuid> (Group Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "checks_evaluated": 0,
- "checks_failed": 0,
- "completed_assets": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "health_score": 0,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "partition_value": "string",
- "previous_health_score": 0,
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_filter": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_time": "2019-08-24T14:15:22Z",
- "schedule_id": "8929bb67-0da1-406c-99d2-5447376a4f58",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "total_assets": 0,
- "trigger": "manual",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
], - "next_cursor": "string"
}| run_id required | string <uuid> (Run Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "checks_evaluated": 0,
- "checks_failed": 0,
- "completed_assets": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "health_score": 0,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "partition_value": "string",
- "previous_health_score": 0,
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_filter": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_time": "2019-08-24T14:15:22Z",
- "schedule_id": "8929bb67-0da1-406c-99d2-5447376a4f58",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "total_assets": 0,
- "trigger": "manual",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| run_id required | string <uuid> (Run Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "assets": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "data_asset_name": "string",
- "execution_mode": "direct",
- "position": 0,
- "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9",
- "state": "queued",
- "validation_result_id": "75d6a334-625d-4395-bb2a-de2db07abd55"
}
], - "cancel_requested": false,
- "completed_assets": 0,
- "in_flight": true,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "run_id": "dded282c-8ebd-44cf-8ba5-9a234973d1ec",
- "status": "string",
- "total_assets": 0,
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "checks_evaluated": 0,
- "checks_failed": 0,
- "completed_assets": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "health_score": 0,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "partition_value": "string",
- "previous_health_score": 0,
- "result_id": "cd1ea384-5045-47f3-9678-d1fc6f8c8581",
- "rule_filter": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "run_time": "2019-08-24T14:15:22Z",
- "schedule_id": "8929bb67-0da1-406c-99d2-5447376a4f58",
- "started_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "total_assets": 0,
- "trigger": "manual",
- "validation_id": "4b787051-9941-4155-aa13-68df4d20d7bc"
}
], - "next_cursor": "string"
}| org_id required | string <uuid> (Org Id) |
required | object (RuleTemplateCreate) Body for
| ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "rules": [
- { }
], - "tags": [
- "string"
]
}
}{ }| template_id required | string <uuid> (Template Id) |
| org_id required | string <uuid> (Org Id) |
required | object (RuleTemplateUpdate) Body for Every field is optional — an omitted field leaves the column unchanged.
| ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "rules": [
- { }
], - "tags": [
- "string"
]
}
}{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleTemplateCreate) Body for
| ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "rules": [
- { }
], - "tags": [
- "string"
]
}
}{ }| template_id required | string <uuid> (Template Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| template_id required | string <uuid> (Template Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleTemplateUpdate) Body for Every field is optional — an omitted field leaves the column unchanged.
| ||||||||
| |||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "rules": [
- { }
], - "tags": [
- "string"
]
}
}{ }The live rules the caller can see (optionally one asset's / one validation's), newest first.
| org_id required | string <uuid> (Org Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
Validation Id (string) or Validation Id (null) (Validation Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }One rule, with the (visible) validations that hold it.
| org_id required | string <uuid> (Org Id) |
| rule_id required | string <uuid> (Rule Id) |
{- "data": {
- "created_at": "2019-08-24T14:15:22Z",
- "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "description": "string",
- "enabled": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "kwargs": { },
- "memory_flag": "string",
- "memory_note": "string",
- "memory_updated_at": "2019-08-24T14:15:22Z",
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "severity": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}The live rules the caller can see (optionally one asset's / one validation's), newest first.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Data Asset Id (string) or Data Asset Id (null) (Data Asset Id) | |
Validation Id (string) or Validation Id (null) (Validation Id) | |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) |
{ }One rule, with the (visible) validations that hold it.
| org_id required | string <uuid> (Org Id) |
| rule_id required | string <uuid> (Rule Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": {
- "created_at": "2019-08-24T14:15:22Z",
- "created_validation_id": "1836ea1b-444e-41e5-afc7-a29f8d8b37c1",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "description": "string",
- "enabled": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "kwargs": { },
- "memory_flag": "string",
- "memory_note": "string",
- "memory_updated_at": "2019-08-24T14:15:22Z",
- "name": "string",
- "position": 0,
- "rule_type": "string",
- "severity": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "validation_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) | |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, for |
| include_defaults | boolean (Include Defaults) Default: true Include assets' default validations ( |
{ }required | object (RuleGroupCreate) Body for
| ||||||||||
| |||||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "tags": [
- "string"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_restricted": false
}
}{ }| group_id required | string <uuid> (Group Id) |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, for |
{ }| group_id required | string <uuid> (Group Id) |
required | object (RuleGroupUpdate) Body for Every field is optional — an omitted field leaves the column unchanged.
| ||||||||||
| |||||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "tags": [
- "string"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_restricted": true
}
}{ }DEPRECATED: the assets this validation's rules cover (derived).
| group_id required | string <uuid> (Group Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "created_at": "2019-08-24T14:15:22Z",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "deprecated": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9"
}
]
}| group_id required | string <uuid> (Group Id) |
required | object (RuleGroupMemberCreate) Body for RETIRED (EPIC PR 9): the asset-list write returns 410 Gone. Add rules to
the validation instead ( | ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9"
}
}nullRun the validation: exactly its live, enabled rules (EPIC PR 3).
The rules run one GX pass per (asset, effective batch) unit — each
rule on its membership batch binding unless the optional body overrides
it (batch_definition_id + partition_value / partition_values,
several values = several runs), or restricts the run to some of the
validation's rules (rule_ids). See ValidationRunRequest.
| group_id required | string <uuid> (Group Id) |
object (ValidationRunRequest) Optional body of A run executes the validation's live, enabled rules, one GX pass per
Omit the body (or send | |||||||||
| |||||||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "partition_value": "string",
- "partition_values": [
- "string"
], - "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "has_agent_member": false,
- "member_count": 0,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "runnable_count": 0,
- "runs": [
- {
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "partition_value": "string"
}
], - "status": "pending"
}
}Cooperatively cancel one in-flight run of this validation.
Flags cancel_requested = True on every NON-TERMINAL run_queue row
of the run (the agent-mode members still out at the DQ Agent, keyed on
rule_group_id + parent_result_id) AND records the intent on the
parent rollup so the worker's in-process DIRECT-member loop sees it. A
direct asset already running is interrupted (the worker polls the flag
while its runner works and terminates it within a few seconds, writing no
result for it); pending assets never start and flip to cancelled.
Best-effort for agent members: the flag is honoured only if the on-prem agent hasn't already claimed + started the member (it runs autonomously and we do NOT hard-kill it). Idempotent: re-issuing on the same run re-flags the same rows (already-flagged / already-terminal rows are unaffected) and returns the current live count.
| group_id required | string <uuid> (Group Id) |
required | object (RuleGroupRunCancelInput) The cancel request payload (run-engine Phase 1a).
| ||||
| |||||
{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09"
}
}{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "members_flagged": 0,
- "parent_flagged": true,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495"
}
}| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
Limit (integer) or Limit (null) (Limit) | |
Cursor (string) or Cursor (null) (Cursor) | |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, for |
| include_defaults | boolean (Include Defaults) Default: true Include assets' default validations ( |
{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleGroupCreate) Body for
| ||||||||||
| |||||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "tags": [
- "string"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_restricted": false
}
}{ }| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": [
- {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "rule_groups": [
- {
- "group_id": "306db4e0-7449-4501-b76f-075576fe2d8f",
- "group_name": "string"
}
]
}
]
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Trailing window, in days, for |
{ }| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleGroupUpdate) Body for Every field is optional — an omitted field leaves the column unchanged.
| ||||||||||
| |||||||||||
{- "data": {
- "description": "string",
- "name": "string",
- "tags": [
- "string"
], - "team_id": "810007d0-bec5-486c-b5d1-28fcd8a079ba",
- "team_restricted": true
}
}{ }DEPRECATED: the assets this validation's rules cover (derived).
| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
{- "data": [
- {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "created_at": "2019-08-24T14:15:22Z",
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "deprecated": true,
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9"
}
]
}| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleGroupMemberCreate) Body for RETIRED (EPIC PR 9): the asset-list write returns 410 Gone. Add rules to
the validation instead ( | ||||
| |||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "rule_suite_id": "7de675f1-25f4-41f2-b5bd-0134614383d9"
}
}null| group_id required | string <uuid> (Group Id) |
| member_id required | string <uuid> (Member Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
nullRun the validation: exactly its live, enabled rules (EPIC PR 3).
The rules run one GX pass per (asset, effective batch) unit — each
rule on its membership batch binding unless the optional body overrides
it (batch_definition_id + partition_value / partition_values,
several values = several runs), or restricts the run to some of the
validation's rules (rule_ids). See ValidationRunRequest.
| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
object (ValidationRunRequest) Optional body of A run executes the validation's live, enabled rules, one GX pass per
Omit the body (or send | |||||||||
| |||||||||
{- "data": {
- "batch_definition_id": "a6461f10-95ac-4c7c-88ae-d2162c5ff75a",
- "partition_value": "string",
- "partition_values": [
- "string"
], - "rule_ids": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
}{- "data": {
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "has_agent_member": false,
- "member_count": 0,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495",
- "runnable_count": 0,
- "runs": [
- {
- "execution_run_id": "6dd8e472-1787-4aaa-a91c-370df341f4fe",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "partition_value": "string"
}
], - "status": "pending"
}
}Cooperatively cancel one in-flight run of this validation.
Flags cancel_requested = True on every NON-TERMINAL run_queue row
of the run (the agent-mode members still out at the DQ Agent, keyed on
rule_group_id + parent_result_id) AND records the intent on the
parent rollup so the worker's in-process DIRECT-member loop sees it. A
direct asset already running is interrupted (the worker polls the flag
while its runner works and terminates it within a few seconds, writing no
result for it); pending assets never start and flip to cancelled.
Best-effort for agent members: the flag is honoured only if the on-prem agent hasn't already claimed + started the member (it runs autonomously and we do NOT hard-kill it). Idempotent: re-issuing on the same run re-flags the same rows (already-flagged / already-terminal rows are unaffected) and returns the current live count.
| group_id required | string <uuid> (Group Id) |
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
required | object (RuleGroupRunCancelInput) The cancel request payload (run-engine Phase 1a).
| ||||
| |||||
{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09"
}
}{- "data": {
- "data_asset_id": "65527ff7-0bd6-4962-9e53-09b32d973a2f",
- "members_flagged": 0,
- "parent_flagged": true,
- "parent_result_id": "990ea0c4-5ca0-4c3b-8e49-38ee9c4b7d09",
- "rule_group_id": "7057292d-5800-4631-b72e-1396f184b495"
}
}Atomically register the supplied discovered tables as DataAssets.
Replaces the modal's N sequential client-side POST /data-assets calls
with one transaction so a half-success can't leave a partial state. Each
table is find-or-created keyed by (datasource_id, name) — re-registering
an existing name is a no-op (counted as skipped), so the call is idempotent.
Returns {"data": {assets_registered, assets_skipped, asset_ids}};
asset_ids carries every resolved asset id (created or pre-existing) so
the UI can navigate straight to them.
| org_id required | string <uuid> (Org Id) |
| workspace_id required | string <uuid> (Workspace Id) |
| datasource_id required | string <uuid> (Datasource Id) |
Array of objects (Tables) | |||||||||||||
Array
| |||||||||||||
{- "tables": [
- {
- "format": "string",
- "kind": "table",
- "name": "string",
- "path": "string",
- "row_count_estimate": 0,
- "schema": "string"
}
]
}{ }Consume a pending team invite and start a session.
Public, unauthenticated endpoint — anyone holding the raw dqi_
token can call it (that's the point of an invite link). The handler:
token_hash.expires_at. All three failure modes return the same opaque 400
so a probing client can't distinguish "already used" from
"expired" from "never existed".User if the invite email already maps to
one; otherwise creates a fresh row (requires password + the
optional full_name).OrgMembership (and the
WorkspaceMembership when the invite is workspace-scoped) at
the invite's role.accepted_at so the row can't be replayed./auth/login
so the UI can call the user authenticated to the new org / workspace.Rate limiting is not applied here — the invite token itself is the single-use credential, and an attacker who can guess a 32-char base62 token has bigger problems than we can solve in this layer.
Full Name (string) or Full Name (null) (Full Name) | |
Password (string) or Password (null) (Password) | |
| token required | string (Token) |
{- "full_name": "string",
- "password": "string",
- "token": "string"
}{- "access_token": "string",
- "csrf_token": "string",
- "expires_in": 0,
- "refresh_token": "string",
- "token_type": "bearer"
}Re-prove identity with the user's password and return a fresh access
token (auth_fresh_at = now).
The refresh cookie is intentionally NOT rotated: only the access-token
claims update. CSRF protection isn't applied because this is an
Authorization-bearer flow (no cookie carries the credential being
challenged); rate limiting matches /auth/login so it's as hard to
brute-force.
Personal API keys and agent tokens (dqk_ / dqa_) cannot use
this endpoint — they have no associated user password. We surface that
as 401 invalid credentials for the agent-token path (no user to
look up) without branching on token type, since the DB lookup naturally
fails when principal.user_id is None.
| password required | string (Password) |
{- "password": "string"
}{- "access_token": "string",
- "csrf_token": "string",
- "expires_in": 0,
- "refresh_token": "string",
- "token_type": "bearer"
}#43: begin a self-service password reset.
Enumeration-safe: every path returns the same 200 ack. A reset link is only
minted + emailed when the email maps to an ACTIVE account that actually has
a local password (SSO-only users have hashed_password IS NULL and are
silently skipped — there's nothing to reset), and only when the deployment
is configured to deliver mail (PUBLIC_BASE_URL for the link +
SMTP_HOST for delivery). Requesting a new link invalidates the account's
previous unused links so only the newest one works.
| email required | string <email> (Email) |
{- "email": "user@example.com"
}{- "message": "string"
}Exchange a Google IAP assertion for a DQ Cloud session.
Trusts the X-Goog-IAP-JWT-Assertion header that Cloud Load
Balancing's IAP forwards to backends when available. The endpoint
validates the JWT signature against Google's JWKS, checks audience
User keyed by the email claim.Deployment shape: the UI sits behind an external HTTPS LB with IAP enabled. On page load the UI POSTs to this endpoint; if it returns a session, the password-login form is skipped.
Two modes, picked by configuration with NO fall-through (#280):
Verified mode (settings.iap_audience set): require the
signed X-Goog-IAP-JWT-Assertion header and verify it against
Google's JWKS for the configured audience. A missing or invalid
assertion is a hard 401 — the header-trust branch is disabled in
this mode so a stripped-JWT request cannot downgrade verified
mode into header-trust mode.
Header-trust fallback (settings.iap_audience empty): trust
the X-Goog-Authenticated-User-Email / X-Goog-Authenticated-User-Id
header pair. Cloud Run-backed serverless NEGs behind IAP only
consistently forward those headers — the signed JWT assertion is
dropped somewhere on the Cloud Run path even with IAP enabled at the
LB. Header trust is safe in that configuration because:
ingress = INGRESS_TRAFFIC_INTERNAL_ONLY, so the
only callers are other resources in the project's VPC.The header carries an accounts.google.com: prefix (IAP's
subject-namespace convention) — strip it.
JIT-provisioning is gated by :attr:Settings.iap_jit_provision_domains:
only emails whose domain is on the allow-list get auto-created. New
users join :attr:Settings.iap_default_org_slug at the role named by
:attr:Settings.iap_default_jit_role (default :attr:Role.EDITOR; see
_iap_default_jit_role) with no password — the IAP layer is the only
auth surface for these accounts. PR #275 normalised every legacy
"member" role to "editor", so we mint the canonical enum
value here rather than the old string.
Existing-account hijack defence (#281): when the IAP-asserted email
maps to an existing user, _assert_iap_signin_allowed runs before
we mint a session. It refuses with 403 when the account has a
password (and isn't IAP-linked), when it carries a non-IAP
external_identity, or when the user's org has an enforced
SSO IdentityProvider that isn't IAP.
Runs BEFORE workspace context is set on the session, so it operates
with the unrestricted-bypass GUC state (TENANCY-2 #273). The
endpoint only touches public-schema tables (users,
org_memberships, workspaces, workspace_memberships,
refresh_tokens) so the missing tenant search_path is fine.
{- "access_token": "string",
- "csrf_token": "string",
- "expires_in": 0,
- "refresh_token": "string",
- "token_type": "bearer"
}| email required | string <email> (Email) |
| password required | string (Password) |
{- "email": "user@example.com",
- "password": "string"
}{- "access_token": "string",
- "csrf_token": "string",
- "expires_in": 0,
- "refresh_token": "string",
- "token_type": "bearer"
}RBAC-3: return the calling principal's role + flattened permission set.
The UI mirrors the server's RBAC-1 matrix here rather than hard-coding
a TypeScript copy: the source of truth lives in
:mod:dq_cloud_api.auth.roles, and GET /auth/me/permissions projects
it through the caller's role so the SPA can hide / disable controls the
backend would 403 anyway.
Response shape (stable; the UI's usePermissions() hook depends on it)::
{
"role": "editor",
"permissions": ["can_view_results", "can_create_datasource", ...]
}
Notes:
Permission enum's value), not the Python member names. That
lines up with everything else on the wire — JWT claims, DB columns,
audit metadata — and lets the UI compare against the same literals.Role.AGENT / Role.SCIM) and any unknown
role string return an empty permission list. They authorise through
a different gate (workspace pinning / SCIM route gate) and shouldn't
drive UI affordances on the human-RBAC matrix.principal.role is org-scoped, so it's resolved with
:func:coerce_org_role — same behaviour as :func:has_org_permission,
which is what require_permission uses. A stored JWT from an older
release still resolves cleanly: legacy "member" → editor and (the
org-scope difference) legacy "admin" → super_admin, so the
projected permission set the UI gates against matches the set the API
will actually enforce.principal. That keeps the
endpoint cheap enough for the UI to call on every navigation if it
ever wanted to (the TanStack Query layer caches 5 min today).fresh_auth / step-up gate. The endpoint is read-only and
surfaces no secret beyond what the JWT itself already carries.{ }#366: return the calling user's identity for the app nav/header.
The JWT carries only opaque ids (sub / org / ws) plus the
role — enough to authorise, not enough to display who is logged in.
The nav has no human-readable identity, so a user juggling multiple
accounts can't tell which one is active without logging out. This endpoint
resolves the principal's sub to the stored email / full_name
and pairs it with the org name + role so the sidebar can render an
identity block.
Response shape (the UI's useMe() hook depends on it)::
{
"user_id": "…",
"email": "ada@example.com",
"full_name": "Ada Lovelace",
"role": "editor",
"org_id": "…",
"org_name": "Acme",
"workspace_id": "…" | null,
"ai_available": true,
"team_ids": ["…"]
}
Notes:
dqa_ / dqs_) have no associated user
(principal.user_id is None) and no human identity to surface; they
get a 404 — the nav only renders for browser sessions, which always
carry a user JWT.ai_available (Phase-1 hybrid) is the org-level, viewer-readable "is
any AI engine reachable?" flag — native in effect OR a default BYOLLM
config present, computed by :func:ai_available so it can't drift from
the resolver. It lets a non-LLM-manager (editor / viewer) know AI works
in native mode without reading the CAN_MANAGE_LLM_CONFIGS-gated
ai-policy endpoint, fixing the dead "configure a provider" button.{ }#43: complete a self-service password reset.
Consumes the dqpr_ token from the emailed link: verifies its hash, that
it's unused and unexpired, then rotates the user's bcrypt password, marks
the token (and any siblings) used, and REVOKES every outstanding refresh
token so all existing sessions are killed — a reset is the canonical
"I may have been compromised" action, so nothing the old password minted
survives. A bad / expired / already-used token yields an indistinguishable
400 so the endpoint isn't a token-probing oracle.
| new_password required | string (New Password) >= 8 characters |
| token required | string (Token) |
{- "new_password": "stringst",
- "token": "string"
}{- "message": "string"
}SSO-6: create a new user account.
Two branches:
JoinRequest row and
return SignupPendingApprovalResponse. The UI prompts the user
to wait for admin approval.LoginResponse. A
provisioning failure returns 503 and commits nothing.Both branches normalise the email lower-case + strip so the
case-insensitive uniqueness contract on users.email is honoured.
Rate limits (429 + Retry-After): every attempt counts against a
loose per-IP hourly bucket (auth_signup_rate_limit_per_hour). The
create-org branch also charges two daily buckets before it does any work,
because each org it creates costs a whole tenant schema: a tight per-IP
one (auth_signup_org_create_per_ip_rate_limit_per_day), then one
global cap (auth_signup_org_create_rate_limit_per_day). A client the
per-IP bucket blocks never reaches, and so never charges, the global one.
IPv6 clients share the per-IP create-org bucket per /64. If the branch
fails after charging and before its commit (503, a lost-race 409, any
other error), both create-org hits are refunded.
Both branches refuse a duplicate email with 409 — propose-join is a
flow for new users; an existing user requesting access to an org
they already see should go through the login + invite path.
| email required | string <email> (Email) |
Full Name (string) or Full Name (null) (Full Name) | |
Org Name (string) or Org Name (null) (Org Name) | |
| password required | string (Password) |
{- "email": "user@example.com",
- "full_name": "string",
- "org_name": "string",
- "password": "string"
}nullFEATURE-31: IdP-driven callback — see :func:_sso_callback_impl.
C-186-1 (a): the callback is a top-level BROWSER navigation, so no
failure may render as JSON in the user's tab. Every error — a refused
link, an expired state, an IdP/token problem, an unexpected exception —
is logged server-side with its (non-secret)
detail, and 303-redirects to /login?sso_error=<code>; the login page
maps the code to friendly copy. The codes are enumerated in
:data:SSO_ERROR_CODES and carry nothing secret.
POST is registered alongside GET for the SAML HTTP-POST binding (the
handler always read SAMLResponse / RelayState from the form, but
only GET was routed, so a SAML login 405'd).
| org_slug required | string (Org Slug) |
nullFEATURE-31: IdP-driven callback — see :func:_sso_callback_impl.
C-186-1 (a): the callback is a top-level BROWSER navigation, so no
failure may render as JSON in the user's tab. Every error — a refused
link, an expired state, an IdP/token problem, an unexpected exception —
is logged server-side with its (non-secret)
detail, and 303-redirects to /login?sso_error=<code>; the login page
maps the code to friendly copy. The codes are enumerated in
:data:SSO_ERROR_CODES and carry nothing secret.
POST is registered alongside GET for the SAML HTTP-POST binding (the
handler always read SAMLResponse / RelayState from the form, but
only GET was routed, so a SAML login 405'd).
| org_slug required | string (Org Slug) |
nullSSO-1: list the identity providers configured for org_slug.
Public, unauthenticated. The login page calls this before the
user has a session so the SPA can render a picker (or skip straight to
the only configured IdP). An attacker who can already guess an org
slug can also see whether the slug exists via /sso/{slug}/start,
so this endpoint doesn't widen the existing oracle — it just exposes
the same set of orgs through a less hostile shape.
Response shape (each row)::
{
"provider_id": "<uuid>",
"display_name": "Acme Okta",
"kind": "oidc" | "saml"
}
Information disclosure (read this before adding fields): only fields that are safe to expose to an unauthenticated viewer go on this response. We deliberately do NOT return:
issuer (OIDC) — reveals the customer's IdP host, useful for
a targeted-phishing setup.client_id — semi-secret for some IdPs (Azure AD treats it as
sensitive; surfacing it pre-auth is a regression).entity_id / sso_url / x509_cert (SAML) — same posture.default_role — the org's RBAC posture is not the public's
business.enforced — whether password login is on/off would tell a probing
client which orgs to spear-phish via the password path.config_encrypted — this would defeat HARDEN-16.Status codes:
| org_slug required | string (Org Slug) |
nullFEATURE-31: kick off an SSO flow for org_slug.
Returns a JSON body {redirect_url} rather than a 302 so the SPA
can drive the navigation (and add tracking / extension state) before
handing control to the IdP. The browser then performs the actual
redirect.
The state parameter we put on the IdP URL is the FEATURE-31 CSRF
defence: a callback with no recognised state is rejected, so a
third-party site can't trick a logged-out user into starting a
session under an attacker-controlled IdP.
| org_slug required | string (Org Slug) |
Provider Id (string) or Provider Id (null) (Provider Id) |
nullList or filter the principal's org's teams.
Supports displayName eq, externalId eq, and id eq. Other
attributes fall through to an empty result rather than 400 — same
posture as the Users router so an IdP probing for a custom
attribute doesn't disable provisioning. Tombstoned teams are
excluded everywhere.
Filter (string) or Filter (null) (Filter) | |
Startindex (integer) or Startindex (null) (Startindex) | |
Count (integer) or Count (null) (Count) |
nullCreate a team from a SCIM Group.
Behaviour matrix:
displayName missing → 400.externalId set and a live team already carries it → 409
scimType=uniqueness. Re-pushing an already-provisioned group
is the IdP's "I expected this to not exist; reconcile" signal,
not "create a duplicate".externalId set and a tombstoned team carries it → restore
the row in place: clear deleted_at, sync name/slug, and
ADDITIVELY apply any members in the POST body. Pre-DELETE
memberships are preserved (SSO-4.1) because Okta's churn cycle
(DELETE → POST with empty members → PATCH add) would
otherwise wipe them between the POST and the PATCH. Targeted
removals after restore happen via PATCH op=remove.
Preserves the original team id and audit history (the SSO-4
contract).externalId (manual SCIM create, rare) → just create a
fresh team. Slug collisions are auto-suffixed.| property name* additional property | any |
{ }nullSoft-delete the team.
Sets deleted_at = NOW(); preserves the row, memberships, and
workspace grants so a same-externalId recreate restores them
intact. Subsequent GETs 404. The first-party Teams API
(routers/teams.py) also filters tombstones from every read.
Why not hard delete: the IdP can delete + recreate a group within seconds (Okta's "assignment churn" cycle) and a hard delete would lose the original team id — every audit row pointing at it would dangle, and any admin-added manual memberships would be wiped silently. Soft delete keeps the audit trail intact and makes "restore on recreate" trivial.
| group_id required | string <uuid> (Group Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Apply RFC 7644 §3.5.2 PATCH operations to a team-backed Group.
Supported ops (Okta + Entra IT's actual emit set):
replace path=displayName — rename the team.replace path=externalId — update the IdP-side group id.replace path=members — wholesale replace SCIM membership.add path=members — append members (idempotent).remove path=members[value eq "<id>"] — drop one member.remove path=members — clear all SCIM members.Manual / SSO-JIT membership rows are never touched — that's the
added_via='scim' filter inside :func:_reconcile_team_members
and the symmetric guard inside the per-row remove.
| group_id required | string <uuid> (Group Id) |
| property name* additional property | any |
{ }nullReplace the team's name / externalId / SCIM-stamped membership.
Does NOT touch manual or SSO-JIT membership rows: those are owned
by other code paths and surviving a wholesale SCIM PUT is the whole
point of the added_via column. displayName and externalId
are mutable here; renaming sucks in a new slug but auto-suffixes on
collision so a PUT can't 409 on a same-display-name rename inside
one org.
| group_id required | string <uuid> (Group Id) |
| property name* additional property | any |
{ }nullList or filter users in the principal's org.
Okta polls this with filter=userName eq "..." for every assignment
decision; the EqualityFilter path keeps that O(1) via the unique
index on users.email.
Filter (string) or Filter (null) (Filter) | |
Startindex (integer) or Startindex (null) (Startindex) | |
Count (integer) or Count (null) (Count) |
nullProvision a user into the principal's org.
Idempotent on email: if a global :class:UserModel row already exists
we attach an :class:OrgMembership to it (the user is a member of
multiple tenants). If an active membership already exists, returns
409 + scimType=uniqueness (RFC 7644 §3.3) so the IdP marks the
provisioning attempt as "already provisioned".
| property name* additional property | any |
{ }nullDeprovision a user from this org.
We don't delete the global :class:UserModel row — the same person
might still belong to other tenants in the deployment. We delete
only the membership row (and any workspace memberships hanging off
it via cascade-equivalent application code below). Okta treats a 204
as "user deprovisioned"; subsequent GETs return 404.
| user_id required | string <uuid> (User Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}PATCH applies a list of operations (RFC 7644 §3.5.2).
We only implement the patch shapes the Okta + Entra test matrices exercise:
replace for active (deprovision / reactivate).replace for name.givenName / familyName / userName.replace with a value object (Okta's default shape).Anything else returns 400 with scimType=invalidPath; the IdP
surface this to the admin who can then either correct the connector
mapping or open a ticket against us.
| user_id required | string <uuid> (User Id) |
| property name* additional property | any |
{ }nullPUT replaces the whole user representation (RFC 7644 §3.5.1).
| user_id required | string <uuid> (User Id) |
| property name* additional property | any |
{ }null