TypeScript SDK Migration Guide
The TypeScript SDK provides a fully typed interface for integrating Agent Offer Network from Node.js and edge-friendly JavaScript runtimes.
Migration examples
This guide retains older source-compatible userProfile and pagination
examples. New integrations must use the TypeScript SDK Reference
and its canonical Protocol v1.0 request shape; those legacy inputs are not emitted
on the current wire.
Documented baseline
Install with npm install @agentoffernetwork/sdk@^1.0.0. Runtime baseline: Node 20+.
Prerequisites
Make sure you have completed the Quick Start and understand the Core Concepts before proceeding.
Installation
npm install @agentoffernetwork/sdk@^1.0.0Initialization
The SDK uses a factory function initialize() that returns an AgentOfferClient instance.
Full Configuration
import { initialize } from '@agentoffernetwork/sdk';
import type { AgentOfferClient } from '@agentoffernetwork/sdk';
const client: AgentOfferClient = await initialize({
apiKey: process.env.AGENTOFFERNETWORK_API_KEY!,
mode: 'live', // 'live' | 'mock'
baseUrl: 'https://api.aon.pro', // optional, defaults to this environment
timeout: 5000, // optional, ms
appId: process.env.AON_APP_ID, // optional, sent as x-aon-app-id header
});Context helpers
Use createContextForPlatform() when you already know the host platform and want stable
attribution. Keep detectContext() for legacy runtime inference or when you want the SDK
to fill in ambient defaults.
import { createContextForPlatform, detectContext } from "@agentoffernetwork/sdk"
const explicit = createContextForPlatform("mcp-skill", {
interests: ["noise cancelling headphones"],
})
const inferred = detectContext({
interests: ["travel deals"],
})Configuration Options
apiKeyYour API key for authentication. Never hardcode -- use environment variables.
modelive: real API calls. mock: local mock data for development and testing.
baseUrlAPI base URL. Defaults to https://api.aon.pro.
timeoutRequest timeout in milliseconds. Must be positive. Defaults to 5000.
appIdApp identifier. Sent as x-aon-app-id header on protected endpoints (live mode).
Environment Variable Best Practices
# .env
AON_API_KEY=your-api-key-here
AON_MODE=liveconst client = await initialize({
apiKey: process.env.AON_API_KEY!,
mode: (process.env.AON_MODE as 'live' | 'mock') ?? 'mock',
});API Key security
Never commit API keys to version control. Use environment variables or a secret manager. See Best Practices for more on security.
Querying Offers
The queryOffers method accepts QueryOffersParams and returns QueryOffersResponse.
Basic Query
const response = await client.queryOffers({
intent: {
content: [{ type: 'input_text', text: 'project management tools for small teams' }],
},
context: {
userProfile: {
language: 'en',
deviceInfo: { deviceType: 'other', os: 'other' },
},
},
});
console.log(`Request ID: ${response.requestId}`);
console.log(`Found ${response.offers.length} offers`);Using detectContext
The detectContext helper auto-detects platform, language, and session. It also maintains a rolling top-10 interest history across calls:
import { initialize, detectContext } from '@agentoffernetwork/sdk';
const client = await initialize({ apiKey: process.env.AON_API_KEY!, mode: 'live' });
const context = detectContext({
interests: ['project management', 'team collaboration'],
});
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'best project management tools' }] },
context,
});Advanced Constraints
Use the constraints parameter for public deterministic constraints. The first public surface only exposes canonical category constraints:
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'affordable SaaS tools' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
constraints: {
categoryIds: ['computers_electronics.computers.software'],
},
pagination: {
limit: 10,
offset: 0,
},
});Constraint Parameters
categoryIdsConstrain by AON Taxonomy v1 category id. Parent ids match descendant offer categories.
Pagination
// Page through results
let offset = 0;
const limit = 20;
let hasMore = true; // SDK-derived convenience, not a protocol response field.
while (hasMore) {
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'headphones' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
pagination: { limit, offset },
});
for (const offer of response.offers) {
console.log(offer.offerInfo.title);
}
hasMore = response.hasMore; // SDK-derived convenience.
offset += limit;
}Response Handling and Type Safety
The response is fully typed. TypeScript will enforce correct field access:
import type { Offer, QueryOffersResponse, CategoryId, Price } from '@agentoffernetwork/sdk';
const response: QueryOffersResponse = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'laptops' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
});
// All fields are typed (current nested protocol shape)
const offer: Offer = response.offers[0];
const entityName: string = offer.entity.name;
const categoryId: CategoryId = offer.offerInfo.category.id;
const price: Price | undefined = offer.offerInfo.commercial?.price;
// Discriminated unions for action type
if (offer.action.type === 'web_redirect') {
console.log(`Redirect to: ${offer.action.payload.target}`);
} else if (offer.action.type === 'app_deep_link') {
// Handle deep link with optional fallback
const fallback = offer.action.payload.fallbackUrl;
}
// Content part discrimination
const intent = {
content: [
{ type: 'input_text' as const, text: 'search query' },
{ type: 'input_image' as const, image_url: 'https://example.com/photo.jpg' },
],
};Event Tracking
reportClick
Report a click event when the user interacts with a tracking link. Returns a ClickResult with a trackingId:
import type { ClickEvent, ClickResult } from '@agentoffernetwork/sdk';
// In live mode, use the exact AON tracking URL supplied by your integration layer.
// Do not replace it with the advertiser destination (`offer.action.payload.target`).
const clickEvent: ClickEvent = {
offerId: offer.offerId,
trackingUrl: process.env.AON_TRACKING_ENDPOINT ?? 'https://tracking.example.test/click/mock-track-1',
timestamp: new Date().toISOString(),
agentId: 'my-agent-v1',
sessionId: 'session-123',
context: { conversationId: 'conv-456' },
};
const clickResult: ClickResult = await client.reportClick(clickEvent);
// clickResult.trackingId -- use this for subsequent conversion tracking
// clickResult.timestamp -- server-recorded timestampTracking URL semantics
In live mode, trackingUrl must be the exact AON tracking URL supplied by your integration
layer. The advertiser destination is offer.action.payload.target. These are not the same
URL and should not be documented as interchangeable.
When to report events
Click: When the user clicks the tracking URL or you programmatically redirect them.
Conversion: Conversions are not reported by the SDK. The Partner that fulfils the
offer reports them server-side through POST /v1/postback/{partner_id}
(see Postbacks & Attribution); the trackingId from
reportClick is what ties that postback back to this click.
formatRecommendation
Format an offer as human-readable recommendation text with tracking link and disclosure:
import type { FormatOptions } from '@agentoffernetwork/sdk';
// Default: markdown style
const markdown = client.formatRecommendation(offer);
// Brief: single-line compact
const brief = client.formatRecommendation(offer, { style: 'brief' });
// Detailed: multi-line with full info
const detailed = client.formatRecommendation(offer, { style: 'detailed' });
// Custom options
const custom = client.formatRecommendation(offer, {
style: 'markdown',
includeDisclosure: true,
disclosureText: 'Ad',
includePrice: true,
});FormatOptions
styleOutput style. Default: 'markdown'.
includeDisclosureWhether to include a disclosure identifier. Default: true.
disclosureTextCustom disclosure text. Default: 'Sponsored'.
includePriceWhether to include price information. Default: true.
Rendering Multiple Offers
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'headphones' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
});
const recommendations = response.offers.map((offer, i) => {
const formatted = client.formatRecommendation(offer, { style: 'markdown' });
return `### ${i + 1}.\n${formatted}`;
});
console.log(recommendations.join('\n\n'));Error Handling
All SDK errors extend the AonError base class. Use instanceof to handle specific error types:
import {
AonError,
AonAuthError,
AonRateLimitError,
AonNetworkError,
AonValidationError,
AonApiError,
} from '@agentoffernetwork/sdk';
try {
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'headphones' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
});
} catch (error) {
if (error instanceof AonAuthError) {
// Invalid or expired API key
console.error(`Auth error: ${error.message}`);
} else if (error instanceof AonRateLimitError) {
// Too many requests -- back off and retry
console.error(`Rate limited: ${error.message}`);
} else if (error instanceof AonNetworkError) {
// Timeout, DNS failure, etc.
console.error(`Network error: ${error.message}`);
} else if (error instanceof AonValidationError) {
// Invalid parameters
console.error(`Validation error: ${error.message}`);
} else if (error instanceof AonApiError) {
// Server returned a non-SUCCESS business code
console.error(`API error [${error.code}]: ${error.message}`);
} else if (error instanceof AonError) {
// Catch-all for any AON error
console.error(`AON error [${error.code}]: ${error.message}`);
} else {
throw error; // Re-throw unexpected errors
}
}Error Class Hierarchy
| Class | Code | When |
|---|---|---|
AonError | varies | Base class for all SDK errors |
AonAuthError | AUTH_ERROR | Invalid or expired API key |
AonRateLimitError | RATE_LIMIT | Request rate limit exceeded |
AonNetworkError | NETWORK_ERROR | Timeout, DNS failure, connection error |
AonValidationError | VALIDATION_ERROR | Invalid parameters passed to SDK methods |
AonApiError | varies | Server returned a non-SUCCESS business code |
Retry with Exponential Backoff
import type { AgentOfferClient, QueryOffersParams, QueryOffersResponse } from '@agentoffernetwork/sdk';
async function queryWithRetry(
client: AgentOfferClient,
params: QueryOffersParams,
maxRetries = 3,
): Promise<QueryOffersResponse> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.queryOffers(params);
} catch (error) {
if (error instanceof AonRateLimitError && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
if (error instanceof AonNetworkError && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 500;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Mock Mode
Mock mode returns realistic fake data without making real API calls. Use it for development and testing.
Development with Mock Mode
const client = await initialize({
apiKey: 'any-key-works-in-mock',
mode: 'mock',
});
// Works exactly like live mode
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'headphones' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
});Testing with MockAgentOfferClient
In mock mode, the client is actually a MockAgentOfferClient with additional test helper methods:
import { initialize } from '@agentoffernetwork/sdk';
import type { MockAgentOfferClient } from '@agentoffernetwork/sdk';
const client = await initialize({
apiKey: 'test-key',
mode: 'mock',
}) as MockAgentOfferClient; // Type assertion for test helpers
// Perform operations
const response = await client.queryOffers({
intent: { content: [{ type: 'input_text', text: 'test query' }] },
context: { userProfile: { language: 'en', deviceInfo: { deviceType: 'other', os: 'other' } } },
});
await client.reportClick({
offerId: response.offers[0].offerId,
trackingUrl: process.env.AON_TRACKING_ENDPOINT ?? 'https://tracking.example.test/click/mock-track-1',
timestamp: new Date().toISOString(),
});
// Verify recorded events
const events = client.getRecordedEvents();
console.log(events.length); // 1
console.log(events[0].type); // 'click'
console.log(events[0].timestamp); // ISO stringMock mode tip
getRecordedEvents() returns all click and conversion events recorded during the session. Use this in unit tests to verify your integration logic without hitting real APIs.
API Reference
For the complete API surface, see the TypeScript SDK Reference.
Next Steps
- Python SDK Guide -- Same concepts, Python-specific patterns
- Skill/Plugin Development -- MCP-based integration
- Best Practices -- Production patterns, security, and FAQ