PDP Adjudication API
The Policy Decision Point (PDP) evaluates content against active constitutions and returns a decision kernel -- the verdict, rationale, and which norms applied.
POST /api/v1/pdp/adjudicate
Submit a message for policy adjudication.
Request
curl -X POST "https://api.creed.space/api/v1/pdp/adjudicate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "How do I reset my password?",
"persona": "ambassador",
"constitutions": ["UEF"],
"adherence_level": 3,
"influence_scope": "advise_only"
}'Request Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
message | string | Yes | -- | Text to evaluate (max 32,000 chars) |
persona | string | No | ambassador | Persona context for evaluation |
constitutions | string[] | No | ["UEF"] | Constitution IDs to evaluate against (max 20) |
adherence_level | integer | No | 3 | Strictness from 1 (lenient) to 5 (strict) |
influence_scope | string | No | advise_only | One of advise_only, compare_options, motivate_with_disclosure |
context | object | No | null | Additional context attributes |
nonce | string | No | null | Client-supplied nonce for verification (max 100 chars) |
transparency_level | integer | No | 0 | 0-3, controls detail in response |
Response -- Decision Kernel
{
"decision": "permit",
"rationale": "Message is a routine support request with no safety concerns.",
"norms": [
{"t": "P", "act": "provide_support", "ref": "pdp"}
],
"precedence": ["UEF"],
"sources": [],
"caveats": [],
"nonce": "abc123",
"hash": "sha256:...",
"timestamp": 1716566400.0,
"decision_id": "pdp_abc123"
}Each norm has a type t: O (obligation: a required action), F (forbidden action) or P (permitted action). act names the action and ref names the source of the norm.
Decision Values
The kernel uses this public vocabulary:
| Decision | Meaning |
|---|---|
permit | Content may proceed |
divert | Content should be altered or redirected to a safer alternative |
forbid | Content violates policy and must be withheld |
depends | More context or human review is needed |
Internal decision types that have no direct public equivalent (for example transform, creed_conflict and partial_compliance) are reported as divert or depends. Treat any decision other than permit as not permitted.
Gate on permit, not on block
The kernel never returns allow, block, modify or escalate. Code that checks decision == "block" will never match and will let forbidden content through. Allow content only when decision == "permit", and withhold or review everything else.
Kernel Fields
| Field | Type | Description |
|---|---|---|
decision | string | The policy decision |
rationale | string | Human-readable explanation (max 500 chars) |
norms | object[] | Constitutional norms that informed the decision |
precedence | string[] | Constitution priority order applied |
sources | object[] | Evidence sources |
caveats | string[] | Qualifications or warnings |
nonce | string | Echo of client nonce if provided |
hash | string | Cryptographic hash of the kernel for verification |
timestamp | float | Unix timestamp of the decision |
decision_id | string | null | Stable ID for the decision dossier, override and appeal endpoints. Not part of the kernel hash |
Verify a Decision
These endpoints need a signed-in user session (dashboard session cookie or login JWT), not an API key. A call with X-API-Key returns 401.
After receiving a kernel, verify that it was applied:
# Check if kernel was applied
GET /api/v1/pdp/verify/{nonce}
# Confirm kernel application (called by AI after use)
POST /api/v1/pdp/verify/{nonce}/confirmBatch Evaluation
Evaluate multiple actions in a single request (1-100 items). Each action consumes one rate-limit token.
curl -X POST "https://api.creed.space/api/v1/pdp/batch-evaluate" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{"action": "Send email to user", "context": "password reset"},
{"action": "Delete all records", "context": "admin cleanup"}
],
"constitution_ids": ["UEF"],
"session_id": "sess_abc123"
}'Batch Response
{
"results": [
{
"decision_type": "allow",
"allowed": true,
"confidence": 0.95,
"reasons": ["..."],
"reason_codes": [],
"risk_score": 0.05,
"decision_id": "pdp_abc123",
"evaluation_time_ms": 12.4
},
{
"decision_type": "block",
"allowed": false,
"confidence": 0.9,
"reasons": ["..."],
"reason_codes": ["..."],
"risk_score": 0.92,
"decision_id": "pdp_def456",
"evaluation_time_ms": 15.1
}
],
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"total": 2,
"blocked_count": 1
}Batch results report decision_type in the internal vocabulary (allow, block, modify, transform, escalate, creed_conflict, partial_compliance), not the public kernel vocabulary. Gate on the boolean allowed field, and treat allowed: false or a missing allowed as not permitted.
Appeal a Decision
Request re-evaluation of a previous decision with additional context:
curl -X POST "https://api.creed.space/api/v1/pdp/appeal" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"decision_id": "pdp_abc123",
"original_message": "The blocked message text",
"appeal_reason": "This was a legitimate support question about..."
}'The server verifies that the decision belongs to the authenticated account, that original_message exactly matches the evaluated input, and that the decision is still inside its bounded appeal window. The original server-side constitution set is reused. Callers cannot substitute a different policy set.
An accepted appeal returns a short-lived, single-use authorization:
{
"allowed": true,
"decision": "allow",
"reason": "Current policy permits an exact retry with verified context.",
"appeal_authorization_token": "pdpapl1.<authorization-id>.<secret>",
"appeal_authorization_expires_at": "2026-07-29T12:05:00Z",
"source_thread_id": "01890f31-9bd2-7cc1-98c8-123456789abc"
}As with batch results, the appeal decision uses the internal vocabulary; gate on allowed.
Retry the exact original message on source_thread_id through POST /api/v1/runs/stream. Send the returned token and the exact same appeal reason as top-level request fields:
{
"input": {"content": "The blocked message text"},
"configurable": {
"thread_id": "01890f31-9bd2-7cc1-98c8-123456789abc",
"runConfig": {}
},
"appeal_authorization_token": "pdpapl1.<authorization-id>.<secret>",
"appeal_reason": "This was a legitimate support question about..."
}The authorization is bound to the authenticated account, decision, thread, original input, appeal reason, and server constitution set. It is consumed atomically and cannot be replayed. The retry still runs the current PDP and Guardian policies; an accepted appeal is verified context for one evaluation, not a bypass. Appeal responses are marked Cache-Control: no-store.
Influence Ledger
Requires a signed-in user session (dashboard session cookie or login JWT), not an API key. A call with X-API-Key returns 401.
Track how PDP decisions influence AI behavior over time:
GET /api/v1/pdp/influence/ledger?limit=50Returns influence entries with the decision history for the authenticated account.
Error Responses
Only API-key authentication (401) and scope (403) errors follow RFC 7807 (application/problem+json); see Authentication. Branch on the HTTP status code, not the body.
422 Unprocessable Entity -- validation failure (for example message longer than 32,000 characters):
{
"detail": [
{"loc": ["body", "message"], "msg": "String should have at most 32000 characters", "type": "string_too_long"}
],
"error": "Validation error"
}429 Too Many Requests -- default 100 requests per 60 seconds per key:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retry_after": 30
}Wait for the number of seconds in the Retry-After header. The per-IP limit that runs before authentication returns a different body ({"error": {"type": "rate_limit_exceeded", ...}, "detail": "Rate limit exceeded"}), so read Retry-After rather than parsing the body.
500 Internal Server Error -- audit trail failure (strict mode):
{
"error": "Audit trail recording failed; request aborted for safety",
"status_code": 500
}Fail-Closed
In strict audit mode (default), requests are rejected if the audit trail cannot be recorded -- no decision goes unlogged.