01Clients and partners
There are two ways to work with Adducta, and they are different things.
- A client is a business whose site we advertise. It gets a
site key (
adu_site_...), which is public and goes in the page, and a client key (adu_live_...), which is secret and stays on its server. A client key reaches that business and nothing else. - A partner is a platform whose own users advertise through us
without ever leaving it. It gets a partner key
(
adu_live_...), which reaches every business the platform brought to us, and nothing else.
One company can be both: a platform we advertise, whose users also advertise through us. It then holds one key for each role, and neither stands in for the other. The partner key cannot report for the company's own site, and the client key cannot act for its users. The two look alike, so ask the API which one you are holding:
GET https://api.adducta.com/v1/me
Authorization: Bearer adu_live_...
{ "kind": "integration", "key": "client", "clients": [ { "name": "..." } ] }
02The tag
On every page an ad can land on, in the head or just before the end of the body:
<script src="https://adducta.com/attribution.js"
data-site-key="adu_site_..."></script>
It gives each visitor a random id, kept in the browser's local storage
under adu_visitor, and keeps the first ad click that
brought them (gclid, gbraid,
wbraid, fbclid, msclkid and the
utm_ tags) for 90 days, which is as long as the ad
platforms take a conversion afterwards. It sets no cookies, loads
nothing else, and never throws: a failure in it leaves your page exactly
as it was.
The site key is public on purpose. It can record an arrival and the events your catalogue allows from a page, and it can read nothing.
Your sites and their keys, each with the exact line to paste, are at
GET /v1/sites. To give a new site its key:
POST https://api.adducta.com/v1/clients/{client_id}/sites
Authorization: Bearer adu_live_...
{ "label": "plumber.example" }
201 { "key": "adu_site_...", "tag": "<script src=...></script>", ... }
A client holds at most 20 live sites. POST /v1/sites/{id}/revoke
switches one off; what it recorded stays.
03Forms
The visitor id is what joins an account to the ad click that brought it. Keep it with the account, and everything in section 06 becomes possible. The simplest way is an attribute on the form that creates the account or the enquiry:
<form action="/signup" method="post" data-adu-form>
...
</form>
As the form submits, the tag adds hidden fields: visitor_id,
and the click identifiers and utm_ tags if an ad brought
the visitor. It works whether the browser posts the form or your own
script reads it into FormData, because the tag runs before
your handlers do. A field you already have with the same name is left
alone.
From a script instead, call window.adducta.decorate(form)
before submitting, or read the values directly with
window.adducta.attribution(), which returns
{ visitor_id, gclid, ... }.
04What the page can see
Some things worth paying for happen on the page: somebody used your calculator, or went to sign up. Name them in your catalogue (see section 07), then mark them in the page:
<a href="/signup" data-adu-event="trial_start">Try it free</a>
<form data-adu-event="quote_requested"> ... </form> <!-- on submit -->
window.adducta.event("calculator_used");
On a link or a button the event is the click, on a form it is the submission, and elements added to the page later work the same way. Each name is sent once per page view, and recorded once per visitor for ever: a button pressed five times is one person deciding once.
The endpoint behind this answers the same to everything, including a name that is not in your catalogue, because it is public and a difference would let anybody map your funnel. To check what arrived, see section 09.
05Enquiries
An enquiry is somebody handing over their details. Send it to us as well as to yourself, and we grade it before we tell the ad platform what it was worth:
POST https://api.adducta.com/v1/intake/enquiry
Content-Type: application/json
{
"site_key": "adu_site_...",
"visitor_id": "<window.adducta.attribution().visitor_id>",
"name": "...", "email": "...", "phone": "...",
"message": "What they asked for, in their words",
"gclid": "...", "utm_source": "...", "utm_term": "..."
}
A message matters more than it looks. The grade is read from what the person said and did, and an empty message reads as somebody who said nothing. If your form asks nothing, say what they did: "Created an account and started the free trial."
This endpoint is public like the tag, and it allows 60 requests an hour from one address. That suits a page. A server forwarding many enquiries from one address should tell us, and we will give it a route of its own under its key.
06What happens after the page
The things you actually want more of happen later, on your own server: the account that gets used, the plan that gets paid for, the job that gets booked, the quote that gets accepted. Report them, and the ad platform learns to find more people like the ones who did them, instead of more people who press buttons.
POST https://api.adducta.com/v1/outcomes
Authorization: Bearer adu_live_...
Content-Type: application/json
{
"visitor_id": "3f2b8c1e-9a4d-4e2b-8f1a-0c5d7e9b1a2c",
"name": "subscription_paid",
"site_key": "adu_site_...",
"occurred_at": "2026-09-24T14:03:11Z"
}
visitor_idis the one you kept with the account (section 03).nameis a name from your catalogue.site_keyis needed when your key reaches more than one site, which a partner key usually does.occurred_atis optional, for an outcome you report late. Not in the future, and not older than 90 days.
Nothing else is accepted, and a field this contract does not have is refused rather than ignored. There is no value field on purpose: what each outcome is worth is in your catalogue.
Unlike the page endpoints, this one tells you what happened, because the caller holds your key:
| Answer | Meaning |
|---|---|
201, recorded: true | Counted. If ad_click_at is set, an ad click brought this visitor and it goes to the ad platform within the hour. |
201, ad_click_at: null | Counted in Adducta's own numbers, but no ad click is known for this visitor on this site, so no ad platform hears of it. If you expected one, the visitor id kept with the account is not the one the tag gave them. |
200, recorded: false | Already recorded for this visitor. Each outcome counts once per visitor, for ever, so reporting it again is safe, and is a fine way to retry. |
| 404 | None of the sites your key reaches has that site key. |
| 422 | The name is not in the catalogue, the time is out of range, the key reaches several sites and you did not say which, or the body has a field it should not. |
| 403 | Sent with a person's login rather than a key. Outcomes come from servers. |
Outcomes worth money are marked server only in the catalogue. The page cannot report them, because the site key is in the page for anyone to read, and a paid plan must not be something anybody can claim.
07The catalogue
Every site has a catalogue of the things it may report. Each entry carries:
- a name, lowercase with underscores, like
quote_requested; - a value on one scale for every business, where a lead worth phoning is 100 and the most anything can be is 1,000;
- the reason for that value, written down, because a number that steers somebody's advertising has to be arguable;
- what the ad platform calls it: a sign-up, a qualified lead, a purchase, a paid subscription, a booked appointment;
- whether only your server may report it.
The values are the safety of counting several things at once. A calculator used fifty times a day at 5 cannot outvote one paid plan at 1,000, so the ad platform chases the right people. That is why they are agreed with us rather than sent in a request, and why a new entry is added before the first report of it rather than because of one. Tell us what your business counts as progress and we will propose the catalogue, with the reasons, for you to agree.
08For partner platforms
A partner's users advertise through Adducta from inside the partner's
own product and never see ours. Everything above works the same for
them, with the partner key in place of a client key and the
site_key of the user's site on every server call. What the
partner key can do today:
| Step | Route |
|---|---|
| See your own position: clients, limits, balance | GET /v1/partner/me, GET /v1/partner/ledger |
| Bring a user as an advertiser | POST /v1/partner/clients with name and an optional daily_cap_cents |
| Change what they may spend a day | PATCH /v1/partner/clients/{id} |
| Give a user's site its key | POST /v1/clients/{id}/sites with a label; the answer carries the tag to put on it. GET /v1/sites?client_id=... lists them |
| Ask for a campaign | POST /v1/campaigns/plan?client_id=... with the brief: business, offer, geography, landing page, daily budget, and what makes a lead worth calling |
| Read the plan, and approve it | GET /v1/campaigns/{id}/plan, then POST /v1/campaigns/{id}/submit, which is the user's approval |
| Build it at the ad platform, paused | POST /v1/campaigns/{id}/launch. Takes up to a minute; a second call while the first is building answers 409 instead of building twice. The answer lists anything still in the way of starting |
| Start and stop | POST /v1/campaigns/{id}/start, /pause; a whole advertiser with POST /v1/clients/{id}/pause and /resume |
| Read results | GET /v1/campaigns, /v1/leads, /v1/spend, /v1/decisions |
| Answer the agent | POST /v1/campaigns/{id}/offers/{decision_id}, POST /v1/campaigns/{id}/tell |
| Report what happened after the page | POST /v1/outcomes, with the user's site_key |
Graded leads arrive at your endpoint as they happen, as a signed
lead.graded webhook: X-Adducta-Event,
X-Adducta-Delivery, X-Adducta-Timestamp and
X-Adducta-Signature, which is
sha256= and an HMAC of the timestamp, a dot and the raw
body, with the secret you were given once.
A partner is also the best source of outcomes there is. A CRM knows which of its users' leads became deals, a booking system knows which appointments were kept, and reporting those against the visitor who arrived from an ad is worth more to the auction than anything a page can say.
Still done by us, for now
These steps are real and quick, and today a person at Adducta does them when you ask. Each will become a route; this page will say so the day it does.
- Opening the ad account a user's campaigns run in.
- Agreeing a site's catalogue.
- Registering your webhook endpoint, and events beyond
lead.graded. - Topping up a partner balance, which follows a bank transfer.
09Checking that it works
- The tag. Open a landing page with
?gclid=teston the address, and look for a request toapi.adducta.com/v1/intake/touchin the browser's network panel, and foradu_visitorin local storage. - A form. Submit it and check that your server received
visitor_id. - A page event. Look for a request to
/v1/intake/eventcarrying the name. We can tell you whether it was recorded; the endpoint never will. - An outcome. Report one for a visitor id you kept, and read the
answer:
ad_click_atsays whether an ad click is known for them. - Your key.
GET /v1/mesays which role it holds and what it reaches.
Anything unclear, or a route you need that is not here: hello@adducta.com.