Skip to main content

Recertification API

Manage recertification cycles and access audits for periodic review of data products and source system roles.

All cycle endpoints are scoped to a space via the {slug} path parameter. Most write operations require Space Admin permissions.

Endpoints Overview​

Cycle CRUD​

MethodEndpointDescription
POST/spaces/{slug}/governance/cyclesCreate a cycle
GET/spaces/{slug}/governance/cyclesList cycles
GET/spaces/{slug}/governance/cycles/{cycle_id}Get cycle detail
DELETE/spaces/{slug}/governance/cycles/{cycle_id}Delete a cycle

Cycle Actions​

MethodEndpointDescription
POST.../cycles/{cycle_id}/populateAuto-populate audit items
POST.../cycles/{cycle_id}/completeMark as completed
POST.../cycles/{cycle_id}/cancelCancel a cycle
POST.../cycles/{cycle_id}/reopenReopen a cancelled cycle
POST.../cycles/{cycle_id}/archiveArchive a cycle
POST.../cycles/{cycle_id}/unarchiveUnarchive a cycle

Cycle Configuration​

MethodEndpointDescription
PATCH.../cycles/{cycle_id}/workflowSet/clear workflow definition
PATCH.../cycles/{cycle_id}/configUpdate templates and filters
GET/spaces/{slug}/governance/filter-suggestionsAutocomplete values for filters

Audit Operations​

MethodEndpointDescription
POST/governance/auditsCreate an audit record
PATCH/governance/audits/{audit_id}Review an audit item
POST/spaces/{slug}/governance/audits/{audit_id}/recertifyCreate a recertification request
POST.../cycles/{cycle_id}/recertify-allBulk recertify all pending items

Workflow Defaults​

MethodEndpointDescription
GET/spaces/{slug}/governance/workflow-defaultsList workflow defaults
POST/spaces/{slug}/governance/workflow-defaultsCreate a workflow default
PATCH.../workflow-defaults/{default_id}Update a workflow default
DELETE.../workflow-defaults/{default_id}Delete a workflow default

Recertification Cycles​

Create Cycle​

POST /spaces/{slug}/governance/cycles

Requires: Space Admin

Request Body​

{
"name": "Q1 2026 PII Review",
"due_date": "2026-03-31T23:59:59Z",
"cycle_type": "product",
"description": "Quarterly review of PII-tagged data products."
}
FieldTypeRequiredDescription
namestring✅Cycle name
due_datedatetime✅Review deadline
cycle_typestring✅product or access
descriptionstring—Optional description

Response​

{
"id": "uuid",
"name": "Q1 2026 PII Review",
"cycle_type": "product",
"status": "open",
"due_date": "2026-03-31T23:59:59Z",
"start_date": "2026-02-10T10:00:00Z",
"completed_at": null,
"space_id": "uuid",
"created_by_id": "uuid",
"is_archived": false,
"workflow_definition_id": null,
"request_title_template": null,
"request_description_template": null,
"filters": null,
"audit_count": 0,
"reviewed_count": 0
}

List Cycles​

GET /spaces/{slug}/governance/cycles?include_archived=false
ParameterTypeDefaultDescription
include_archivedboolfalseInclude archived cycles

Returns: RecertificationCycleResponse[]


Get Cycle Detail​

GET /spaces/{slug}/governance/cycles/{cycle_id}

Returns the cycle with all embedded audit items, audit counts, templates, filters, and workflow configuration.

Returns: RecertificationCycleDetail (extends RecertificationCycleResponse with an audits array)


Delete Cycle​

DELETE /spaces/{slug}/governance/cycles/{cycle_id}

Requires: Space Admin

Permanently deletes the cycle and all its audit items. Returns 204 No Content.


Cycle Actions​

Populate​

POST /spaces/{slug}/governance/cycles/{cycle_id}/populate

Requires: Space Admin

Scans the space for resources matching the cycle's filters and creates audit items. Idempotent — re-populating skips resources that already have an audit row.

Response​

{
"created": 42
}

Complete​

POST /spaces/{slug}/governance/cycles/{cycle_id}/complete

Requires: Space Admin

Transitions the cycle to completed status (read-only).


Cancel​

POST /spaces/{slug}/governance/cycles/{cycle_id}/cancel

Requires: Space Admin

Aborts the cycle. Can be reopened later.


Reopen​

POST /spaces/{slug}/governance/cycles/{cycle_id}/reopen

Requires: Space Admin

Reopens a cancelled cycle to resume the review process.


