HTTP API v1 & Mobile Integration
NotPixel exposes a language-agnostic JSON HTTP API at https://api.notpixel.ai. You can integrate directly from Python, Go, Java, PHP, Ruby, Rust, or orchestrate Mobile apps (React Native, iOS Swift, Android Kotlin, Flutter) through your backend.
OpenAPI 3.1.0 Specification: Download or import /openapi.json (also served live at https://api.notpixel.ai/openapi.json) to generate typed clients with openapi-generator, oapi-codegen, or Postman/Insomnia.
Base URL & Conventions
- Base URL:
https://api.notpixel.ai - Content-Type:
application/json - Idempotency:
- Pass a stable
requestIdper ad opportunity onPOST /api/v1/pickso network retries return the existing auction decision (_meta.deduplicated: true) without double-charging. POST /api/v1/clickis automatically idempotent perimpressionId.POST /api/v1/convertis idempotent per(impressionId, externalId).POST /api/v1/feedbackis idempotent pereventId.
- Pass a stable
Endpoints Reference
1. Select a Contextual Ad (POST /api/v1/pick)
Returns an eligible contextual ad and the identifiers needed to display it and track events.
Send a non-zero, normalized 384-dimensional vector generated locally with
Xenova/bge-small-en-v1.5, using mean pooling and L2 normalization. The local SDK entry points
generate this vector automatically. For remote processing, send input instead
of embedding, as shown below. Different embedding models are not compatible.
Placements
Create a placement under the verified domain or app in Publisher → Settings → Domains & apps.
Pass its public code as placement on /api/v1/pick. The code identifies the property on backend
requests without context.origin. The browser confirms from that same verified origin; native
clients use an attested app session. Use a separate requestId for each position on a page and
reuse it for retries of that opportunity. Preview events are excluded from the placement report.
Remote context processing
If you do not run a local model, send sanitized context and let NotPixel process it using its Cloudflare inference infrastructure:
{
"publisherId": "pub_10565",
"requestId": "req_message_001",
"input": "How can I optimize PostgreSQL queries?",
"context": { "lang": "en" }
}Send exactly one of input or embedding; sending both or neither is invalid.
There is no REST mode parameter. Remote processing requires an eligible active
publisher and is subject to included usage limits. Context is sanitized again and
limited to 256 characters before inference; it is not stored in NotPixel’s
request logs or database. The request body limit is 16 KiB for either format.
Remote quota exhaustion returns 429 remote_embedding_quota_exceeded with
Retry-After. Unavailable processing or inference failure returns 503. Continue
your application’s LLM answer without an ad when processing is unavailable.
SDK or direct REST integration
With a NotPixel SDK, local is the default; choose remote explicitly with
embeddingMode (Python: embedding_mode). The installation assistant selects
the correct packages and initialization. NotPixel manages the model and precision.
For direct REST calls using local processing, your application generates the
vector before calling /api/v1/pick. Run the model in your application or on your own backend.
In that local path, NotPixel receives the vector instead of the text. In the
remote path, send input and NotPixel generates the vector for you.
The required contract is:
- Model:
Xenova/bge-small-en-v1.5. - Output: exactly 384 finite numeric values, with at least one non-zero value.
- Processing: mean pooling over tokens using the attention mask, followed by L2 normalization.
- Use the quantized
q8artifact, as in the example below.
Matching dimensions alone do not make vectors compatible. Do not substitute a
different 384-dimensional model or use CLS pooling. This model is designed for
English; setting context.lang does not translate text or switch embedding models.
Sanitize email and phone information locally and limit the context to 256 characters
to follow the SDK’s preprocessing. Do not include prompt text or personal data in
optional context metadata.
Generate the vector and call REST without the SDK
This Node.js example uses Transformers.js directly. Install it with
npm install @huggingface/transformers@3.8.1 and save the example as pick-ad.mjs.
The first use downloads model files; reuse the extractor for subsequent requests.
import { randomUUID } from 'node:crypto';
import { pipeline } from '@huggingface/transformers';
const extractor = await pipeline(
'feature-extraction',
'Xenova/bge-small-en-v1.5',
{ dtype: 'q8' },
);
// Relevant context, already free of email/phone information.
const contextText = 'How can I optimize PostgreSQL queries?'.slice(0, 256);
const output = await extractor(contextText, {
pooling: 'mean',
normalize: true,
});
const embedding = Array.from(output.data);
if (embedding.length !== 384 || !embedding.every(Number.isFinite) ||
!embedding.some(value => value !== 0)) {
throw new Error('Invalid context embedding');
}
// Keep this ID and payload when retrying the same ad opportunity.
const requestId = `req_${randomUUID()}`;
const response = await fetch('https://api.notpixel.ai/api/v1/pick', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publisherId: 'YOUR_PUBLISHER_ID',
requestId,
embedding,
context: { lang: 'en' },
}),
});
if (!response.ok) throw new Error(`NotPixel HTTP ${response.status}`);
const result = await response.json();
console.log(result.ad); // An eligible ad, or null when no ad is available.The REST response contains the ad decision and tracking identifiers. Your application calls its chosen LLM provider separately and displays the returned ad alongside that provider’s answer. Never send an ad request if local embedding fails; your application can still deliver the LLM answer.
Request body shape
The array below is abbreviated for readability. A real request must include all 384 generated values.
{
"publisherId": "pub_10565",
"requestId": "req_chat_msg_9f8a7b6c",
"embedding": [/* all 384 numeric values from your local model */],
"context": {
"lang": "en"
}
}Response (200 OK)
{
"requestId": "req_chat_msg_9f8a7b6c",
"impressionId": "550e8400-e29b-41d4-a716-446655440000",
"ad": {
"id": "660e8400-e29b-41d4-a716-446655440111",
"contentRevision": 1,
"category": "general",
"headline": "Neon Serverless Postgres",
"body": "Branch your database like code and scale to zero automatically.",
"disclosure": "Sponsored",
"cta": {
"label": "Start Free",
"url": "https://neon.tech?utm_source=notpixel"
}
},
"_meta": {
"priceCents": 1,
"priceMilliCents": 650,
"auctionMode": "live"
}
}When no ad matches the minimum relevance or floor price, the API returns 200 OK with "ad": null and _meta.noFillReason (no_candidates, budget_exhausted, below_min_relevance, below_floor_price, targeting_mismatch).
2. Validate Cached Ad (POST /api/v1/ads/validate)
Validates whether a cached ad and contentRevision are still active and approved before displaying from a local cache.
{
"adId": "660e8400-e29b-41d4-a716-446655440111",
"contentRevision": 1,
"impressionId": "550e8400-e29b-41d4-a716-446655440000",
"publisherId": "pub_10565"
}Response (200 OK): { "valid": true }
3. Acknowledge Impression (POST /api/v1/events)
Acknowledges that an auctioned impression from /api/v1/pick was rendered to the user.
/api/v1/events only acknowledges impressions already created by /api/v1/pick. Unauctioned impressionId or requestId values return 404 Impression not found.
{
"impressionId": "550e8400-e29b-41d4-a716-446655440000"
}Response (200 OK): { "ok": true }
4. Track Click (POST /api/v1/click)
Records a click on an auctioned impression. Repeated calls with the same impressionId are deduplicated and return the original clickId with "duplicate": true.
{
"impressionId": "550e8400-e29b-41d4-a716-446655440000",
"metadata": {
"placement": "chat_footer",
"platform": "ios"
}
}Response (200 OK):
{
"ok": true,
"clickId": "770e8400-e29b-41d4-a716-446655440222"
}5. Track Conversion (POST /api/v1/convert)
Records a post-click or post-impression conversion. Provide externalId (for example, your order or signup ID) for automatic idempotency.
{
"impressionId": "550e8400-e29b-41d4-a716-446655440000",
"type": "signup",
"valueCents": 2900,
"externalId": "usr_signup_99412",
"metadata": {
"plan": "pro"
}
}Response (200 OK):
{
"ok": true,
"conversionId": "880e8400-e29b-41d4-a716-446655440333",
"duplicate": false
}6. Submit User Feedback (POST /api/v1/feedback)
Records when a user hides an ad (action: "hide") or reports a policy violation (action: "report").
{
"eventId": "990e8400-e29b-41d4-a716-446655440444",
"impressionId": "550e8400-e29b-41d4-a716-446655440000",
"action": "report",
"reason": "misleading",
"details": "Landing page price does not match offer"
}Response (201 Created or 200 OK on replay):
{
"ok": true,
"eventId": "990e8400-e29b-41d4-a716-446655440444"
}Python SDK
Python 0.3.0 includes Ads / AsyncAds with offer, streaming, batch, prefetch and
tracking. See the Python SDK guide for a complete integration. Direct
HTTP remains available when you want to manage transport yourself.
Backend Examples (Python SDK, FastAPI HTTP, Go, curl)
Python SDK (notpixel)
# pip install notpixel
from notpixel import AsyncAds
# Generates 384-dimensional embeddings locally (BAAI/bge-small-en-v1.5).
ads = AsyncAds(publisher_id="pub_10565", cache=True)
async def handle_chat(prompt: str) -> dict:
# Runs your LLM and contextual ad selection in parallel (best-effort)
result = await ads.respond(
input=prompt,
llm=lambda: call_your_llm(prompt),
)
return {
"text": result.text,
"ad": result.ad.to_dict() if result.ad else None,
}Mobile Integration (React Native, iOS, Android, Flutter)
For React Native and Expo, use the React Native SDK. It forwards calls to your chosen AI provider and looks up ads in parallel. Context processing is local by default, with explicit remote processing available. Native components handle presentation and tracking.
Mobile apps can also consume ads selected by their existing Node.js or Python backend. Use the tracking endpoints below for custom renderers. Keep permanent AI provider credentials on the integrator’s server.
The snippets below illustrate custom HTTP renderers; the React Native SDK provides visibility-aware tracking and feedback out of the box.
React Native / Expo
import Ads, { type Ad } from '@notpixel/react-native/local';
import { AdCard } from '@notpixel/react-native/react';
const ads = new Ads({ publisherId: 'YOUR_PUBLISHER_ID' });
// Render an ad received from your existing backend with native tracking.
export function MobileAd({ ad, impressionId }: {
ad: Ad;
impressionId: string;
}) {
return <AdCard ad={ad} impressionId={impressionId} client={ads} />;
}