The /v1 API

The API is the product — everything the dashboard does goes through the same endpoints. Authenticate with the API key issued for your org (dashboard → Settings, or ask your Hotswap contact) as a bearer token:

curl https://api.1tap.at/v1/org \
  -H "Authorization: Bearer 1tap_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Endpoints

EndpointWhat it does
GET /v1/orgYour org, plan and usage.
POST /v1/linksCreate a link: {"domain","destination_url","short_code?","title?","expires_at?","rules?"}. Alias auto-generates when omitted. rules = smart routing (below).
GET /v1/linksList links (keyset pagination via ?limit=&cursor=).
PATCH /v1/links/{id}Change destination, title, status (active|paused), expiry or rules ("rules": null clears).
DELETE /v1/links/{id}Delete (org-admin / API key only).
POST /v1/links/bulk?domain=CSV body (destination_url,short_code,title header, ≤1000 rows) → per-row results.
GET /v1/links/report?range=30dEvery link with clicks (range) + conversions (all-time) — the "did my batch get clicked" call.
GET /v1/analytics?type=&range=&link_id=overview · timeseries · top_links · countries · devices · referrers.
POST/GET /v1/domains, POST /v1/domains/{id}/verifyConnect and verify custom domains.
PUT /v1/integrationsConfigure Meta CAPI: {"provider":"meta_capi","config":{"pixel_id","access_token"}}.
POST /v1/conversionsPublic — see the snippet below; requires a valid click_id.
POST/GET /v1/foldersProjects to organize links. Set folder_id + tags on a link; filter with ?folder_id= / ?tag=.
POST/GET/DELETE /v1/webhooksSubscribe an endpoint to conversion.created, link.clicked, campaign.clicked. See below.
POST/GET /v1/campaignsStateless bulk — see below.

Campaigns — a million links, no million rows

For one-per-customer sends (email/WhatsApp) where you only need "who clicked". Create one campaign; links are computed and signed, never stored; a row is written only when a customer actually clicks theirs.

POST /v1/campaigns
{ "domain": "go.yourbrand.com", "name": "Diwali WA",
  "destination_template": "https://yourbrand.com/welcome?c={ref}", "sent_count": 1000000 }
→ { "campaign": { "id": "camp_…", "secret": "…", "link_pattern": "https://go.yourbrand.com/c/camp_….{base64url(ref)}.{hmac16}", … } }

Get each customer's link — batched (≤10k/call), no writes:

POST /v1/campaigns/camp_…/generate   { "refs": ["cust-1","cust-2", …] }
→ { "links": [ { "ref": "cust-1", "url": "https://go.yourbrand.com/c/camp_….Y3VzdC0x.a1b2c3d4e5f6a7b8" }, … ] }

Or generate all 1M locally with zero API calls — for each ref:

sig  = HMAC_SHA256(campaign.secret, campaignId + "." + ref)  // first 16 hex chars
link = https://{domain}/c/{campaignId}.{base64url(ref)}.{sig}

Then pull the clickers:

GET /v1/campaigns/camp_…/clicks?limit=1000&cursor=…
→ { "clicks": [ { "ref": "cust-1", "click_count": 2, "first_clicked_at": …, "last_clicked_at": … } ], "cursor": … }

Webhooks

Register an endpoint and pick events. Every delivery is POST {event, created_at, data} with an X-1Tap-Signature header = HMAC-SHA256 of the raw body using the secret returned on create. Best-effort (no retries) — verify the signature, respond 2xx.

POST /v1/webhooks   { "url": "https://yourapp.com/hooks/1tap", "events": ["conversion.created"] }
→ { "id": "wh_…", "secret": "whsec_…" }   // secret shown once

Errors are always {"error":{"message":"…"}} with a meaningful status (400 validation, 401/403 auth, 404 not-yours-or-missing, 409 conflict, 429 slow-down).

Smart routing rules

Rules evaluate top-down, first match wins, all conditions in a rule must match; no match → the default destination:

{
  "domain": "go.yourbrand.com",
  "destination_url": "https://yourbrand.com/sale",
  "rules": {
    "version": 1,
    "rules": [
      { "country": ["IN"], "device": "mobile", "to": "https://yourbrand.onelink.me/app" },
      { "percent": 50, "to": "https://yourbrand.com/sale-b" },
      { "schedule": { "end": 1767206400 }, "to": "https://yourbrand.com/pre-launch" }
    ]
  }
}

Conversion tracking (attribution)

1) Ask us (or your org admin) to turn attribution on — your links start appending a signed ?1tap_id=. 2) Add the snippet to the destination site:

<script src="https://1tap.at/1tap-convert.js" async></script>

3) Call it when the visitor converts — same visit or up to 30 days later:

_1tap.convert();                          // a lead
_1tap.convert("Purchase", 1499, "INR");   // a purchase with value

Conversions appear in your link report. If Meta CAPI is configured, each event is also forwarded server-side to Meta with event_id deduplication — safe to run alongside a browser pixel.

Prefer raw HTTP? POST /v1/conversions with {"click_id","event_name?","value?","currency?"} — no auth header; the signed click id is the credential.