# REST API

> Create links, Smart Links, forms and Multilink pages from your own code, and read their analytics, with the same rules as the Walink app.

The Walink API lets a script, a backend or any no-code tool that sends HTTP requests do what you do in the app. Every request runs through the app's own rules, so addresses, allowance and ownership are checked the same way, and every create makes a draft, except a free link, so nothing goes live by accident.

This page gets you to a first call. Every endpoint and field is in the [full API reference](https://api.walink.com/docs), which also has a console to try requests.

## What you can automate

| Resource                           | What the API does                                                   |
| ---------------------------------- | ------------------------------------------------------------------- |
| Premium Links (`/v1/links`)        | List, create drafts, edit, publish, unpublish, delete, style the QR |
| Smart Links (`/v1/smart-links`)    | The same, with destinations and routing                             |
| Forms (`/v1/forms`)                | The same, plus the responses people sent                            |
| Multilink pages (`/v1/multilinks`) | The same                                                            |
| Free links (`/v1/free-links`)      | List, create, read, delete                                          |
| Analytics (`/v1/analytics`)        | Figures and breakdowns by country, source, device and more          |
| Account (`/v1/account`)            | Profile, allowance in use, free links held                          |

Any Walink account can use the API. Drafts are free. Publishing uses your subscription's allowance and analytics need a subscription, as in the app.

## Create a token

1. [Sign in to Walink](https://app.walink.com/login?lang=en), open the account menu (the round button at the top right) and select **Developers**.
2. Under **API Tokens**, select **Create token**.
3. Give it a **Name**, such as "Reporting script", and pick when it **Expires after**: **30 days**, **90 days**, **1 year** (the default) or **No expiration**.
4. Under **Scopes**, tick only what the token needs. **Quick presets** fills in **Read only** or **Full access**. Scopes can't be changed later.
5. Select **Create token**. The **Save your token** window shows it once: copy it somewhere safe, such as a password manager, then select **I've saved it**.

Tokens start with `wlk_live_`, and an account can hold 25 active tokens. In the tokens table, **Rotate** gives you a new token with the same name and scopes while the old one keeps working for 7 days. **Revoke** stops a token for good, within 30 seconds.

### Scopes

`write` includes `read`. Nothing includes `publish`, `delete` or `responses:read`: each is granted on its own, and none is on by default.

| Scope                     | Allows                                                         |
| ------------------------- | -------------------------------------------------------------- |
| `links:read`              | Read Premium Links, Smart Links and free links                 |
| `links:write`             | Create and edit drafts                                         |
| `links:publish`           | Publish, unpublish, edit anything published, create free links |
| `links:delete`            | Delete                                                         |
| `forms:*`, `multilinks:*` | The same four for forms and Multilink pages                    |
| `account:read`            | Profile, allowance and usage                                   |
| `analytics:read`          | Analytics                                                      |
| `responses:read`          | Form responses, which hold personal data people sent you       |

`analytics:read` and `multilinks:read` also show the names of your published links, Smart Links and forms, even without their own read scope.

## Your first request

The base URL is `https://api.walink.com/v1`. Send the token as `Authorization: Bearer <token>` and request bodies as JSON. The examples read the token from a `WALINK_API_TOKEN` environment variable, so it stays out of your code.

List your Premium Links (needs `links:read`):

```sh
curl https://api.walink.com/v1/links \
  -H "Authorization: Bearer $WALINK_API_TOKEN"
```

```json
{
	"data": [
		{
			"id": "lnk_3f2a9c1e-7b4d-4e8a-9f10-2c6d8e4b1a77",
			"title": "Tienda Luna orders",
			"handle": "tienda-luna",
			"plan": "chat",
			"status": "published",
			"url": "https://wa.link/tienda-luna",
			"created_at": "2026-11-05T14:32:07.000Z",
			"updated_at": "2026-11-05T14:32:07.000Z"
		}
	],
	"has_more": false,
	"next_cursor": null
}
```

Lists are newest first. They take `limit` (25 by default, 100 at most) and `cursor`: pass `next_cursor` to get the next page.

### Create a draft

A create always makes a draft (needs `links:write`). This one opens a chat with a US number:

```sh
curl -X POST https://api.walink.com/v1/links \
  -H "Authorization: Bearer $WALINK_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: tienda-luna-promo-1" \
  -d '{
    "handle": "tienda-luna-promo",
    "plan": "chat",
    "title": "Tienda Luna promo",
    "phone": "4155550123",
    "country_code": "1",
    "country_alpha": "US",
    "message": "Hello Tienda Luna, I saw your promo"
  }'
```

The answer is `201` with the full record, its `id` and `"status": "draft"`.

- `handle` is the address after `wa.link/`. It can't be changed after the link is created, and a draft doesn't reserve it: the first account to publish on it gets it.
- `plan` is the link type: `chat`, `catalog`, `channel` or `group`.
- `Idempotency-Key` makes a retry safe for 24 hours: the same key and body return the first answer instead of a second draft.

## Publish

Publishing is its own call, and it needs `links:publish`:

```sh
curl -X POST https://api.walink.com/v1/links/lnk_3f2a9c1e-7b4d-4e8a-9f10-2c6d8e4b1a77/publish \
  -H "Authorization: Bearer $WALINK_API_TOKEN"
```

Like **Publish** in the app, it uses one unit of your Premium Links allowance ($9 a year per link). Without a free unit the answer is `402 pool_exhausted`, with the numbers in `details`. Add allowance on the app's [Billing](https://app.walink.com/billing?lang=en) page ([how billing works](https://walink.com/docs/billing)).

- `POST .../publish-check` is a dry run: what still blocks the publish and how much allowance it would use.
- `POST .../unpublish` takes the link offline and frees its unit. Its address stops working, printed QR codes included, and another account can then claim it.
- `PATCH /v1/links/{id}` changes only the fields you send. On a published link the change is live at once, so it needs `links:publish` too.

Smart Links, forms and Multilink pages follow the same steps under their own paths. Smart Links use the `links` scopes.

## Free links on the API

`/v1/free-links` makes the free links every account can hold, up to 5. They work differently:

- A free link is live the moment it's created, never a draft. That's why creating one needs `links:publish`, not `links:write`.
- Walink picks its random address. You send a phone number or WhatsApp username, an optional message and an optional private `title`, which can't be changed later.
- It can be listed, read and deleted, never edited. Its id starts with `fln_`, and `/v1/links` never shows it.
- Editing it or seeing its analytics needs an upgrade to Premium in the app, which changes its id to `lnk_`.

With 5 already, a create answers `409 free_link_limit_reached`, and deleting one frees its slot. More in [free links](https://walink.com/docs/free-links).

## Errors and rate limits

Every error has the same shape:

```json
{
	"error": {
		"code": "insufficient_scope",
		"message": "This token needs the links:publish scope.",
		"param": null,
		"doc_url": "https://api.walink.com/docs#description/errors",
		"request_id": "req_4f1c9a0b7d2e4c8f9a1b3c5d7e9f0a2b",
		"details": { "required": ["links:publish"] }
	}
}
```

| Status | Common codes                                                 | Meaning                                              |
| ------ | ------------------------------------------------------------ | ---------------------------------------------------- |
| 401    | `invalid_token`                                              | Missing, expired or revoked token                    |
| 402    | `pool_exhausted`, `past_due_locked`, `subscription_required` | Allowance or billing, fixed in the app               |
| 403    | `insufficient_scope`                                         | `details.required` names the scope                   |
| 404    | `not_found`                                                  | Also for ids on other accounts                       |
| 409    | `handle_taken`, `free_link_limit_reached`                    | The address is taken, or a limit is hit              |
| 422    | `validation_failed`, `handle_immutable`                      | Invalid request. `details.fields` lists the problems |
| 429    | `rate_limited`                                               | Wait `Retry-After` seconds                           |

| Requests                               | Limit                   |
| -------------------------------------- | ----------------------- |
| Reads                                  | 120 a minute per token  |
| Creates, edits, publishing and deletes | 30 a minute per account |
| Analytics                              | 10 a minute per account |
| Address checks                         | 30 a minute per account |

The limits are approximate, and running out of allowance is never a `429`. Through the API, an account can hold up to 500 drafts per product. Every response carries a `Request-Id` header: quote it when you contact support.

## Full reference

- [API reference](https://api.walink.com/docs): every endpoint and field, with a console.
- [OpenAPI 3.1 document](https://api.walink.com/openapi.json) and [enums and limits](https://api.walink.com/v1/reference), no token needed.
- [llms.txt](https://api.walink.com/llms.txt): the API's rules, for AI tools.

The same token connects AI assistants too: see [Connect AI assistants](https://walink.com/docs/mcp).
