Offline conversions
Upload a CSV of offline conversions to one platform. Raw email/phone in, SHA-256 out — the platform never sees unhashed PII. One bad batch never blocks the rest.
Signature
ts
import { uploadOffline } from 'adscapi';
const result: OfflineResult = await uploadOffline({
platform: 'bing' | 'google' | 'meta',
csv: string, // header row + one conversion per line
secrets?: AdscapiSecrets, // default: process.env
dryRun?: boolean, // parse + hash + build, do not POST
});Options
| Field | Type | Notes |
|---|---|---|
platform | 'bing' | 'google' | 'meta' | Platforms with a documented offline-conversion import endpoint |
csv | string | Raw CSV text (header + rows). See columns below |
secrets | AdscapiSecrets? | Defaults to process.env |
dryRun | boolean? | Parse + hash + build payloads, but do not POST |
CSV columns
Header is case-insensitive. Missing columns are fine; unknown columns are ignored; blank lines are dropped.
csv
email,phone,event,value,currency,timestamp,gclid,msclkid,fbclid jane@example.com,,purchase,49.00,USD,1710000000,Cj0KCQ...,, bob@acme.com,+14155550100,lead,,,2024-03-15T12:00:00Z,,abc123,
| Column | Notes |
|---|---|
email / phone | Raw. Hashed before upload |
event | Canonical name; defaults to purchase |
value / currency | Optional conversion value |
timestamp | Unix seconds, milliseconds, or ISO/RFC date string → unix seconds |
gclid / msclkid / fbclid | Click ids — required by some platforms (see below) |
Per-platform requirements
- Bing — needs
MSADS_ACCESS_TOKEN,MSADS_DEV_TOKEN,MSADS_CUSTOMER_ID,MSADS_ACCOUNT_ID. A row needs at least one ofmsclkid/ email / phone. Batches of 1,000. - Google — needs
GOOGLE_ACCESS_TOKEN,GADS_CUSTOMER_ID,GADS_DEVELOPER_TOKEN,GADS_CONVERSION_ACTION_ID. Rows without agclidare skipped. Batches of 2,000. - Meta — needs
META_PIXEL_ID(dataset id) +META_CAPI_TOKEN. A row needs email or phone.action_source: physical_store; events older than 62 days are rejected. Batches of 1,000.
Missing secrets return { uploaded: 0, failed: 0, skipped: 'secrets absent' } — nothing throws.
Return — OfflineResult
ts
type OfflineResult = {
platform: 'bing' | 'google' | 'meta';
uploaded: number; // rows POSTed inside a successful batch
failed: number; // rows inside a batch the platform rejected
skipped: number | string; // row count skipped, or a string reason when nothing ran
error?: string; // first batch failure detail, when failed > 0
dryRun?: boolean;
};Example
ts
import { uploadOffline } from 'adscapi';
import { readFileSync } from 'node:fs';
const csv = readFileSync('./conversions.csv', 'utf8');
// email,phone,event,value,currency,timestamp,gclid,msclkid,fbclid
// jane@example.com,,purchase,49,USD,1710000000,Cj0...,,
// bob@acme.com,+14155550100,purchase,120,USD,1710001000,,abc123,
// Dry-run first — counts what would upload
const preview = await uploadOffline({ platform: 'google', csv, dryRun: true });
console.log(preview);
// → { platform: 'google', uploaded: 1, failed: 0, skipped: 1, dryRun: true }
const result = await uploadOffline({ platform: 'google', csv });
if (result.error) console.error(result.failed, 'failed:', result.error);
console.log('uploaded', result.uploaded, 'skipped', result.skipped);shell
# Same path via the CLI npx adscapi offline --platform google --file ./conversions.csv npx adscapi offline --platform meta --file ./conversions.csv --dry-run
Related
- conversions.track() — live server-side events
- Errors —
PlatformHttpErroron non-2xx batches