---
title: Share Link
kind: resource
slug: share-link
summary: "A tokenized URL for sharing a Droplet — internal (SSO) or external (public domain), optionally gated by password, email verification, or an allow-list."
---

# Share Link

A tokenized URL for sharing a Droplet — internal (SSO) or external (public domain), optionally gated by password, email verification, or an allow-list.

A **Share Link** is the external-sharing primitive for [Droplets](/docs/resources/droplet). Internal links live on the CloudFront domain and require Hi Marley SSO; external links live on the public `fromhimarley.com` domain and can be additionally gated by magic-link email verification, a password, or an allow-list of exact emails / apex domains / wildcard subdomains. Default external expiry is **30 days**; default magic-link verification is **on** for external links.

## The Share Link object

### Attributes

- **`name`** `string` — Owner-facing label for the link (e.g. "For design review", "Board presentation"). Shown in the owner's share-links panel and in audit events; never shown to recipients.
- **`token`** `string` — The opaque token in the share URL (`?token={token}`). Globally unique. Lambda@Edge looks it up in DynamoDB to enforce access rules before the asset is served.
- **`isExternal`** `boolean` — `false` (default) — internal link on the CloudFront domain, requires Hi Marley SSO. `true` — external link on `fromhimarley.com`, accessible to anyone (subject to the gates below).
- **`requireVerifiedEmail`** `boolean` — Magic-link verification before viewing. Recipient enters their email, we email them a one-time link + OTP, we record the verified email on every view. Defaults to **`true`** for external links.
- **`allowedEmails`** `string[]` — Optional allow-list. Each entry is one of: an exact email (`alice@acme.com`), an apex domain (`@acme.com`), or a wildcard subdomain (`*.acme.com`). Patterns with fewer than 2 labels after the wildcard (e.g. `*.com`) are rejected server-side. Empty array = no restriction.
- **`expiresAt`** `timestamp · nullable` — When the link stops working. External links default to **30 days** from creation; pass `null` (subject to your role) to remove expiry. After expiry, the edge returns 410.
- **`enabled`** `boolean` — Set `false` to immediately disable the link. The edge cache is re-synced and CloudFront is invalidated on disable. The link is preserved (re-enabling restores access).
- **`appId`** `string` — The id of the [Droplet](/docs/resources/droplet) this link grants access to. Set at creation and immutable thereafter.
- **`id`** `string · less-common` — CUID primary key. Used as `{linkId}` in PATCH / DELETE paths.
- **`passwordHash`** `string · nullable · less-common` — Argon2 hash of the link password, if one was set. Never returned in API responses — the API exposes `hasPassword: boolean` instead.
- **`edgePasswordHash`** `string · nullable · less-common` — Companion edge-friendly hash (HMAC-SHA256) used by Lambda@Edge for the same password check. Kept in sync with `passwordHash` on every write.
- **`requireEmail`** `boolean · less-common` — **(Legacy)** Unverified email capture before viewing — the recipient typed an email, we trusted them. Superseded by `requireVerifiedEmail` (magic-link). Preserved for old links; new external links default to `requireVerifiedEmail: true`.
- **`createdById`** `string · nullable · less-common` — The User who created the link. Non-owner editors can only modify links they themselves created.
- **`createdAt`** `timestamp · less-common` — ISO 8601 timestamp of creation.
- **`updatedAt`** `timestamp · less-common` — ISO 8601 timestamp of last modification.

### JSON example

```json
{
  "id": "clx6pqr...",
  "appId": "clx1abc...",
  "token": "abc123def456",
  "name": "For Acme partner review",
  "isExternal": true,
  "requireEmail": false,
  "requireVerifiedEmail": true,
  "allowedEmails": ["alice@acme.com", "@acme-legal.com", "*.partner.com"],
  "expiresAt": "2026-07-14T12:00:00.000Z",
  "enabled": true,
  "createdById": "usr_alex",
  "createdAt": "2026-06-14T12:00:00.000Z",
  "updatedAt": "2026-06-14T12:00:00.000Z"
}
```

## Methods

### List share links

