# Links

> Create links from your server and read their details and lifetime metrics.

Source: https://docs.grovs.io/docs/api-reference/links

Create links from your server. Read their details and lifetime metrics.

These endpoints live on the SDK host: `https://sdk.sqd.link` on grovs Cloud, `https://sdk.<SERVER_HOST>` when you self-host. Send your [project key](https://docs.grovs.io/docs/api-reference/authentication) in the `PROJECT-KEY` header. Send `production` or `test` in the `ENVIRONMENT` header.

Each environment has its own links. A link created in `test` is not visible in `production`.

**Endpoints**

- `POST /api/v1/sdk/generate_link`
- `GET /api/v1/sdk/link/{link_path}`
- `GET /api/v1/sdk/metrics_for_link/{link_path}`
- `GET /api/v1/sdk/metrics_for_project`

## Generate a link

`POST /api/v1/sdk/generate_link`

Creates a link with your own data, per-platform redirects and tracking parameters, and returns its URL.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key. Find it in **Developers › API Key** in the dashboard.
- `ENVIRONMENT` (string, required): `production` or `test`. Any other value returns `400`.
- `Content-Type` (string, required): `application/json`.

**Body: core**

- `title` (string, optional): Title shown in the link preview.
- `subtitle` (string, optional): Subtitle shown in the link preview.
- `data` (string, optional): **Stringified JSON** object delivered to your app by the SDK when the link opens it. Example: `"{\"screen\":\"booking\"}"`. A string that is not valid JSON returns `400`.
- `tags` (string, optional): **Stringified JSON** array of tags. Example: `"[\"clinics\",\"autumn\"]"`. The reserved tag `smart_banner` is removed.

**Body: platform redirects**

- `ios_custom_redirect` (object, optional): iOS redirect with `url` (string) and `open_app_if_installed` (boolean, **required**). Without `open_app_if_installed` the whole redirect is ignored. With `true`, the app opens if it is installed and `url` is used otherwise. With `false`, `url` is always used and deferred deep linking is off for this link on iOS. You can also send the object as stringified JSON.
- `android_custom_redirect` (object, optional): Android redirect. Same shape and rules as `ios_custom_redirect`.
- `desktop_custom_redirect` (object, optional): Desktop browser fallback with `url` (string). `open_app_if_installed` is ignored and always stored as `false`.

**Body: link preview**

- `show_preview_ios` (boolean | null, optional): Show the preview page on iOS. Omit it, or send `null`, to use the project's redirect rule. `true` or `false` overrides the rule for this link.
- `show_preview_android` (boolean | null, optional): Show the preview page on Android. Same rules.
- `show_preview` (boolean, optional): Shorthand for both platforms. It applies to each platform whose own field you did not send.
- `copy_to_clipboard_ios` (boolean | null, optional): Copy the link to the clipboard from the iOS preview page, so the SDK can match the install to this tap. Omit it or send `null` to use the project setting. Only works when the preview page is shown on iOS. See [Copy to clipboard](https://docs.grovs.io/docs/links/copy-to-clipboard).
- `copy_to_clipboard_android` (boolean | null, optional): Same, for Android.

**Body: tracking**

- `tracking_campaign` (string, optional): Campaign name, for example `autumn_checkups`.
- `tracking_source` (string, optional): Traffic source, for example `newsletter` or `instagram`.
- `tracking_medium` (string, optional): Medium, for example `email`, `cpc` or `social`.

### Returns

The new link's URL, with status `200`.

**Response**

- `link` (string): The link URL, ready to share.

> **Note:** `data` and `tags` must be strings that contain JSON. If you send a raw object or array instead, it is dropped without an error and the link is created without it.

> **Note:** Links created through this endpoint are marked as SDK-generated. They are kept out of the default link list in the dashboard; the link search shows them with its SDK filter.

> **Warning:** Keep your `PROJECT-KEY` on your server. If it leaks, contact **<support@grovs.io>** to replace it.

**Errors**

| Status | Meaning                                                                                               |
| ------ | ----------------------------------------------------------------------------------------------------- |
| 400    | PROJECT-KEY is missing, ENVIRONMENT is not production or test, or the body or data is not valid JSON. |
| 403    | The PROJECT-KEY does not match a project: { "error": "Invalid credentials" }.                         |

See [Errors](https://docs.grovs.io/docs/api-reference/errors) for the shared error format.

**Request**

```bash
curl -X POST "https://sdk.sqd.link/api/v1/sdk/generate_link" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Book a check-up",
  "subtitle": "Same-week appointments at Medly clinics",
  "data": "{\"screen\":\"booking\",\"clinic_id\":\"42\"}",
  "tags": "[\"clinics\",\"autumn\"]",
  "ios_custom_redirect": {
    "url": "https://medly.health/ios",
    "open_app_if_installed": true
  },
  "android_custom_redirect": {
    "url": "https://medly.health/android",
    "open_app_if_installed": true
  },
  "desktop_custom_redirect": { "url": "https://medly.health/book" },
  "show_preview_ios": true,
  "show_preview_android": false,
  "tracking_campaign": "autumn_checkups",
  "tracking_source": "newsletter",
  "tracking_medium": "email"
}'
```

**Response 200 OK**

```json
{
  "link": "https://medly.sqd.link/2c1e79"
}
```

**Response 403 Forbidden**

```json
{
  "error": "Invalid credentials"
}
```

## Get link details

`GET /api/v1/sdk/link/{link_path}`

Returns a link by its path, looked up in the environment named by the `ENVIRONMENT` header. Use it to read a link's data for reporting, routing or validation on your server.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key. Find it in **Developers › API Key** in the dashboard.
- `ENVIRONMENT` (string, required): `production` or `test`. Any other value returns `400`.

**Path**

- `link_path` (string, required): The part of the link URL after the last `/`, for example `2c1e79`. Links created in the test environment carry a `-test` suffix (`2c1e79-test`); send it as is.

The request has no body.

### Returns

`{ "link": { … } }` with the fields below.

**Response: link**

- `id` (integer): Numeric link ID.
- `name` (string | null): Name set in the dashboard. Usually `null` for links created through the API.
- `path` (string): Path of the link URL.
- `access_path` (string): Full link URL.
- `title` (string | null): Preview title.
- `subtitle` (string | null): Preview subtitle.
- `image` (string | null): Preview image URL, if one is set.
- `data` (object | null): Your custom data, parsed into an object.
- `tags` (array): Tags of the link.
- `active` (boolean): Whether the link is active.
- `sdk_generated` (boolean): `true` for links created through the API or an SDK.
- `generated_from_platform` (string | null): Where the link was created, for example `API`, `iOS` or `Android`.
- `ads_platform` (string | null): Ads platform the link was made for, if any.
- `campaign_id` (integer | null): Numeric ID of the dashboard campaign the link belongs to, if any.
- `visitor_id` (integer | null): Numeric ID of the app user who created the link, for links created from your app.
- `updated_at` (string): ISO 8601 time of the last change.

**Response: preview and redirects**

- `show_preview_ios` (boolean | null): Whether the preview page shows on iOS. `null` means the project's redirect rule applies.
- `show_preview_android` (boolean | null): Same, for Android.
- `copy_to_clipboard_ios` (boolean | null): Whether the iOS preview page copies the link to the clipboard. `null` means the project setting applies. See [Copy to clipboard](https://docs.grovs.io/docs/links/copy-to-clipboard).
- `copy_to_clipboard_android` (boolean | null): Same, for Android.
- `ios_custom_redirect` (object | null): `url` and `open_app_if_installed`, or `null` when none is set.
- `android_custom_redirect` (object | null): `url` and `open_app_if_installed`, or `null` when none is set.
- `desktop_custom_redirect` (object | null): `url`, and `open_app_if_installed` which is always `false`, or `null` when none is set.

**Response: tracking**

- `tracking_campaign` (string | null): Campaign name.
- `tracking_source` (string | null): Traffic source.
- `tracking_medium` (string | null): Medium.

**Errors**

| Status | Meaning                                                                    |
| ------ | -------------------------------------------------------------------------- |
| 400    | PROJECT-KEY is missing or ENVIRONMENT is not production or test.           |
| 403    | The PROJECT-KEY does not match a project.                                  |
| 404    | No link with that path in this environment: { "error": "Link not found" }. |

> **Note:** A link created in one environment is not visible in the other. If you get `404` for a link you know exists, check that `ENVIRONMENT` matches the environment the link was created in.

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/link/{link_path}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "link": {
    "id": 50510,
    "name": null,
    "path": "2c1e79",
    "title": "Book a check-up",
    "subtitle": "Same-week appointments at Medly clinics",
    "active": true,
    "sdk_generated": true,
    "data": {
      "screen": "booking",
      "clinic_id": "42"
    },
    "tags": ["clinics", "autumn"],
    "updated_at": "2026-10-02T12:43:53.604Z",
    "show_preview_ios": true,
    "show_preview_android": false,
    "copy_to_clipboard_ios": null,
    "copy_to_clipboard_android": null,
    "ads_platform": null,
    "generated_from_platform": "API",
    "tracking_source": "newsletter",
    "tracking_medium": "email",
    "tracking_campaign": "autumn_checkups",
    "visitor_id": null,
    "campaign_id": null,
    "image": null,
    "access_path": "https://medly.sqd.link/2c1e79",
    "ios_custom_redirect": {
      "url": "https://medly.health/ios",
      "open_app_if_installed": true
    },
    "android_custom_redirect": {
      "url": "https://medly.health/android",
      "open_app_if_installed": true
    },
    "desktop_custom_redirect": {
      "url": "https://medly.health/book",
      "open_app_if_installed": false
    }
  }
}
```

**Response 404 Not Found**

```json
{
  "error": "Link not found"
}
```

## Get link metrics

`GET /api/v1/sdk/metrics_for_link/{link_path}`

Returns one link's descriptive fields and its lifetime totals. The path is looked up in the environment named by the `ENVIRONMENT` header. Deactivated links are returned too.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key. Find it in **Developers › API Key** in the dashboard.
- `ENVIRONMENT` (string, required): `production` or `test`. Any other value returns `400`.

**Path**

- `link_path` (string, required): The link's path, for example `2c1e79`, or `2c1e79-test` in the test environment.

### Returns

`{ "metrics": { … } }`. The object has the same descriptive fields as [Get link details](#get-link-details), without `image`, `access_path` and the custom redirects, plus the totals below.

**Response: totals**

- `total_views` (integer): Times the link was opened in a browser.
- `total_opens` (integer): Times the link opened the app, including deferred opens after an install.
- `total_installs` (integer): Installs attributed to the link.
- `total_reinstalls` (integer): Reinstalls attributed to the link.
- `total_time_spent` (integer): Total time in the app of users attributed to the link, in seconds.
- `total_reactivations` (integer): Users attributed to the link who came back after at least seven days away.
- `total_app_opens` (integer): App opens by users attributed to the link.
- `total_user_referred` (integer): Installs referred by the link's creator, for links created by a user in your app.
- `total_revenue` (integer): Revenue attributed to the link, in cents. `0` unless revenue collection is on.

Totals cover the link's whole life, limited by your plan's analytics retention. On grovs Cloud, events older than the retention period are not counted.

**Errors**

| Status | Meaning                                                                    |
| ------ | -------------------------------------------------------------------------- |
| 400    | PROJECT-KEY is missing or ENVIRONMENT is not production or test.           |
| 403    | The PROJECT-KEY does not match a project.                                  |
| 404    | No link with that path in this environment: { "error": "Link not found" }. |

See [Errors](https://docs.grovs.io/docs/api-reference/errors) for the shared error format.

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/metrics_for_link/{link_path}" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
{
  "metrics": {
    "id": 50510,
    "name": null,
    "path": "2c1e79",
    "title": "Book a check-up",
    "subtitle": "Same-week appointments at Medly clinics",
    "active": true,
    "sdk_generated": true,
    "data": { "screen": "booking", "clinic_id": "42" },
    "tags": ["clinics", "autumn"],
    "updated_at": "2026-10-02T12:43:53.604Z",
    "show_preview_ios": true,
    "show_preview_android": false,
    "copy_to_clipboard_ios": null,
    "copy_to_clipboard_android": null,
    "ads_platform": null,
    "generated_from_platform": "API",
    "tracking_source": "newsletter",
    "tracking_medium": "email",
    "tracking_campaign": "autumn_checkups",
    "visitor_id": null,
    "campaign_id": null,
    "total_views": 1250,
    "total_opens": 840,
    "total_installs": 312,
    "total_reinstalls": 18,
    "total_time_spent": 96420,
    "total_reactivations": 41,
    "total_app_opens": 2210,
    "total_user_referred": 0,
    "total_revenue": 149900
  }
}
```

**Response 404 Not Found**

```json
{
  "error": "Link not found"
}
```

## Get project metrics

`GET /api/v1/sdk/metrics_for_project`

Returns every **active** link in the environment named by the `ENVIRONMENT` header, with its lifetime totals, sorted by views from most to least. Everything comes back in one response; there is no pagination.

Auth: Project key. See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

**Headers**

- `PROJECT-KEY` (string, required): Your project key. Find it in **Developers › API Key** in the dashboard.
- `ENVIRONMENT` (string, required): `production` or `test`. Any other value returns `400`.

The endpoint takes no query parameters and no body.

### Returns

A JSON array. Each item has the same shape as the `metrics` object of [Get link metrics](#get-link-metrics): the link's descriptive fields plus `total_views`, `total_opens`, `total_installs`, `total_reinstalls`, `total_time_spent`, `total_reactivations`, `total_app_opens`, `total_user_referred` and `total_revenue`.

Links deactivated in the dashboard are left out, and so are the links smart banners create. Totals cover each link's whole life, limited by your plan's analytics retention.

> **Note:** A project with many links gets a large response. Cache it on your side instead of polling it often.

**Errors**

| Status | Meaning                                                          |
| ------ | ---------------------------------------------------------------- |
| 400    | PROJECT-KEY is missing or ENVIRONMENT is not production or test. |
| 403    | The PROJECT-KEY does not match a project.                        |

See [Errors](https://docs.grovs.io/docs/api-reference/errors) for the shared error format.

**Request**

```bash
curl -X GET "https://sdk.sqd.link/api/v1/sdk/metrics_for_project" \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

**Response 200 OK**

```json
[
  {
    "id": 50510,
    "name": null,
    "path": "2c1e79",
    "title": "Book a check-up",
    "subtitle": "Same-week appointments at Medly clinics",
    "active": true,
    "sdk_generated": true,
    "data": { "screen": "booking", "clinic_id": "42" },
    "tags": ["clinics", "autumn"],
    "updated_at": "2026-10-02T12:43:53.604Z",
    "show_preview_ios": true,
    "show_preview_android": false,
    "copy_to_clipboard_ios": null,
    "copy_to_clipboard_android": null,
    "ads_platform": null,
    "generated_from_platform": "API",
    "tracking_source": "newsletter",
    "tracking_medium": "email",
    "tracking_campaign": "autumn_checkups",
    "visitor_id": null,
    "campaign_id": null,
    "total_views": 1250,
    "total_opens": 840,
    "total_installs": 312,
    "total_reinstalls": 18,
    "total_time_spent": 96420,
    "total_reactivations": 41,
    "total_app_opens": 2210,
    "total_user_referred": 0,
    "total_revenue": 149900
  },
  {
    "id": 50488,
    "name": "Flu shot reminder",
    "path": "8f21ac",
    "title": "Flu shots are open",
    "subtitle": null,
    "active": true,
    "sdk_generated": false,
    "data": { "screen": "vaccines" },
    "tags": [],
    "updated_at": "2026-09-28T08:10:02.117Z",
    "show_preview_ios": null,
    "show_preview_android": null,
    "copy_to_clipboard_ios": null,
    "copy_to_clipboard_android": null,
    "ads_platform": null,
    "generated_from_platform": null,
    "tracking_source": "email",
    "tracking_medium": "newsletter",
    "tracking_campaign": "flu_2026",
    "visitor_id": null,
    "campaign_id": 41,
    "total_views": 630,
    "total_opens": 402,
    "total_installs": 95,
    "total_reinstalls": 3,
    "total_time_spent": 31877,
    "total_reactivations": 12,
    "total_app_opens": 1104,
    "total_user_referred": 0,
    "total_revenue": 0
  }
]
```
