{"openapi":"3.1.0","info":{"title":"Attributely — Conversions API","version":"1.0.0","contact":{"name":"Attributely","url":"https://attributely.co"},"license":{"name":"Proprietary","url":"https://attributely.co"},"description":"Server-to-server API for reporting conversions (revenue or funnel events) into the attribution system. Companion to the Stripe Connect webhook: use the webhook for zero-config Stripe ingestion, and this API for non-Stripe billing, non-payment conversions, server-side accuracy (pixel-independent), richer value, and backfill.\n\n## Authentication\nSend your per-site API key as a Bearer token: `Authorization: Bearer ar_live_…`. The key resolves the owning site and is stored hashed at rest (shown once at creation).\n\n## Identifying the buyer\nSend `email` (required) — it attributes the sale to the buyer's journey (the pixel already linked email → visitor at signup). For most integrations that is the only identifier you need. `ar_token` is an optional precision signal you pass ONLY if the pixel cookie is on the current request — never a cached value.\n\n## Idempotency\n`external_id` is the idempotency key. Re-POSTing the same `external_id` returns the existing conversion (200) instead of creating a duplicate (201). Safe to retry.\n\n## Currencies\n`currency` accepts any real ISO 4217 code at ingest. Multi-currency REPORTING converts the 30 ECB-supported currencies (the live list at https://api.frankfurter.dev/v1/currencies); a conversion in any other currency is recorded and shown as captured-but-unconvertible — never zeroed, never silently dropped.\n\n## Regional limits\n`state`/`zip` match keys are normalized for US addresses only at launch — non-US values are dropped rather than sent wrong. Conversions from the EU/UK/CH are consent-suppressed at forward time: only Google receives them (PII-stripped, consent denied) and only when a Google click id is present, so EU/UK measurement is limited at launch."},"servers":[{"url":"https://attributely.co/api/v1","description":"Production"}],"security":[{"siteApiKey":[]}],"tags":[{"name":"Conversions","description":"Report revenue and funnel conversions."}],"paths":{"/conversions":{"post":{"tags":["Conversions"],"operationId":"createConversion","summary":"Record a conversion","description":"Records a single conversion for the authenticated site. Idempotent on `external_id`. On success it enters the same pipeline as a Stripe-sourced conversion (identity resolution + forward-job enqueue happen server-side).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionRequest"},"examples":{"purchase":{"summary":"A paid order (revenue — amount + currency required)","value":{"external_id":"ord_8842","event":"purchase","email":"alice@acme.com","amount_cents":4900,"currency":"usd"}},"subscribe":{"summary":"A new subscription (first charge)","value":{"external_id":"sub_inv_001","event":"subscribe","email":"alice@acme.com","amount_cents":2900,"currency":"eur","occurred_at":"2026-06-01T12:00:00Z"}},"lead":{"summary":"A demo request (funnel — no value needed)","value":{"external_id":"lead_311","event":"lead","email":"alice@acme.com"}},"schedule_with_match_keys":{"summary":"A booked meeting with optional match keys (raises EMQ)","value":{"external_id":"cal_evt_77","event":"schedule","email":"alice@acme.com","match_keys":{"firstName":"Alice","lastName":"Nguyen","phone":"+14155552671","country":"US","state":"CA","zip":"94107"}}}}}}},"responses":{"200":{"description":"Duplicate `external_id` — the existing conversion is returned unchanged (idempotent).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionEnvelope"},"examples":{"recorded":{"summary":"The previously recorded conversion, echoed unchanged","value":{"data":{"id":"8814","event_id":"4f6f9f1a-6b9b-4f6e-9c1d-2a9f1f0c7d42","external_id":"ord_8842","event":"purchase","status":"recorded","amount_cents":4900,"currency":"usd","email":"alice@acme.com","occurred_at":"2026-06-11T18:26:17.284+00:00","created_at":"2026-06-11T18:26:17.291746+00:00"}}}}}}},"201":{"description":"Conversion created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionEnvelope"},"examples":{"recorded":{"summary":"A newly recorded purchase","value":{"data":{"id":"8814","event_id":"4f6f9f1a-6b9b-4f6e-9c1d-2a9f1f0c7d42","external_id":"ord_8842","event":"purchase","status":"recorded","amount_cents":4900,"currency":"usd","email":"alice@acme.com","occurred_at":"2026-06-11T18:26:17.284+00:00","created_at":"2026-06-11T18:26:17.291746+00:00"}}}}}}},"401":{"description":"Missing or invalid site API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"error":{"value":{"error":{"code":"unauthorized","message":"Invalid or revoked API key."}}}}}}},"422":{"description":"Request validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"error":{"value":{"error":{"code":"validation_error","message":"The conversion request failed validation.","details":[{"field":"currency","message":"currency is required for event \"purchase\"","code":"custom"}]}}}}}}},"429":{"description":"Rate limit exceeded. (Documented for client robustness; enforcement deferred.)","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"error":{"value":{"error":{"code":"rate_limited","message":"Too many requests. Retry after the indicated delay."}}}}}}},"500":{"description":"Unexpected server error. Safe to retry with the same `external_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"error":{"value":{"error":{"code":"internal_error","message":"Could not record the conversion. Please retry."}}}}}}}}}}},"components":{"securitySchemes":{"siteApiKey":{"type":"http","scheme":"bearer","bearerFormat":"ar_live_…","description":"The per-site API key (`ar_live_…` — created in Integrations → API), sent as a Bearer token. Resolves the owning site."}},"schemas":{"ConversionRequest":{"type":"object","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255,"description":"Your unique id for this conversion (order/charge/lead id). THIS IS THE IDEMPOTENCY KEY — reusing it returns the existing conversion, never a duplicate.","examples":["ord_8842"]},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Customer email — REQUIRED. The authoritative identity key: it resolves the sale to the buyer's journey (the pixel's identify() links email → visitor during signup). Every SaaS sale has one, so for most integrations this is the only identifier you send.","examples":["alice@acme.com"]},"amount_cents":{"description":"Amount in minor units (e.g. cents). Non-negative; the event carries the sign semantics. Requires `currency`. Must be a POSITIVE integer (≥ 1) for `purchase`, `subscribe`, and `refund`; `0` is valid for `start_trial`.","examples":[4900],"type":"integer","minimum":0,"maximum":9007199254740991},"currency":{"description":"ISO 4217 currency code. Required when `amount_cents` is sent. Any real code is accepted and recorded; multi-currency REPORTING converts the 30 ECB-supported currencies (the live list at api.frankfurter.dev/v1/currencies) into your reporting currency — a conversion in any other currency is recorded and shown as captured-but-unconvertible, never zeroed or dropped.","examples":["usd"],"type":"string","pattern":"^[A-Za-z]{3}$"},"occurred_at":{"description":"When the conversion happened (ISO 8601). Defaults to server receipt time.","examples":["2026-06-01T12:00:00Z"],"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"ar_token":{"description":"OPTIONAL precision signal — the pixel's first-party visitor token (the value of the `ar_token` cookie, or `window.ar.getToken()` client-side). Read it from the CURRENT request's cookies and pass it verbatim; never cache, store, or hardcode it — a stale token misattributes the sale. You do not need this: `email` already attributes the sale. `ar_token` only sharpens the journey match when it is genuinely present on the request.","examples":["ar_3cff4252fa964eec8b1bbs695e763b65"],"type":"string","pattern":"^ar_[A-Za-z0-9_-]{8,}$"},"traits":{"description":"Free-form passthrough for context. For match keys, prefer the typed `match_keys` field below (name, phone, billing location).","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"match_keys":{"description":"Optional typed match keys (name, phone, billing location) to raise ad-platform match quality. Normalized + hashed before forwarding; never required, never gates the email identity.","$ref":"#/components/schemas/ConversionMatchKeys"},"event":{"$ref":"#/components/schemas/ConversionEvent"}},"required":["external_id","email","event"],"additionalProperties":false,"description":"A conversion to record via the public API. Identify the buyer with `email` (required); add the optional `ar_token` only if you already have it. Idempotent on `external_id`. `amount_cents`+`currency` are required for `purchase`, `subscribe`, and `refund`.","dependentRequired":{"amount_cents":["currency"]},"allOf":[{"if":{"properties":{"event":{"enum":["purchase","subscribe","refund"]}},"required":["event"]},"then":{"required":["amount_cents","currency"],"properties":{"external_id":{},"email":{},"amount_cents":{"minimum":1},"currency":{},"occurred_at":{},"ar_token":{},"traits":{},"match_keys":{},"event":{}}}}]},"ConversionMatchKeys":{"type":"object","properties":{"firstName":{"description":"Customer first name. Hashed before forwarding.","examples":["Alice"],"type":"string","minLength":1},"lastName":{"description":"Customer last name. Hashed before forwarding.","examples":["Nguyen"],"type":"string","minLength":1},"phone":{"description":"Customer phone in any common format; normalized to E.164 before forwarding (dropped if not parseable).","examples":["+14155552671"],"type":"string","minLength":1},"externalId":{"description":"Stable customer id (your user id / CRM id). Forwarded as Meta external_id; hashed before send.","type":"string","minLength":1},"country":{"description":"ISO 3166-1 alpha-2 country code (e.g. US).","examples":["US"],"type":"string","minLength":1},"city":{"description":"Billing city. Hashed before forwarding.","examples":["San Francisco"],"type":"string","minLength":1},"state":{"description":"State/region. US states are normalized to 2-letter ANSI; non-US values are dropped at launch (never sent wrong).","type":"string","minLength":1},"zip":{"description":"Postal code. US ZIPs are normalized to 5 digits; non-US values are dropped at launch (never sent wrong).","examples":["94107"],"type":"string","minLength":1}},"description":"Optional PII match keys (name, phone, billing location) that raise ad-platform match quality (EMQ). Each field is sent only to platforms that accept it, normalized + hashed before forwarding. None are required."},"ConversionEvent":{"type":"string","enum":["purchase","subscribe","start_trial","lead","complete_registration","schedule","refund"],"description":"What the customer did. `purchase`/`subscribe` = revenue (require a positive `amount_cents` + `currency`); `start_trial` = a $0 trial start; `lead` = an MQL / demo request / webinar registration; `complete_registration` = a signup / account creation; `schedule` = a booked meeting; `refund` = a reversal (requires a positive `amount_cents` + `currency`; reduces net revenue, not forwarded)."},"Conversion":{"type":"object","properties":{"id":{"type":"string","description":"Our internal conversion id."},"event_id":{"type":"string","description":"The shared dedup id forwarded to ad platforms (CAPI event id). Use it to reconcile downstream."},"external_id":{"type":"string","description":"Your id for this conversion, echoed back (the idempotency key)."},"event":{"$ref":"#/components/schemas/ConversionEvent"},"status":{"type":"string","const":"recorded","description":"Recording status. (Per-platform forwarding status lives on the forward jobs.)"},"amount_cents":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Stored amount in minor units; null for value-less events."},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stored ISO 4217 currency code; null for value-less events."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Stored customer email (the identity key)."},"occurred_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$","description":"When the conversion happened (ISO 8601, UTC with numeric offset).","examples":["2026-06-11T18:26:17.291746+00:00"]},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$","description":"When we received it (ISO 8601, UTC with numeric offset).","examples":["2026-06-11T18:26:17.291746+00:00"]}},"required":["id","event_id","external_id","event","status","amount_cents","currency","email","occurred_at","created_at"],"additionalProperties":false,"description":"A recorded conversion."},"ConversionEnvelope":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Conversion"}},"required":["data"],"additionalProperties":false,"description":"Success envelope: the recorded conversion under `data`."},"FieldError":{"type":"object","properties":{"field":{"type":"string","examples":["currency"]},"message":{"type":"string","examples":["Must be a 3-letter ISO 4217 code"]},"code":{"examples":["invalid_format"],"type":"string"}},"required":["field","message"],"additionalProperties":false},"ErrorResponse":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable error code.","examples":["unauthorized","validation_error","internal_error"]},"message":{"type":"string","description":"Human-readable summary. Never leaks internal details."},"details":{"description":"Field-level validation errors (present for validation_error).","type":"array","items":{"$ref":"#/components/schemas/FieldError"}}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}