`GET /api/apps/{slug}/shares`

Return every share link for a Droplet, with view counts and the rendered URL. Owners and editors only.

**Parameters**

- **`slug`** _(string · path · required)_ — The Droplet slug.

**CLI**

```bash
drop shares list q2-retro-board
```

**MCP**

```json
{
  "tool": "drop_share_links",
  "arguments": {
    "slug": "q2-retro-board"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/apps/q2-retro-board/shares' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
[
  {
    "id": "clx6pqr",
    "token": "abc123def456",
    "name": "For design review",
    "hasPassword": false,
    "requireEmail": false,
    "requireVerifiedEmail": true,
    "expiresAt": "2026-07-14T12:00:00.000Z",
    "enabled": true,
    "createdAt": "2026-06-14T12:00:00.000Z",
    "viewCount": 5,
    "isExternal": true,
    "allowedEmails": [],
    "url": "https://fromhimarley.com/q2-retro-board?token=abc123def456"
  }
]
```

### Create a share link

`POST /api/apps/{slug}/shares`

Create a new share link. External links default to a 30-day expiry and use the public `fromhimarley.com` domain. Internal links use the CloudFront domain and require Hi Marley SSO. Optional gates: `password`, `requireVerifiedEmail` (magic-link), `allowedEmails` (exact / apex-domain / wildcard subdomain).

**Parameters**

- **`slug`** _(string · path · required)_ — The Droplet slug.
- **`name`** _(string · body · required)_ — Display name for the link.
- **`isExternal`** _(boolean · body)_ — External (public domain) vs internal (SSO). Default false.
- **`password`** _(string · body)_ — Optional password to protect the link.
- **`requireEmail`** _(boolean · body)_ — (Legacy) unverified email capture.
- **`requireVerifiedEmail`** _(boolean · body)_ — Magic-link verification. Default true for external.
- **`allowedEmails`** _(string[] · body)_ — Allow-list: exact email / @domain / *.subdomain.
- **`expiresAt`** _(string · body)_ — ISO timestamp expiry.

**CLI**

```bash
drop shares create q2-retro-board --name "For Acme partner review" --is-external --require-verified-email --allowed-emails alice@acme.com,@acme-legal.com,*.partner.com
```

**MCP**

```json
{
  "tool": "drop_create_share_link",
  "arguments": {
    "slug": "q2-retro-board",
    "name": "For Acme partner review",
    "isExternal": true,
    "requireVerifiedEmail": true,
    "allowedEmails": [
      "alice@acme.com",
      "@acme-legal.com",
      "*.partner.com"
    ]
  }
}
```

**HTTP**

```bash
curl -X POST 'https://drop.himarley.ai/api/apps/q2-retro-board/shares' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"For Acme partner review","isExternal":true,"requireVerifiedEmail":true,"allowedEmails":["alice@acme.com","@acme-legal.com","*.partner.com"]}'
```

**Response** (200)

```json
{
  "id": "clx6pqr",
  "token": "abc123def456",
  "name": "For Acme partner review",
  "hasPassword": false,
  "requireEmail": false,
  "requireVerifiedEmail": true,
  "expiresAt": "2026-07-14T12:00:00.000Z",
  "enabled": true,
  "createdAt": "2026-06-14T12:00:00.000Z",
  "viewCount": 0,
  "isExternal": true,
  "allowedEmails": [
    "alice@acme.com",
    "@acme-legal.com",
    "*.partner.com"
  ],
  "url": "https://fromhimarley.com/q2-retro-board?token=abc123def456"
}
```

### Update a share link

`PATCH /api/apps/{slug}/shares/{linkId}`

Partial update — only supply fields you want to change. Common cases: toggle `enabled` to disable / re-enable, extend `expiresAt`, replace `allowedEmails`, rotate the password. Allow-list edits emit a `SHARE_LINK_ALLOWLIST_UPDATED` audit event with the sorted before/after diff. Allow-list validation is all-or-nothing — one bad entry rejects the whole list.

**Parameters**

