Skip to content

Badge API

The Badge API provides portable, verifiable identity for agents through Trust Badges—signed JWS tokens that prove an agent's identity and trust level.

RFC-002 Implementation

This API implements RFC-002: Trust Badge System for portable agent identity.


Quick Start

from capiscio_sdk import verify_badge, parse_badge, TrustLevel

# Verify a badge from another agent
result = verify_badge(
    token,
    trusted_issuers=["https://registry.capisc.io"],
    audience="https://my-service.example.com",
)

if result.valid:
    print(f"✅ Agent {result.claims.subject} verified")  # Agent DID
    print(f"   Trust Level: {result.claims.trust_level.value}")  # "0" to "4"
    print(f"   Domain: {result.claims.domain}")
    print(f"   IAL: {result.claims.ial}")  # Identity Assurance Level
else:
    print(f"❌ Verification failed: {result.error}")

Functions

capiscio_sdk.badge.verify_badge

verify_badge(
    token: str,
    *,
    trusted_issuers: Optional[List[str]] = None,
    audience: Optional[str] = None,
    mode: Union[VerifyMode, str] = VerifyMode.ONLINE,
    skip_revocation_check: bool = False,
    skip_agent_status_check: bool = False,
    public_key_jwk: Optional[str] = None,
    fail_open: bool = False,
    stale_threshold_seconds: int = REVOCATION_CACHE_MAX_STALENESS,
    options: Optional[VerifyOptions] = None
) -> VerifyResult

Verify a Trust Badge token.

Performs full RFC-002 verification including: - JWS signature verification - Claims validation (exp, iat, iss, sub, aud) - Revocation check (online/hybrid modes) - Agent status check (online/hybrid modes) - RFC-002 v1.3 §7.5: Staleness fail-closed for levels 2-4

PARAMETER DESCRIPTION
token

The badge JWT/JWS token to verify.

TYPE: str

trusted_issuers

List of trusted issuer URLs. If empty, all issuers accepted.

TYPE: Optional[List[str]] DEFAULT: None

audience

Your service URL for audience validation.

TYPE: Optional[str] DEFAULT: None

mode

Verification mode (online, offline, hybrid).

TYPE: Union[VerifyMode, str] DEFAULT: ONLINE

skip_revocation_check

Skip revocation check (testing only).

TYPE: bool DEFAULT: False

skip_agent_status_check

Skip agent status check (testing only).

TYPE: bool DEFAULT: False

public_key_jwk

Override public key JWK for offline verification.

TYPE: Optional[str] DEFAULT: None

fail_open

Allow stale cache for levels 2-4 (WARNING: violates RFC-002 default).

TYPE: bool DEFAULT: False

stale_threshold_seconds

Cache staleness threshold (default: 300 per RFC-002 §7.5).

TYPE: int DEFAULT: REVOCATION_CACHE_MAX_STALENESS

options

VerifyOptions object (alternative to individual args).

TYPE: Optional[VerifyOptions] DEFAULT: None

RETURNS DESCRIPTION
VerifyResult

VerifyResult with validation status and claims.

Example

result = verify_badge( token, trusted_issuers=["https://registry.capisc.io"], audience="https://my-service.example.com", )

if result.valid: print(f"Agent {result.claims.agent_id} verified at level {result.claims.trust_level}")

capiscio_sdk.badge.parse_badge

parse_badge(token: str) -> BadgeClaims

Parse badge claims without verification.

Use this to inspect badge contents before full verification, or to extract claims for display purposes.

PARAMETER DESCRIPTION
token

The badge JWT/JWS token to parse.

TYPE: str

RETURNS DESCRIPTION
BadgeClaims

BadgeClaims object with parsed claims.

RAISES DESCRIPTION
ValueError

If the token cannot be parsed.

Example

claims = parse_badge(token) print(f"Badge for: {claims.agent_id}") print(f"Issued by: {claims.issuer}") print(f"Expires: {claims.expires_at}")

