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
namestringCycle name
due_datedatetimeReview deadline
cycle_typestringproduct or access
descriptionstringOptional 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_typestringOptional 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_typestringproduct or access
workflow_definition_idUUIDWorkflow to assign
conditionsobjectMatch conditions (e.g., {"tags": [...]})
priorityintegerHigher values are evaluated first (default: 0)
namestringHuman-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
recommendationstringapprove or revoke
min_confidencestringMinimum confidence: high (default), medium, low
reviewer_idUUIDOverride 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_idUUIDUser to exclude
reasonstringReason 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