- **`slug`** _(string · path · required)_ — The Droplet slug.
- **`linkId`** _(string · path · required)_ — The share link id.
- **`name`** _(string · body)_ — New name.
- **`enabled`** _(boolean · body)_ — Toggle enabled (disable invalidates the edge cache).
- **`password`** _(string | null · body)_ — New password, or null to clear.
- **`requireEmail`** _(boolean · body)_ — Require unverified email capture.
- **`requireVerifiedEmail`** _(boolean · body)_ — Require magic-link verification.
- **`allowedEmails`** _(string[] · body)_ — Replace the allow-list.
- **`expiresAt`** _(string | null · body)_ — ISO timestamp, or null to clear.

**CLI**

```bash
drop shares update q2-retro-board clx6pqr --allowed-emails alice@acme.com,bob@acme.com,@partner.com
```

**MCP**

```json
{
  "tool": "drop_update_share_link",
  "arguments": {
    "slug": "q2-retro-board",
    "linkId": "clx6pqr",
    "allowedEmails": [
      "alice@acme.com",
      "bob@acme.com",
      "@partner.com"
    ]
  }
}
```

**HTTP**

```bash
curl -X PATCH 'https://drop.himarley.ai/api/apps/q2-retro-board/shares/clx6pqr' \
  -H "Authorization: Bearer $DROP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"allowedEmails":["alice@acme.com","bob@acme.com","@partner.com"]}'
```

**Response** (200)

```json
{
  "id": "clx6pqr",
  "token": "abc123def456",
  "name": "For Acme partner review",
  "hasPassword": false,
  "requireEmail": false,
  "requireVerifiedEmail": true,
  "expiresAt": "2026-07-14T12:00:00.000Z",
  "enabled": true,
  "createdAt": "2026-06-14T12:00:00.000Z",
  "isExternal": true,
  "allowedEmails": [
    "alice@acme.com",
    "bob@acme.com",
    "@partner.com"
  ]
}
```

### Delete a share link

`DELETE /api/apps/{slug}/shares/{linkId}`

Delete the link and remove it from the edge access cache. The link stops working immediately. There is no soft-delete — use `enabled: false` via [Update](#update) for recoverable disable.

**Parameters**

- **`slug`** _(string · path · required)_ — The Droplet slug.
- **`linkId`** _(string · path · required)_ — The share link id.

**CLI**

```bash
drop shares delete q2-retro-board clx6pqr
```

**MCP**

```json
{
  "tool": "drop_delete_share_link",
  "arguments": {
    "slug": "q2-retro-board",
    "linkId": "clx6pqr"
  }
}
```

**HTTP**

```bash
curl -X DELETE 'https://drop.himarley.ai/api/apps/q2-retro-board/shares/clx6pqr' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
{
  "success": true
}
```

### List views

`GET /api/apps/{slug}/shares/{linkId}/views`

Return the view log for a share link — one row per view with email (if captured / verified), IP address, and timestamp. Useful for after-the-fact "who actually opened this" auditing on external shares. Up to 100 most recent rows.

**Parameters**

- **`slug`** _(string · path · required)_ — The Droplet slug.
- **`linkId`** _(string · path · required)_ — The share link id.

**CLI**

```bash
drop shares views q2-retro-board clx6pqr
```

**MCP**

```json
{
  "tool": "drop_share_link_views",
  "arguments": {
    "slug": "q2-retro-board",
    "linkId": "clx6pqr"
  }
}
```

**HTTP**

```bash
curl -X GET 'https://drop.himarley.ai/api/apps/q2-retro-board/shares/clx6pqr/views' \
  -H "Authorization: Bearer $DROP_TOKEN"
```

**Response** (200)

```json
[
  {
    "id": "clxview1",
    "email": "alice@acme.com",
    "verified": true,
    "ipAddress": "203.0.113.***",
    "viewedAt": "2026-06-15T14:22:00.000Z"
  },
  {
    "id": "clxview2",
    "email": "bob@acme.com",
    "verified": true,
    "ipAddress": "203.0.113.***",
    "viewedAt": "2026-06-15T15:08:00.000Z"
  }
]
```
