Skip to content

Latest commit

 

History

History
454 lines (338 loc) · 10.1 KB

File metadata and controls

454 lines (338 loc) · 10.1 KB

CTP Platform SDK

A TypeScript SDK for implementing the Conversational Transfer Protocol (CTP) on LLM platforms.

CTP enables platforms to securely transfer conversation context to external destinations when users follow links, enabling personalized experiences while preserving privacy.

Features

  • Discovery: Fetch and cache destination CTP discovery documents
  • Token Generation: Create nested JWTs (JWE + JWS) per the CTP specification
  • Signal Extraction: LLM-powered extraction of shopping signals from conversations
  • Schema Validation: Validate signals against JSON Schema (draft 2020-12)
  • Security: SSRF protection, URL validation, secure key management
  • React Hooks: Client-side hooks for CTP-aware components

Installation

The SDK is currently bundled with the reference platform. To use it in your own project, copy the /lib/ctp directory or extract it as an npm package.

Dependencies

npm install jose ajv ajv-formats

Quick Start

1. Generate Signing Keys

import { encodeJwkToBase64, generateSigningKeyPair } from "./lib/ctp";

const keyId = "platform-key-2026";
const result = await generateSigningKeyPair("ES256", keyId);

if (result.success) {
  console.log(
    "Private Key (base64):",
    encodeJwkToBase64(result.data.privateKey),
  );
  console.log("Key ID:", keyId);
}

Or use the CLI script:

npx tsx scripts/generate-keys.ts

2. Configure Environment

CTP_PLATFORM_ISSUER=https://your-platform.example.com
CTP_SIGNING_KEY_ID=platform-key-2026
CTP_SIGNING_KEY_PRIVATE=<base64-encoded-private-jwk>

3. Create a CTP Client

import { createCtpClient } from "./lib/ctp";

const client = createCtpClient({
  platformIssuer: "https://platform.example.com",
  signingKey: privateKeyJwk,
  signingKeyId: "platform-key-2026",
  signingAlgorithm: "ES256",
});

Or create from environment variables:

import { createCtpClientFromEnv } from "./lib/ctp";

const result = createCtpClientFromEnv();
if (result.success) {
  const client = result.data;
}

4. Check Destination Support

const support = await client.checkDestination(
  "https://shop.example.com/products",
);

if (support.supported) {
  console.log("CTP supported!");
  console.log("Discovery:", support.discovery);
}

5. Extract Signals from Conversation

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { createOpenAIExtractor } from "./lib/ctp";

const extractFn = createOpenAIExtractor(openai, generateText, "gpt-5.2");

const signals = await client.extractSignals(
  [
    { role: "user", content: "I'm looking for running shoes under $150" },
    {
      role: "assistant",
      content: "I'd recommend checking out Nike or Brooks...",
    },
  ],
  extractFn,
);

// Result:
// {
//   itemOffered: { category: 'Shoes > Athletic > Running', brand: 'Nike' },
//   priceSpecification: { maxPrice: 150, priceCurrency: 'USD' }
// }

6. Prepare a CTP-Augmented Link

const result = await client.prepareLink({
  url: "https://shop.example.com/running-shoes",
  messages: conversationMessages,
  extractFn: extractFn,
});

if (result.success) {
  console.log("Original URL:", result.data.originalUrl);
  console.log("Augmented URL:", result.data.augmentedUrl);
  console.log("Signals:", result.data.signals);
}

API Reference

Core Functions

createCtpClient(config)

Creates a CTP client instance.

interface CtpClientConfig {
  platformIssuer: string; // Platform's issuer URL
  signingKey: JWK; // Private signing key (JWK)
  signingKeyId: string; // Key ID for the signing key
  signingAlgorithm: JwsAlgorithm; // 'ES256' | 'ES384' | 'RS256' | etc.
  discoveryCacheTtl?: number; // Cache TTL in ms (default: 1 hour)
  jwksCacheTtl?: number; // JWKS cache TTL in ms (default: 1 hour)
}

checkCtpSupport(url)

Check if a destination supports CTP.

const result = await checkCtpSupport("https://shop.example.com");
// { supported: boolean, discovery?: DestinationDiscovery, error?: string }

prepareCtpLink(options)

Prepare a CTP-augmented link with full flow.

const result = await prepareCtpLink({
  url: "https://shop.example.com/product",
  messages: conversationMessages,
  config: ctpClientConfig,
  extractFn: llmExtractFunction,
  signals: preExtractedSignals, // optional
});

Discovery

fetchDestinationDiscovery(origin)

Fetch a destination's CTP discovery document.

const result = await fetchDestinationDiscovery("https://shop.example.com");
if (result.success) {
  const discovery: DestinationDiscovery = result.data;
}

fetchJwks(jwksUri)

Fetch a JWKS from a URL.

const result = await fetchJwks(
  "https://shop.example.com/.well-known/jwks.json",
);

Token Generation

createToken(options)

Create a CTP token (nested JWE + JWS).