Archive / Unarchive​

POST /spaces/{slug}/governance/cycles/{cycle_id}/archive
POST /spaces/{slug}/governance/cycles/{cycle_id}/unarchive

Requires: Space Admin

Toggles the cycle's visibility in the default list view.


Cycle Configuration​

Update Workflow​

PATCH /spaces/{slug}/governance/cycles/{cycle_id}/workflow

Requires: Space Admin

{
"workflow_definition_id": "uuid-or-null"
}

Set a workflow definition for the cycle. Recertification requests created from this cycle will be routed through the specified workflow. Pass null to clear.


Update Config (Templates & Filters)​

PATCH /spaces/{slug}/governance/cycles/{cycle_id}/config

Requires: Space Admin

{
"request_title_template": "Recertify {{ product_name }} ({{ product_type }})",
"request_description_template": "## Review for {{ product_name }}\n\n**Criticality:** {{ criticality }}",
"filters": {
"tags": ["pii", "gdpr"],
"criticality": ["High", "Critical"]
}
}

All fields are optional — only provided fields are updated.

Template Variables​

The following Jinja2 variables are available in title and description templates:

Common variables (all cycle types):

VariableSourceDescription
resource_nameAuditDisplay name of the resource
resource_typeAuditResource type (e.g., Table)
cycle_nameCycleName of the parent cycle
space_nameSpaceName of the space

Product cycle variables:

VariableSourceDescription
product_nameProductData product name
product_typeProductProduct type
criticalityProductCriticality level
sensitivityProductSensitivity classification
providerProductData provider

Access cycle variables (SourceSystemRole audits):

VariableTypeDescription
role_namestringRole name
source_system_namestringSource system the role belongs to
role_access_listlist[dict]Users with active access — each dict has user_name, user_email, product_name, granted_at
role_access_tablestringPre-rendered markdown table of users with active access
Example: access table in description​
request_description_template: |
## Users with access to {{ role_name }}

{{ role_access_table }}

Renders to:

## Users with access to Analyst PII Role

| User | Email | Product | Granted |
|---|---|---|---|
| Alice Smith | alice@example.com | MySnowFlakeDB | 2026-01-15 |
| Bob Jones | bob@example.com | MySnowFlakeDB | 2026-02-01 |

Filter Suggestions​

GET /spaces/{slug}/governance/filter-suggestions

Returns distinct values available in the space for autocomplete.

{
"product_types": ["Table", "Dashboard", "Report"],
"criticalities": ["Critical", "High", "Medium", "Low"],
"tags": ["pii", "gdpr", "financial"]
}

Audit Operations​

Create Audit​

POST /governance/audits

Manually create a single audit record (usually automated by cycle population).

{
"cycle_id": "uuid",
"resource_id": "uuid",
"resource_name": "customer_orders",
"resource_type": "Table"
}

Review Audit​

PATCH /governance/audits/{audit_id}

Approve or reject an audit item.

{
"status": "approved",
"comments": "Access is still required for daily operations."
}
FieldTypeDescription
statusstringapproved or rejected
commentsstringOptional reviewer notes

Create Recertification Request​

POST /spaces/{slug}/governance/audits/{audit_id}/recertify

Creates a recertification request linked to the audit item. The request title and description are rendered from the cycle's Jinja2 templates (if configured).

Optional Body​

{
"description": "Fallback description if no template is configured."
}

Response​

Returns the created request object.


Bulk Recertify​

POST /spaces/{slug}/governance/cycles/{cycle_id}/recertify-all

Requires: Space Admin

Creates recertification requests for all pending audit items in the cycle. Skips items that already have an active request.

{
"created": 38,
"skipped": 4
}

Response Models​

RecertificationCycleResponse​

FieldTypeDescription
idUUIDCycle identifier
namestringCycle name
descriptionstring | nullOptional description
cycle_typestringproduct or access
statusstringopen, in_review, completed, cancelled
due_datedatetimeReview deadline
start_datedatetimeWhen the cycle was created
completed_atdatetime | nullWhen the cycle was completed
space_idUUIDSpace the cycle belongs to
created_by_idUUID | nullUser who created the cycle
is_archivedbooleanWhether the cycle is hidden from default views
workflow_definition_idUUID | nullLinked workflow definition
request_title_templatestring | nullJinja2 template for request titles
request_description_templatestring | nullJinja2 template for request descriptions
filtersobject | nullPopulation filter configuration
audit_countintegerTotal audit items
reviewed_countintegerDecided audit items

