OrcLink

OrcLink API v1.0.0

Read outcomes, journeys, calls, report rows and spend; write conversions, revenue and opportunity events; subscribe to outcome events.

  • Authentication: Authorization: Bearer orc_live_... (an API key from Data out > API). Keys are never accepted in the URL: a request with a key parameter, or any parameter value that starts with orc_live_, is refused (400 key_in_url).
  • Scopes: read (the GET endpoints and REST hook subscriptions), write (POST /api/v1/conversions, PUT /api/v1/revenue/{externalId} and PUT /api/v1/opportunities/{externalId}).
  • Errors: {"error": {"code": "...", "message": "..."}} (POST /api/v1/conversions and the two PUT endpoints keep {"error": "..."}). Messages never echo submitted values.
  • Rate limits per key: 120 requests per minute for reads, 30 per minute for reports, 60 per minute for hooks, 600 per minute for conversions, revenue and opportunities together. Per workspace, for all its keys together: 600 per minute for reads, 60 per minute for reports, 600 per minute for hooks (429 rate_limited). One performance report per key runs at a time (429 report_running while one runs).
  • Paging: pass next_cursor as cursor until it is null.
  • Privacy: no response carries an email, phone, IP address, caller number, page path, hash, olid, click id, session id or visitor key.
  • Outcomes, touchpoints and calls are person-level: in HIPAA mode they need the workspace BAA, and stage names are generic labels.
  • HIPAA mode: campaign, campaign group, ad group, ad content, keyword, source, medium, ad account, ad account id and custom channel names (free text) are omitted from every response and webhook body (a custom channel leaves as "Custom channel"; built-in channel names always leave) unless the workspace owner turns on "Include campaign names in data out", confirming: "I confirm our campaign, campaign group, ad group, ad content, keyword, source, medium, ad account, ad account id and custom channel names contain no patient information." A confirmation of an older text releases nothing until it is confirmed again.
  • OrcLink numbers are claims: the source system owns the outcome and its value.

Your own record id (external_id) is included in outcomes and webhook events; do not put a name, email or phone in it. An id is shown as an id only when the ad platform made it: a CSV upload's campaign and ad group text follows the names rule.

Endpoints

post/api/v1/conversionsscope: write

Send one conversion or up to 100

Server-to-server conversions (quiz complete, intake, checkout, a lead from your backend). Email and phone are hashed on receipt and never stored or echoed. An event opts in to a canonical outcome with stage, kind or status, or an event_name that is a dictionary stage. external_id must be an opaque id (not an email address or a formatted phone number).

Body: ConversionEvent | { events: ConversionEvent[] }

  • 200 OK{ results: ConversionResult[] }
  • 400 Invalid body (paths and messages only).{ error: string, issues: { path: string, message: string }[] }
  • 401 Invalid or missing API key.{ error: string }
  • 429 Rate limited.{ error: string }
put/api/v1/revenue/{externalId}scope: write

Record revenue from your system of record

Records one revenue event for the record `externalId` (an invoice, a sale, a closed deal: its id in your system). Your system owns the number: a later PUT for the same id with another value replaces the gross value and is kept as a revision (value history). Status never moves backward (a lost or cancelled record is not reopened by a late event). The record is matched to a click by `olid`, then by the hashed email or phone, then by `lead_id` (the id your page sent with `orc.identify`; lower confidence, never sent to ad platforms). The path id is never used to match. Idempotent: the same `event_id` with the same body is recorded once (200, status `duplicate`); the same `event_id` with another body is refused (409). Email and phone are hashed on receipt and never stored, logged or returned. Validation errors name the field, never the value. One id space for revenue and opportunities: a revenue write closes the opportunity with the same id when it carries the same person or no email / phone. A record made by the other type with another email or phone is refused (409, code identity_conflict) unless allow_identity_change is true. A write of the same type with a new email or phone replaces it.

ParameterInDescription
externalId *pathThe record's id in your system, 1 to 128 characters. Must be an opaque id: an email address or a formatted phone number (+ or punctuation, 7 to 15 digits) is refused. Digits only is accepted (an invoice number). Must not start with orc_test_.

