Skip to content

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.0

Initialization

The SDK uses a factory function initialize() that returns an AgentOfferClient instance.

Full Configuration

TypeScript SDK initialization
TypeScript
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

Parameter
apiKey
stringRequired

Your API key for authentication. Never hardcode -- use environment variables.

mode
'live' | 'mock'Required

live: real API calls. mock: local mock data for development and testing.

baseUrl
stringOptional

API base URL. Defaults to https://api.aon.pro.

timeout
numberOptional

Request timeout in milliseconds. Must be positive. Defaults to 5000.

appId
stringOptional

App 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=live
const 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

Parameter
categoryIds
CategoryId[]Optional

Constrain 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 timestamp

Tracking 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

Parameter
style
'brief' | 'detailed' | 'markdown'Optional

Output style. Default: 'markdown'.

includeDisclosure
booleanOptional

Whether to include a disclosure identifier. Default: true.

disclosureText
stringOptional

Custom disclosure text. Default: 'Sponsored'.

includePrice
booleanOptional

Whether 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

ClassCodeWhen
AonErrorvariesBase class for all SDK errors
AonAuthErrorAUTH_ERRORInvalid or expired API key
AonRateLimitErrorRATE_LIMITRequest rate limit exceeded
AonNetworkErrorNETWORK_ERRORTimeout, DNS failure, connection error
AonValidationErrorVALIDATION_ERRORInvalid parameters passed to SDK methods
AonApiErrorvariesServer 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 string

Mock 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

Start from Quick Start