RecertificationCycleDetail​

Extends RecertificationCycleResponse with:

FieldTypeDescription
auditsRecertificationAuditResponse[]Embedded audit items

RecertificationAuditResponse​

FieldTypeDescription
idUUIDAudit identifier
cycle_idUUIDParent cycle
resource_idUUIDResource being reviewed
resource_namestring | nullResource display name
resource_typestring | nullResource type (e.g., Table, Dashboard, SourceSystemRole)
statusstringpending, approved, rejected, expired
reviewer_idUUID | nullWho made the decision
reviewed_atdatetime | nullWhen the decision was made
commentsstring | nullReviewer comments
product_idstring | nullAssociated data product ID (product cycles)
source_system_idstring | nullAssociated source system ID (access cycles)
has_pending_requestbooleanWhether a recertification request is in-flight
request_idstring | nullID of the linked recertification request

Enumerations​

RecertificationCycleType​

ValueDescription
productReviews data products
accessReviews source system roles

RecertificationCycleStatus​

ValueDescription
openCycle is active and accepting reviews
in_reviewCycle has been populated and reviews are in progress
completedAll reviews are done (read-only)
cancelledCycle was cancelled (can be reopened)

RecertificationStatus (Audit)​

ValueDescription
pendingAwaiting review
approvedReviewer approved continued access
rejectedReviewer rejected continued access
expiredReview window expired without action

Workflow Defaults​

Workflow defaults define which workflow definition is automatically assigned to new recertification cycles based on cycle type and optional conditions (e.g., tags). Higher-priority defaults are evaluated first; the first match wins. If no conditional default matches, the unconditional default (no conditions) is used as a fallback.

List Defaults​

GET /spaces/{slug}/governance/workflow-defaults?cycle_type=access
ParameterTypeDefaultDescription
cycle_typestring—Optional filter: product or access

Returns: RecertWorkflowDefaultResponse[]


Create Default​

POST /spaces/{slug}/governance/workflow-defaults

Requires: Space Admin

{
"cycle_type": "access",
"workflow_definition_id": "uuid",
"conditions": {"tags": ["pii"]},
"priority": 10,
"name": "PII Access Workflow"
}
FieldTypeRequiredDescription
cycle_typestring✅product or access
workflow_definition_idUUID✅Workflow to assign
conditionsobject—Match conditions (e.g., {"tags": [...]})
priorityinteger—Higher values are evaluated first (default: 0)
namestring—Human-readable label

Returns: RecertWorkflowDefaultResponse (201 Created)


Update Default​

PATCH /spaces/{slug}/governance/workflow-defaults/{default_id}

Requires: Space Admin

All fields are optional — only provided fields are updated.

{
"priority": 20,
"name": "Updated PII Workflow"
}

Returns: RecertWorkflowDefaultResponse


Delete Default​

DELETE /spaces/{slug}/governance/workflow-defaults/{default_id}

Requires: Space Admin

Returns 204 No Content.


RecertWorkflowDefaultResponse​

FieldTypeDescription
idUUIDDefault identifier
space_idUUIDSpace the default belongs to
cycle_typestringproduct or access
workflow_definition_idUUIDLinked workflow definition
conditionsobject | nullMatch conditions
priorityintegerEvaluation priority (higher = first)
namestring | nullDisplay name
created_atdatetimeWhen the default was created

Webhook Events​

The following webhook events are dispatched during recertification and access management operations. Subscribe to these events via the Webhooks API.

Recertification Events​

Event TypeTriggerPayload
recertification.cycle_createdNew cycle createdcycle_id, space_id, cycle_type, name
recertification.cycle_completedCycle marked as completedcycle_id, space_id, reviewed_count, audit_count
recertification.access_revokedAccess revoked during recertification reviewaudit_id, cycle_id, resource_id, user_id

Auto-Revoke Events​

Event TypeTriggerPayload
access.auto_revokedUser deactivated (admin, SCIM, or system)user_id, source, revoked_count, product_ids

The access.auto_revoked event is dispatched per space — if a user had access to products in 3 different spaces, 3 separate webhook events are fired, each containing only the product IDs from that space.

Example Webhook Payload​

{
"event_type": "access.auto_revoked",
"space_id": "uuid",
"timestamp": "2026-02-14T15:30:00Z",
"payload": {
"user_id": "uuid",
"source": "auto_departed",
"revoked_count": 3,
"product_ids": ["uuid-1", "uuid-2", "uuid-3"]
}
}

Auto-Revoke on User Deactivation​