capiscio_sdk.badge.request_badge async

request_badge(
    agent_id: str,
    *,
    ca_url: str = "https://registry.capisc.io",
    api_key: Optional[str] = None,
    domain: Optional[str] = None,
    trust_level: Union[
        TrustLevel, str
    ] = TrustLevel.LEVEL_1,
    audience: Optional[List[str]] = None,
    timeout: float = 30.0
) -> str

Request a new Trust Badge from a Certificate Authority.

This sends a request to the CA to issue a new badge for the agent. The CA will verify the agent's identity based on the trust level and return a signed badge token.

Uses the capiscio-core gRPC service for the actual request.

PARAMETER DESCRIPTION
agent_id

The agent identifier to request a badge for.

TYPE: str

ca_url

Certificate Authority URL (default: CapiscIO registry).

TYPE: str DEFAULT: 'https://registry.capisc.io'

api_key

API key for authentication with the CA.

TYPE: Optional[str] DEFAULT: None

domain

Agent's domain (required for verification).

TYPE: Optional[str] DEFAULT: None

trust_level

Requested trust level per RFC-002 §5: - 1 (REG): Registered - Account registration - 2 (DV): Domain Validated - DNS/HTTP proof - 3 (OV): Organization Validated - Legal entity - 4 (EV): Extended Validated - Security audit Note: LEVEL_0 (Self-Signed) is not available via CA request.

TYPE: Union[TrustLevel, str] DEFAULT: LEVEL_1

audience

Optional audience restrictions for the badge.

TYPE: Optional[List[str]] DEFAULT: None

timeout

Request timeout in seconds (not used with gRPC).

TYPE: float DEFAULT: 30.0

RETURNS DESCRIPTION
str

The signed badge JWT token.

RAISES DESCRIPTION
ValueError

If the CA returns an error.

Example

token = await request_badge( agent_id="my-agent", ca_url="https://registry.capisc.io", api_key=os.environ["CAPISCIO_API_KEY"], domain="example.com", )

Save the token for later use

with open("badge.jwt", "w") as f: f.write(token)

capiscio_sdk.badge.request_badge_sync

request_badge_sync(
    agent_id: str,
    *,
    ca_url: str = "https://registry.capisc.io",
    api_key: Optional[str] = None,
    domain: Optional[str] = None,
    trust_level: Union[
        TrustLevel, str
    ] = TrustLevel.LEVEL_1,
    audience: Optional[List[str]] = None,
    timeout: float = 30.0
) -> str

Synchronous version of request_badge.

Uses the capiscio-core gRPC service for the actual request. See request_badge for full documentation.

capiscio_sdk.badge.request_pop_badge async

request_pop_badge(
    agent_did: str,
    private_key_jwk: str,
    *,
    ca_url: str = "https://registry.capisc.io",
    api_key: Optional[str] = None,
    ttl_seconds: int = 300,
    audience: Optional[List[str]] = None,
    timeout: float = 30.0
) -> str

Request a Trust Badge using Proof of Possession (RFC-003).

This requests a badge using the PoP challenge-response protocol, providing IAL-1 assurance with cryptographic key binding. The agent must prove possession of the private key associated with their DID.

Uses the capiscio-core gRPC service for the actual PoP flow.

PARAMETER DESCRIPTION
agent_did

The agent DID (did:web:... or did🔑...).

TYPE: str

private_key_jwk

Private key in JWK format (JSON string).

TYPE: str

ca_url

Certificate Authority URL (default: CapiscIO registry).

TYPE: str DEFAULT: 'https://registry.capisc.io'

api_key

API key for authentication with the CA.

TYPE: Optional[str] DEFAULT: None

ttl_seconds

Requested badge TTL in seconds (default: 300).

TYPE: int DEFAULT: 300

audience

Optional audience restrictions for the badge.

TYPE: Optional[List[str]] DEFAULT: None

timeout

