Developer Guide
How to integrate OpenProdkt Governance into an application or agent.
If you want to make your first call, start with the Quickstart. This page covers the concepts and behaviors you need once you are building for real.
The integration model
Governance is a pre-flight check. Your application asks before it acts.
The product never executes your action for you. It answers a question and records the answer.
Core concepts
| Concept | What it is |
|---|---|
| Asset | A thing you govern — an AI agent, model endpoint, API, or database |
| Asset key | Stable identifier your code sends at runtime, for example billing.refund-agent |
| Group | A set of approvers responsible for reviewing an asset |
| Policy | A rule attached to an asset that produces an outcome |
| Decision | One evaluation, with a permanent ID |
| Approval | A human review task created by an approval_required decision |
| Policy revision | An immutable snapshot of a policy at a point in time |
Authentication
There are two credential types and they are deliberately separate.
| Credential | Used by | Obtained via |
|---|---|---|
| JWT | Humans in the Console | Email one-time code |
| API key | Applications and agents | Created in the Console |
Both are sent the same way:
Authorization: Bearer <jwt-or-api-key>
Never put an API key in a browser, mobile app, or any client you do not control. API keys are server-side credentials.
Scopes
Every API key carries scopes. Grant the minimum your integration needs — a service that only evaluates decisions needs decision:run and nothing more. A request missing the required scope returns 403.
Identifying the asset
An evaluation request identifies its asset by either asset.id or asset.key — exactly one, never both. A keyed request also sends application, service, and environment, each a lowercase DNS label.
{
"version": "v1",
"asset": { "id": "ast_e8f1a2c3" },
"operation_id": "refund-2026-001",
"transport": { "kind": "http" },
"metadata": { "amount": 9000, "currency": "USD" }
}
Prefer asset.key for readable source code. Conflicting identity fields return 409 asset_identity_conflict.
All context belongs inside metadata. Top-level action, amount, and workflow fields are rejected.
Unknown assets are surfaced, not blocked
If you send an asset key that is not registered, the result is allow with reason asset_not_registered. The key is recorded as a discovery observation for an administrator to review and promote into a governed asset.
This is intentional: an unrecognized tool is never silently blocked, but it is always surfaced for review. See Detection and Decision Flow for the full picture.
Interpreting outcomes
| Outcome | Meaning | Correct handling |
|---|---|---|
allow | Permitted | Proceed |
approval_required | Needs human review. SDKs receive HTTP 202; the proxy receives HTTP 403 with a challenge | Pause and wait; do not proceed. Retry the same operation_id after approval |
deny | Blocked | Stop and surface the reason |
The most common integration bug is treating approval_required as either success or failure. It is neither — it means not yet.
An asset with no matching policy defaults to allow with reason no_matching_policy. Governance is opt-in: define a policy for any workflow you want restricted or reviewed.
Errors
| Status | Meaning | Action |
|---|---|---|
400 | Invalid request | Fix the payload; do not retry unchanged |
401 | Missing or invalid credential | Check the key; do not retry |
403 | Missing scope or insufficient role, or a denied or terminal approval | Grant the scope or stop; do not retry unchanged |
403 with code=approval_required (proxy) | Approval pending | Wait for approval (see Approvals); retry after retry_after_seconds |
404 | Resource not found | Verify the ID |
409 | State conflict, such as approving twice | Re-read current state |
409 | asset_identity_conflict, operation_id_conflict, or approval_retry_mismatch | Do not retry unchanged; send the original context or use a new operation_id |
409 | operation_in_progress | Retry after a short backoff |
402 | Decision, approval, or notification quota exhausted | Check /v1/usage and the active plan |
429 | Rate limit exceeded | Back off and retry |
503 | Policy evaluation failed; the request fails closed (policy_evaluation_failed) | Retry with backoff |
5xx | Server error | Retry with backoff |
Only 429, 5xx, and 409 operation_in_progress are worth retrying. A 402 quota response is terminal until the quota period resets or the plan changes. Use exponential backoff with jitter for retryable responses, and cap the attempts.
Idempotency
Evaluations of assets with one or more assigned policies are recorded and consume one decision unit. Approval-required operations are idempotent by operation_id: repeating the same request returns the existing approval and adds no usage. Unknown assets are pass-through: they create no decision record and are not billed. Registered assets with no assigned policies are allowed with reason no_policies_assigned, are recorded in decision history, and are not billed. Each recorded evaluation consumes quota, so do not repeat a successful call.
Approve and reject are state transitions. Approving an already-approved item returns a conflict rather than double-approving.
For discovery ingest, the discovery_id you supply is the idempotency key. Repeated observations aggregate instead of duplicating.
Operation IDs
Every evaluation requires an operation_id that identifies one business operation, such as a refund ID or an idempotency key. Use letters, digits, ., _, :, and -, up to 128 characters, so the same ID works through the SDKs and the transparent proxy. Reusing an ID with a changed authorization context returns 409 operation_id_conflict. An approved retry must repeat the same operation_id, asset, transport, and metadata.
Pagination
List endpoints accept limit and offset, and return total alongside the results.
GET /v1/assets?limit=25&offset=0
limit accepts 1–100 and defaults to 25. Page by advancing offset until you have consumed total.
Most list endpoints also accept search for case-insensitive partial matching.
Rate limits and quota
Requests are subject to per-key throttling and separate monthly decision, approval, and notification quotas. Rate-limit exhaustion returns 429; a monthly usage quota returns 402 with a machine-readable code.
Two things worth knowing:
- The Playground consumes quota. It calls the real decision API.
- Health and documentation endpoints do not. Only billable actions count.
Read current consumption from the usage endpoint rather than tracking it yourself.
GET /v1/usage reports decisionRequests, approvalRequests, and notificationRequests, together with matching monthly limits under limits. A quota response uses one of decision_quota_exceeded, approval_quota_exceeded, or notification_quota_exceeded so clients can identify which capacity is exhausted.
Usage units
OpenProdkt meters policy execution separately from approvals and notifications:
| Activity | Units |
|---|---|
| Asset discovery or inventory ingestion | 0 |
/v1/evaluate: unknown asset pass-through | 0 |
/v1/evaluate: registered asset with no policies (recorded in decision history) | 0 |
| Evaluation of an asset with assigned policies | 1 decision unit, even when no condition matches |
| Playground evaluation of a registered asset | 1 decision unit, including no-policy or no-match tests |
| Playground pass-through for an unknown asset key | 0 |
| Creation of a pending approval | 1 approval unit, in addition to its decision unit |
| Duplicate request for the same pending operation | 0 additional units |
| Approval status polling or resolution | 0 |
| Approved approval redemption/retry | 0 |
| PDP-created approval lifecycle notification request | 1 notification unit per request |
| Delivery retries, queue redelivery, or duplicate webhook handling | 0 additional units |
An approval lifecycle notification is counted when OpenProdkt creates the outbound request, not when a receiver acknowledges it. Retrying delivery does not create another unit. The usage response reports all three counters and their active monthly limits.
Environments
The sandbox is available at https://api.sandbox.openprodkt.com. Make the base URL configurable rather than hardcoding it, so the same build can target a different environment.
Testing your integration
- Register an asset with a representative definition.
- Add an
allowpolicy for the normal case and anapproval_requiredpolicy for the sensitive case. - Use the Playground with Include evaluation trace enabled to confirm which policy wins and why.
- Exercise all three outcomes in your code path before shipping.
- Confirm each decision appears in Decision Explorer with the expected policy revision.
The evaluation trace is the fastest way to debug an unexpected outcome. It shows every policy considered, in order, and why each matched or did not.
Policy evaluation order
Understanding this prevents most "wrong outcome" surprises:
- Load policies attached to the asset.
- Evaluate newest first.
- First match wins — evaluation stops immediately.
- If nothing matches, deny.
A broad new policy can shadow a narrower older one. When an outcome surprises you, check the trace before changing rules.
Next steps
- SDKs for language clients
- API Reference for the full endpoint surface
- Policy Engine for rule authoring
- Auth and Access for roles and scopes