---
title: "Create a link"
method: POST
path: "/links"
tags: ["Links"]
---

# Create a link

`POST /links`

## Request

- Base URL: `https://api.warplink.app/v1`
- URL: `https://api.warplink.app/v1/links`
- Auth: HTTP bearer

## Request body

- object
  - `destination_url` string, uri, required
  - `slug` string: Custom slug. Auto-generated if omitted.
  - `name` string: The link's name in the dashboard. Shows the slug if omitted.
  - `domain` string: Domain to serve this link from. A link with an app defaults to the app's default_link_domain. A web-only link (no app) defaults to aplnk.to, and an existing link with no app keeps aplnk.to. A legacy app (legacy_apex_enabled) can use aplnk.to, its subdomain (link_domain) or a live custom domain. A new app can use its subdomain or a live custom domain, never aplnk.to (422 INVALID_DOMAIN). An explicit domain is never replaced. The slug test is reserved on an app subdomain. An app's previous subdomain (previous_link_domain) never takes new links.
  - `ios_url` string, uri
  - `ios_fallback_url` string, uri
  - `android_url` string, uri
  - `android_fallback_url` string, uri
  - `og_title` string
  - `og_description` string
  - `og_image_url` string, uri
  - `tags` string[]
  - `expires_at` string, date-time: Must be in the future.
  - `password` string
  - `app_id` string, uuid
  - `utm_source` string: Added to the redirect URL as utm_source when no higher source supplies one. Order for utm_source: an injected utm_source in "always" mode wins; otherwise the visitor's own utm_source when parameters.forwarding passes it, then a utm_source already in the destination URL, then an injected utm_source in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `utm_medium` string: Added to the redirect URL as utm_medium when no higher source supplies one. Order for utm_medium: an injected utm_medium in "always" mode wins; otherwise the visitor's own utm_medium when parameters.forwarding passes it, then a utm_medium already in the destination URL, then an injected utm_medium in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `utm_campaign` string: Added to the redirect URL as utm_campaign when no higher source supplies one. Order for utm_campaign: an injected utm_campaign in "always" mode wins; otherwise the visitor's own utm_campaign when parameters.forwarding passes it, then a utm_campaign already in the destination URL, then an injected utm_campaign in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `match_window_hours` integer: Deferred deep link attribution match window in hours.
  - `parameters` LinkParamConfig, nullable: Per-link query-parameter rules. `injected` params are appended to the destination URL; `forwarding` controls which incoming params pass through. Org-level defaults are inherited and merged with link-level config (link wins on key conflict). Precedence: link > org. Send `null` (or omit) to clear/inherit the org defaults.
    - `injected` object[], required: Params appended to the destination URL.
      - `key` string, required
      - `value` string, required
      - `mode` 'always' | 'default' | 'null', nullable, required: `always` overrides an incoming param; `default` only fills when absent; `null` inherits `defaultMode`.
    - `forwarding` object, nullable: Incoming-param forwarding. `null` inherits the org default.
      - `enabled` boolean, required
      - `allowlist` string[], nullable, required: When enabled, only these incoming params forward. `null` forwards all.
    - `defaultMode` 'always' | 'default' | 'null', nullable: Fallback mode for injected params whose `mode` is null. `null` inherits the org default.
    - `inheritOrgInjected` boolean, required: Whether to inherit org-level injected params.
    - `suppressedOrgKeys` string[], required: Org injected-param keys to suppress on this link.

## Response `201`

Link created