Request timeout in seconds (not used with gRPC).

TYPE: float DEFAULT: 30.0

RETURNS DESCRIPTION
str

The signed badge JWT token with IAL-1 assurance.

RAISES DESCRIPTION
ValueError

If the CA returns an error or PoP verification fails.

Example

import json

Load private key

with open("private.jwk") as f: private_key_jwk = json.dumps(json.load(f))

token = await request_pop_badge( agent_did="did:web:registry.capisc.io:agents:my-agent", private_key_jwk=private_key_jwk, ca_url="https://registry.capisc.io", api_key=os.environ["CAPISCIO_API_KEY"], )

Verify the badge has IAL-1

claims = parse_badge(token)

Note: IAL-1 badges have a 'cnf' claim with key binding

capiscio_sdk.badge.request_pop_badge_sync

request_pop_badge_sync(
    agent_did: str,
    private_key_jwk: str,
    *,
    ca_url: str = "https://registry.capisc.io",
    api_key: Optional[str] = None,
    ttl_seconds: int = 300,
    audience: Optional[List[str]] = None,
    timeout: float = 30.0
) -> str

Synchronous version of request_pop_badge.

Uses the capiscio-core gRPC service for the actual PoP flow. See request_pop_badge for full documentation.

capiscio_sdk.badge.start_badge_keeper

start_badge_keeper(
    mode: str,
    *,
    agent_id: str = "",
    api_key: str = "",
    ca_url: str = "https://registry.capisc.io",
    private_key_path: str = "",
    output_file: str = "badge.jwt",
    domain: str = "",
    ttl_seconds: int = 300,
    renew_before_seconds: int = 60,
    check_interval_seconds: int = 30,
    trust_level: Union[
        TrustLevel, str, int
    ] = TrustLevel.LEVEL_1
) -> Generator[dict, None, None]

Start a badge keeper daemon (RFC-002 §7.3).

The keeper automatically renews badges before they expire, ensuring continuous operation. Returns a generator of keeper events.

PARAMETER DESCRIPTION
mode

'ca' for CA mode, 'self-sign' for development

TYPE: str

agent_id

Agent UUID (required for CA mode)

TYPE: str DEFAULT: ''

api_key

API key (required for CA mode)

TYPE: str DEFAULT: ''

ca_url