Body: RevenueEvent

  • 200 Updated an existing outcome, or a duplicate of an event already recorded (status duplicate).DataInWriteResult
  • 201 Created a new outcome.DataInWriteResult
  • 400 Invalid body: not JSON, an unknown field, or a field that fails its rule. Each issue names a field; values are never echoed.DataInError
  • 401 Missing or invalid API key.DataInError
  • 403 The API key has no write scope.DataInError
  • 409 The event_id was already used with a different body (no code). Or this id already belongs to a record of another type and another person (code identity_conflict): use a different id, or send allow_identity_change: true. Or the same event_id is being written by another request right now: then the answer carries Retry-After (30 seconds), retry the same request.DataInError
  • 413 Body larger than 64 KB.DataInError
  • 429 Rate limited: 600 requests per minute per API key, shared with the Conversions API. See Retry-After.DataInError
  • 500 The event could not be recorded; retry with the same event_id.DataInError
put/api/v1/opportunities/{externalId}scope: write

Record an opportunity stage from your system of record

Records one stage event for the opportunity `externalId` (its id in your CRM or booking system). `stage` is required and is looked up in your outcome dictionary (name or alias; case, spaces, hyphens and underscores do not matter). A stage the dictionary does not know is recorded at the entry stage, flagged `unmapped_stage`, and never sent to ad platforms. Stages and statuses move forward only: a late event for an earlier stage does not move the opportunity back, and a lost opportunity is reopened only by a win. Matching, idempotency and privacy are the same as for PUT /api/v1/revenue/{externalId}.

ParameterInDescription
externalId *pathThe opportunity's id in your system, 1 to 128 characters, opaque (not an email address or a formatted phone number), and not starting with orc_test_.

Body: OpportunityEvent

  • 200 Updated an existing outcome, or a duplicate of an event already recorded (status duplicate).DataInWriteResult
  • 201 Created a new outcome.DataInWriteResult
  • 400 Invalid body: not JSON, an unknown field, or a field that fails its rule. Each issue names a field; values are never echoed.DataInError
  • 401 Missing or invalid API key.DataInError
  • 403 The API key has no write scope.DataInError
  • 409 The event_id was already used with a different body (no code). Or this id already belongs to a record of another type and another person (code identity_conflict): use a different id, or send allow_identity_change: true. Or the same event_id is being written by another request right now: then the answer carries Retry-After (30 seconds), retry the same request.DataInError
  • 413 Body larger than 64 KB.DataInError
  • 429 Rate limited: 600 requests per minute per API key, shared with the Conversions API. See Retry-After.DataInError
  • 500 The event could not be recorded; retry with the same event_id.DataInError
get/api/v1/outcomesscope: read

List outcomes

Canonical outcomes (not test, not duplicate), newest first by occurred_at. Outcomes from AI-agent clicks are included and labeled traffic_class ai_agent.

