Public token flows
Some actions are performed by recipients who are not API-key holders — a podcaster reviewing a campaign offer, accepting a network invitation, or completing opt-in. These flows are driven by a magic-link token (a long, opaque string in a link AdBridge emails to the recipient) rather than an API key.
These endpoints live under /v1/public/** and are the only /v1 endpoints that do not require an
Authorization header.
The shape of a flow
- Open the link. The recipient opens a magic link containing the token, then your page calls the
context endpoint to render the relevant details (e.g. the offer or invitation). The context
response includes a
verifiedflag. - Send a code. To perform a sensitive action, the recipient first proves control of their email. Call the send-code endpoint; AdBridge emails a short numeric code.
- Verify the code. Submit the code to the verify-code endpoint. This must succeed before the action is allowed.
- Complete the action. Accept/reject the offer, accept the invitation, complete opt-in, create a voice, etc.
Example: accepting a campaign offer
The campaign-offer flow is rooted at
/v1/public/campaign-offer/{networkId}/{campaignId}/{token}. The steps below read the context, email
a code, verify it, then accept.
- cURL
- Python
- Node.js
- Go
- Java
BASE="https://api.adbridge.ai/v1/public/campaign-offer/$NETWORK_ID/$CAMPAIGN_ID/$TOKEN"
# 1. Render the offer
curl -s "$BASE"
# 2. Email a verification code to the podcast contact
curl -s -X POST "$BASE/send-code"
# 3. Verify the code the recipient received
curl -s -X POST "$BASE/verify-code" \
-H "Content-Type: application/json" \
-d '{"code": "123456"}'
# 4. Accept the offer (only succeeds once verified)
curl -s -X POST "$BASE/accept" \
-H "Content-Type: application/json" \
-d '{}'
import os
import requests
base = (
"https://api.adbridge.ai/v1/public/campaign-offer/"
f"{os.environ['NETWORK_ID']}/{os.environ['CAMPAIGN_ID']}/{os.environ['TOKEN']}"
)
requests.get(base).raise_for_status() # 1. context
requests.post(f"{base}/send-code").raise_for_status() # 2. email a code
requests.post(f"{base}/verify-code", json={"code": "123456"}).raise_for_status() # 3. verify
requests.post(f"{base}/accept", json={}).raise_for_status() # 4. accept
const base =
`https://api.adbridge.ai/v1/public/campaign-offer/` +
`${process.env.NETWORK_ID}/${process.env.CAMPAIGN_ID}/${process.env.TOKEN}`;
const json = (path, body) =>
fetch(`${base}${path}`, {
method: body ? "POST" : "GET",
headers: body ? { "Content-Type": "application/json" } : undefined,
body: body ? JSON.stringify(body) : undefined,
});
await json(""); // 1. context
await json("/send-code", {}); // 2. email a code
await json("/verify-code", { code: "123456" }); // 3. verify
await json("/accept", {}); // 4. accept
base := fmt.Sprintf(
"https://api.adbridge.ai/v1/public/campaign-offer/%s/%s/%s",
os.Getenv("NETWORK_ID"), os.Getenv("CAMPAIGN_ID"), os.Getenv("TOKEN"))
post := func(path, body string) {
resp, err := http.Post(base+path, "application/json", strings.NewReader(body))
if err != nil {
log.Fatal(err)
}
resp.Body.Close()
}
http.Get(base) // 1. context
post("/send-code", "") // 2. email a code
post("/verify-code", `{"code":"123456"}`) // 3. verify
post("/accept", "{}") // 4. accept
String base = "https://api.adbridge.ai/v1/public/campaign-offer/%s/%s/%s"
.formatted(System.getenv("NETWORK_ID"), System.getenv("CAMPAIGN_ID"), System.getenv("TOKEN"));
HttpClient client = HttpClient.newHttpClient();
void post(String path, String body) throws Exception {
client.send(HttpRequest.newBuilder(URI.create(base + path))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.discarding());
}
client.send(HttpRequest.newBuilder(URI.create(base)).GET().build(),
HttpResponse.BodyHandlers.ofString()); // 1. context
post("/send-code", ""); // 2. email a code
post("/verify-code", "{\"code\":\"123456\"}"); // 3. verify
post("/accept", "{}"); // 4. accept
The exact endpoints for each flow are in the API Reference under the Public — tags (Opt-in, Campaign offer, Invitations, Opt-in voice).
Rate limiting & lockouts
Because these endpoints are unauthenticated, they are rate-limited per token and per client IP:
- Send / verify / action calls are limited; exceeding the limit returns
429 Too Many Requestswith aRetry-Afterheader (seconds). - The verify step also enforces an attempt lockout after too many wrong codes.
- Codes expire (a short window); an expired or never-sent code returns
409 Conflict.
Honor Retry-After and surface a clear message to the recipient rather than retrying tightly.
Security notes
- Treat the magic-link token as a secret credential for the duration of the flow.
- The context endpoints return only curated, page-rendering fields — no internal ids or the raw code.
- These flows are intended to be called from the recipient's browser/app, not from your server with an API key.