When a user is deactivated — whether via the admin panel, SCIM provisioning, or programmatically — all their active product access is immediately and automatically revoked.

How It Works​

  1. User deactivated → AutoRevokeService finds all active ProductAccess rows
  2. All grants are bulk-updated: is_active = false, revocation_reason set to the source
  3. Audit trail entries created for each revoked grant (access.auto_revoked)
  4. Webhook events dispatched to each affected space

Revocation Sources​

SourceDescription
auto_departedUser departure detected (SCIM deprovision, admin toggle)
adminManual admin deactivation
recertificationRevoked as part of recertification review

Database Schema​

The product_access table includes a revocation_reason column:

ColumnTypeDescription
revocation_reasonVARCHARWhy the access was revoked (auto_departed, admin, recertification, etc.)

Smart Recertification​

Smart Recertification enriches audit items with usage signals and generates automated recommendations, reducing the time reviewers spend on obvious access decisions.

Analyze Cycle​

POST /spaces/{slug}/recertification/cycles/{cycle_id}/analyze

Runs the smart analysis engine on all audits in a cycle. The engine:

  1. Loads the exclusion list and skips excluded users
  2. Builds signal maps (user status, access history, role changes)
  3. Enriches each audit item with signals
  4. Applies rule-based recommendations
  5. Persists results to the database

Response: 200 OK

{
"approve": 83,
"revoke": 12,
"uncertain": 5,
"excluded": 3,
"total": 103
}

Errors:

  • 400 Bad Request — Cycle not found or not in an active state

Signal Enrichment​

Each audit item is annotated with the following signal columns:

SignalTypeDescription
user_activeboolWhether the user account is currently active
last_accessed_atdatetime | nullLast access timestamp
days_since_grantintDays since the access was granted
role_changedboolWhether the user's role changed since the grant
role_at_grantstring | nullThe user's role when access was originally granted

Recommendation Rules​

Rules are evaluated in priority order (highest priority first, skipped when disabled):

RuleRecommendationConfidenceToggle
Departed user (deactivated > 90 days)RevokeHighdeparted_user_enabled
Departed user (recently deactivated)RevokeMediumdeparted_user_enabled
Inactive grant (no access > 180 days)RevokeMedium—
Inactive grant (no access > 90 days)RevokeLow—
Role mismatch since grantRevokeMedium—
Recently active with matching roleApproveHigh—
Active user with recent access (< 30 days)ApproveHigh—
Active user with moderate access (< 90 days)ApproveMedium—

Bulk Apply Recommendations​

POST /spaces/{slug}/recertification/cycles/{cycle_id}/bulk-apply

Apply matching recommendations as reviews in bulk.

Request Body:

FieldTypeRequiredDescription
recommendationstring✅approve or revoke
min_confidencestring—Minimum confidence: high (default), medium, low
reviewer_idUUID—Override reviewer (defaults to caller)

Response: 200 OK

{
"applied": 75,
"skipped": 8
}

Exclusion List​

Users on the exclusion list are skipped during cycle population and smart analysis.

List Exclusions​

GET /spaces/{slug}/recertification/exclusions

Response: 200 OK — Array of exclusion entries

Add Exclusion​

POST /spaces/{slug}/recertification/exclusions

Request Body:

FieldTypeRequiredDescription
user_idUUID✅User to exclude
reasonstring—Reason for exclusion (e.g., "Service account")

Response: 201 Created

Remove Exclusion​

DELETE /spaces/{slug}/recertification/exclusions/{user_id}

Response: 204 No Content


Rule Configuration​

Space-level configuration for smart recertification thresholds and toggles.

Get Rule Config​

GET /spaces/{slug}/recertification/rule-config

Response: 200 OK

{
"departed_user_enabled": true,
"inactive_threshold_days": 180,
"moderate_threshold_days": 90,
"recent_threshold_days": 30,
"auto_revoke_departed": true,
"auto_revoke_grace_days": 30
}

Update Rule Config​

PATCH /spaces/{slug}/recertification/rule-config

Request Body (all fields optional):

FieldTypeDefaultDescription
departed_user_enabledbooltrueEnable departed user detection
inactive_threshold_daysint180Days before a grant is considered inactive
moderate_threshold_daysint90Days for moderate access classification
recent_threshold_daysint30Days for recent access classification
auto_revoke_departedbooltrueAuto-revoke departed users
auto_revoke_grace_daysint30Grace period for auto-revoke appeals

Response: 200 OK — Updated config