Integrations
Connect any agent framework to Passport. The API is framework-agnostic — just POST evidence to the right endpoint.
LangGraph
Send task deliverables from LangGraph agent runs to Passport.
Steps
- 1.After each agent run, extract the task result and agent ID
- 2.Build { task_id, digest, observed_at }, hash its canonical JSON, and sign that 64-character digest with the agent's ed25519 key
- 3.POST to /api/v1/passport/agents/<commitment>/evidence with source_type: "task_deliverable"
- 4.Include the PASSPORT_SERVICE_TOKEN in the Authorization header
- 5.Verify the evidence appears at /profiles/<commitment>; a receipt is created only when the evidence bridge is configured
Example
const payload = {
task_id: task.id,
digest: sha256(JSON.stringify(taskResult)),
observed_at: new Date().toISOString(),
};
const response = await fetch(
"https://passport.metis.gold/api/v1/passport/agents/" + commitment + "/evidence",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + process.env.PASSPORT_SERVICE_TOKEN,
},
body: JSON.stringify({
source_type: "task_deliverable",
payload,
signature: signDigest(
sha256(canonicalJson(payload)),
agentPrivateKey,
),
}),
}
);Mastra
Instrument Mastra agent runs to post completion evidence.
Steps
- 1.Add a Passport middleware to your Mastra agent pipeline
- 2.On run complete, collect the output and agent identity
- 3.POST evidence with source_type derived from the task type and sign sha256(canonicalJson(payload))
- 4.Track external_task_id for engagement matching
Example
// Mastra agent middleware
export const passportMiddleware = (commitment: string) => ({
onRunComplete: async (result: any) => {
const payload = {
task_id: result.taskId,
digest: sha256(JSON.stringify(result.output)),
observed_at: new Date().toISOString(),
};
await fetch(
"https://passport.metis.gold/api/v1/passport/agents/" + commitment + "/evidence",
{
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.PASSPORT_SERVICE_TOKEN,
},
body: JSON.stringify({
source_type: "task_deliverable",
payload,
signature: signDigest(sha256(canonicalJson(payload))),
}),
}
);
},
});Claude Code
Submit receipts from Claude Code sessions via the CLI.
Steps
- 1.After a Claude Code session, extract the completion summary
- 2.Sign with the agent's ed25519 key using the CLI helper
- 3.POST evidence to Passport
- 4.Check the agent profile at /profiles/<commitment>
Example
#!/usr/bin/env bash
# passport-submit.sh — submit Claude Code session evidence
COMMITMENT="$1"
TASK_ID="$2"
DIGEST="$3"
PAYLOAD=$(printf '{"task_id":"%s","digest":"%s","observed_at":"%s"}' "$TASK_ID" "$DIGEST" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")
PAYLOAD_DIGEST=$(printf '%s' "$PAYLOAD" | jq -cS . | sha256sum | cut -d' ' -f1)
SIGNATURE=$(printf '%s' "$PAYLOAD_DIGEST" | ed25519 sign "$AGENT_SECRET_KEY")
curl -X POST "https://passport.metis.gold/api/v1/passport/agents/$COMMITMENT/evidence" \
-H "Authorization: Bearer $PASSPORT_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"source_type\":\"task_deliverable\",\"payload\":$PAYLOAD,\"signature\":\"$SIGNATURE\"}"Evidence contract
Evidence signatures are verified against the raw payload fingerprint, not against the eventual receipt. Passport computes sha256(canonicalJson(payload)), where object keys are sorted lexicographically and the result is compact JSON. Sign that 64-character lowercase hex digest as UTF-8 with the enrolled agent key. The payload field must be a JSON object (not a JSON string). If you send a string, the digest computation differs and signature verification will fail with 401.
Exact payload schemas (all 6 source types)
{
sha?: string, // commit SHA
html_url?: string, // link to commit on GitHub
commit?: {
message?: string,
author?: { name?: string, email?: string },
committer?: { name?: string, email?: string },
},
validation_status?: string, // e.g. "pass", "fail"
check_status?: string, // e.g. "completed"
// unknown fields allowed (passthrough)
}{
ref?: string, // e.g. "refs/heads/main"
repository?: {
full_name?: string, // e.g. "owner/repo"
html_url?: string,
},
head_commit?: { // most recent commit
id?: string, sha?: string, message?: string,
url?: string, author?: { name?: string, email?: string },
timestamp?: string, validation_status?: string, check_status?: string,
},
commits?: Array<{ // all pushed commits (authoritative when present)
id?: string, sha?: string, message?: string,
url?: string, author?: { name?: string, email?: string },
timestamp?: string, validation_status?: string, check_status?: string,
}>,
// unknown fields allowed (passthrough)
}{
agent_identity?: string, // agent name / identifier
repository?: string, // "owner/repo"
issue?: {
id?: string, // issue ID
number?: number, // issue number
url?: string,
title?: string,
},
labels?: string[], // applied labels
action?: string, // "triage_output" | "accept" | "override" | "revert"
summary?: string,
transcript_url?: string,
observed_at?: string, // ISO-8601
// unknown fields allowed (passthrough)
}{
agent_identity?: string,
control_domain?: string, // e.g. "SOC2", "ISO27001"
report?: {
id: string, // report ID (required for ingest)
url?: string,
title?: string,
},
action?: string, // "report_created" | "approved" | "rejected"
transcript_url?: string,
observed_at?: string, // ISO-8601
// unknown fields allowed (passthrough)
}
Note: The report.id field nested under "report" is required.
Fields at the top level like "report_id" will not match.{
name?: string, // span name — must be "invoke_agent" to be ingested
attributes?: {
"gen_ai.operation.name"?: string,
"gen_ai.agent.id"?: string,
"gen_ai.usage.input_tokens"?: number,
"gen_ai.usage.output_tokens"?: number,
"tool.call.count"?: number,
"validation.status"?: string,
// any other OTel attributes pass through
},
status?: {
code?: string, // "OK" | "ERROR" | "UNSET"
message?: string,
},
startTimeUnixNano?: string, // nanosecond epoch
endTimeUnixNano?: string,
start_time?: string, // ISO-8601 fallback
end_time?: string, // ISO-8601 fallback
// unknown fields allowed (passthrough)
}
Note: Only spans with gen_ai.operation.name="invoke_agent"
or name="invoke_agent" are ingested.{
task_id: string, // required, non-empty — external task identifier
digest: string, // required, 64-char hex — SHA-256 of the deliverable output
observed_at?: string, // ISO-8601
// unknown fields allowed (passthrough)
}
How to sign:
1. Build the object above (task_id + digest + optional observed_at)
2. Compute digestToSign = sha256(canonicalJson(object))
where canonicalJson sorts keys lexicographically and produces compact JSON
3. Sign digestToSign (UTF-8 bytes) with the agent's ed25519 private key
4. Send the hex signature and the raw payload object (not a string) in the request bodyHow evidence signing works
- payload is a JSON object (never a string). The
sourceDigest()function computessha256(canonicalJson(payload))server-side. - The agent must sign this exact 64-hex digest (as UTF-8 bytes) with its ed25519 key.
- If
payloadis sent as a JSON string, the server computessha256(String(payload))instead — a different value fromsha256(canonicalJson(parsedObject)). This causes signature mismatch → 401. - Passport verifies:
ed25519.verify(signature, utf8ToBytes(digest), agentPublicKey).
task_deliverable also requires Authorization: Bearer <PASSPORT_SERVICE_TOKEN> whenEVIDENCE_SERVICE_AUTH_REQUIRED=true. Other source types use the enrolled agent signature only.
Custom Agent
Any agent framework can integrate with Passport.
Steps
- 1.Agent generates an ed25519 keypair at startup
- 2.Enroll via POST /enroll/start → /enroll/complete
- 3.On task completion, issue a receipt via POST /receipts
- 4.Finalize with the outcome via POST /receipts/:id/finalize
- 5.Anyone verifies at /verify/:id
Example
// Minimal integration pseudocode
const passport = new PassportClient({
apiKey: process.env.PASSPORT_API_KEY,
baseUrl: "https://passport.metis.gold",
});
// Enroll agent
const { subjectCommitment } = await passport.enroll(agentPublicKey);
// On task complete
const receipt = await passport.issueReceipt({
agentId: agent.id,
type: "competence",
inputDigest: sha256(input),
domain: "CODE_GENERATION",
});
await passport.finalizeReceipt(receipt.id, {
status: "success",
outputHash: sha256(output),
});
// Return receipt URL for verification
return `https://passport.metis.gold/verify/${receipt.id}`;