Module 3: Your First VCP Integration

A guided tutorial: install the SDK, make your first decision call, and verify the result.

DEV 30 min

Learning Objectives

By the end of this module, you will be able to:

  • Install the VCP SDK (Python or TypeScript)
  • Connect to the Creed Space API
  • Make a decide call that evaluates a tool action against a constitution
  • Interpret the decision result (ALLOW, DENY, REQUIRE_HUMAN)
  • Verify a decision token

3.1 — Installation

Python:

pip install creed-sdk

TypeScript:

npm install @creed-space/sdk

3.2 — Getting Your API Key

  1. Sign up at creed.space
  2. Navigate to Dashboard → API Keys → Create New Key
  3. Copy your key (format: crd_test_... for sandbox, crd_live_... for production)

3.3 — Your First Decision

The core VCP operation is decide — "Should this action be allowed, given these values?"

Note: The Python SDK is async. All examples use await and should be run inside an async function or with asyncio.run().

Python:

import asyncio
from creed_sdk import CreedClient

async def main():
    client = CreedClient(api_key="crd_test_your_key_here")

    result = await client.decide(
        tool_name="send_email",
        arguments={"to": "patient@example.com", "body": "Your test results are..."},
        constitution_id="healthcare_v3",
    )

    if result.decision == "ALLOW":
        print(f"Approved. Risk score: {result.risk.score}")
        print(f"Decision token: {result.decision_token}")
    elif result.decision == "DENY":
        print(f"Denied. Reasons: {result.reasons}")
        print(f"Guidance: {result.guidance}")
    elif result.decision == "REQUIRE_HUMAN":
        print(f"Needs human review. Review ID: {result.review_id}")

    await client.close()

asyncio.run(main())

TypeScript:

import { createClient } from '@creed-space/sdk';

const client = createClient({ apiKey: 'crd_test_your_key_here' });

const result = await client.decide({
  toolName: 'send_email',
  arguments: { to: 'patient@example.com', body: 'Your test results are...' },
  constitutionId: 'healthcare_v3',
});

console.log(result.decision); // "ALLOW" | "DENY" | "REQUIRE_HUMAN"

3.4 — Understanding the Response

Every decision result includes these common fields:

FieldDescription
decisionThe verdict: ALLOW, DENY, or REQUIRE_HUMAN
run_id / runIdUnique identifier for this evaluation run
action_id / actionIdIdentifier for this specific action within the run
args_hash / argsHashSHA-256 hash of the arguments (for later verification)
risk.scoreNumeric risk assessment (0.0–1.0)
risk.labelsCategorical risk labels that triggered

ALLOW-specific fields:

FieldDescription
decision_token / decisionTokenSigned JWT proof of this decision (use for downstream authorisation)
expires_at / expiresAtWhen the decision token expires

DENY-specific fields:

FieldDescription
reasonsList of strings explaining why the action was denied
guidanceDictionary of suggestions for how to modify the action to be allowed

3.5 — Verifying a Decision

Decision tokens prove that a specific action was evaluated and approved. Downstream systems can verify this without re-evaluating:

Python:

auth = await client.authorize(
    decision_token=result.decision_token,
    tool_name="send_email",
    args_hash=result.args_hash,
)

if auth.authorized:
    # Proceed with the action — it's been vetted
    send_the_email()
else:
    print(f"Token invalid: {auth.error}")

TypeScript:

const auth = await client.authorize({
  decisionToken: result.decisionToken,
  toolName: 'send_email',
  argsHash: result.argsHash,
});

if (auth.authorized) {
  // Proceed — action has been vetted
  sendTheEmail();
} else {
  console.log(`Token invalid: ${auth.error}`);
}

3.6 — Callback-Based Flow

Both SDKs support callback-based flow control, allowing you to handle each decision type with dedicated functions:

Python:

result = await client.decide(
    tool_name="send_email",
    arguments={"to": "patient@example.com", "body": "Results..."},
    constitution_id="healthcare_v3",
    on_allow=lambda d: print(f"Authorized: {d.decision_token}"),
    on_deny=lambda d: print(f"Denied: {d.reasons}"),
    on_require_human=lambda d: print(f"Human review needed"),
)

TypeScript:

const result = await client.decide({
  toolName: 'send_email',
  arguments: { to: 'patient@example.com', body: 'Results...' },
  constitutionId: 'healthcare_v3',
  onAllow: (d) => console.log('Authorized:', d.decisionToken),
  onDeny: (d) => console.log('Denied:', d.reasons),
  onRequireHuman: (d) => console.log('Human review needed'),
});

Exercise

  1. Use the VCP Inspector to decode a sample CSM-1 token and understand what constitution it represents.
  2. Make three decide calls with different actions — one that should be allowed, one that should be denied, and one that's ambiguous. Observe how the constitution shapes the decisions.

VCP integration starts with a single API call. decide is the fundamental operation — everything else builds on it.

Try It

Use the Token Playground to experiment with CSM-1 tokens interactively, or explore the Gentian demo to see VCP decisions in action.