const result = await createToken({
  platformIssuer: "https://platform.example.com",
  destinationOrigin: "https://shop.example.com",
  signals: { itemOffered: { category: "Shoes" } },
  platformPrivateKey: privateJwk,
  platformKeyId: "platform-key-2026",
  destinationPublicKey: destPublicJwk,
  destinationKeyId: "dest-key-1",
  encAlg: "RSA-OAEP-256",
  encEnc: "A256GCM",
  sigAlg: "ES256",
});

Signal Extraction

extractSignals(messages, options)

Extract structured signals from conversation using an LLM.

const result = await extractSignals(messages, {
  extractFn: async (systemPrompt, userPrompt) => {
    // Call your LLM here
    return llmResponse;
  },
  schemaProperties: ["itemOffered.category", "priceSpecification.maxPrice"],
});

createOpenAIExtractor(openai, generateText, model?)

Create an extraction function for OpenAI models.

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";

const extractFn = createOpenAIExtractor(openai, generateText, "gpt-5.2");

Schema Validation

validateSignals(signals, schema)

Validate signals against a JSON Schema.

const result = validateSignals(signals, schemaObject);

filterByProperties(signals, properties)

Filter signals to only include requested properties.

const filtered = filterByProperties(signals, [
  "itemOffered.category",
  "priceSpecification.maxPrice",
]);

Key Management

generateSigningKeyPair(algorithm, keyId)

Generate an ES256/RS256 key pair for signing.

const result = await generateSigningKeyPair("ES256", "my-key-id");
if (result.success) {
  const { publicKey, privateKey } = result.data;
}

buildJwks(keys)

Build a JWKS document from public keys.

const jwks = buildJwks([publicKey1, publicKey2]);

Security

safeFetch(url, options)

Fetch with SSRF protection, timeout, and size limits.

const result = await safeFetch("https://example.com/api", {
  timeout: 5000,
  maxSize: 1024 * 1024,
});

generateJti()

Generate a cryptographically secure JWT ID.

const jti = generateJti(); // 'ctp_xK9...' (128+ bits of entropy)

React Hooks

useCtpSupport(url)

Check if a URL's destination supports CTP.

const { supported, discovery, isChecking, error } = useCtpSupport(url);

useCtpSupportBatch(urls)

Batch check multiple URLs for CTP support.

const { results, isChecking } = useCtpSupportBatch(urls);
// results is Map<string, { supported: boolean, discovery?: DestinationDiscovery }>

useCtpSignals(messages)

Extract signals from conversation messages.

const { signals, isExtracting, error } = useCtpSignals(messages);

useCtpPrepareLink()

Hook to prepare CTP-augmented links.

const { prepareLink, isLoading } = useCtpPrepareLink();
const result = await prepareLink(url, signals);

Types

Signal Types

interface Signals {
  itemOffered?: {
    category?: string; // e.g., 'Shoes > Athletic > Running'
    name?: string; // Product name
    brand?: string; // Brand name
  };
  priceSpecification?: {
    minPrice?: number;
    maxPrice?: number;
    priceCurrency?: string; // ISO 4217 (e.g., 'USD')
  };
  eligibleRegion?: string;
  location?: {
    latitude?: number;
    longitude?: number;
    addressLocality?: string;
  };
  aggregateRating?: { ratingValue?: number; reviewCount?: number };
}

Discovery Types

interface DestinationDiscovery {
  version: string;
  issuer: string;
  jwks_uri: string;
  supported_algs: JweAlgorithm[];
  supported_enc: JweEncryption[];
  signal_schema?: string;
  signal_properties?: string[];
}

interface PlatformDiscovery {
  version: string;
  issuer: string;
  jwks_uri: string;
  supported_signing_algs: JwsAlgorithm[];
}

Error Handling

All async functions return a CtpResult<T> type:

type CtpResult<T> =
  | { success: true; data: T }
  | { success: false; error: CtpError };

interface CtpError {
  code: CtpErrorCode;
  message: string;
  cause?: unknown;
}

Error codes include:

  • invalid_token - Token format invalid
  • token_expired - Token has expired
  • signature_invalid - JWS signature failed
  • issuer_mismatch - Issuer doesn't match
  • schema_mismatch - Signals don't match schema
  • unauthorized_schema - Schema URI is not the standard CTP schema
  • discovery_failed - Failed to fetch discovery
  • ssrf_blocked - URL blocked for security
  • token_too_large - Token exceeds 4096 chars

Constants

import {
  CTP_QUERY_PARAM, // 'ctp'
  CTP_SCHEMAS, // { SIGNALS, ... }
  CTP_VERSION, // '0.1.0'
  DEFAULT_ALGORITHMS, // { JWE_ALG: 'RSA-OAEP-256', ... }
  FETCH_LIMITS, // { TIMEOUT_MS: 5000, MAX_RESPONSE_SIZE: 1MB }
  TOKEN_LIMITS, // { MAX_TOKEN_LENGTH: 4096, ... }
} from "./lib/ctp";

Security Considerations

  1. SSRF Protection: All URL fetching validates against private IP ranges
  2. Token Expiry: Tokens expire within 10 minutes
  3. Consent Required: Per-click consent must be obtained before generating tokens
  4. Key Rotation: Support multiple keys in JWKS during rotation periods

License

Apache License 2.0