ParameterInDescription
fromqueryFirst day (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: 30 days before `to`.
toqueryLast day, inclusive (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: now. At most 400 days.
kindqueryOutcome kind. One of: order, lead, call, form, appointment, opportunity, deal.
statusqueryStatus (test and duplicate outcomes are never listed). One of: open, won, lost, cancelled, unqualified.
stagequeryStage name, or its generic label in HIPAA mode.
sourcequerySource system.
matchqueryHow the outcome was matched to a click. One of: olid, identity, external_id, none.
limitqueryRows per page, 1 to 200 (default 50).
cursorqueryThe next_cursor of the previous page (opaque).
  • 200 OK{ data: Outcome[], next_cursor: string | null }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/outcomes/{id}scope: read

Get one outcome

ParameterInDescription
id *path
  • 200 OK{ data: Outcome }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 404 Not found (also for another workspace).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/touchpointsscope: read

List an outcome's touchpoints

The stored journey of one outcome, in order (the journey attribution credit is computed from). Never a landing path, click id, olid or session id.

ParameterInDescription
outcome_id *queryThe outcome id.
limitqueryRows per page, 1 to 200 (default 50).
cursorqueryThe next_cursor of the previous page (opaque).
  • 200 OK{ data: Touchpoint[], next_cursor: string | null }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/callsscope: read

List calls

Call records, newest first by started_at. Never the caller number, its hash or digits, nor the dialed number.

ParameterInDescription
fromqueryFirst day (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: 30 days before `to`.
toqueryLast day, inclusive (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: now. At most 400 days.
limitqueryRows per page, 1 to 200 (default 50).
cursorqueryThe next_cursor of the previous page (opaque).
  • 200 OK{ data: Call[], next_cursor: string | null }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/reports/performancescope: read

Performance report rows

Rows of the attribution performance report by channel, source, campaign, campaign group, ad group or keyword under a rule-based model. Credited numbers are a claim; placeholder values and AI-agent outcomes are apart. In HIPAA mode only rows by channel, unless the owner turned on "Include campaign names in data out" (else 403 campaign_names_off).

ParameterInDescription
fromqueryFirst day, YYYY-MM-DD (UTC). Default: 30 days ago.
toqueryLast day, YYYY-MM-DD (UTC). Default: today.
dimensionqueryRow dimension. One of: channel, source, campaign, campaignGroup, adGroup, keyword.
modelqueryAttribution model (rule-based only: the modelled models are dashboard-only). One of: first_touch, last_touch, last_non_direct, linear, time_decay, position_based, u_shaped, w_shaped, full_path, custom.
stagequeryCount arrivals at this stage as conversions (name, or generic label in HIPAA mode).
comparequeryAdd the previous period of equal length. One of: 1, true.
halfLifequerytime_decay half-life in days.
pwqueryposition_based weights first,middle,last.
cwquerycustom weights by position, comma separated.
  • 200 OK{ data: PerformanceReport }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/spendscope: read

List daily ad spend

Daily ad cost per platform, account, campaign, ad group and keyword, newest day first. Amounts are in each row currency, never converted.

ParameterInDescription
fromqueryFirst day (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: 30 days before `to`.
toqueryLast day, inclusive (YYYY-MM-DD, UTC) or an ISO 8601 time. Default: now. At most 400 days.
platformqueryAd platform (google, meta, microsoft, tiktok, linkedin, reddit, stackadapt, csv).
limitqueryRows per page, 1 to 200 (default 50).
cursorqueryThe next_cursor of the previous page (opaque).
  • 200 OK{ data: SpendRow[], next_cursor: string | null }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
post/api/v1/hooksscope: read

Subscribe a REST hook

Subscribe target_url to one event (the Zapier app triggers use this). The target receives the same signed bodies as a webhook endpoint: verify X-OrcLink-Signature with the secret this answer returns (shown once; only its encrypted form is stored). Refused in HIPAA mode (403 hipaa_mode). target_url must be https and its host must resolve to a public address (else 400 invalid_target_url, "The host is not allowed"). A revoked key stops its hooks. The same key subscribing the same event again within 30 days of an unsubscribe gets the same hook back (same id, the new target_url, a new secret), also when the workspace is over its plan quota; another event or another key is a new hook and then gets 402 over_quota.

Body: { target_url: string, event: string }

  • 201 Subscribed.{ id: string, event: string, secret: string }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 402 The workspace is over its plan quota and this would be a new hook (over_quota). Re-subscribing an event this key unsubscribed within 30 days is not refused.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 409 Too many hooks for the workspace (too_many_hooks).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
delete/api/v1/hooks/{id}scope: read

Unsubscribe a REST hook

Only the API key that subscribed the hook can unsubscribe it (another key, or a hook already unsubscribed, gets 404). The hook stops at once; it is kept 30 days so the same key can subscribe the same event again (the same hook comes back with the new target_url), then deleted with its delivery log. At most 50 unsubscribed hooks per key and event are kept (the oldest go first); a hook a workspace manager turned off is never brought back by the key. A workspace manager can delete any hook in Data out.

ParameterInDescription
id *path
  • 200 Unsubscribed.{ id: string, deleted: boolean }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 404 Not found (also another workspace, or a dashboard webhook endpoint).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error
get/api/v1/hooks/samplescope: read

Sample event

One fixed example body for an event type (no workspace data): the shape a hook receives.

ParameterInDescription
event *queryEvent type. One of: form.created, chat.created, call.completed, opportunity.updated, revenue.recorded, outcome.stage_changed.
  • 200 OK{ data: WebhookEvent[] }
  • 400 Invalid parameter, invalid cursor, or an API key sent in the URL (key_in_url).Error
  • 401 Missing, invalid or revoked API key.Error
  • 403 The key lacks the scope (insufficient_scope), or HIPAA mode is on without the workspace BAA (baa_required, person-level endpoints).Error
  • 429 Rate limited per key or per workspace (rate_limited), or a performance report of the key is still running (report_running); see Retry-After.Error
  • 500 Internal error.Error

Webhooks

Add an endpoint in Data out. Each delivery is signed: X-OrcLink-Signature: v1=<hex HMAC-SHA256(secret, "<X-OrcLink-Timestamp>.<raw body>")>. Compute it over the raw body, compare in constant time, and refuse a timestamp more than 5 minutes away from your clock. For 24 hours after a secret rotation the header carries two signatures, v1=<new>,v1=<old>: accept the delivery when any entry matches. A second rotation within the 24 hours drops the first old secret. Dedupe by X-OrcLink-Event-Id (a retry or a replay sends the same id), and order events by occurred_at, not by arrival: deliveries can arrive out of order. Events: form.created, chat.created, call.completed, opportunity.updated, revenue.recorded, outcome.stage_changed.

// Node.js receiver
import crypto from 'node:crypto';

function verify(secret, rawBody, signature, timestamp) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from('v1=' + crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex'));
  // Two entries for 24 hours after a secret rotation: any match.
  return (signature ?? '').split(',').some((s) => {
    const given = Buffer.from(s.trim());
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}
post(your endpoint)

form.created (sent by OrcLink to your endpoint)

OrcLink POSTs this JSON to each enabled endpoint that selected the event. Verify X-OrcLink-Signature: v1=<hex HMAC-SHA256(secret, "<X-OrcLink-Timestamp>.<raw body>")> in constant time. For 24 hours after a secret rotation the header carries two signatures, v1=<new>,v1=<old>: accept the delivery when any v1= entry matches your secret, so a rotation never breaks a live receiver. A second rotation within the 24 hours drops the first old secret (at most two signatures); and refuse a timestamp more than 300 seconds (5 minutes) from your clock. Dedupe by X-OrcLink-Event-Id: a retry or a replay sends the same id. Deliveries can arrive out of order (retries, replays): order events by occurred_at, not by arrival. Answer 2xx within 5 s; other answers are retried with backoff; redirects are not followed; 410 Gone disables the endpoint. In HIPAA mode stage names are generic labels.

ParameterInDescription
X-OrcLink-Event-Id *header
X-OrcLink-Event-Type *header One of: form.created.
X-OrcLink-Timestamp *header
X-OrcLink-Signature *header
X-OrcLink-Delivery-Attempt *header

Body: WebhookEvent

  • 200 Received. Any 2xx counts.

Schemas

Error

  • error { code: string, message: string }

Match

  • method string
  • label string
  • confidence string — external_id matches are lower confidence and never sent to ad platforms.

Outcome

  • id string — The outcome's own opaque id.
  • kind string
  • source string — The system that owns the outcome (shopify, salesforce, hubspot, ghl, ctm, callrail, api, form, chat, ...).
  • external_id string | null — The source's own id of the record (order id, CRM record id, call id). Your own record id is included, also in HIPAA mode; do not put a name, email or phone in it. null when it is email-shaped or phone-formatted (never sent).
  • stage string — Current stage (dictionary name). In HIPAA mode a generic label (event_1, event_2, ...): decode it on the Compliance tab.
  • status string
  • value number | null — Net value (gross less refunds) when known, else gross. The source system owns it.
  • gross_value number | null
  • currency string | null — ISO 4217 code.
  • value_basis string
  • placeholder_value boolean — true when the value is the stage default (a placeholder), not a source amount.
  • match Match
  • traffic_class string — ai_agent: the matched click was made by an AI agent; report it apart.
  • occurred_at string
  • stage_reached_at string | null
  • closed_at string | null
  • updated_at string

Touchpoint

  • id string
  • outcome_id string
  • position integer — 0 = first touch.
  • kind string — static_number: a call to a static (offline) tracking number, one channel-level touch (no keyword, no visit).
  • label string | null — "static number (channel-level)" on a static_number touch, else null.
  • at string
  • channel string | null — Built-in channel name, or a workspace channel rule label; in HIPAA mode without "Include campaign names in data out" a workspace label is "Custom channel".
  • source string | null — Omitted in HIPAA mode unless the owner turned on "Include campaign names in data out".
  • medium string | null — Omitted in HIPAA mode unless campaign names are turned on.
  • campaign string | null — Omitted in HIPAA mode unless campaign names are turned on.
  • is_direct boolean

Call

  • id string
  • outcome_id string | null
  • provider string
  • direction string
  • started_at string
  • answered_at string | null
  • ended_at string | null
  • duration_sec integer
  • talk_sec integer
  • result string
  • label string | null — The provider label; null in HIPAA mode (free text).
  • has_recording boolean

SpendRow

  • id string
  • date string
  • platform string
  • account_id string | null — The ad account id. A manager may type it (StackAdapt, Reddit, X), so in HIPAA mode without "Include campaign names in data out" it is null, except "csv" for an uploaded row.
  • campaign_id string | null — The ad platform id. A CSV-uploaded row (account_id "csv") stores the campaign TEXT here, so in HIPAA mode without "Include campaign names in data out" it is null, like the names.
  • campaign_name string | null — Omitted in HIPAA mode unless the owner turned on "Include campaign names in data out".
  • ad_group_id string | null — The ad platform id; for a CSV row the uploaded text, null in HIPAA mode without the opt-in.
  • ad_group_name string | null — Omitted in HIPAA mode unless campaign names are turned on.
  • keyword string | null — Omitted in HIPAA mode unless campaign names are turned on.
  • spend number
  • currency string
  • impressions integer
  • clicks integer
  • platform_conversions number | null — The ad platform's own count (its claim).

PerformanceReport

See the dashboard Attribution > Performance report for definitions (CPL, CPA, ROAS, ROI per currency).

  • days { from: string, to: string }
  • compare object | null
  • dimension string
  • model string
  • model_label string
  • stage string | null
  • stage_columns string[]
  • cost_available boolean
  • labels object
  • rows { key: string, label: string, kind: string, current: Metrics, previous: Metrics | null }[]
  • more_rows integer
  • totals Metrics
  • ai_agent object — Outcomes from AI-agent clicks: one line apart from rows and totals.
  • undated object | null
  • notes object
  • cost_gaps { platform: string, account_id: string, label: string | null, days: integer, ranges: { from: string, to: string }[] }[] — Days a connected ad account has no stored cost. account_id (a manager may type it) and label (the account name) are omitted in HIPAA mode without "Include campaign names in data out".

Money

  • currency string — ISO 4217. XXX: currency not reported (the source record had none): never converted, never in ROAS or ROI.
  • minor integer
  • amount number

Metrics

  • visits { human: integer, aiAgent: integer, crawler: integer }
  • stages object
  • conversions number
  • wonConversions number
  • revenue Money[] — Values of won outcomes only.
  • placeholderRevenue Money[]
  • pipeline Money[] — Pipeline (open): reported values of outcomes not won; never in revenue, ROAS or ROI.
  • cost array | null
  • cpl array | null
  • cpa array | null
  • roas array | null
  • roi array | null

ConversionEvent

  • event_name string
  • event_id string | null — Your dedupe id (also the browser pixel dedupe key). It is sent to ad platforms as is, so it must be an opaque id: an email address, a URL, an IP address or an id that looks like a phone number (7 to 15 digits with dashes, dots, spaces, parentheses or a +, for example 415-555-0100 or 2026-10-05-000123) is refused with 400. Use letters, or digits only (an order number 5551234567 is fine). One refused event_id refuses the whole request. An order with the same external id as an order from your connected store or payment source is not sent to ad platforms (the store's own order is): the ids are compared exactly, and only when the store's order arrived first.
  • occurred_at string | number | null — ISO 8601, unix seconds or unix milliseconds; at most 5 minutes in the future (clock skew); a later time is refused.
  • value number | null
  • currency string | null
  • olid string | null
  • session_id string | null
  • visitor_id string | null
  • email string | null — Hashed on receipt; never stored or echoed.
  • phone string | null — Hashed on receipt; never stored or echoed.
  • customer_id string | null
  • external_id string | null — Opaque id; an email address or a formatted phone number is refused.
  • stage string | null
  • kind string | null
  • status string | null

ConversionResult

  • event_id string | null
  • status string
  • match_method string | null
  • duplicate boolean
  • error string
  • outcome_id string | null
  • stage string | null
  • outcome_status string | null
  • outcome_match_method string | null
  • unmapped_stage boolean
  • outcome_error string

RevenueEvent

At least one of email, phone, olid or lead_id is required. An unknown field is refused. A field sent as null counts as not sent.

  • event_id string — Unique id of this event (the idempotency key). Opaque (not an email or phone, not starting with orc_test_). Never sent to ad platforms: the relay uses its own keyed event id. To dedupe with a browser pixel use POST /api/v1/conversions.
  • date string — ISO 8601: 2026-10-01 or 2026-10-01T14:30:00Z (a date-time needs a time zone). Not more than one day in the future.
  • value number | string — The revenue amount, 0 or more. A numeric string is accepted.
  • currency string — ISO 4217 code.
  • label string — Free text (first 100 characters kept). Dropped when it holds an email or phone, and never stored in HIPAA mode.
  • stage string — A stage of your outcome dictionary (name or alias). Default: the lowest win stage.
  • status string — Default: won.
  • email string — Hashed on receipt; never stored, logged or returned.
  • phone string — Hashed on receipt (7 to 15 digits); never stored, logged or returned.
  • olid string — The OrcLink click id.
  • lead_id string — The lead or order id your page sent with orc.identify. Lower-confidence match; never sent to ad platforms.
  • backfill boolean — true: history. The event is written and attributed in reports but never sent to ad platforms. Not sent: an event whose date is more than 24 hours before it arrives is treated as history, because old records are never sent to ad platforms as new conversions. false: the event is live whatever its date (ad platforms still refuse events older than 7 days, so those are never sent).
  • allow_identity_change boolean — true: this write may move the id to another person (another type and another person: its email or phone differs from the stored record). Without it such a write is refused (409, code identity_conflict).

OpportunityEvent

At least one of email, phone, olid or lead_id is required. An unknown field is refused. A field sent as null counts as not sent.

  • event_id string — Unique id of this event (the idempotency key). Opaque (not an email or phone, not starting with orc_test_). Never sent to ad platforms: the relay uses its own keyed event id. To dedupe with a browser pixel use POST /api/v1/conversions.
  • date string — When the opportunity reached this stage. ISO 8601: 2026-10-01 or 2026-10-01T14:30:00Z (a date-time needs a time zone). Not more than one day in the future.
  • stage string — A stage of your outcome dictionary (name or alias).
  • value number | string | null — Optional, 0 or more. A numeric string is accepted. Without it the stage default from the dictionary is used (labeled as a placeholder).
  • currency string — ISO 4217 code.
  • label string — Free text (first 100 characters kept). Dropped when it holds an email or phone, and never stored in HIPAA mode.
  • status string — Only a status your system states outright; otherwise the dictionary decides.
  • email string — Hashed on receipt; never stored, logged or returned.
  • phone string — Hashed on receipt (7 to 15 digits); never stored, logged or returned.
  • olid string — The OrcLink click id.
  • lead_id string — The lead or order id your page sent with orc.identify. Lower-confidence match; never sent to ad platforms.
  • backfill boolean — true: history. The event is written and attributed in reports but never sent to ad platforms. Not sent: an event whose date is more than 24 hours before it arrives is treated as history, because old records are never sent to ad platforms as new conversions. false: the event is live whatever its date (ad platforms still refuse events older than 7 days, so those are never sent).
  • allow_identity_change boolean — true: this write may move the id to another person (another type and another person: its email or phone differs from the stored record). Without it such a write is refused (409, code identity_conflict).

DataInWriteResult

  • outcome_id string
  • external_id string
  • status string
  • stage string
  • outcome_status string
  • match_method string
  • unmapped_stage boolean — The stage is not in the dictionary; the outcome was recorded at the entry stage and is not sent to ad platforms.
  • revision boolean — This write changed the value of an existing outcome (a revision was recorded).
  • backfill boolean — What was stored for this event: written as history (never sent to ad platforms). A duplicate answers with the flag of the first write, not of this request.
  • relay null | { relayed: boolean, event_id: string, status: string, reason: string }

DataInError

The error body of the revenue and opportunities endpoints: a plain message, like POST /api/v1/conversions.

  • error string
  • code string — Set when the 409 is an identity conflict.
  • issues { path: string, message: string }[]

WebhookEvent

  • id string — The event id: the same on a retry or a replay. Dedupe by it.
  • type string
  • api_version string
  • created_at string
  • occurred_at string — When the stage or status was reached. Order events by it: deliveries can arrive out of order.
  • data { outcome: Outcome + { previous_stage: string | null, previous_status: string | null, event_stage: string, event_status: string, attribution: { model: string, channel: string | null, source: string | null, medium: string | null, campaign: string | null } } }
  • privacy { hipaa_mode: boolean, generic_stage_names: boolean, campaign_names: boolean }