CA URL (default: https://registry.capisc.io)

TYPE: str DEFAULT: 'https://registry.capisc.io'

private_key_path

Path to private key JWK (required for self-sign)

TYPE: str DEFAULT: ''

output_file

Path to write badge file

TYPE: str DEFAULT: 'badge.jwt'

domain

Agent domain

TYPE: str DEFAULT: ''

ttl_seconds

Badge TTL (default: 300)

TYPE: int DEFAULT: 300

renew_before_seconds

Renew this many seconds before expiry (default: 60)

TYPE: int DEFAULT: 60

check_interval_seconds

Check interval (default: 30)

TYPE: int DEFAULT: 30

trust_level

Trust level for CA mode (1-4, default: 1)

TYPE: Union[TrustLevel, str, int] DEFAULT: LEVEL_1

YIELDS DESCRIPTION
dict

KeeperEvent dicts with keys: type, badge_jti, subject, trust_level, expires_at, error, error_code, timestamp, token

TYPE:: dict

Example

CA mode - production

for event in start_badge_keeper( mode="ca", agent_id="my-agent-uuid", api_key=os.environ["CAPISCIO_API_KEY"], ): if event["type"] == "renewed": print(f"Badge renewed: {event['badge_jti']}") elif event["type"] == "error": print(f"Error: {event['error']}")

Self-sign mode - development

for event in start_badge_keeper( mode="self-sign", private_key_path="private.jwk", ): print(f"Event: {event['type']}")


Classes

capiscio_sdk.badge.BadgeClaims dataclass

Parsed badge claims from a Trust Badge token.

ATTRIBUTE DESCRIPTION
jti

Unique badge identifier (UUID).

TYPE: str

issuer

Badge issuer URL (CA) or did:key for self-signed.

TYPE: str

subject

Agent DID (did:web format).

TYPE: str

audience

Optional list of intended audience URLs.

TYPE: List[str]

issued_at

When the badge was issued.

TYPE: datetime

expires_at

When the badge expires.

TYPE: datetime

trust_level

Trust level per RFC-002 §5: - 0 (SS): Self-Signed - Development only - 1 (REG): Registered - Account registration - 2 (DV): Domain Validated - DNS/HTTP proof - 3 (OV): Organization Validated - Legal entity - 4 (EV): Extended Validated - Security audit

TYPE: TrustLevel

domain

Agent's verified domain.

TYPE: str

agent_name

Human-readable agent name.

TYPE: str

agent_id

Extracted agent ID from subject DID.

TYPE: str

ial

Identity Assurance Level (RFC-002 §7.2.1): - "0": Account-attested (no key proof) - "1": Proof of Possession (key holder verified, has cnf claim)

TYPE: str

raw_claims

Original JWT claims dict for advanced access.

TYPE: Optional[dict]

agent_id property

agent_id: str

Extract agent ID from subject DID.

is_expired property

is_expired: bool

Check if the badge has expired.

is_not_yet_valid property

is_not_yet_valid: bool

Check if the badge is not yet valid.

has_key_binding property

has_key_binding: bool

Check if this badge has IAL-1 key binding (ial='1' and cnf claim).

Per RFC-002 §7.2.1, IAL-1 badges MUST include a 'cnf' (confirmation) claim that cryptographically binds the badge to the agent's private key.

confirmation_key property

confirmation_key: Optional[dict]

Get the confirmation key (cnf claim) if present.

Returns the JWK thumbprint or key from the cnf claim for IAL-1 badges. Returns None for IAL-0 badges or if cnf is not present.

from_dict classmethod

from_dict(data: dict) -> BadgeClaims

Create BadgeClaims from a dictionary.

to_dict

to_dict() -> dict

Convert to dictionary.

Preserves the cnf (confirmation) claim for IAL-1 badges to support round-trip serialization.

capiscio_sdk.badge.VerifyResult dataclass

Result of badge verification.

ATTRIBUTE DESCRIPTION
valid

Whether the badge is valid.

TYPE: bool

claims

Parsed badge claims (if valid or parseable).

TYPE: Optional[BadgeClaims]

error

Error message if verification failed.

TYPE: Optional[str]

error_code

RFC-002 error code if applicable.

TYPE: Optional[str]

warnings

Non-fatal issues encountered.

TYPE: List[str]

mode

Verification mode that was used.

TYPE: VerifyMode

capiscio_sdk.badge.VerifyOptions dataclass

Options for badge verification.

ATTRIBUTE DESCRIPTION
mode

Verification mode (online, offline, hybrid).

TYPE: VerifyMode

trusted_issuers

List of trusted issuer URLs.

TYPE: List[str]

audience

Expected audience (your service URL).

TYPE: Optional[str]

skip_revocation_check

Skip revocation check (testing only).

TYPE: bool

skip_agent_status_check

Skip agent status check (testing only).

TYPE: bool

public_key_jwk

Override public key (for offline verification).

TYPE: Optional[str]

fail_open

Allow stale cache for levels 2-4 (WARNING: violates RFC-002 default).

TYPE: bool

stale_threshold_seconds

Cache staleness threshold in seconds (default: 300 per RFC-002 §7.5).

TYPE: int

capiscio_sdk.badge.VerifyMode

Bases: Enum

Badge verification mode.

ONLINE class-attribute instance-attribute

ONLINE = 'online'

Perform real-time checks against the registry (revocation, agent status).

OFFLINE class-attribute instance-attribute

OFFLINE = 'offline'

Use only local trust store and revocation cache.

HYBRID class-attribute instance-attribute

HYBRID = 'hybrid'

Try online checks, fall back to cache if unavailable.

capiscio_sdk.badge.TrustLevel

Bases: Enum

Trust level as defined in RFC-002 §5.

Levels

LEVEL_0 (SS): Self-Signed - did:key, iss == sub. Development only. LEVEL_1 (REG): Registered - Account registration with CA. LEVEL_2 (DV): Domain Validated - DNS/HTTP domain ownership proof. LEVEL_3 (OV): Organization Validated - Legal entity verification. LEVEL_4 (EV): Extended Validated - Manual review + security audit.

LEVEL_0 class-attribute instance-attribute

LEVEL_0 = '0'

Self-Signed (SS) - did:key, iss == sub. Development only.

LEVEL_1 class-attribute instance-attribute

LEVEL_1 = '1'

Registered (REG) - Account registration with CA.

LEVEL_2 class-attribute instance-attribute

LEVEL_2 = '2'

Domain Validated (DV) - DNS/HTTP domain ownership proof.

LEVEL_3 class-attribute instance-attribute

LEVEL_3 = '3'

Organization Validated (OV) - Legal entity verification.

LEVEL_4 class-attribute instance-attribute

LEVEL_4 = '4'

Extended Validated (EV) - Manual review + security audit.

from_string classmethod

from_string(value: str) -> TrustLevel

Create TrustLevel from string value.


Error Codes

RFC-002 defines these verification error codes:

Code Description
BADGE_MALFORMED Token format invalid
BADGE_SIGNATURE_INVALID Signature verification failed
BADGE_EXPIRED Badge past expiration time
BADGE_NOT_YET_VALID Badge not yet valid (iat in future)
BADGE_ISSUER_UNTRUSTED Issuer not in trusted list
BADGE_AUDIENCE_MISMATCH Your service not in audience
BADGE_REVOKED Badge has been revoked
BADGE_CLAIMS_INVALID Required claims missing/invalid
BADGE_AGENT_DISABLED Agent has been disabled

Patterns

Middleware Pattern

from capiscio_sdk import verify_badge
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.middleware("http")
async def verify_badge_middleware(request: Request, call_next):
    # Skip for health checks
    if request.url.path == "/health":
        return await call_next(request)

    # Get badge from Authorization header
    auth = request.headers.get("Authorization", "")
    if not auth.startswith("Bearer "):
        raise HTTPException(401, "Missing badge")

    token = auth[7:]
    result = verify_badge(
        token,
        trusted_issuers=["https://registry.capisc.io"],
        audience=str(request.base_url),
    )

    if not result.valid:
        raise HTTPException(401, f"Invalid badge: {result.error}")

    # Attach claims to request state (matches SDK middleware pattern)
    request.state.agent = result.claims
    request.state.agent_id = result.claims.issuer
    return await call_next(request)

Use Built-in Middleware

For production, consider using the SDK's built-in CapiscioMiddleware which handles body integrity verification and proper error responses per RFC-002 §9.1.

Trust Level Gate

from capiscio_sdk import verify_badge, TrustLevel

def require_trust_level(token: str, min_level: TrustLevel) -> bool:
    """Require minimum trust level for sensitive operations.

    RFC-002 §5 Trust Levels:
    - LEVEL_0: Self-Signed (SS) - development only
    - LEVEL_1: Registered (REG) - account registration
    - LEVEL_2: Domain Validated (DV) - DNS/HTTP proof
    - LEVEL_3: Organization Validated (OV) - legal entity
    - LEVEL_4: Extended Validated (EV) - security audit
    """
    result = verify_badge(token)

    if not result.valid:
        return False

    # TrustLevel enum values are strings "0"-"4", compare as integers
    return int(result.claims.trust_level.value) >= int(min_level.value)

# Usage
if require_trust_level(token, TrustLevel.LEVEL_2):
    # Allow sensitive operation (DV or higher)
    pass

See Also