- Link
  - `id` string, uuid
  - `short_url` string
  - `slug` string
  - `name` string, nullable: The link's own name in the dashboard. Null shows the slug.
  - `domain` string
  - `destination_url` string, uri
  - `ios_url` string, uri, nullable
  - `ios_fallback_url` string, uri, nullable
  - `android_url` string, uri, nullable
  - `android_fallback_url` string, uri, nullable
  - `og_title` string, nullable
  - `og_description` string, nullable
  - `og_image_url` string, uri, nullable
  - `app_id` string, uuid, nullable
  - `utm_source` string, nullable: Added to the redirect URL as utm_source when no higher source supplies one. Order for utm_source: an injected utm_source in "always" mode wins; otherwise the visitor's own utm_source when parameters.forwarding passes it, then a utm_source already in the destination URL, then an injected utm_source in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `utm_medium` string, nullable: Added to the redirect URL as utm_medium when no higher source supplies one. Order for utm_medium: an injected utm_medium in "always" mode wins; otherwise the visitor's own utm_medium when parameters.forwarding passes it, then a utm_medium already in the destination URL, then an injected utm_medium in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `utm_campaign` string, nullable: Added to the redirect URL as utm_campaign when no higher source supplies one. Order for utm_campaign: an injected utm_campaign in "always" mode wins; otherwise the visitor's own utm_campaign when parameters.forwarding passes it, then a utm_campaign already in the destination URL, then an injected utm_campaign in "default" mode (this field is then not used), then this field. Applies to http and https URLs only; not to apps.apple.com, itunes.apple.com, play.google.com, apps.microsoft.com or custom-scheme URLs.
  - `match_window_hours` integer, nullable
  - `expires_at` string, date-time, nullable
  - `is_active` boolean
  - `tags` string[]
  - `parameters` LinkParamConfig, nullable: Per-link query-parameter rules. `injected` params are appended to the destination URL; `forwarding` controls which incoming params pass through. Org-level defaults are inherited and merged with link-level config (link wins on key conflict). Precedence: link > org. Send `null` (or omit) to clear/inherit the org defaults.
    - `injected` object[], required: Params appended to the destination URL.
      - `key` string, required
      - `value` string, required
      - `mode` 'always' | 'default' | 'null', nullable, required: `always` overrides an incoming param; `default` only fills when absent; `null` inherits `defaultMode`.
    - `forwarding` object, nullable: Incoming-param forwarding. `null` inherits the org default.
      - `enabled` boolean, required
      - `allowlist` string[], nullable, required: When enabled, only these incoming params forward. `null` forwards all.
    - `defaultMode` 'always' | 'default' | 'null', nullable: Fallback mode for injected params whose `mode` is null. `null` inherits the org default.
    - `inheritOrgInjected` boolean, required: Whether to inherit org-level injected params.
    - `suppressedOrgKeys` string[], required: Org injected-param keys to suppress on this link.
  - `created_at` string, date-time
  - `updated_at` string, date-time
  - `redirected_from` object[]: Old URLs of this link that redirect to it with a permanent 301, oldest first. Present only on GET /v1/links/{id} (and the get_link MCP tool), not on list, update, activate or deactivate responses.
    - `url` string, uri: The old URL.
    - `since` string, date-time: When the link moved and the old URL began to redirect.

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden. APP_REQUIRED: the organization has no registered app yet. Create an app with POST /apps, then create the link. LINK_LIMIT_EXCEEDED: the plan's link limit has been reached.
- `409`: SLUG_TAKEN: the slug is already taken on that domain, it is the old address of a moved link, or it is held for a link of the same app that still waits to move there (details.slug and details.domain, plus details.held_by with the old domain and slug for a held address). A slug must be unique only on the domain you pick. DEFAULT_DOMAIN_UNAVAILABLE: the request names no domain and the app's saved default is a custom domain that is no longer live (details.allowed_link_domains lists what to choose instead). APP_LINK_STATE_CHANGED: the app's address or default changed while the link was being created; read the app and retry.
- `422`: INVALID_DOMAIN: the domain is not allowed for this link. A new app cannot use aplnk.to. Use the app's subdomain or a live custom domain.
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/warplink/apis/warplink-api.md) · [All operations](https://skmtc.dev/warplink/apis/warplink-api/llms.txt) · [OpenAPI document](https://skmtc.dev/warplink/apis/warplink-api/revisions/2a90ae8c44e8?raw)
