---
title: "Report a conversion from your server"
method: POST
path: "/v1/projects/{projectId}/conversions"
tags: ["Conversions"]
---

# Report a conversion from your server

`POST /v1/projects/{projectId}/conversions`

## Request

- Base URL: `https://serp-agent.com`
- URL: `https://serp-agent.com/v1/projects/{projectId}/conversions`
- Auth: HTTP bearer

Reports one real conversion — a lead your own systems already accepted. The browser tracker (one <script> line, see https://serp-agent.com/docs/tracking) can only observe that a form was SENT; a form backend, CRM webhook or booking system knows whether the lead was accepted, which is what makes a server report trustworthy enough to count as a business outcome.

Attribution: the conversion is credited to one article, in this order — `articleUrl` (explicit), `ctaTrackingId`, `pageUrl`, then `landingUrl` (the page the visit started on, which is what usually names the article). The response says which one was used. A conversion that matches no article is still stored and counted for the project.

Idempotency: pass `externalId` — your own lead id. A repeat with the same id is ignored and answered 200 with `duplicate: true`, so retries and webhook redeliveries cannot inflate the numbers.

Never send personal data: no names, emails, phone numbers or message bodies. Only the event type, the pages involved and your own lead id.

## Path parameters

- `projectId` string, required

## Request body

- object
  - `eventType` 'FORM_SUBMIT' | 'CALENDAR_BOOKED' | 'PHONE_CLICK' | 'EMAIL_CLICK' | 'CTA_CLICK', required: What happened. FORM_SUBMIT and CALENDAR_BOOKED are the ones a server normally reports.
  - `externalId` string: Your own id for this lead. Makes the call idempotent — a repeat is ignored.
  - `pageUrl` string, uri: Page the conversion happened on.
  - `landingUrl` string, uri: Page the visit started on. Usually the article that earned the lead.
  - `articleUrl` string, uri: Explicit article URL when you already know which article to credit. Strongest evidence.
  - `occurredAt` string, date-time: When it happened (ISO 8601). Defaults to now; must not be more than 5 minutes in the future.
  - `ctaTrackingId` string: Value of the data-sa-cta attribute on the button, when your pages use one.
  - `utm_source` string
  - `utm_medium` string
  - `utm_campaign` string
  - `utm_content` string

## Response `200`

Already reported under this `externalId` — nothing was stored a second time.

- ConversionRecorded: Outcome of a reported conversion.
  - `recorded` boolean, required: True when this call stored a new conversion.
  - `duplicate` boolean, required: True when the same externalId was already reported.
  - `article_attributed` boolean, required: True when the conversion was credited to one of your articles.
  - `attribution` 'explicit' | 'cta' | 'page' | 'landing' | 'none', required: Which signal credited the article: articleUrl (explicit), ctaTrackingId (cta), pageUrl (page), landingUrl (landing), or none.

## Other responses

- `201`: Conversion stored.
- `400`: Malformed body, unknown eventType, a URL that is not a URL, or occurredAt in the future. Code: `invalid_request`.
- `401`: Missing, malformed, unknown, revoked, or expired API key. Codes: `unauthorized`, `key_revoked`, `key_expired`.
- `404`: No such project on this account. Code: `not_found`.
- `429`: Rate limit exceeded (600 requests/minute per API key). Honor `Retry-After`.

---

[API](https://skmtc.dev/serp-agent/apis/serp-agent-api.md) · [All operations](https://skmtc.dev/serp-agent/apis/serp-agent-api/llms.txt) · [OpenAPI document](https://skmtc.dev/serp-agent/apis/serp-agent-api/revisions/438a943019ac?raw)
