---
title: "Capture Agent Signup Attribution"
method: POST
path: "/agent/onboarding/attribution"
tags: ["Agents"]
---

# Capture Agent Signup Attribution

`POST /agent/onboarding/attribution`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/agent/onboarding/attribution`
- Auth: HTTP bearer

Record first-touch attribution - UTM tags, ad click ids, landing page.

FIRST WRITER WINS on every key, so this is safe to call once per session
and safe to retry. Nothing already captured is overwritten.

Every field is optional, which means an empty body is legal and does
nothing. The response reports each destination separately
(`organization_persisted`, `contact_persisted`, ...) because attribution
lands in more than one place and a partial write is worth knowing about -
`complete` is the single field that says all of them took.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- SignupAttributionRequest: Mirror of `CaptureAttributionRequest`. Every field is optional and every length cap is the app's, so a value this contract accepts is one the handler accepts too.
  - `entry_point` string, nullable: Signup entry-point slug.
  - `utm_source` string, nullable
  - `utm_medium` string, nullable
  - `utm_campaign` string, nullable
  - `utm_term` string, nullable
  - `utm_content` string, nullable
  - `utm_id` string, nullable
  - `gclid` string, nullable
  - `wbraid` string, nullable
  - `gbraid` string, nullable
  - `fbclid` string, nullable
  - `landing_page_url` string, nullable
  - `landing_at` string, date-time, nullable
  - `signup_referrer` string, nullable

## Response `200`

Successful Response

- ApiResponseOnboardingPayload
  - `data` OnboardingPayload, required: An open envelope for an onboarding or discovery result. Open on purpose. Discovery state carries a per-field coverage matrix whose field set is the product's own onboarding questionnaire - it changes as the questionnaire does, and a pinned mirror would silently DROP a field the customer had answered. On a surface whose whole job is "what do we still not know about you", a dropped field reads as an answered question.
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/graph8/apis/graph8-developer-api.md) · [All operations](https://skmtc.dev/graph8/apis/graph8-developer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/graph8/apis/graph8-developer-api/revisions/89fc11143b70?raw)
