# Hamla for developers

> Marketing automation a business installs with one script tag.
> Events are free and unmetered on every plan; contacts and sending are what cost.

## Install

Add before `</head>`. The business id comes from Settings → Website in Hamla
and is public — it appears in every page that loads the SDK.

```html
<script src="https://app.hamla.io/s/{businessId}.js" async></script>
```

This covers visits, sources, on-site behaviour, and `hamla.track()` calls made
in the page. No key: a published bundle cannot hold one.

## No account yet — POST /api/install

If you are an agent building someone a site, you do not have their Hamla login
and you will not get one mid-task. This returns a working install immediately,
with no authentication and no key issued.

```bash
curl -X POST https://app.hamla.io/api/install \
  -H "Content-Type: application/json" \
  -d '{
    "website": "raqmi.co",
    "name": "Raqmi",
    "locale": "en"
  }'
```

```json
{
  "success": true,
  "data": {
    "businessId": "cmf8q1x4k0000",
    "snippet": "<script src=\"https://app.hamla.io/s/cmf8q1x4k0000.js\" async></script>",
    "claimUrl": "https://app.hamla.io/claim/9f3c...",
    "expiresAt": "2026-09-19T08:00:00.000Z",
    "note": "This install records anonymous traffic only until it is claimed. Print the claim link for the person who owns the site — it is single-use and expires."
  },
  "message": "Install created"
}
```

Put `snippet` in the page. **Print `claimUrl` for the person who owns the
site** — that is the one step only you can take, and the install is deleted if
nobody follows it.

What an unclaimed install may do, and what it refuses:

- **Anonymous traffic only.** Page views, sessions, sources, on-site events. An
  `identity` carrying an `email` or `phone` is refused with **422** and a
  sentence naming the claim as the fix — never silently dropped.
  `platformCustomerId` stays allowed: it is your own opaque id and names
  nobody outside your system.
- **No credential is issued.** The snippet carries a business id, which is
  already public in every page that loads the SDK. `sk_live_` comes from
  Settings → API keys, after a human owns the business.
- **No campaign can launch.** Nothing messages a real person until someone owns
  the business and can answer for it.
- **Five installs per hour per IP.**
- **The claim link works once and expires in 72 hours.** If nobody claims it,
  the business and everything it collected are deleted.

The owner opening that link sees their own traffic — visitors, sources, how
many reached checkout and left — and one button. Not a signup form.

### Lovable, v0, Bolt

They cannot install a skill or connect an MCP server, so paste this in:

```
Add Hamla to this site so I get analytics and marketing on it.

1. POST to https://app.hamla.io/api/install with JSON {"website": "<this site's domain>"}.
   No API key, no account, no auth header. It returns
   { "data": { "businessId", "snippet", "claimUrl" } }.
2. Put the returned "snippet" string in the <head> of every page, exactly as given.
   Do not invent or edit the business id inside it.
3. When you are done, show me the "claimUrl" value. I need it to take ownership —
   it works once and expires in 72 hours.

Rules: never send a customer's email or phone to Hamla from this site, and never
put a Hamla secret key in front-end code. Neither is needed for the above.
```

## Events — POST /api/sdk/track

Server-side. Requires a secret key from Settings → API keys.

```
Authorization: Bearer sk_live_...
```

A publishable key (`pk_live_`) is refused with 403 here: it ships in page
source, so honouring it would let any visitor invent revenue.

```bash
curl -X POST https://app.hamla.io/api/sdk/track \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "event": "subscription_started",
    "type": "purchase",
    "identity": { "email": "omar@raqmi.co" },
    "value": 29,
    "currency": "JOD",
    "idempotencyKey": "invoice_9911"
  }'
```

### Fields

- `identity` **(required)** — Who it happened to — one of email, phone or platformCustomerId. Any one is enough, and it does not have to be the email: a clinic that only ever collects phone numbers sends phone, forever.
- `event` **(required)** — The moment, in your own vocabulary, 1–64 characters. A clinic sends treatment_completed, a school sends lesson_finished. Triggers and audiences match it exactly.
- `type` — What the event MEANS, from the seven below. Leave it out and Hamla infers from the amount and from names it recognises; send it and nothing is ever guessed.
- `value, currency` — The amount and its currency. Counted toward the contact's revenue according to the type — a clinic's money is money.
- `occurredAt` — ISO 8601 or epoch milliseconds. The past is allowed, which is what makes backfilling possible; more than five minutes into the future is refused.
- `properties` — Any JSON worth keeping with the event, forever. Up to 32,768 bytes, stored nested rather than spread into the payload root.
- `traits` — What is true NOW — plan, subscription_status. Creates its own custom field on first sight and newest value wins, so segments can filter on it immediately.
- `tags` — Merge-added to the contact, never replacing tags it already carries.
- `idempotencyKey` — Your invoice or order id. The same key twice is recorded once, which is what makes a retry — or a re-run of your whole backfill — free.

### What `type` means

Your `event` name stays yours, in your own language. `type` says what Hamla
should do with it.

- `purchase` — Money received. Adds to revenue, counts as an order.
- `refund` — Money returned. Subtracts from revenue.
- `booking` — Committed, not yet paid. No revenue.
- `lead` — Identity captured. No revenue.
- `checkout_started` — Started, not finished. No revenue.
- `fulfilled` — They received it. No revenue.
- `cancelled` — It ended. No revenue.

### Backfill — POST /api/sdk/track/batch

1–500 past events per request, same field rules, each with its real
`occurredAt`. Backfilled history **never starts a campaign** and never fires a
segment-entry trigger. Give every row an `idempotencyKey` and the job is safe
to re-run.

### Browser

`hamla.track('download_ebook', { asset: 'guide.pdf' })` — no key needed. These
count as deliberate and can start campaigns. Anything involving money or your
own records should come from your server.

## Node

```bash
npm install @gethamla/node
```

Wraps the same HTTP API with queueing and retries. Never throws, never blocks;
call `flush()` before a serverless runtime freezes.

## MCP

```
https://app.hamla.io/mcp
```

OAuth. Registry name `io.hamla/hamla`. Exposes the same tools Hamla's own
agent runs on — analytics, campaigns, contacts, segments, imports.

## More

- https://hamla.io/llms.txt — this site, indexed
- https://docs.hamla.io/llms.txt — the documentation, indexed
- https://docs.hamla.io/reference/events — every field and error status
- https://github.com/usehamla — agent skills and rules

Handles: `usehamla` on GitHub, `@gethamla` on npm, `hamla.io` on the web.
