API Reference
All endpoints are served from https://peruwnbrqkvmrldhpoom.supabase.co/functions/v1.
The SDK handles auth headers automatically. For raw HTTP calls, include:
Authorization: Bearer <supabase_anon_key>— See Mobile SDKs for the anon key valueX-AM-API-Key: <your_api_key>— Your developer API key from signup
SDK Configuration
const client = new PenguinClient({
apiKey: 'am_test_...', // Required. Your API key.
baseUrl: '...', // Optional. Override the API base URL.
supabaseAnonKey: '...', // Optional. Override the Supabase anon key.
timeoutMs: 10_000, // Optional. Request timeout (default: 10s).
maxRetries: 2, // Optional. Retries for GET requests (default: 2).
debug: false, // Optional. Enable debug logging (default: false).
});
:::info Retry behavior
GET requests are automatically retried on 408, 429, and 5xx errors with exponential backoff. POST requests are not retried by default to prevent duplicate side effects. Event tracking (trackEvent) is an exception — it is idempotent and retried automatically.
:::
POST /decide
Get contextual ads matched to user intent. This is the primary endpoint.
SDK
const response = await client.decideFromContext({
context: 'I need a tax accountant',
max_results: 3,
min_quality_score: 0.5,
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
context | string | Yes | The user's message or query |
user_intent | string | No | Additional intent context |
categories | string[] | No | IAB categories to target |
blocked_categories | string[] | No | IAB categories to exclude |
blocked_advertisers | string[] | No | Advertiser IDs to exclude |
min_quality_score | number | No | Minimum quality score (0-1) |
min_relevance_score | number | No | Minimum relevance score (0-1) |
max_results | number | No | Max ads to return (default: 5) |
response_format | string | No | concise or verbose |
max_wait_ms | number | No | Maximum auction wait time |
placement_id | string | No | Identifier for ad placement location |
Response
{
"status": "fill",
"request_id": "req_abc123",
"decision_id": "dec_xyz",
"units": [
{
"creative": {
"title": "CloudTax — AI Tax Filing",
"body": "File your freelancer taxes in 15 minutes with AI guidance",
"cta": "Start Free",
"landing_url": "https://cloudtax.ca"
},
"click_url": "https://...supabase.co/functions/v1/track-click/eyJ...",
"tracking_token": "trk_abc...",
"_score": {
"relevance": 0.92,
"quality": 0.85,
"bid": 0.50,
"final": 0.39
},
"_meta": {
"unit_id": "uuid-here",
"campaign_id": "uuid-here",
"clearing_price_cents": 35,
"position": 1
}
}
]
}
cURL
curl -X POST https://peruwnbrqkvmrldhpoom.supabase.co/functions/v1/decide \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SUPABASE_ANON_KEY" \
-H "X-AM-API-Key: $AM_API_KEY" \
-d '{"context": "I need a tax accountant"}'
POST /event
Track ad events (impressions, clicks, conversions).
decideFromContext() automatically tracks impressions. You only need this for manual tracking or custom events.
SDK
await client.trackEvent({
event_id: 'evt_unique_id',
occurred_at: new Date().toISOString(),
agent_id: 'agt_your_id',
unit_id: 'ad-unit-uuid',
event_type: 'impression',
tracking_token: 'trk_...',
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | Yes | Unique event identifier |
occurred_at | string | Yes | ISO 8601 timestamp |
agent_id | string | Yes | Your agent ID |
unit_id | string | Yes | Ad unit ID from response |
event_type | string | Yes | impression, click, conversion, ad_shown, ad_dismissed, agent_call |
tracking_token | string | No | Tracking token from ad |
metadata | object | No | Additional event data |
Response
{ "accepted": true }
POST /feedback
Submit developer predictions about ad relevance for bonus payouts.
SDK
// Manual prediction
await client.sendFeedback({
tracking_token: ad.tracking_token,
reaction: 'positive',
});
// Auto-analysis from user's response text
await client.sendFeedback({
tracking_token: ad.tracking_token,
user_response: "That's exactly what I was looking for!",
conversation_history: ['I need a tax tool', 'Here is CloudTax...'],
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
tracking_token | string | Yes | Token from the ad response |
reaction | string | No* | positive, neutral, or negative |
user_response | string | No* | User's response text for auto-analysis |
conversation_history | string[] | No | Recent messages for context |
context | string | No | Additional context (max 1000 chars) |
idempotency_key | string | No | Prevent duplicate submissions |
*One of reaction or user_response is required.
Response
{
"status": "received",
"feedback_id": "evt_abc123",
"resolution_date": "2026-04-20",
"potential_bonus": "20%",
"message": "Feedback recorded. Bonus will be calculated after 7-day conversion window."
}
POST /service-result
Report the outcome of an agent-to-agent service transaction.
SDK
await client.logServiceResult({
transaction_id: 'txn_abc123',
success: true,
metadata: { items_processed: 5 },
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
transaction_id | string | Yes | Transaction ID |
success | boolean | Yes | Whether the service completed successfully |
metadata | object | No | Additional result data |
Response
{
"transaction_id": "txn_abc123",
"success": true,
"payment_triggered": true,
"payment_amount": 0.35,
"message": "Service completed successfully. Payment triggered."
}
POST /rate-business
Rate a business interaction quality. Requires a verified click first.
SDK
await client.rateBusiness({
tracking_token: ad.tracking_token,
ad_unit_id: ad._meta!.unit_id,
rating: 4,
context: 'Fast response, helpful information',
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
tracking_token | string | Yes | Token from the ad response |
ad_unit_id | string | Yes | Ad unit ID |
rating | number | Yes | 1-5 rating |
context | string | No | Optional review text (max 500 chars) |
POST /agent-signup
Register a new developer/agent. No API key needed.
SDK
const agent = await PenguinClient.signup({
owner_email: 'dev@example.com',
agent_name: 'MyBot',
});
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
owner_email | string | Yes | Developer email |
agent_name | string | Yes | Agent name (1-100 chars) |
sdk_type | string | No | SDK identifier |
declared_placements | string[] | No | Where ads will appear |
REST API (api-v1)
A simplified REST API that mirrors the core functionality. Useful for non-TypeScript integrations.
| Endpoint | Description |
|---|---|
GET /api-v1/openapi.json | OpenAPI 3.1.0 spec |
POST /api-v1/search | Search for capabilities |
POST /api-v1/session/start | Start interactive session |
POST /api-v1/session/message | Continue session |
POST /api-v1/session/end | End session |
POST /api-v1/feedback | Submit feedback |
Search example (Python)
import requests
ANON_KEY = 'eyJhbGciOiJIUzI1NiIs...' # See Mobile SDKs page for full key
API_KEY = 'am_live_your_key'
response = requests.post(
'https://peruwnbrqkvmrldhpoom.supabase.co/functions/v1/api-v1/search',
headers={
'Authorization': f'Bearer {ANON_KEY}',
'X-AM-API-Key': API_KEY,
},
json={'query': 'best CRM for startups', 'max_results': 3},
)
data = response.json()
for cap in data['capabilities']:
print(f"{cap['name']} — {cap['description']}")
Error handling
The SDK exports typed error classes for structured error handling:
import {
PenguinClient,
APIRequestError,
NetworkError,
TimeoutError,
ValidationError,
} from 'penguin-sdk';
try {
const response = await client.decideFromContext({ context: userMessage });
} catch (err) {
if (err instanceof ValidationError) {
// Invalid parameters — fix your request
console.error('Bad request:', err.message);
} else if (err instanceof TimeoutError) {
// Server didn't respond in time
console.error('Timeout after', err.timeoutMs, 'ms');
} else if (err instanceof APIRequestError) {
// Server returned an error status
console.error('API error:', err.status, err.code, err.body);
} else if (err instanceof NetworkError) {
// Network failure after retries
console.error('Network error:', err.message);
}
}
Error codes
| Status | Code | Description |
|---|---|---|
| 400 | Various | Invalid request parameters |
| 401 | authentication_failed | Missing or invalid API key |
| 403 | forbidden | Resource doesn't belong to your agent |
| 404 | not_found | Resource not found |
| 409 | duplicate_* | Idempotent — already processed |
| 429 | rate_limited | Too many requests. Check Retry-After header |
| 500 | internal_error | Server error — safe to retry |
| 503 | service_unavailable | Feature temporarily disabled |