# Dub.co Docs

## Docs

- [Bulk create links](https://dub.co/docs/api-reference/endpoint/bulk-create-links): Bulk create up to 100 links for the authenticated workspace.
- [Bulk delete links](https://dub.co/docs/api-reference/endpoint/bulk-delete-links): Bulk delete up to 100 links for the authenticated workspace.
- [Bulk update links](https://dub.co/docs/api-reference/endpoint/bulk-update-links): Bulk update up to 100 links with the same data for the authenticated workspace.
- [Create a customer](https://dub.co/docs/api-reference/endpoint/create-a-customer): Create a customer for the authenticated workspace.
- [Create a domain](https://dub.co/docs/api-reference/endpoint/create-a-domain): Create a domain for the authenticated workspace.
- [Create a new link](https://dub.co/docs/api-reference/endpoint/create-a-new-link): Create a new link for the authenticated workspace.
- [Create a new tag](https://dub.co/docs/api-reference/endpoint/create-a-new-tag): Create a new tag for the authenticated workspace.
- [Delete a customer](https://dub.co/docs/api-reference/endpoint/delete-a-customer): Delete a customer from a workspace.
- [Delete a domain](https://dub.co/docs/api-reference/endpoint/delete-a-domain): Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain.
- [Delete a link](https://dub.co/docs/api-reference/endpoint/delete-a-link): Delete a link for the authenticated workspace.
- [Retrieve a customer](https://dub.co/docs/api-reference/endpoint/retrieve-a-customer): Retrieve a customer by ID for the authenticated workspace.
- [Retrieve a link](https://dub.co/docs/api-reference/endpoint/retrieve-a-link): Retrieve the info for a link.
- [Retrieve a list of customers](https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-customers): Retrieve a list of customers for the authenticated workspace.
- [Retrieve a list of domains](https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-domains): Retrieve a list of domains associated with the authenticated workspace.
- [Retrieve a list of events](https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-events): Retrieve a paginated list of events for the authenticated workspace.
- [Retrieve a list of links](https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-links): Retrieve a paginated list of links for the authenticated workspace.
- [Retrieve a list of tags](https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-tags): Retrieve a list of tags for the authenticated workspace.
- [Retrieve a QR code](https://dub.co/docs/api-reference/endpoint/retrieve-a-qr-code): Retrieve a QR code for a link.
- [Retrieve a workspace](https://dub.co/docs/api-reference/endpoint/retrieve-a-workspace): Retrieve a workspace for the authenticated user.
- [Retrieve analytics](https://dub.co/docs/api-reference/endpoint/retrieve-analytics): Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters.
- [Retrieve links count](https://dub.co/docs/api-reference/endpoint/retrieve-number-of-links): Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links.
- [Track a lead](https://dub.co/docs/api-reference/endpoint/track-lead): Track a lead for a short link.
- [Track a sale](https://dub.co/docs/api-reference/endpoint/track-sale): Track a sale for a short link.
- [Update a customer](https://dub.co/docs/api-reference/endpoint/update-a-customer): Update a customer for the authenticated workspace.
- [Update a domain](https://dub.co/docs/api-reference/endpoint/update-a-domain): Update a domain for the authenticated workspace.
- [Update a link](https://dub.co/docs/api-reference/endpoint/update-a-link): Update a link for the authenticated workspace. If there's no change, returns it as it is.
- [Update a tag](https://dub.co/docs/api-reference/endpoint/update-a-tag): Update a tag in the workspace.
- [Update a workspace](https://dub.co/docs/api-reference/endpoint/update-a-workspace): Update a workspace by ID or slug.
- [Upsert a link](https://dub.co/docs/api-reference/endpoint/upsert-a-link): Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.
- [Errors](https://dub.co/docs/api-reference/errors): Troubleshoot problems with this comprehensive breakdown of all error codes.
- [Introduction](https://dub.co/docs/api-reference/introduction): Fundamental concepts of Dub.co's API.
- [Pagination](https://dub.co/docs/api-reference/pagination): Learn how to paginate through resources in the API.
- [Rate limits](https://dub.co/docs/api-reference/rate-limits): Learn about Dub's API rate limits.
- [API Keys](https://dub.co/docs/api-reference/tokens): Learn how API keys work on Dub
- [Introduction](https://dub.co/docs/conversions/clicks/introduction): Click-tracking with Dub Conversions
- [Auth0](https://dub.co/docs/conversions/leads/auth0): Learn how to track a lead conversion event with Auth0 and the Dub SDK
- [Clerk](https://dub.co/docs/conversions/leads/clerk): Learn how to track a lead conversion event with Clerk and the Dub SDK
- [NextAuth.js](https://dub.co/docs/conversions/leads/next-auth): Learn how to track a lead conversion event with NextAuth.js and the Dub SDK
- [Supabase Auth](https://dub.co/docs/conversions/leads/supabase-auth): Learn how to track a lead conversion event with Supabase Auth and the Dub SDK
- [Quickstart](https://dub.co/docs/conversions/quickstart): Learn how to get started with Dub Conversions
- [Shopify](https://dub.co/docs/conversions/sales/shopify): Learn how to track a sale conversion event with Shopify
- [Stripe](https://dub.co/docs/conversions/sales/stripe): Learn how to track a sale conversion event with Stripe
- [Data model](https://dub.co/docs/data-model): A quick overview of how Dub is structured.
- [Examples](https://dub.co/docs/examples): Explore ideas and examples of what you can build with the Dub API
- [Overview](https://dub.co/docs/integrations): Integrate Dub.co with your favorite tools and services.
- [Integrate with Dub](https://dub.co/docs/integrations/quickstart): Learn how to set up Dub OAuth applications to authenticate users with OAuth 2.0.
- [Webhooks](https://dub.co/docs/integrations/webhooks): Use webhooks to build custom integrations with Dub.
- [Introduction](https://dub.co/docs/introduction): Dub is the link management platform for modern marketing teams.
- [Local Development](https://dub.co/docs/local-development): A guide on how to run Dub.co's codebase locally.
- [Django](https://dub.co/docs/quickstart/django): Learn how to integrate Dub with Django.
- [Express](https://dub.co/docs/quickstart/express): Learn how to integrate Dub with Express.
- [Flask](https://dub.co/docs/quickstart/flask): Learn how to integrate Dub with Flask.
- [Introduction](https://dub.co/docs/quickstart/go): Learn how to integrate Dub with Go.
- [Laravel](https://dub.co/docs/quickstart/laravel): Learn how to integrate Dub with Laravel.
- [Next.js](https://dub.co/docs/quickstart/nextjs): Learn how to integrate Dub with Next.js.
- [Nuxt](https://dub.co/docs/quickstart/nuxt): Learn how to integrate Dub with Nuxt.
- [Introduction](https://dub.co/docs/quickstart/php): Learn how to integrate Dub with PHP.
- [Introduction](https://dub.co/docs/quickstart/python): Learn how to integrate Dub with Python.
- [Ruby on Rails](https://dub.co/docs/quickstart/rails): Learn how to integrate Dub with Ruby on Rails.
- [Remix](https://dub.co/docs/quickstart/remix): Learn how to integrate Dub with Remix.
- [Introduction](https://dub.co/docs/quickstart/ruby): Learn how to integrate Dub with Ruby.
- [Sinatra](https://dub.co/docs/quickstart/sinatra): Learn how to integrate Dub with Sinatra.
- [Introduction](https://dub.co/docs/quickstart/typescript): Learn how to integrate Dub with TypeScript.
- [Overview](https://dub.co/docs/sdks/cli/overview): Dub CLI for easily shortening URLs
- [Framer](https://dub.co/docs/sdks/client-side/installation-guides/framer): How to add Dub Analytics to your Framer site
- [React](https://dub.co/docs/sdks/client-side/installation-guides/react): How to add Dub Analytics to your React or Next.js site
- [Webflow](https://dub.co/docs/sdks/client-side/installation-guides/webflow): How to add Dub Analytics to your Webflow site
- [WordPress](https://dub.co/docs/sdks/client-side/installation-guides/wordpress): How to add Dub Analytics to your WordPress site
- [Introduction](https://dub.co/docs/sdks/client-side/introduction): Client-side JavaScript SDK for for Dub Conversions
- [Overview](https://dub.co/docs/sdks/overview): Open-source client libraries for the Dub.co API
- [dub.analytics.retrieve](https://dub.co/docs/sdks/typescript/analytics/retrieve): Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters.
- [dub.domains.create](https://dub.co/docs/sdks/typescript/domains/create): Create a domain for the authenticated workspace.
- [dub.domains.delete](https://dub.co/docs/sdks/typescript/domains/delete): Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain.
- [dub.domains.list](https://dub.co/docs/sdks/typescript/domains/list): Retrieve a list of domains associated with the authenticated workspace.
- [dub.domains.update](https://dub.co/docs/sdks/typescript/domains/update): Edit a domain for the authenticated workspace.
- [dub.events.list](https://dub.co/docs/sdks/typescript/events/list): Retrieve a list of events for the authenticated workspace. The list will be paginated and the provided query parameters allow filtering the returned events.
- [Count](https://dub.co/docs/sdks/typescript/links/count): Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links.
- [Create](https://dub.co/docs/sdks/typescript/links/create): Create a new link for the authenticated workspace.
- [Create Many](https://dub.co/docs/sdks/typescript/links/create-many): Bulk create up to 100 links for the authenticated workspace.
- [Delete](https://dub.co/docs/sdks/typescript/links/delete): Delete a link for the authenticated workspace.
- [Get](https://dub.co/docs/sdks/typescript/links/get): Retrieve info for a link by specifying either the domain and key or the linkId or externalId
- [List](https://dub.co/docs/sdks/typescript/links/list): Retrieve a list of links for the authenticated workspace. The list will be paginated and the provided query parameters allow filtering the returned links.
- [Update](https://dub.co/docs/sdks/typescript/links/update): Edit a link for the authenticated workspace. Support partial updates.
- [Upsert](https://dub.co/docs/sdks/typescript/links/upsert): Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.
- [dub.metatags.get](https://dub.co/docs/sdks/typescript/metatags/get): Retrieve the metatags for a URL.
- [Overview](https://dub.co/docs/sdks/typescript/overview): TypeScript SDK for Dub.co
- [dub.tags.create](https://dub.co/docs/sdks/typescript/tags/create): Create a new tag for the authenticated workspace.
- [dub.tags.list](https://dub.co/docs/sdks/typescript/tags/list): Retrieve a list of tags for the authenticated workspace.
- [dub.tags.update](https://dub.co/docs/sdks/typescript/tags/update): Update a tag for the authenticated user.
- [dub.workspaces.get](https://dub.co/docs/sdks/typescript/workspaces/get): Retrieve a workspace for the authenticated user.
- [dub.workspaces.update](https://dub.co/docs/sdks/typescript/workspaces/update): Update a workspace for the authenticated user.
- [Self-hosting Dub.co](https://dub.co/docs/self-hosting): An end-to-end guide on how to self-host Dub.co – the open-source link management platform.


# Bulk create links

post /links/bulk
Bulk create up to 100 links for the authenticated workspace.

<Warning>
  Bulk link creation does not support the following features: [Link
  cloaking](https://dub.co/help/article/link-cloaking), [Custom social media
  cards](https://dub.co/help/article/custom-social-media-cards). Also, [webhook
  events](/integrations/webhooks) will not be triggered when using this
  endpoint.
</Warning>


# Bulk delete links

delete /links/bulk
Bulk delete up to 100 links for the authenticated workspace.

<Warning>
  This is a destructive action and cannot be undone. Proceed with caution.
  Also, [webhook events](/integrations/webhooks) will not be triggered when using this
  endpoint.
</Warning>


# Bulk update links

patch /links/bulk
Bulk update up to 100 links with the same data for the authenticated workspace.

This endpoint lets you update up to 100 links **with the same data**.

Some potential use cases:

*   Tagging multiple links at once
*   Setting the same expiration date for multiple links
*   Updating UTM parameters for multiple links

<Warning>
  You cannot update the domain or key of a link with this endpoint. Also, [webhook
  events](/integrations/webhooks) will not be triggered when using this
  endpoint.
</Warning>


# Create a customer

post /customers
Create a customer for the authenticated workspace.



# Create a domain

post /domains
Create a domain for the authenticated workspace.



# Create a new link

post /links
Create a new link for the authenticated workspace.



# Create a new tag

post /tags
Create a new tag for the authenticated workspace.



# Delete a customer

delete /customers/{id}
Delete a customer from a workspace.



# Delete a domain

delete /domains/{slug}
Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain.



# Delete a link

delete /links/{linkId}
Delete a link for the authenticated workspace.



# Retrieve a customer

get /customers/{id}
Retrieve a customer by ID for the authenticated workspace.



# Retrieve a link

get /links/info
Retrieve the info for a link.

You can retrieve a link by providing one of the following as a query parameter:

*   `domain` and `key`.
*   `linkId`.
*   `externalId`.


# Retrieve a list of customers

get /customers
Retrieve a list of customers for the authenticated workspace.



# Retrieve a list of domains

get /domains
Retrieve a list of domains associated with the authenticated workspace.



# Retrieve a list of events

get /events
Retrieve a paginated list of events for the authenticated workspace.

<Note>
  Events endpoints require a [Business plan](https://d.to/business) subscription
  or higher.
</Note>


# Retrieve a list of links

get /links
Retrieve a paginated list of links for the authenticated workspace.



# Retrieve a list of tags

get /tags
Retrieve a list of tags for the authenticated workspace.



# Retrieve a QR code

get /qr
Retrieve a QR code for a link.



# Retrieve a workspace

get /workspaces/{idOrSlug}
Retrieve a workspace for the authenticated user.



# Retrieve analytics

get /analytics
Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters.

<Note>
  Analytics endpoints require a [Pro plan](https://d.to/pro) subscription or
  higher.
</Note>


# Retrieve links count

get /links/count
Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links.



# Track a lead

post /track/lead
Track a lead for a short link.



# Track a sale

post /track/sale
Track a sale for a short link.



# Update a customer

patch /customers/{id}
Update a customer for the authenticated workspace.



# Update a domain

patch /domains/{slug}
Update a domain for the authenticated workspace.



# Update a link

patch /links/{linkId}
Update a link for the authenticated workspace. If there's no change, returns it as it is.



# Update a tag

patch /tags/{id}
Update a tag in the workspace.



# Update a workspace

patch /workspaces/{idOrSlug}
Update a workspace by ID or slug.



# Upsert a link

put /links/upsert
Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.



# Errors

Troubleshoot problems with this comprehensive breakdown of all error codes.

Dub.co API returns machine readable error codes, human readable error messages and a link to the docs for more information.

Here is how an error response looks like:

```json
{
  "error": {
    "code": "not_found",
    "message": "The requested resource was not found.",
    "doc_url": "https://dub.co/docs/api-reference/errors#not-found"
  }
}
```

## Error Codes

Here is a list of all error codes Dub.co API returns:

### `bad_request`

*   **Status:** 400
*   **Problem:** The request is malformed, either missing required fields, using wrong datatypes, or being syntactically incorrect.
*   **Solution:** Check the request and make sure it is properly formatted.

### `unauthorized`

*   **Status:** 401
*   **Problem:** The request has not been applied because it lacks valid authentication credentials for the target resource.
*   **Solution:** Make sure you are using the correct API key or access token.

### `forbidden`

*   **Status:** 403
*   **Problem:** The server understood the request, but is refusing to fulfill it because the client lacks proper permission.
*   **Solution:** Make sure you have the necessary permissions to access the resource.

### `not_found`

*   **Status:** 404
*   **Problem:** The server has not found anything matching the request URI.
*   **Solution:** Check the request and make sure the resource exists.

### `conflict`

*   **Status:** 409
*   **Problem:** Another resource already uses the same identifier. For example, workspace slug must be unique.
*   **Solution:** Change the identifier to a unique value.

### `invite_expired`

*   **Status:** 410
*   **Problem:** The invite has expired.
*   **Solution:** Generate a new invite.

### `unprocessable_entity`

*   **Status:** 422
*   **Problem:** The server was unable to process the request because it contains invalid data.
*   **Solution:** Check the request and make sure input data is valid.

### `rate_limit_exceeded`

*   **Status:** 429
*   **Problem:** The request has been rate limited.
*   **Solution:** Wait for a while and try again.

### `internal_server_error`

*   **Status:** 500
*   **Problem:** The server encountered an unexpected condition that prevented it from fulfilling the request.
*   **Solution:** Try again later. If the problem persists, contact support.


# Introduction

Fundamental concepts of Dub.co's API.

<Info>
  Dub.co's API is now generally available. [Read the
  announcement](https://dub.co/blog/announcing-dub-api).
</Info>

## Base URL

Dub.co's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported.

The Base URL for all API endpoints is:

```bash Terminal
https://api.dub.co
```

## Authentication

Authentication to Dub.co's API is performed via the Authorization header with a Bearer token. To authenticate, you need to include the Authorization header with the word `Bearer` followed by your API key in your requests like so:

```bash Terminal
Authorization: Bearer <Your-Token>
```

Learn more about [how to get your API key](/api-reference/tokens).

## Response Codes

The API returns standard HTTP response codes to indicate the success or failure of an API request. Here are a few examples:

| Code  | Description                                                                                    |
| ----- | ---------------------------------------------------------------------------------------------- |
| `200` | The request was successful.                                                                    |
| `400` | The request was invalid or cannot be served.                                                   |
| `401` | The request requires user authentication.                                                      |
| `403` | The server understood the request, but refuses to authorize it.                                |
| `404` | The requested resource could not be found.                                                     |
| `429` | Too many requests.                                                                             |
| `500` | The server encountered an unexpected condition which prevented it from fulfilling the request. |

## Caveats

*   Dub's API is currently in public beta and is subject to change. However, we will do our best to keep breaking changes to a minimum.


# Pagination

Learn how to paginate through resources in the API.

The pagination feature allows you to retrieve a subset of resources from the API. This is useful when you have a large number of resources and you want to retrieve them in smaller chunks.

These list API methods share a common set of parameters that allow you to control the number of items returned and the page number. For example, you can:

*   [retrieve a list of links](/api-reference/endpoint/retrieve-a-list-of-links)
*   [retrieve a list of domains](/api-reference/endpoint/retrieve-a-list-of-domains)
*   [retrieve a list of events](/api-reference/endpoint/retrieve-a-list-of-events)

## Parameters

<ParamField body="page" type="string" default="1">
  The page number to retrieve. By default, the first page is returned.
</ParamField>

<ParamField body="pageSize" type="string">
  The number of items to retrieve per page. The default value varies by
  endpoint. Maximum value is 100.
</ParamField>

## Example

The following example demonstrates how to retrieve the first page of 10 links:

<CodeGroup>
  ```bash cURL
  curl --request GET \
    --url https://api.dub.co/links?page=1&pageSize=10 \
    --header 'Authorization: Bearer <token>'
  ```

  ```typescript TypeScript
  const res = await dub.links.list({
    page: 1,
    pageSize: 10,
  });
  ```

  ```python Python
  res = s.links.list(request={
    "page": 1,
    "page_size": 10,
  })
  ```

  ```go Go
  request := operations.GetLinksRequest{
    Page: dubgo.Float64(1),
    PageSize: dubgo.Float64(10),
  }

  ctx := context.Background()
  res, err := s.Links.List(ctx, request)
  ```

  ```ruby Ruby
  req = ::OpenApiSDK::Operations::GetLinksRequest.new(
    page: 1,
    page_size: 10,
  )

  res = s.links.list(req)
  ```
</CodeGroup>


# Rate limits

Learn about Dub's API rate limits.

Dub's API rate limiting is in conformance with the [IETF standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers):

| Header Name             | Description                                                                     |
| ----------------------- | ------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | The maximum number of requests that the consumer is permitted to make per hour. |
| `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window.              |
| `X-RateLimit-Reset`     | The time at which the current rate limit window resets in UTC epoch seconds.    |
| `Retry-After`           | The number of seconds to wait before retrying the request again.                |

Dub's API is capped at **60 requests per minute** per key on the Free plan, with elevated limits for [Pro plan](https://dub.co/help/article/pro-plan) and above.

This is implemented to ensure a fair usage policy so that excessive use by a single user does not adversely affect the performance and usage of the API by others.

You'll receive a `429 Too Many Requests` response code if the rate limit is exceeded.

## Rate limits by plan

Depending on your Dub.co plan, you can expect the following rate limits:

| Plan                                                  | Rate limit                                                           |
| ----------------------------------------------------- | -------------------------------------------------------------------- |
| Free                                                  | 60 requests per minute                                               |
| [Pro](https://dub.co/help/article/pro-plan)           | 600 requests per minute                                              |
| [Business](https://dub.co/help/article/business-plan) | 3,000 requests per minute                                            |
| [Enterprise](https://dub.co/enterprise)               | Custom – [reach out to sales](https://dub.co/enterprise) for details |

## How to comply with rate limits

Here are some tips on how you can optimize your API setup to comply with our rate limits:

### 1. Bulk link creation

If you need to create a lot of links within a short period of time, try our [bulk link creation endpoint](/api-reference/endpoint/bulk-create-links) instead (create up to 100 links in one API call)

<CodeGroup>
  ```typescript TypeScript
  await dub.links.createMany([
    {
      url: "https://google.com",
    },
    {
      url: "https://twitter.com",
    },
    {
      url: "https://linkedin.com",
    },
  ]);
  ```

  ```python Python
  res = d.links.create_many(request=[
    {
      url: "https://google.com",
    },
    {
      url: "https://twitter.com",
    },
    {
      url: "https://linkedin.com",
    },
  ]);
  ```

  ```go Go
  var request []operations.RequestBody =
    []operations.RequestBody{
      operations.RequestBody{
          URL: "https://google.com",
      },
      operations.RequestBody{
        URL: "https://twitter.com",
      },
      operations.RequestBody{
        URL: "https://linkedin.com",
      },
    }
  ctx := context.Background()
  res, err := s.Links.CreateMany(ctx, request)
  ```

  ```ruby Ruby
  s.links.create_many(
    ::OpenApiSDK::Operations::BulkCreateLinksRequest.new(
      request_body: [
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://google.com",
        ),
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://twitter.com",
        ),
        ::OpenApiSDK::Operations::RequestBody.new(
          url: "https://linkedin.com",
        ),
      ]
    )
  )
  ```
</CodeGroup>

### 2. Fetch workspace-level analytics

If you're using our [Analytics API](/api-reference/endpoint/retrieve-analytics), instead of retrieving the clicks count for every single link, try making a single API call to get workspace-level click analytics instead.

<CodeGroup>
  ```typescript TypeScript
  await dub.analytics.retrieve({
    groupBy: "top_links",
    start: "4 hours ago", // we support natural language for start/end params
  });
  ```

  ```python Python
  res = d.analytics.retrieve(request={
      "groupBy": "top_links",
      "start": "4 hours ago", // we support natural language for start/end params
  })
  ```

  ```go Go
  func main() {
  	// Retrieve the timeseries analytics for the last 7 days for a link
  	request := operations.RetrieveAnalyticsRequest{
          GroupBy: "top_links",
          Start: "4 hours ago", // we support natural language for start/end params
  	}

  	ctx := context.Background()
  	res, err := d.Analytics.Retrieve(ctx, request)
  	if err != nil {
  		log.Fatal(err)
  	}
  	if res.OneOf != nil {
  		// handle response
  	}
  }
  ```

  ```ruby Ruby
  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    group_by: ::OpenApiSDK::Operations::GroupBy::TOP_LINKS,
    start: "4 hours ago", // we support natural language for start/end params
  )

  res = dub.analytics.retrieve(req)

  puts res.raw_response.body
  ```
</CodeGroup>

Alternatively, you can also periodically fetch a [list of links](/api-reference/endpoint/retrieve-a-list-of-links) sorted by last clicked, and [paginate](/api-reference/pagination) through the links until you get the one that has the same `clicks` value as the one you have stored – that way you know you've updated all the links that were clicked on since your last update.

<CodeGroup>
  ```typescript TypeScript
  await dub.links.list({
    sort: "lastClicked",
    page: 1,
  });
  ```

  ```python Python
  res = d.links.list(request={
      "sort": "lastClicked",
      "page": 1,
  })
  ```

  ```go Go
  func main() {
    // Retrieve the links sorted by last clicked
    request := operations.ListLinksRequest{
      Sort: "lastClicked",
      Page: 1,
    }

    ctx := context.Background()
    res, err := d.Links.List(ctx, request)
    if err != nil {
      log.Fatal(err)
    }
    if res.OneOf != nil {
      // handle response
    }
  }
  ```

  ```ruby Ruby
  req = ::OpenApiSDK::Operations::ListLinksRequest.new(
    sort: ::OpenApiSDK::Operations::Sort::LAST_CLICKED,
    page: 1,
  )

  res = dub.links.list(req)

  puts res.raw_response.body
  ```
</CodeGroup>


# API Keys

Learn how API keys work on Dub

API keys on Dub allow you to access your workspace programmatically. This is useful for integrating Dub into your application or with other tools and services.

Each API key is tied to a specific workspace – meaning you can use it to access that workspace's resources without having to worry about "leaking" access to other workspaces.

## Secret and publishable keys

There are two types of API keys on Dub – **Secret keys** and **Publishable keys**.

Here are the key differences between them:

| Type            | Format                     | When to use                                                                                                                                                                                                                                                                                                   |
| :-------------- | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Secret**      | `dub_xxxxxxxx`             | Use this key to authenticate requests on your server. By default, you can use this key to perform any API request without restriction, so it must be stored securely in your app's server-side code (such as in an environment variable or credential management system). Don’t expose this key on a website. |
| **Publishable** | `dub_publishable_xxxxxxxx` | Use this key to authenticate requests on the client side for operations like [client-side click tracking](/conversions/clicks/introduction#client-side-click-tracking).                                                                                                                                       |

## Create a secret API key

You can create an API key by following these steps:

<Steps>
  <Step title="Go to your workspace">
    Go to **Settings** > [**API Keys**](https://app.dub.co/settings/tokens) in your workspace.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/workspace-api-keys.png" alt="Workspace API keys on Dub" width="1468" height="249" />
    </Frame>
  </Step>

  <Step title="Create an API Key">
    Click on the "Create" button and select permissions you want to grant to
    the API key.

    Select between "You" and "Machine" to associate the API key with your account or a [machine user](#machine-users).

    *   **You**: This API key is tied to your user and can make requests against the selected workspace.
    *   **Machine**: A machine user will be added to your workspace, and an API key associated with that machine user will be created.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/add-new-api-key.png" alt="Add new API key on Dub" width="1468" height="249" />
    </Frame>

    When making API calls, if your API Key have insufficient permissions, the error should tell you which permissions you need.

    Make sure to copy your API key and store it in a safe place. You won't be able to see it again.
  </Step>

  <Step title="Use your API Key">
    Now that you have your API key, you can use it to access your workspace's resources programmatically via SDKs or within any API request as a bearer token.

    ```
    Authorization: Bearer dub_xxxx
    ```
  </Step>
</Steps>

<Tip>
  We recommend creating API keys with the least privilege necessary to perform
  the required tasks. This helps to reduce the risk of unauthorized access to
  your workspace.
</Tip>

## Create a publishable API key

To create a publishable key, you can go to **Settings** > [**API Keys**](https://app.dub.co/settings/tokens) and click on **Create publishable key**. The key will be in the format `dub_publishable_xxxxxxxx`.

<Note>
  Publishable keys are currently in beta. If you'd like access, [reach out to us
  via
  email](mailto:support@dub.co?subject=I%20want%20access%20to%20publishable%20keys).
</Note>

## API key permissions

When creating a secret key, you can select the permissions it has, which will give the key access to certain (or all) resources on Dub. Here are the different permission options:

| Permission          | Description                                                                                                                                                                                                                                                                                  |
| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **All permissions** | This API key will have full access to all resources.                                                                                                                                                                                                                                         |
| **Read only**       | This API key will have read-only access to all resources.                                                                                                                                                                                                                                    |
| **Restricted**      | This API key will have restricted access to some resources: <ul><li>[Links](/data-model#links)</li><li>[Analytics](/api-reference/endpoint/retrieve-analytics)</li><li>[Workspaces](/data-model#workspace)</li><li>[Domains](/data-model#domains)</li><li>[Tags](/data-model#tags)</li></ul> |

Depending on your use case, you might want to use one of these 3 options to limit the scope of the API key and improve security.

<Tip>
  You can only set permissions on Secret keys. Publishable keys only have access
  to certain endpoints, and cannot be restricted.
</Tip>

## Machine users

On Dub, you can create API keys that are associated with a "Machine user". This is particularly helpful when you don't want to associate the API key with a particular user in your workspace, to avoid security risks in involving turnover or changes in project ownership.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/api-machine-users.png" alt="Creating an API key associated with a machine user on Dub" width="1528" height="974" />
</Frame>

These machine users will show up on your workspace's **People** tab, but will not contribute to your workspace's user count.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/machine-user.png" alt="Machine user on Dub" width="1468" height="249" />
</Frame>

<Tip>
  If you delete an API key associated with a machine user, the machine user will
  be deleted. Vice versa, if you delete a machine user, their corresponding API
  key will be deleted as well.
</Tip>

<Warning>
  Machine users are available exclusively to workspace owners. If you are not
  the owner of the workspace, this option will be disabled when creating an API
  key.
</Warning>


# Introduction

Click-tracking with Dub Conversions

Clicks are the starting point for all conversion events on Dub.

A click event can be triggered by:

*   A user clicking on a [Dub link](https://dub.co/help/article/how-to-create-link)
*   A user scanning a [Dub QR code](https://dub.co/features/qr-codes)

In [Dub Analytics](https://dub.co/help/article/dub-analytics), you can [filter between QR code scans and link clicks](https://dub.co/help/article/filter-analytics-by-trigger), giving you a full picture of how your various marketing channels are performing.

Clicks are tracked automatically for all Dub short links upon a redirect event – and you also have the flexibility to track clicks via query parameters using our [Client-side SDK](/sdks/client-side/introduction).

## Server-side click-tracking

Server-side click-tracking is enabled by default for all Dub links and come with the following attributes:

| Attribute    | Type    | Description                                                                                      |
| :----------- | :------ | :----------------------------------------------------------------------------------------------- |
| `timestamp`  | string  | The timestamp of the click event                                                                 |
| `id`         | string  | The unique ID of the click event                                                                 |
| `url`        | string  | The destination URL that the link resolved to – this can vary if geo/device-targeting is enabled |
| `continent`  | string  | The continent of the user who clicked the link                                                   |
| `country`    | string  | The country of the user who clicked the link                                                     |
| `city`       | string  | The city of the user who clicked the link                                                        |
| `device`     | string  | The device of the user who clicked the link                                                      |
| `browser`    | string  | The browser of the user who clicked the link                                                     |
| `os`         | string  | The operating system of the user who clicked the link                                            |
| `referer`    | string  | The referrer of the user who clicked the link                                                    |
| `refererUrl` | string  | The full referrer URL of the user who clicked the link                                           |
| `qr`         | boolean | Whether the click event was triggered by a QR code scan                                          |
| `ip`         | string  | The IP address of the user who clicked the link (non-EU users only)                              |

These events happen on the server-side and cannot be blocked by browser extensions or ad-blockers, which improves the accuracy of your analytics data.

## Client-side click-tracking

Alternatively, you can track clicks on the client-side using query parameters (e.g. `?via=john`, `?ref=jane`). This gives you the flexibility to track clicks directly on your website or app, without needing to rely on link redirects.

A few use cases include:

*   You are migrating from an existing affiliate management platform that uses query parameters to track conversions and don't want ask your affiliates to update all their links
*   You need to use an unfurled URL for the platform you're sharing the link on (no short links allowed)
*   You have dynamically generated referral pages (e.g. [Tesla](https://www.tesla.com/referral/peeroke520149)) and want to track clicks using a `track()` function inside your application code.

With our [Client-side SDK](/sdks/client-side/introduction), you can do exactly that.

Here's how you can enable client-side click-tracking for your links:

### Step 1: Add a unique `identifier` to your links

First, you need to add a unique `identifier` to your links. This `identifier` is unique across your workspace and will be used to identify the corresponding link when passed as the query parameter (e.g. `?via=jane`).

There are two ways to add an `identifier` to your links:

<AccordionGroup>
  <Accordion title="Using the Dub Link Builder">
    1.  Inside the Dub Link Builder, click on the **More options** button and select **Advanced Settings**. You can also use the `A` keyboard shortcut.

    <Frame>
      <img className="rounded-lg border border-gray-100" src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/link-builder-advanced-settings.png" alt="Dub Link Builder Advanced Settings" />
    </Frame>

    2.  Under the **Identifier** section, enter a unique value that corresponds to the query parameter you want to use for the click-tracking.

    <Frame>
      <img className="rounded-lg border border-gray-100" src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/link-builder-identifier.png" alt="Dub Link Builder Identifier" />
    </Frame>

    3.  Click on the **Save** button to save the changes.
  </Accordion>

  <Accordion title="Using the Dub API">
    If you're using the [Dub API](/api-reference/introduction) to programmatically create your links, you can use the `identifier` field to specify the unique identifier for the link.

    Here's an example of how to create a link with a custom `identifier`:

    <CodeGroup>
      ```typescript TypeScript
      await dub.links.create({
        url: "https://google.com",
        identifier: "my-custom-identifier",
      });
      ```

      ```python Python
      res = d.links.create(request={
          url: "https://google.com",
          identifier: "my-custom-identifier",
      });
      ```

      ```go Go
      func main() {
          s := dubgo.New(
              dubgo.WithSecurity("DUB_API_KEY"),
          )

          ctx := context.Background()
          res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{
              URL: "https://google.com",
              Identifier: "my-custom-identifier",
          })
          if err != nil {
              log.Fatal(err)
          }
          if res != nil {
              // handle response
          }
      }
      ```

      ```ruby Ruby
      req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
        url: "https://google.com",
        identifier: "my-custom-identifier",
      )

      res = s.links.create(req)
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

### Step 2: Create a Publishable Key

Next, you'll need to create a Publishable Key that you'll use to initialize the [Client-side SDK](/sdks/client-side/introduction). The key has the following format:

```
dub_publishable_xxxxxxxxxxxxxxxxxxxxxxxx
```

You can create a Publishable Key in the [**Tokens** settings page](https://app.dub.co/settings/tokens) in your Dub workspace.

### Step 3: Install the Client-side SDK

Lastly, you'll need to install the [Client-side SDK](/sdks/client-side/introduction) and initialize it with the Publishable Key you created in the previous step.

<CodeGroup>
  ```typescript React/Next.js
  // install the package (e.g. npm install @dub/analytics)
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          apiKey={process.env.NEXT_PUBLIC_DUB_PUBLISHABLE_KEY}
          queryParam="referral" // default is "via"
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```

  ```javascript Other Frameworks
  // include this script tag in your HTML Head tag
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    apiKey="YOUR_DUB_PUBLISHABLE_KEY"
    defer
    queryParam="referral" // default is "via"
  />
  ```
</CodeGroup>

Here's the full list of parameters you can pass to the `<Analytics />` component:

<Accordion title="Parameters">
  <ParamField body="apiHost" type="url" default="https://api.dub.co">
    The base URL for the Dub API. This is useful for setting up reverse proxies to
    avoid adblockers.
  </ParamField>

  <ParamField body="apiKey" type="string">
    The publishable API key to use for client-side click tracking. Get your
    publishable API key from your [Dub workspace's token settings
    page](https://app.dub.co/settings/tokens).
  </ParamField>

  <ParamField body="attributionModel" type="first-click | last-click" default="last-click">
    The attribution model to use for the analytics event. The following
    attribution models are available:

    *   `first-click`: The first click model
        gives all the credit to the first touchpoint in the customer journey.
    *   `last-click`: The last click model gives all the credit to the last
        touchpoint in the customer journey.
  </ParamField>

  <ParamField body="cookieOptions" type="CookieOption Object">
    <Expandable title="properties">
      <ParamField body="domain" type="string">
        Specifies the value for the `Domain` Set-Cookie attribute. This is useful
        for cross-domain tracking. Example: `.example.com`
      </ParamField>

      <ParamField body="expires" type="integer" default="90">
        Specifies the `Date` object to be the value for the `Expires` Set-Cookie
        attribute. Example: `new Date('2024-12-31')`
      </ParamField>

      <ParamField body="expiresInDays" type="integer" default="90">
        Specifies the number (in days) to be the value for the `Expires`
        Set-Cookie attribute. Example: `90`
      </ParamField>

      <ParamField body="path" type="string" default="/">
        Specifies the value for the `Path` Set-Cookie attribute. By default, the
        path is considered the "default path". Example: `/`
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField body="queryParam" type="string" default="via">
    The query parameter to listen to for client-side click-tracking (e.g.
    `?via=abc123`).
  </ParamField>

  <ParamField body="scriptProps" type="HTMLScriptElement Object">
    Custom properties to pass to the script tag. Refer to
    [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) for
    all available options.
  </ParamField>
</Accordion>

### Step 4 (Optional, but recommended): Set up a reverse proxy

To avoid ad-blockers from blocking your click-tracking requests, we recommend setting up a reverse proxy.

Depending on which backend framework you're using, there are a few different ways to do this:

<CodeGroup>
  ```javascript Next.js
  // next.config.js
  module.exports = {
    async rewrites() {
      return [
        {
          source: "/_proxy/dub/track/click",
          destination: "https://api.dub.co/track/click",
        },
      ];
    },
  };
  ```

  ```json Vercel
  // vercel.json
  {
    "rewrites": [
      {
        "source": "/_proxy/dub/track/click",
        "destination": "https://api.dub.co/track/click"
      }
    ]
  }
  ```
</CodeGroup>

Once you've set up your reverse proxy, don't forget to update the `apiHost` parameter in the `<Analytics />` component to point to your proxy URL.

<CodeGroup>
  ```typescript React/Next.js
  import { Analytics as DubAnalytics } from "@dub/analytics/react";

  export default function App() {
    return (
      <Layout>
        <DubAnalytics
          apiHost="/_proxy/dub"
          apiKey={process.env.NEXT_PUBLIC_DUB_PUBLISHABLE_KEY}
          queryParam="referral" // default is "via"
        />
        {/* Your app code here */}
      </Layout>
    );
  }
  ```

  ```javascript Other Frameworks
  // include this script tag in your HTML Head tag
  <script
    src="https://www.dubcdn.com/analytics/script.js"
    apiHost="/_proxy/dub"
    apiKey="YOUR_DUB_PUBLISHABLE_KEY"
    defer
    queryParam="referral" // default is "via"
  />
  ```
</CodeGroup>

### Step 5: Verify your setup

To verify that your click-tracking is working, run your website locally with the specific query parameter (e.g. `?via=jane`) and check if the following is true:

1.  The click tracked correctly in the [**Analytics**](https://app.dub.co/analytics) tab of your Dub workspace.
2.  There is a successful `/track/click` request in your browser's **Network** tab (and no errors in the **Console** tab).
3.  The `dub_id` cookie is being set in your browser upon a successful click-tracking request.


# Auth0

Learn how to track a lead conversion event with Auth0 and the Dub SDK

When it comes to conversion tracking, a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

*   Signing up for an account
*   Adding a product to cart
*   Joining a mailing list

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-lead-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Auth0 for user authentication.

## Prerequisites

Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links:

1.  [Install the @dub/analytics client-side SDK](/conversions/quickstart#step-1-install-the-dub-analytics-client-side-sdk)
2.  [Install the Dub server-side SDK](/conversions/quickstart#step-2-install-the-dub-server-side-sdk)
3.  [Enable conversion tracking for your links](/conversions/quickstart#step-4-enable-conversion-tracking-for-your-links)

## Configure Auth0

Next, configure Auth0 to track a lead conversion event.

Here's how it works in a nutshell:

1.  In the sign in `afterCallback` function, check if the user is a new sign up.
2.  If the user is a new sign up, check if the `dub_id` cookie is present.
3.  If the `dub_id` cookie is present, send a lead event to Dub using `dub.track.lead`
4.  Delete the `dub_id` cookie.

```typescript app/api/auth/[auth0]/route.js
import { handleAuth, handleCallback, type Session } from "@auth0/nextjs-auth0";
import { cookies } from "next/headers";
import { dub } from "@/lib/dub";

const afterCallback = async (req: Request, session: Session) => {
  const userExists = await getUser(session.user.email);

  if (!userExists) {
    createUser(session.user);
    // check if dub_id cookie is present
    const clickId = cookies().get("dub_id")?.value;
    if (clickId) {
      // send lead event to Dub
      await dub.track.lead({
        clickId,
        eventName: "Sign Up",
        customerId: session.user.id,
        customerName: session.user.name,
        customerEmail: session.user.email,
        customerAvatar: session.user.image,
      });
      // delete the dub_id cookie
      cookies().set("dub_id", "", {
        expires: new Date(0),
      });
    }
    return session;
  }
};

export default handleAuth({
  callback: handleCallback({ afterCallback }),
});
```

Here's the full list of attributes you can pass when sending a lead event:

| Property         | Description                                                                                                              | Required |
| :--------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
| `clickId`        | The unique `dub_id` parameter that the lead conversion event is attributed to.                                           | Yes      |
| `eventName`      | The name of the event. Example: "Sign up".                                                                               | Yes      |
| `customerId`     | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | Yes      |
| `customerEmail`  | The email address of the customer. If not passed, a random email address will be generated.                              | No       |
| `customerName`   | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                       | No       |
| `customerAvatar` | The avatar URL of the customer. If not passed, a random avatar URL will be generated.                                    | No       |

## View your conversions

Once you've set this up, all your tracked lead events will show up on your [Analytics dashboard](https://dub.co/help/article/dub-analytics). We provide 3 different views to help you understand your conversions:

*   **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number of leads over time.
*   **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).
*   **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single lead event that occurs across all your links in your workspace.

<Frame>
  <img src="https://assets.dub.co/help/events-stream-dashboard-leads.png" alt="The Events Stream dashboard on Dub showing a real-time stream of lead events" />
</Frame>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Clerk

Learn how to track a lead conversion event with Clerk and the Dub SDK

When it comes to conversion tracking, a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

*   Signing up for an account
*   Adding a product to cart
*   Joining a mailing list

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-lead-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Clerk for user authentication.

## Prerequisites

Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links:

1.  [Install the @dub/analytics client-side SDK](/conversions/quickstart#step-1-install-the-dub-analytics-client-side-sdk)
2.  [Install the Dub server-side SDK](/conversions/quickstart#step-2-install-the-dub-server-side-sdk)
3.  [Enable conversion tracking for your links](/conversions/quickstart#step-4-enable-conversion-tracking-for-your-links)

## Configure Clerk

<Card title="Coming soon" icon="hourglass" iconType="duotone" color="#ca8b04">
  We're working on this guide. Check back soon!
</Card>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# NextAuth.js

Learn how to track a lead conversion event with NextAuth.js and the Dub SDK

When it comes to conversion tracking, a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

*   Signing up for an account
*   Adding a product to cart
*   Joining a mailing list

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-lead-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses NextAuth.js for user authentication.

## Prerequisites

Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links:

1.  [Install the @dub/analytics client-side SDK](/conversions/quickstart#step-1-install-the-dub-analytics-client-side-sdk)
2.  [Install the Dub server-side SDK](/conversions/quickstart#step-2-install-the-dub-server-side-sdk)
3.  [Enable conversion tracking for your links](/conversions/quickstart#step-4-enable-conversion-tracking-for-your-links)

## Configure NextAuth.js Options

Then, set up your NextAuth.js configuration options to track a lead conversion event using the `dub` TypeScript SDK.

Here's how it works in a nutshell:

1.  Use NextAuth's [`signIn` event](https://next-auth.js.org/configuration/events#signin) to detect when there's a new sign up.
2.  If the user is a new sign up, check if the `dub_id` cookie is present.
3.  If the `dub_id` cookie is present, send a lead event to Dub using `dub.track.lead`
4.  Delete the `dub_id` cookie.

Under the hood, Dub records the user as a customer and associates them with the click event that they came from. The user's unique ID is now the source of truth for all future events – hence why we don't need the `dub_id` cookie anymore.

<CodeGroup>
  ```typescript App Router
  // app/api/auth/[...nextauth]/options.ts
  import type { NextAuthOptions } from "next-auth";
  import { cookies } from "next/headers";
  import { dub } from "@/lib/dub";

  export const authOptions: NextAuthOptions = {
    ...otherAuthOptions, // your other NextAuth options
    events: {
      async signIn(message) {
        // if it's a new sign up
        if (message.isNewUser) {
          // check if dub_id cookie is present
          const dub_id = cookies().get("dub_id")?.value;
          if (dub_id) {
            // send lead event to Dub
            await dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerId: user.id,
              customerName: user.name,
              customerEmail: user.email,
              customerAvatar: user.image,
            });
            // delete the dub_id cookie
            cookies().set("dub_id", "", {
              expires: new Date(0),
            });
          }
        }
      },
    },
  };
  ```

  ```typescript Pages Router
  // pages/api/auth/[...nextauth]/options.ts
  import type { NextApiRequest } from "next";
  import type { NextAuthOptions } from "next-auth";
  import { dub } from "@/lib/dub";

  export const getOptions = (req: NextApiRequest): NextAuthOptions => ({
    ...otherAuthOptions, // your other NextAuth options
    events: {
      async signIn(message) {
        // if it's a new sign up
        if (message.isNewUser) {
          // check if dub_id cookie is present
          const { dub_id } = req.cookies;
          if (dub_id) {
            // send lead event to Dub
            await dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerId: user.id,
              customerName: user.name,
              customerEmail: user.email,
              customerAvatar: user.image,
            });
          }
        }
      },
    },
  });
  ```
</CodeGroup>

Here's the full list of attributes you can pass when sending a lead event:

| Property         | Description                                                                                                              | Required |
| :--------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
| `clickId`        | The unique `dub_id` parameter that the lead conversion event is attributed to.                                           | Yes      |
| `eventName`      | The name of the event. Example: "Sign up".                                                                               | Yes      |
| `customerId`     | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | Yes      |
| `customerEmail`  | The email address of the customer. If not passed, a random email address will be generated.                              | No       |
| `customerName`   | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                       | No       |
| `customerAvatar` | The avatar URL of the customer. If not passed, a random avatar URL will be generated.                                    | No       |

## Create a NextAuth.js Route Handler

Finally, import the `authOptions` variable you created earlier and use `NextAuth` to create a handler for your NextAuth.js routes.

<CodeGroup>
  ```typescript App Router
  // app/api/auth/[...nextauth]/index.ts
  import { authOptions } from "./options";
  import NextAuth from "next-auth";

  const handler = NextAuth(authOptions);

  export { handler as GET, handler as POST };
  ```

  ```typescript Pages Router
  // pages/api/auth/[...nextauth]/index.ts
  import type { NextApiRequest, NextApiResponse } from "next";
  import NextAuth from "next-auth";
  import { getOptions } from "./options";

  const handler = (req: NextApiRequest, res: NextApiResponse) =>
    NextAuth(req, res, getOptions(req));

  export default handler;
  ```
</CodeGroup>

## View your conversions

Once you've set this up, all your tracked lead events will show up on your [Analytics dashboard](https://dub.co/help/article/dub-analytics). We provide 3 different views to help you understand your conversions:

*   **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number of leads over time.
*   **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).
*   **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single lead event that occurs across all your links in your workspace.

<Frame>
  <img src="https://assets.dub.co/help/events-stream-dashboard-leads.png" alt="The Events Stream dashboard on Dub showing a real-time stream of lead events" />
</Frame>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Supabase Auth

Learn how to track a lead conversion event with Supabase Auth and the Dub SDK

When it comes to conversion tracking, a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from:

*   Signing up for an account
*   Adding a product to cart
*   Joining a mailing list

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-lead-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Supabase Auth for user authentication.

## Prerequisites

Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links:

1.  [Install the @dub/analytics client-side SDK](/conversions/quickstart#step-1-install-the-dub-analytics-client-side-sdk)
2.  [Install the Dub server-side SDK](/conversions/quickstart#step-2-install-the-dub-server-side-sdk)
3.  [Enable conversion tracking for your links](/conversions/quickstart#step-4-enable-conversion-tracking-for-your-links)

## Configure Supabase Auth

Next, configure Supabase Auth to track a lead conversion event in the auth callback function.

Here's how it works in a nutshell:

1.  In the `/api/auth/callback` route, check if:
    *   the `dub_id` cookie is present.
    *   the user is a new sign up (created in the last 10 minutes).
2.  If the `dub_id` cookie is present and the user is a new sign up, send a lead event to Dub using `dub.track.lead`
3.  Delete the `dub_id` cookie.

<CodeGroup>
  ```typescript Next.js App Router
  // app/api/auth/callback/route.ts
  import { cookies } from "next/headers";
  import { NextResponse } from "next/server";
  import { createClient } from "@/lib/supabase/server";
  import { waitUntil } from "@vercel/functions";
  import { dub } from "@/lib/dub";

  export async function GET(request: Request) {
    const { searchParams, origin } = new URL(request.url);
    const code = searchParams.get("code");
    // if "next" is in param, use it as the redirect URL
    const next = searchParams.get("next") ?? "/";

    if (code) {
      const supabase = createClient(cookies());
      const { data, error } = await supabase.auth.exchangeCodeForSession(code);
      if (!error) {
        const { user } = data;
        const dub_id = cookies().get("dub_id")?.value;
        // if the user is created in the last 10 minutes, consider them new
        const isNewUser =
          new Date(user.created_at) > new Date(Date.now() - 10 * 60 * 1000);
        // if the user is new and has a dub_id cookie, track the lead
        if (dub_id && isNewUser) {
          waitUntil(
            dub.track.lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerId: user.id,
              customerName: user.user_metadata.name,
              customerEmail: user.email,
              customerAvatar: user.user_metadata.avatar_url,
            })
          );
          // delete the clickId cookie
          cookies().delete("dub_id");
        }
        return NextResponse.redirect(`${origin}${next}`);
      }
    }

    // return the user to an error page with instructions
    return NextResponse.redirect(`${origin}/auth/auth-code-error`);
  }
  ```

  ```typescript Next.js Pages Router
  // pages/api/auth/callback.ts
  import { NextApiRequest, NextApiResponse } from "next";
  import { createClient } from "@supabase/supabase-js";
  import { dub } from "@/lib/dub";

  export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse
  ) {
    const { code, next = "/" } = req.query;
    const origin = `${req.headers["x-forwarded-proto"]}://${req.headers.host}`;

    if (typeof code === "string") {
      const supabase = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.SUPABASE_SERVICE_ROLE_KEY!
      );

      const { data, error } = await supabase.auth.exchangeCodeForSession(code);

      if (!error) {
        const { user } = data;
        const { dub_id } = req.cookies;

        // if the user is created in the last 10 minutes, consider them new
        const isNewUser =
          new Date(user.created_at) > new Date(Date.now() - 10 * 60 * 1000);

        // if the user is new and has a dub_id cookie, track the lead
        if (dub_id && isNewUser) {
          dub.track
            .lead({
              clickId: dub_id,
              eventName: "Sign Up",
              customerId: user.id,
              customerName: user.user_metadata.name,
              customerEmail: user.email,
              customerAvatar: user.user_metadata.avatar_url,
            })
            .catch(console.error); // Handle any errors in tracking

          // delete the clickId cookie
          res.setHeader(
            "Set-Cookie",
            `dub_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT`
          );
        }

        return res.redirect(`${origin}${next}`);
      }
    }

    // return the user to an error page with instructions
    return res.redirect(`${origin}/auth/auth-code-error`);
  }
  ```
</CodeGroup>

Here's the full list of attributes you can pass when sending a lead event:

| Property         | Description                                                                                                              | Required |
| :--------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
| `clickId`        | The unique `dub_id` parameter that the lead conversion event is attributed to.                                           | Yes      |
| `eventName`      | The name of the event. Example: "Sign up".                                                                               | Yes      |
| `customerId`     | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | Yes      |
| `customerEmail`  | The email address of the customer. If not passed, a random email address will be generated.                              | No       |
| `customerName`   | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                       | No       |
| `customerAvatar` | The avatar URL of the customer. If not passed, a random avatar URL will be generated.                                    | No       |

<Card title="Supabase Auth + Next.js App Router Example" icon="github" href="https://github.com/steven-tey/extrapolate/blob/main/app/api/auth/callback/route.ts">
  Check out a real-world example of this in action – Extrapolate uses Supabase
  Auth and Next.js App Router to track new user sign-ups.
</Card>

## View your conversions

Once you've set this up, all your tracked lead events will show up on your [Analytics dashboard](https://dub.co/help/article/dub-analytics). We provide 3 different views to help you understand your conversions:

*   **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number of leads over time.
*   **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).
*   **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single lead event that occurs across all your links in your workspace.

<Frame>
  <img src="https://assets.dub.co/help/events-stream-dashboard-leads.png" alt="The Events Stream dashboard on Dub showing a real-time stream of lead events" />
</Frame>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Quickstart

Learn how to get started with Dub Conversions

<Note>
  Dub Conversions is currently in closed beta. If you'd like to join the beta,
  [reach out to us via
  email](mailto:support@dub.co?subject=I%20want%20to%20join%20the%20Dub%20Conversions%20beta).
</Note>

[Dub Conversions](https://dub.co/help/article/dub-conversions) is a powerful tool that lets you to turn any [short link you create on Dub](https://dub.co/help/article/how-to-create-link) into a full attribution engine. This allows you to understand how well your links are translating to actual users and revenue dollars.

Learn more about [how Dub Conversions works](https://dub.co/help/article/dub-conversions).

In this guide, we'll walk you through the steps to get started with Dub Conversions.

## Step 1: Install the Dub analytics client-side SDK

First step is to install the [Dub analytics client-side SDK](/sdks/client-side/introduction).

This will handle the detection of the `dub_id` query parameter and storing it as a first-party cookie, which will be used to attribute subsequent conversion events to the original link.

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-click-event.png" alt="A diagram showing how click events are tracked in the conversion funnel" />
</Frame>

Depending on which framework or platform you're using, you can install the script in different ways:

<CardGroup cols={1}>
  <Card title="React" icon="react" href="/sdks/client-side/installation-guides/react" horizontal>
    Add Dub Analytics to your React or Next.js app.
  </Card>

  <Card title="Website Builder" icon="globe" href="/sdks/client-side/introduction" horizontal>
    Add Dub Analytics to websites built using Webflow, WordPress, or Framer.
  </Card>
</CardGroup>

## Step 2: Install the Dub server-side SDK

Dub Conversions uses [server-side event tracking](https://dub.co/help/article/dub-conversions#how-are-conversions-tracked) to track conversions. To enable this, you'll need to install the Dub server-side SDKs.

Depending on which framework you're using, you can use our [native SDKs](/sdks/overview):

<CardGroup>
  <Card title="TypeScript" icon="npm" href="/sdks/typescript/overview" horizontal>
    TypeScript library for the Dub API
  </Card>

  <Card title="Go" icon="golang" href="https://dub.co/solutions/go" iconType="brands" horizontal>
    Go library for the Dub API
  </Card>

  <Card title="Python" icon="python" href="https://dub.co/solutions/python" horizontal>
    Python library for the Dub API
  </Card>

  <Card title="Ruby" icon="gem" href="https://dub.co/solutions/ruby" horizontal>
    Ruby library for the Dub API
  </Card>
</CardGroup>

If you're using a framework that isn't listed, you can use the Dub REST API to track events on the server-side:

*   [Tracking a lead event](/api-reference/endpoint/track-lead)
*   [Tracking a sale event](/api-reference/endpoint/track-sale)

## Step 3: Start sending events

Now that you've installed the SDKs, you can start sending events to Dub. Since click events are tracked automatically, you'll only need to send events for the following cases:

*   [Lead events](#step-3a-sending-lead-events)
*   [Sale events](#step-3b-sending-sale-events)

### Step 3a: Sending Lead Events

The first event you'll want to send is a `lead` event. This happens when a user performs an action that indicates interest in your product or service. This could be anything from:

*   Signing up for an account
*   Adding a product to cart
*   Joining a mailing list

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-lead-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

Our most common lead event is `Sign Up`, which happens when a user signs up for an account. Depending on which authentication framework you're using, here are a few examples of how to send `Sign Up` lead events:

<CardGroup cols={1}>
  <Card title="NextAuth.js" icon="shield-halved" iconType="solid" href="/conversions/leads/next-auth" horizontal>
    Tracking a lead conversion event with NextAuth.js and the Dub SDK
  </Card>

  <Card title="Clerk" icon="shield-halved" iconType="solid" href="/conversions/leads/clerk" horizontal>
    Tracking a lead conversion event with Clerk and the Dub SDK
  </Card>

  <Card title="Supabase Auth" icon="shield-halved" iconType="solid" href="/conversions/leads/supabase-auth" horizontal>
    Tracking a lead conversion event with Supabase Auth and the Dub SDK
  </Card>

  <Card title="Auth0" icon="shield-halved" iconType="solid" href="/conversions/leads/auth0" horizontal>
    Tracking a lead conversion event with Auth0 and the Dub SDK
  </Card>
</CardGroup>

When sending a lead conversion event, you can include the following properties:

| Property         | Description                                                                                                              | Required |
| :--------------- | :----------------------------------------------------------------------------------------------------------------------- | :------- |
| `clickId`        | The unique `dub_id` parameter that the lead conversion event is attributed to.                                           | Yes      |
| `eventName`      | The name of the event. Example: "Sign up".                                                                               | Yes      |
| `customerId`     | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | Yes      |
| `customerEmail`  | The email address of the customer. If not passed, a random email address will be generated.                              | No       |
| `customerName`   | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou").                       | No       |
| `customerAvatar` | The avatar URL of the customer. If not passed, a random avatar URL will be generated.                                    | No       |

The lead event will serve as the source of truth for the customer's identity and which link they came from. This means that all subsequent actions performed by the customer (e.g. upgrading their plan, purchasing a product) will automatically be attributed to the original link.

### Step 3b: Sending Sale Events

The second event you'll want to send is a `sale` event. This happens when a user purchases your product or service. This could be anything from:

*   Subscribing to a paid plan
*   Usage expansion (upgrading from one plan to another)
*   Purchasing a product

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-sale-event.png" alt="A diagram showing how sale events are tracked in the conversion funnel" />
</Frame>

Depending on which payment processor you're using, we offer native integrations for the following:

<CardGroup cols={1}>
  <Card title="Stripe" icon="stripe" href="/conversions/sales/stripe" horizontal>
    Tracking a sale conversion event with Stripe and the Dub SDK
  </Card>

  <Card title="Shopify" icon="shopify" href="/conversions/sales/shopify" horizontal>
    Tracking a sale conversion event with Shopify and the Dub SDK
  </Card>
</CardGroup>

Alternatively, you can also send sale events manually using [our SDKs](/sdks/overview) or the [`POST /track/sale` API endpoint](/api-reference/endpoint/track-sale).

Here are the properties you can include when sending a sale event:

| Property           | Description                                                 | Required |
| :----------------- | :---------------------------------------------------------- | :------- |
| `customerId`       | The unique ID of the customer in your system.               | Yes      |
| `amount`           | The amount of the sale in cents.                            | Yes      |
| `paymentProcessor` | The name of the payment processor that processed the sale.  | Yes      |
| `eventName`        | The name of the event. Defaults to "Purchase".              | No       |
| `invoiceId`        | The unique ID of the invoice in your system.                | No       |
| `currency`         | The currency of the sale. Defaults to "usd".                | No       |
| `metadata`         | An object containing additional information about the sale. | No       |

## Step 4: Enable conversion tracking for your links

Lastly, you'll need enable conversion tracking for your Dub links for to be able to start tracking conversions. You can do this by:

1.  Go to your [Dub dashboard](https://app.dub.co)
2.  Click on the short link you want to track conversions for, which will open up the [Link Builder](https://dub.co/help/article/dub-link-builder).

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/enable-conversion-tracking.png" alt="Enabling conversion tracking for a link" />
</Frame>

3.  Enable **Conversion Tracking** and click **Save changes**

Alternatively, you can also enable conversion tracking programmatically via the [Dub API](/api-reference). All you need to do is pass `trackConversion: true` when creating or updating a link:

<CodeGroup>
  ```typescript TypeScript
  const link = await dub.links.create({
    url: "https://dub.co",
    trackConversion: true,
  });
  ```

  ```python Python
  link = d.links.create(url="https://dub.co", track_conversion=True)
  ```

  ```go Go
  link, err := d.Links.Create(ctx, &dub.CreateLinkRequest{
      URL: "https://dub.co",
      TrackConversion: true,
  })
  ```

  ```ruby Ruby
  s.links.create_many(
    ::OpenApiSDK::Operations::CreateLinkRequest.new(
      url: "https://dub.co",
      track_conversion: true,
    )
  )
  ```
</CodeGroup>

## Step 5: View your conversions

Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://dub.co/help/article/dub-analytics). We provide 3 different views to help you understand your conversions:

*   **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales.

<Frame>
  <img src="https://assets.dub.co/help/conversions-timeseries-analytics.png" alt="Time-series line chart" />
</Frame>

*   **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img src="https://assets.dub.co/help/conversion-funnel-chart.png" alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

*   **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img src="https://assets.dub.co/help/conversions-events-stream-dashboard.png" alt="The Events Stream dashboard on Dub" />
</Frame>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Shopify

Learn how to track a sale conversion event with Shopify

<Card title="Coming soon" icon="hourglass" iconType="duotone" color="#ca8b04">
  We're working on this guide. Check back soon!
</Card>


# Stripe

Learn how to track a sale conversion event with Stripe

When it comes to conversion tracking, a `sale` event happens when a user purchases your product or service. Examples include:

*   Subscribing to a paid plan
*   Usage expansion (upgrading from one plan to another)
*   Purchasing a product

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://assets.dub.co/help/conversion-sale-event.png" alt="A diagram showing how lead events are tracked in the conversion funnel" />
</Frame>

In this guide, we will be focusing on tracking sale events with Stripe as the payment processor by leveraging Dub's Stripe integration.

## Prerequisites

Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links:

1.  [Install the @dub/analytics client-side SDK](/conversions/quickstart#step-1-install-the-dub-analytics-client-side-sdk)
2.  [Install the Dub server-side SDK](/conversions/quickstart#step-2-install-the-dub-server-side-sdk)
3.  [Enable conversion tracking for your links](/conversions/quickstart#step-4-enable-conversion-tracking-for-your-links)

Then, depending on your authentication provider, follow the relevant guide to set up lead conversion tracking:

<CardGroup cols={2}>
  <Card title="NextAuth.js" icon="shield-halved" iconType="solid" href="/conversions/leads/next-auth" horizontal />

  <Card title="Clerk" icon="shield-halved" iconType="solid" href="/conversions/leads/clerk" horizontal />

  <Card title="Supabase Auth" icon="shield-halved" iconType="solid" href="/conversions/leads/supabase-auth" horizontal />

  <Card title="Auth0" icon="shield-halved" iconType="solid" href="/conversions/leads/auth0" horizontal />
</CardGroup>

## Step 1: Enable the Stripe Integration on Dub

<Steps>
  <Step title="Go to Stripe integration page">
    Navigate to the [Stripe integration page](https://app.dub.co/integrations/stripe) in your Dub workspace.
  </Step>

  <Step title="Enable the Stripe integration">
    Click on the "Enable" button to enable the Stripe integration, which will redirect you to the Stripe App installation flow.
  </Step>

  <Frame>
    <img src="https://assets.dub.co/help/stripe-integration-dub.png" alt="The Stripe integration page on Dub" />
  </Frame>

  <Step title="Install the Stripe app">
    Select the Stripe account to install the app on, and select "Install App".

    <Frame>
      <img src="https://assets.dub.co/help/stripe-integration-install.png" alt="The Stripe integration installation flow" />
    </Frame>
  </Step>

  <Step title="Verify the installation">
    Once this is done, you will be redirected back to your Dub workspace and you
    should see that the integration is now installed.
  </Step>

  <Step title="Optional: Install the app in Stripe test mode">
    Navigate to your [Stripe "Installed Apps" dashboard](https://dashboard.stripe.com/settings/apps/dub.co) and verify that the Dub app is installed. We also recommmend installing the app in test mode to be able to test your end-to-end flow without using real money.

    <Frame>
      <img src="https://assets.dub.co/help/stripe-app-install-test-mode.png" alt="The Stripe integration page on Dub" />
    </Frame>
  </Step>
</Steps>

Dub's Stripe integration will automatically forward the following events to Dub:

*   `customer.created`: When a new customer is created
*   `checkout.session.completed`: When a customer completes a checkout session
*   `invoice.paid`: When an invoice is paid (for tracking recurring subscriptions)

## Step 2: Associate Stripe customer with your customer ID

Next, we'll need to associate the [Stripe customer object](https://docs.stripe.com/api/customers/object) with the user ID in your database (which we tracked in the [lead conversion tracking step](/conversions/quickstart#step-3a-sending-lead-events)).

This will allow Dub to automatically listen for purchase events from Stripe and associate them with the original click event (and by extension, the link that the user came from).

<Accordion title="How does this work?">
  Remember in the [lead conversion tracking guide](/conversions/leads), we passed the user's unique user ID along with the click event ID in the `dub.track.lead` call?

  ```typescript
  await dub.track.lead({
    clickId,
    eventName: "Sign Up",
    customerId: user.id,
    customerName: user.name,
    customerEmail: user.email,
    customerAvatar: user.image,
  });
  ```

  Under the hood, Dub records the user as a customer and associates them with the click event that they came from.

  Then, when the user makes a purchase, Dub will automatically associate the checkout session details (invoice amount, currency, etc.) with the customer – and by extension, the original click event.
</Accordion>

There are 2 ways to associate the Stripe customer object with the user ID in your database:

1.  [Include your customer's unique user ID in checkout sessions](#option-1-include-your-customers-unique-user-id-in-checkout-sessions)
2.  [Pass the user ID and the click event ID in the Stripe customer creation flow](#option-2-pass-the-user-id-and-the-click-event-id-in-the-stripe-customer-creation-flow)

### Option 1: Include your customer's unique user ID in checkout sessions

When you [create checkout sessions](https://docs.stripe.com/api/checkout/sessions/create) for your users, pass your customer's unique user ID in your database as the `dubCustomerId` value in the `metadata` field.

```typescript app/api/upgrade/route.ts
import { stripe } from "@/lib/stripe";

const user = {
  id: "user_123",
  email: "user@example.com",
  teamId: "team_xxxxxxxxx",
};

const priceId = "price_xxxxxxxxx";

const stripeSession = await stripe.checkout.sessions.create({
  customer_email: user.email,
  success_url: "https://app.domain.com/success",
  line_items: [{ price: priceId, quantity: 1 }],
  mode: "subscription",
  client_reference_id: user.teamId,
  metadata: {
    dubCustomerId: user.id, // the unique user ID of the customer in your database
  },
});
```

### Option 2: Pass the user ID and the click event ID in the Stripe customer creation flow

Alternatively, if you don't use Stripe's checkout session creation flow, you can also pass the user ID and the click event ID (`dub_id`) in the [Stripe customer creation flow](https://docs.stripe.com/api/customers/create):

```typescript app/api/create-customer/route.ts
import { stripe } from "@/lib/stripe";

const user = {
  id: "user_123",
  email: "user@example.com",
  teamId: "team_xxxxxxxxx",
};

const dub_id = req.headers.get("dub_id");

await stripe.customers.create({
  email: user.email,
  name: user.name,
  metadata: {
    dubCustomerId: user.id,
    dubClickId: dub_id,
  },
});
```

## Step 3: View conversion results

And that's it – you're all set! You can now sit back, relax, and watch your conversion revenue grow. We provide 3 different views to help you understand your conversions:

*   **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales.

<Frame>
  <img src="https://assets.dub.co/help/conversions-timeseries-analytics.png" alt="Time-series line chart" />
</Frame>

*   **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales).

<Frame>
  <img src="https://assets.dub.co/help/conversion-funnel-chart.png" alt="Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales" />
</Frame>

*   **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace.

<Frame>
  <img src="https://assets.dub.co/help/conversions-events-stream-dashboard.png" alt="The Events Stream dashboard on Dub" />
</Frame>

## Example Apps

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Data model

A quick overview of how Dub is structured.

Whether you are using Dub's [API](/api-reference/introduction) or not, this page is a quick way to understand how Dub works.

Within Dub, all data belongs to a [**Workspace**](#workspace). Within a workspace, you have [Links](#links), [Domains](#domains), [Tags](#tags), and more.

## Workspace

[Workspaces](https://dub.co/help/article/what-is-a-workspace) is the defacto way of organizing your links and working with your team on Dub. You can think of a Dub workspace like a workspace on Slack or Discord – it's a shared space where you can [invite your team members](https://dub.co/help/article/how-to-invite-teammates) to collaborate on links.

When interacting with Dub's API, you'll also need to create a [workspace API key](/api-reference/tokens) to authenticate your requests.

<Accordion title="Workspace model">
  <ResponseField name="id" type="string" required>
    The unique ID of the workspace.
  </ResponseField>

  <ResponseField name="name" type="string" required>
    The name of the workspace.
  </ResponseField>

  <ResponseField name="slug" type="string" required>
    The slug of the workspace.
  </ResponseField>

  <ResponseField name="logo" type="string | null" required>
    The logo of the workspace.
  </ResponseField>

  <ResponseField name="usage" type="number" required>
    The usage of the workspace.
  </ResponseField>

  <ResponseField name="usageLimit" type="number" required>
    The usage limit of the workspace.
  </ResponseField>

  <ResponseField name="linksUsage" type="number" required>
    The links usage of the workspace.
  </ResponseField>

  <ResponseField name="linksLimit" type="number" required>
    The links limit of the workspace.
  </ResponseField>

  <ResponseField name="domainsLimit" type="number" required>
    The domains limit of the workspace.
  </ResponseField>

  <ResponseField name="tagsLimit" type="number" required>
    The tags limit of the workspace.
  </ResponseField>

  <ResponseField name="usersLimit" type="number" required>
    The users limit of the workspace.
  </ResponseField>

  <ResponseField name="plan" type="string" required>
    The plan of the workspace.
  </ResponseField>

  <ResponseField name="stripeId" type="string | null" required>
    The Stripe ID of the workspace.
  </ResponseField>

  <ResponseField name="billingCycleStart" type="number" required>
    The date and time when the billing cycle starts for the workspace.
  </ResponseField>

  <ResponseField name="createdAt" type="string" required>
    The date and time when the workspace was created.
  </ResponseField>

  <ResponseField name="users" type="object[]" required>
    The role of the authenticated user in the workspace.

    <Expandable title="properties">
      <ResponseField name="users.role" type="string" required>
        The role of the authenticated user in the workspace.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="domains" type="object[]" required>
    The domains of the workspace.

    <Expandable title="properties">
      <ResponseField name="domains.slug" type="string">
        The domain of the workspace.
      </ResponseField>

      <ResponseField name="domains.primary" type="string">
        Indicates if the domain is the primary domain.
      </ResponseField>
    </Expandable>
  </ResponseField>
</Accordion>

## Links

Links are the bread and butter of Dub. You can shorten any URL to a Dub link, which you can then share with your audience. Links can be [created](/api-reference/endpoint/create-a-new-link), [updated](/api-reference/endpoint/edit-a-link), and [deleted](/api-reference/endpoint/delete-a-link) via the [Dub API](/api-reference/introduction) or the [Dub dashboard](https://app.dub.co).

<Accordion title="Link model">
  <ResponseField name="id" type="string" required>
    The unique ID of the short link.
  </ResponseField>

  <ResponseField name="domain" type="string" required>
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or dub.sh if the workspace has no domains).
  </ResponseField>

  <ResponseField name="key" type="string" required>
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ResponseField>

  <ResponseField name="externalId" type="string | null" required>
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future. Must be prefixed with `ext_` when provided to
    `links.get`, `links.update`, and `links.delete` methods.
  </ResponseField>

  <ResponseField name="url" type="string" required>
    The destination URL of the short link.
  </ResponseField>

  <ResponseField name="archived" type="boolean" required default="false">
    Whether the short link is archived.
  </ResponseField>

  <ResponseField name="expiresAt" type="string | null" required>
    The date and time when the short link will expire in ISO-8601 format.
  </ResponseField>

  <ResponseField name="expiredUrl" type="string | null" required>
    The URL to redirect to when the short link has expired.
  </ResponseField>

  <ResponseField name="password" type="string | null" required>
    The password required to access the destination URL of the short link.
  </ResponseField>

  <ResponseField name="proxy" type="boolean" required default="false">
    Whether the short link uses Custom Social Media Cards feature.
  </ResponseField>

  <ResponseField name="title" type="string | null" required>
    The title of the short link generated via api.dub.co/metatags. Will be used
    for Custom Social Media Cards if proxy is true.
  </ResponseField>

  <ResponseField name="description" type="string | null" required>
    The description of the short link generated via api.dub.co/metatags. Will be
    used for Custom Social Media Cards if proxy is true.
  </ResponseField>

  <ResponseField name="image" type="string | null" required>
    The image of the short link generated via api.dub.co/metatags. Will be used
    for Custom Social Media Cards if proxy is true.
  </ResponseField>

  <ResponseField name="rewrite" type="boolean" required default="false">
    Whether the short link uses link cloaking.
  </ResponseField>

  <ResponseField name="ios" type="string | null" required>
    The iOS destination URL for the short link for iOS device targeting.
  </ResponseField>

  <ResponseField name="android" type="string | null" required>
    The Android destination URL for the short link for Android device targeting.
  </ResponseField>

  <ResponseField name="publicStats" type="boolean" required default="false">
    Whether the short link's stats are publicly accessible.
  </ResponseField>

  <ResponseField name="tagId" type="string | null" required>
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tags` instead.
  </ResponseField>

  <ResponseField name="tags" type="object[] | null" required>
    The tags assigned to the short link.

    <Expandable title="properties">
      <ResponseField name="tags.id" type="string" required>
        The unique ID of the tag.
      </ResponseField>

      <ResponseField name="tags.name" type="string" required>
        The name of the tag.
      </ResponseField>

      <ResponseField name="tags.color" type="string" required>
        The color of the tag.
      </ResponseField>
    </Expandable>
  </ResponseField>

  <ResponseField name="comments" type="string | null" required>
    The comments for the short link.
  </ResponseField>

  <ResponseField name="shortLink" type="string" required>
    The full URL of the short link, including the https protocol (e.g.
    `https://dub.sh/try`).
  </ResponseField>

  <ResponseField name="qrCode" type="string" required>
    The full URL of the QR code for the short link (e.g.
    `https://api.dub.co/qr?url=https://dub.sh/try`).
  </ResponseField>

  <ResponseField name="utm_source" type="string | null" required>
    The UTM source of the short link.
  </ResponseField>

  <ResponseField name="utm_medium" type="string | null" required>
    The UTM medium of the short link.
  </ResponseField>

  <ResponseField name="utm_campaign" type="string | null" required>
    The UTM campaign of the short link.
  </ResponseField>

  <ResponseField name="utm_term" type="string | null" required>
    The UTM term of the short link.
  </ResponseField>

  <ResponseField name="utm_content" type="string | null" required>
    The UTM content of the short link.
  </ResponseField>

  <ResponseField name="userId" type="string" required>
    The user ID of the creator of the short link.
  </ResponseField>

  <ResponseField name="workspaceId" type="string" required>
    The workspace ID of the short link.
  </ResponseField>

  <ResponseField name="clicks" type="number" required default="0">
    The number of clicks on the short link.
  </ResponseField>

  <ResponseField name="lastClicked" type="string | null" required>
    The date and time when the short link was last clicked.
  </ResponseField>

  <ResponseField name="createdAt" type="string" required>
    The date and time when the short link was created.
  </ResponseField>

  <ResponseField name="updatedAt" type="string" required>
    The date and time when the short link was last updated.
  </ResponseField>

  <ResponseField name="projectId" type="string" required>
    The project ID of the short link. This field is deprecated – use `workspaceId`
    instead.
  </ResponseField>
</Accordion>

## Domains

On Dub, you can [add custom domains](https://dub.co/help/article/how-to-add-custom-domain) to create branded short links for better brand recognition. You can also [set a primary domain](https://dub.co/help/article/how-to-set-primary-domain) for it to be used as the default domain for new links (both via the API and the dashboard).

<Accordion title="Domain model">
  <ParamField body="id" type="string" required>
    The unique identifier of the domain.
  </ParamField>

  <ParamField body="slug" type="string" required>
    The domain name.
  </ParamField>

  <ParamField body="verified" type="boolean" default={false} required>
    Whether the domain is verified.
  </ParamField>

  <ParamField body="primary" type="boolean" default={false} required>
    Whether the domain is the primary domain for the workspace.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false} required>
    Whether the domain is archived.
  </ParamField>

  <ParamField body="placeholder" type="string" default="https://dub.co/help/article/what-is-dub" required>
    Provide context to your teammates in the link creation modal by showing them
    an example of a link to be shortened.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null" required>
    The URL to redirect to when a link under this domain has expired.
  </ParamField>

  <ParamField body="target" type="string | null" required>
    The page your users will get redirected to when they visit your domain.
  </ParamField>

  <ParamField body="type" type="enum<string>" required>
    The type of redirect to use for this domain. Available values are `redirect`
    and `rewrite`.
  </ParamField>

  <ParamField body="clicks" type="number" default={0} required>
    The number of clicks on the domain.
  </ParamField>
</Accordion>

## Tags

Tags are a way to organize your links. You can [add tags to your links](https://dub.co/help/article/how-to-use-tags) to categorize them and make them easier to find. You can also [filter analytics by tags](https://dub.co/blog/introducing-tags#filtering-analytics-by-tags) to get a better understanding of how your campaigns are performing.

<Accordion title="Tag model">
  <ResponseField name="id" type="string" required>
    The unique ID of the tag.
  </ResponseField>

  <ResponseField name="name" type="string" required>
    The name of the tag.
  </ResponseField>

  <ResponseField name="color" type="string" required>
    The color of the tag.
  </ResponseField>
</Accordion>


# Examples

Explore ideas and examples of what you can build with the Dub API

## Dub Links

With Dub's [API](/api-reference), you can integrate Dub's link infrastructure into your application. This includes use cases like:

*   Programmatically creating short links
*   Using OAuth 2.0 to authenticate with Dub

With Dub's powerful [Analytics API](/api-reference/endpoint/retrieve-analytics), you can access real-time analytics data for your links. This allows you to build user-facing analytics dashboards, a la the ones on [OSS Gallery](https://oss.gallery/projects/dub).

<CardGroup cols={2}>
  <Card title="Dublet" icon="github" href="https://github.com/dubinc/dublet" color="#333333">
    Mini link shortener built with the Dub Typescript SDK
  </Card>

  <Card title="OAuth Example App" icon="github" href="https://github.com/dubinc/examples/tree/main/oauth" color="#333333">
    Example app that shows how to use OAuth 2.0 to authenticate with Dub
  </Card>

  <Card title="OSS Gallery" icon="github" href="https://github.com/dubinc/oss-gallery" color="#333333">
    Programmatically create short links on-demand using Dub's TypeScript SDK
  </Card>

  <Card title="Ray.so" icon="github" href="https://github.com/raycast/ray-so/blob/main/app/api/shorten-url/route.ts" color="#333333">
    Create code snippets, browse AI prompts, create extension icons and more.
  </Card>
</CardGroup>

## Dub Conversions

With [Dub Conversions](/conversions/quickstart), you can build white-labeled referral dashboards that live directly in your application (leveraging our [Analytics API](/api-reference/endpoint/retrieve-analytics) as well).

You can also combine Dub Conversions with [Webhooks](/integrations/webhooks) to build custom referral workflows:

*   give both the referrer and referee 1 month free of your product
*   send a swag link when a user reaches 5 referrals

<Note>
  Dub Conversions is currently in closed beta. If you'd like to join the beta,
  [reach out to us via
  email](mailto:support@dub.co?subject=I%20want%20to%20join%20the%20Dub%20Conversions%20beta).
</Note>

<CardGroup cols={2}>
  <Card title="Dub Conversions Demo App" icon="github" href="https://github.com/dubinc/examples/tree/main/conversions" color="#333333">
    Example app that shows how to use Dub Conversions with the Dub Typescript
    SDK
  </Card>
</CardGroup>


# Overview

Integrate Dub.co with your favorite tools and services.

Integrations allow you to extend the capabilities of Dub and seamlessly connect with third-party platforms and services. By leveraging these integrations, you can enhance your workflows, automate tasks, connect with your favorite tools, and more.

Below are some of the apps that integrate with Dub:

## Official integrations

<CardGroup cols={2}>
  <Card
    title="Raycast"
    icon={
     <svg
       width="228"
       height="228"
       viewBox="0 0 228 228"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
       className="h-8 w-8"
     >
       <g clipPath="url(#clip0_1218_28)">
         <path
           fillRule="evenodd"
           clipRule="evenodd"
           d="M227.987 113.987L216.097 125.89L170.987 80.7803V56.987L227.987 113.987ZM114.039 0L102.149 11.8901L147.246 56.987H171.039L114.039 0ZM88.5212 25.5177L76.6181 37.4208L96.1972 56.987H119.99L88.5212 25.5177ZM171.039 108.048V131.842L190.54 151.408L202.443 139.518L171.039 108.048ZM164.18 152.238L170.987 145.43H82.5177V56.987L75.7104 63.8073L62.9386 51.1003L51.0484 62.9905L63.8073 75.7623L56.987 82.5177V96.1454L37.4208 76.5662L25.5177 88.4693L56.987 119.939V147.168L11.8901 102.097L0 113.987L114.039 227.987L125.942 216.097L80.8321 170.987H108.061L139.531 202.469L151.434 190.566L131.855 170.987H145.482L152.29 164.18L165.061 176.939L176.952 165.048L164.18 152.238Z"
           fill="#FF6363"
         />
       </g>
       <defs>
         <clipPath id="clip0_1218_28">
           <rect width="228" height="228" fill="white" />
         </clipPath>
       </defs>
     </svg>
    }
    href="https://d.to/ray"
  >
    Shorten links with Raycast
  </Card>

  <Card
    title="Zapier"
    icon={
     <svg
       xmlns="http://www.w3.org/2000/svg"
       width="64"
       height="64"
       viewBox="0 0 64 64"
       className="h-8 w-8"
     >
       <path
         d="M63.207 26.418H44.432l13.193-13.193c-1.015-1.522-2.03-2.537-3.045-4.06a29.025 29.025 0 0 1-4.059-3.552L37.33 18.807V.54a17.252 17.252 0 0 0-5.074-.507A15.629 15.629 0 0 0 27.18.54v18.775l-13.7-13.7A13.7 13.7 0 0 0 9.42 9.166c-1.015 1.522-2.537 2.537-3.552 4.06L19.06 26.418H.794l-.507 5.074a15.629 15.629 0 0 0 .507 5.074H19.57l-13.7 13.7a27.198 27.198 0 0 0 7.611 7.611l13.193-13.193V63.46a17.252 17.252 0 0 0 5.074.507 15.629 15.629 0 0 0 5.074-.507V44.686L50.014 57.88a13.7 13.7 0 0 0 4.059-3.552 29.025 29.025 0 0 0 3.552-4.059L44.432 37.074h18.775A17.252 17.252 0 0 0 63.715 32a19.028 19.028 0 0 0-.507-5.582zm-23.342 5.074a25.726 25.726 0 0 1-1.015 6.597 15.223 15.223 0 0 1-6.597 1.015 25.726 25.726 0 0 1-6.597-1.015 15.223 15.223 0 0 1-1.015-6.597 25.726 25.726 0 0 1 1.015-6.597 15.223 15.223 0 0 1 6.597-1.015 25.726 25.726 0 0 1 6.597 1.015 29.684 29.684 0 0 1 1.015 6.597z"
         fill="#ff4a00"
       />
     </svg>
    }
    href="https://d.to/zapier"
  >
    Shorten and manage your links with Zapier
  </Card>

  <Card
    title="Make.com"
    icon={
     <svg
       xmlns="http://www.w3.org/2000/svg"
       width="512"
       height="512"
       viewBox="0 0 512 512"
       className="h-8 w-8"
     >
       <g id="Make-App-Icon-Circle" transform="translate(3757 -1767)">
         <circle
           id="Ellipse_10"
           data-name="Ellipse 10"
           cx="256"
           cy="256"
           r="256"
           transform="translate(-3757 1767)"
           fill="#6d00cc"
         />
         <path
           id="Path_141560"
           data-name="Path 141560"
           d="M244.78,14.544a7.187,7.187,0,0,0-7.186,7.192V213.927a7.19,7.19,0,0,0,7.186,7.192h52.063a7.187,7.187,0,0,0,7.186-7.192V21.736a7.183,7.183,0,0,0-7.186-7.192ZM92.066,17.083,5.77,188.795a7.191,7.191,0,0,0,3.192,9.654l46.514,23.379a7.184,7.184,0,0,0,9.654-3.2l86.3-171.711a7.184,7.184,0,0,0-3.2-9.654L101.719,13.886a7.2,7.2,0,0,0-9.654,3.2m72.592.614L127.731,204.876a7.189,7.189,0,0,0,5.632,8.442l51.028,10.306a7.2,7.2,0,0,0,8.481-5.665L229.8,30.786a7.19,7.19,0,0,0-5.637-8.442L173.133,12.038a7.391,7.391,0,0,0-1.427-.144,7.194,7.194,0,0,0-7.048,5.8"
           transform="translate(-3676.356 1905.425)"
           fill="#fff"
         />
       </g>
     </svg>
    }
    href="https://d.to/make"
  >
    Shorten links with Make.com
  </Card>

  <Card
    title="Slack"
    icon={
     <svg
       height="512px"
       version="1.1"
       viewBox="0 0 512 512"
       width="512px"
       className="h-8 w-8"
     >
       <path
         d="M122.643,316.682c0,26.596-21.727,48.323-48.321,48.323c-26.593,0-48.319-21.728-48.319-48.323    c0-26.592,21.727-48.318,48.319-48.318h48.321V316.682z"
         fill="#E01E5A"
       />
       <path
         d="M146.996,316.682c0-26.592,21.728-48.318,48.321-48.318c26.593,0,48.32,21.727,48.32,48.318V437.68    c0,26.592-21.728,48.319-48.32,48.319c-26.594,0-48.321-21.728-48.321-48.319V316.682z"
         fill="#E01E5A"
       />
       <path
         d="M195.317,122.643c-26.594,0-48.321-21.728-48.321-48.321c0-26.593,21.728-48.32,48.321-48.32    c26.593,0,48.32,21.728,48.32,48.32v48.321H195.317L195.317,122.643z"
         fill="#36C5F0"
       />
       <path
         d="M195.317,146.997c26.593,0,48.32,21.727,48.32,48.321c0,26.593-21.728,48.318-48.32,48.318H74.321    c-26.593,0-48.319-21.726-48.319-48.318c0-26.595,21.727-48.321,48.319-48.321H195.317L195.317,146.997z"
         fill="#36C5F0"
       />
       <path
         d="M389.359,195.318c0-26.595,21.725-48.321,48.32-48.321c26.593,0,48.318,21.727,48.318,48.321    c0,26.593-21.726,48.318-48.318,48.318h-48.32V195.318L389.359,195.318z"
         fill="#2EB67D"
       />
       <path
         d="M365.004,195.318c0,26.593-21.728,48.318-48.321,48.318c-26.593,0-48.32-21.726-48.32-48.318    V74.321c0-26.593,21.728-48.32,48.32-48.32c26.594,0,48.321,21.728,48.321,48.32V195.318L365.004,195.318z"
         fill="#2EB67D"
       />
       <path
         d="M316.683,389.358c26.594,0,48.321,21.727,48.321,48.321c0,26.592-21.728,48.319-48.321,48.319    c-26.593,0-48.32-21.728-48.32-48.319v-48.321H316.683z"
         fill="#ECB22E"
       />
       <path
         d="M316.683,365.005c-26.593,0-48.32-21.728-48.32-48.323c0-26.592,21.728-48.318,48.32-48.318H437.68    c26.593,0,48.318,21.727,48.318,48.318c0,26.596-21.726,48.323-48.318,48.323H316.683z"
         fill="#ECB22E"
       />
     </svg>
    }
    href="https://d.to/slack"
  >
    Shorten your links directly in Slack
  </Card>
</CardGroup>

## Unofficial integrations

<CardGroup cols={2}>
  <Card
    title="Dropshare"
    icon={
     <svg
       enableBackground="new 0 0 192 192"
       version="1.1"
       viewBox="0 0 192 192"
       xmlSpace="preserve"
       xmlns="http://www.w3.org/2000/svg"
       className="h-8 w-8"
     >
       <path d="m116.79 161.25c-7.8555 0.43628-15.084-0.36078-22.154-2.6081-27.018-8.5872-40.897-32.967-33.371-60.333 7.1198-25.887 21.645-47.383 41.904-64.919 6.9466-6.0129 10.529-6.2819 17.175-0.56768 23.003 19.778 39.283 43.9 44.622 74.2 3.9883 22.633-12.605 45.743-36.448 52.036-3.6866 0.97296-7.5013 1.4603-11.728 2.1916m-27.913-23.617c4.471 3.2535 8.9669 6.4465 14.888 6.2448 3.0063-0.10246 5.2491-1.4134 5.7613-4.4785 0.48747-2.9174-1.2853-4.9677-3.8861-5.6374-8.279-2.132-13.581-7.1151-16.461-15.184-1.1455-3.2089-4.1126-5.0832-7.6864-3.491-3.4384 1.5319-4.8823 4.209-3.4146 8.1668 2.0992 5.661 5.902 9.9739 10.799 14.379z" />
       <path d="m69.528 52.549c7.0827-8.0213 14.148-15.547 22.636-22.382 0.50252 2.9362-1.2719 3.721-2.2755 4.8835-13.768 15.949-26.109 32.782-34.83 52.164-12.457 27.687-2.1361 51.367 18.137 67.354 1.4834 1.1697 3.6999 1.6931 4.1595 4.0898-2.3604 1.2021-4.244-0.30447-6.1145-1.0696-27.442-11.225-38.68-39.951-26.577-67.943 5.9541-13.771 14.856-25.598 24.865-37.097z" />
     </svg>
    }
    href="https://d.to/dropshare"
  >
    Shorten your Dropshare links
  </Card>

  <Card
    title="Shortshare"
    icon={
    <svg width="60px" height="32px" viewBox="0 0 85 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <g id="Page-1" stroke="#000000" stroke-width="1" fill="none" fill-rule="evenodd">
    <g id="logo-white" transform="translate(9.000000, 5.000000)" fill="#FFFFFF" fill-rule="nonzero">
     <path d="M57.6097244,32.1946043 L49.8119122,23.5652379 L53.5118193,19.4200627 L67.5862069,35 L63.9246255,35 C61.5200013,35.0007178 59.2267988,33.9819624 57.6097244,32.1946043 Z" id="Path">
    </path>
     <path d="M34.9290895,34.8902821 L17.6963401,34.8902821 C8.08232654,34.8902821 0.0952999167,27.2215366 0,17.6156725 C-0.0391754929,12.9622274 1.78340246,8.48541294 5.06383055,5.17890966 C8.34425864,1.87240639 12.8111401,0.00976830213 17.4729991,0.00442938709 L34.9261314,0.00442938709 C35.6366282,0.00443423288 36.313429,0.306790968 36.7868128,0.835677698 L46.2529184,11.3923836 L42.5596581,15.4984254 L33.5772113,5.52196924 L17.6549258,5.52196924 C11.0981686,5.52196924 5.62853447,10.7191168 5.52647802,17.2613215 C5.48137766,20.4541587 6.71945592,23.531968 8.96406106,25.8070052 C11.2086662,28.0820424 14.2727085,29.3646813 17.4715201,29.3683129 L33.5757322,29.3683129 L57.3889042,2.84219005 C59.0114655,1.03357429 61.3286169,0 63.7607765,0 L67.3667712,0 L36.7882918,34.0605103 C36.314758,34.5881564 35.6387601,34.8898579 34.9290895,34.8902821 L34.9290895,34.8902821 Z" id="Path">
     </path>
    </g>
    </g>
    </svg>
    }
    href="https://d.to/shortshare"
  >
    Use your Dub.co links on Shortshare
  </Card>

  <Card
    title="Bubble"
    icon={
     <svg
       width="256px"
       height="276px"
       viewBox="0 0 256 276"
       version="1.1"
       xmlns="http://www.w3.org/2000/svg"
       preserveAspectRatio="xMidYMid"
       className="h-8 w-8"
     >
       <title>Bubble</title>
       <g>
         <path
           d="M156.687886,71.5306475 C131.928045,71.5306475 107.524298,82.1604077 88.8931172,103.059013 L88.8931172,0 L52.0240423,0 L52.0240423,173.506229 C52.0240423,173.510361 52.0240423,173.514494 52.0240423,173.519315 C52.0240423,229.845404 97.6852437,275.506605 154.012021,275.506605 C210.33811,275.506605 256,229.845404 256,173.519315 C256,117.193226 213.013974,71.5306475 156.687886,71.5306475 M154.012021,236.224775 C119.380066,236.224775 91.3051835,208.149893 91.3051835,173.518626 C91.3051835,138.886671 119.380066,110.811789 154.012021,110.811789 C188.643288,110.811789 216.71817,138.886671 216.71817,173.518626 C216.71817,208.150582 188.643288,236.224775 154.012021,236.224775"
           fill="#262626"
         ></path>
         <path
           d="M25.2199377,225.066041 C11.2909777,225.066041 1.0658141e-14,236.357707 1.0658141e-14,250.285979 C1.0658141e-14,264.21425 11.2909777,275.505916 25.2199377,275.505916 C39.1488977,275.505916 50.4398754,264.21425 50.4398754,250.285979 C50.4398754,236.357707 39.1488977,225.066041 25.2199377,225.066041"
           fill="#0000FF"
         ></path>
       </g>
     </svg>
    }
    href="https://d.to/bubble"
  >
    Shorten links with Bubble
  </Card>

  <Card
    title="Pipedream"
    icon={
     <svg
       enableBackground="new 0 0 600 600"
       version="1.1"
       viewBox="0 0 600 600"
       xmlSpace="preserve"
       xmlns="http://www.w3.org/2000/svg"
       className="h-8 w-8"
     >
       <path
         d="m413 601h-412v-600h600v600h-188m-249.49-374.5c2.13e-4 93.651 0.035782 187.3-0.10211 280.95-0.007019 4.7677 1.1727 6.2025 6.0619 6.152 24.327-0.25134 48.657-0.10834 72.987-0.12824 1.7477-0.001465 3.4952-0.19446 5.0492-0.28687v-105.64c0.7103 0.038513 1.1189-0.064697 1.3299 0.09433 0.79394 0.59842 1.5523 1.252 2.2729 1.9386 10.855 10.343 23.482 17.235 38.202 20.536 13.754 3.0848 27.621 4.5111 41.654 3.8274 41.472-2.0209 73.697-19.744 94.556-56.379 11.256-19.769 17.778-41.175 21.464-63.506 4.8599-29.442 4.9199-59.068 2.7362-88.712-1.6924-22.973-5.3217-45.622-13.603-67.302-10.566-27.662-27.689-49.597-56.101-60.187-33.964-12.66-68.366-13.049-102.57-0.19016-14.517 5.4573-27.194 13.949-39.095 24.427-4.8366-8.7217-9.6348-17.075-14.102-25.601-1.47-2.8053-3.1826-3.6822-6.2601-3.4362-11.125 0.88915-22.271 1.5068-33.407 2.2732-6.9087 0.4755-13.812 1.0345-21.073 1.5829 0 43.313 0 85.947 1.38e-4 129.58z"
         fill="#34D28B"
       />
       <path
         d="m163.51 226c-7.7e-5 -43.134-7.7e-5 -85.767-7.7e-5 -129.08 7.2617-0.5484 14.165-1.1074 21.073-1.5829 11.135-0.76641 22.282-1.3841 33.407-2.2732 3.0774-0.24596 4.7901 0.63092 6.2601 3.4362 4.4677 8.5261 9.2659 16.879 14.102 25.601 11.901-10.479 24.579-18.97 39.095-24.427 34.204-12.859 68.606-12.47 102.57 0.19016 28.412 10.59 45.535 32.525 56.101 60.187 8.2808 21.681 11.91 44.329 13.603 67.302 2.1837 29.644 2.1236 59.27-2.7362 88.712-3.6861 22.331-10.208 43.737-21.464 63.506-20.859 36.635-53.084 54.358-94.556 56.379-14.033 0.68375-27.899-0.74255-41.654-3.8274-14.72-3.3014-27.347-10.193-38.202-20.536-0.72058-0.68655-1.4789-1.3402-2.2729-1.9386-0.211-0.15903-0.61964-0.055817-1.3299-0.09433v105.64c-1.554 0.092408-3.3015 0.2854-5.0492 0.28687-24.329 0.019897-48.66-0.12311-72.987 0.12824-4.8892 0.050537-6.0689-1.3843-6.0619-6.152 0.13789-93.651 0.10232-187.3 0.10205-281.45m116.41-57.756c-11.344 4.0749-21.357 10.208-28.822 19.795-1.7525 2.2504-2.9519 5.5924-2.9638 8.4383-0.18326 43.805-0.013077 87.611-0.26497 131.42-0.033584 5.8413 1.8959 10.108 5.549 14.271 16.857 19.213 38.371 23.786 62.562 20.29 18.379-2.6563 30.69-13.119 37.316-30.036 3.3137-8.4608 5.9928-17.341 7.5757-26.277 4.6788-26.412 4.1864-53.067 1.8481-79.672-1.0942-12.45-3.4579-24.725-8.878-36.174-5.7627-12.173-15.005-20.134-28.25-23.214-15.049-3.4998-30.019-3.3802-45.672 1.1632z"
         fill="#fff"
       />
       <path
         d="m280.3 168.15c15.266-4.4467 30.236-4.5663 45.285-1.0665 13.244 3.0802 22.487 11.041 28.25 23.214 5.4201 11.449 7.7837 23.724 8.878 36.174 2.3383 26.605 2.8307 53.26-1.8481 79.672-1.5829 8.9356-4.262 17.816-7.5757 26.277-6.6257 16.917-18.937 27.38-37.316 30.036-24.191 3.4964-45.705-1.077-62.562-20.29-3.6531-4.1637-5.5826-8.4301-5.549-14.271 0.25189-43.804 0.081711-87.61 0.26497-131.42 0.011917-2.8459 1.2114-6.1879 2.9638-8.4383 7.4656-9.5867 17.479-15.72 29.209-19.892z"
         fill="#34D28B"
       />
     </svg>
    }
    href="https://d.to/pipedream"
  >
    Shorten links with Pipedream
  </Card>
</CardGroup>

## Building your own integrations

You can build your own integrations with Dub.co's link infrastructure using our [API](/api-reference/introduction).

1.  Read the documentation on how to [create links](/api-reference/endpoint/create-a-new-link).
2.  Learn how to [integrate Dub into your application](/integrations/quickstart).
3.  [Reach out to us](mailto:support@dub.co?subject=New%20Dub.co%20Integration) to feature your integration in the integrations marketplace.

## Upcoming integrations

Here are some services we're working on integrating with Dub.co:

<Card>
  <b>WordPress</b>
</Card>

<Card>
  <b>Chrome Extension</b>
</Card>

## No-code Resources

If you're looking to integrate with Dub using no-code tools like Make.com and Smartsuite, here are some resources to help you get started:

<iframe width="100%" className="aspect-video" src="https://www.youtube.com/embed/KQU3qqhuj3Q?si=riQPyNeRzDgP3VbC" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />


# Integrate with Dub

Learn how to set up Dub OAuth applications to authenticate users with OAuth 2.0.

Integrations allow you to extend the capabilities of Dub and seamlessly connect with third-party platforms and services.

In this guide, you will learn how to create and manage integrations on Dub, allowing you to incorporate Dub's link management infrastructure into your application.

## Integrating via OAuth 2.0 (recommended)

Dub supports OAuth 2.0 authentication, which is **recommended** if you build integrations extending Dub's functionality.

We recommend you use a OAuth client library to integrate the OAuth flow. You can find recommended libraries in a variety of programming languages [here](https://oauth.net/code/).

### Set up OAuth 2.0

Here is a step-by-step guide on how to set up OAuth 2.0 authentication with Dub.

<Steps>
  <Step title="Create an OAuth2 application in Dub">
    *   Go to the [OAuth Apps tab](https://app.dub.co/settings/oauth-apps) in your workspace.
    *   Click on **Create OAuth App**.
    *   Fill in the required fields to create an OAuth2 application.
  </Step>

  <Step title="Redirect users to authorization URL">
    When you want to authenticate a user, you need to redirect them to the Dub OAuth authorization URL.

    ```
    GET https://app.dub.co/oauth/authorize
    ```

    Parameters:

    | Property        | Description                                                                                                                       |
    | --------------- | --------------------------------------------------------------------------------------------------------------------------------- |
    | `client_id`     | The client ID of your OAuth application.                                                                                          |
    | `redirect_uri`  | The URL to redirect the user to after they authorize the application. Make sure this URL is registered in your OAuth application. |
    | `response_type` | Expected response type. It should be `code`.                                                                                      |
    | `scope`         | A space separated list of scopes that you want to request access to. Read more about scopes [here](#scopes).                      |
    | `state`         | The state parameter to prevent against CSRF attacks. Read more about it [here](https://auth0.com/docs/protocols/state-parameters) |

    An example URL would look like this:

    ```
    GET https://app.dub.co/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=SOME_SCOPE&state=SOME_STATE
    ```

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/oauth-consent.png" alt="OAuth consent screen" width="1468" height="249" />
    </Frame>
  </Step>

  <Step title="Exchange code for an access token">
    The `code` parameter is returned in the query string when the user is redirected back to your application. You can exchange this code for an access token by making a POST request to the Dub OAuth token URL.

    ```
    POST https://api.dub.co/oauth/token
    ```

    <Warning>
      The `Content-Type` header should be set to `application/x-www-form-urlencoded`.
    </Warning>

    Parameters:

    | Property        | Description                                                                  |
    | --------------- | ---------------------------------------------------------------------------- |
    | `code`          | The code you received when the user was redirected back to your application. |
    | `client_id`     | The client ID of your OAuth application.                                     |
    | `client_secret` | The client secret of your OAuth application.                                 |
    | `redirect_uri`  | The same redirect URI you used in the authorization URL.                     |
    | `grant_type`    | The grant type. It should be `authorization_code`.                           |

    Response:

    After a successful request, you will receive a JSON response with the access token.

    ```json
    {
      "access_token": "dub_access_token_ae8ebf6f97e6200d886ef48a5...",
      "refresh_token": "7f5acfbe14bca0a20fe6e430ddb7bb494eed160bd...",
      "token_type": "Bearer",
      "expires_in": 7200,
      "scope": "links.write tags.write domains.read"
    }
    ```

    We recommend using the [PKCE](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce) flow for native desktop or mobile application or a single-page app (SPA) where the `client_secret` cannot be hidden.

    With PKCE, the `client_secret` is **never sent to the authorization server**, preventing the `client_secret` from being leaked from the client application.

    Instead of using the `client_secret`, you will need to generate a `code_verifier` and `code_challenge` and use them to exchange for an access token.

    For example [Dub Raycast extension](https://github.com/raycast/extensions/tree/main/extensions/dub) uses PKCE to authenticate users.
  </Step>

  <Step title="Make an API request with the access token">
    Once you have obtained a valid access token, you can use it to make requests to the Dub API.

    You can initialize [Dub SDK](http://localhost:3334/sdks/overview) with the access token.

    Here is an example of how you can create a link using the [Dub TypeScript SDK](http://localhost:3334/sdks/typescript):

    ```javascript
    import { Dub } from "dub";

    const dub = new Dub({
      token: <ACCESS_TOKEN>,
    });

    const link = await dub.links.create({
      url: "https://google.com",
    });
    ```

    Or pass the access token in the header: `Authorization: Bearer <ACCESS_TOKEN>`

    ```shell
      curl --request POST \
      --url https://api.dub.co/links \
      --header 'Authorization: Bearer <ACCESS_TOKEN>' \
      --header 'Content-Type: application/json'
    ```
  </Step>

  <Step title="Refresh the access token">
    Dub access tokens are short-lived, depending on the `expires_in` value. Dub will respond with `401 Unauthorized` if you try to use an expired access token.

    To refresh the access token, you need to make a POST request to the Dub OAuth token URL with the `refresh_token` you obtained when exchanging the code for an `access_token`.

    ```
    POST https://api.dub.co/oauth/token
    ```

    <Warning>
      The `Content-Type` header should be set to `application/x-www-form-urlencoded`.
    </Warning>

    Parameters:

    | Property        | Description                                                                  |
    | --------------- | ---------------------------------------------------------------------------- |
    | `client_id`     | The client ID of your OAuth application.                                     |
    | `client_secret` | The client secret of your OAuth application.                                 |
    | `grant_type`    | The grant type. It should be `refresh_token`.                                |
    | `refresh_token` | The refresh token you received when exchanging the code for an access token. |

    Response:

    After a successful request, you will receive a JSON response with the new access token.

    ```json
    {
      "access_token": "dub_access_token_ae8ebf6f97e6200d886ef48a5...",
      "refresh_token": "7f5acfbe14bca0a20fe6e430ddb7bb494eed160bd...",
      "token_type": "Bearer",
      "expires_in": 7200,
      "scope": "links.write tags.write domains.read"
    }
    ```

    This will invalidate the old access token and refresh token.
  </Step>
</Steps>

### Scopes

You can request access to specific scopes when redirecting users to the Dub OAuth authorization URL. Scopes are permissions that the user needs to grant to your application.

Dub supports the following scopes for OAuth 2.0:

| Scope              | Description                                                         |
| ------------------ | ------------------------------------------------------------------- |
| `workspaces.read`  | Read access to workspaces.                                          |
| `workspaces.write` | Write access to workspaces.                                         |
| `links.read`       | Read access to links.                                               |
| `links.write`      | Write access to links.                                              |
| `tags.read`        | Read access to tags.                                                |
| `tags.write`       | Write access to tags.                                               |
| `analytics.read`   | Read access to analytics.                                           |
| `domains.read`     | Read access to domains.                                             |
| `domains.write`    | Write access to domains.                                            |
| `user.read`        | Read access to user information. This scope is included by default. |

### Examples

<CardGroup cols={2}>
  <Card title="OAuth 2.0 Example" icon="github" href="https://github.com/dubinc/examples/tree/main/oauth" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>

## Integrating via API keys (not recommended)

Dub also supports API key authentication; however, it is **not recommended** for building integrations. It should only be used for internal integrations or personal projects that do not require user consent.

Learn more about [API Keys](/api-reference/tokens).


# Webhooks

Use webhooks to build custom integrations with Dub.

<Note>
  Dub Webhooks are currently in closed beta and would require a [Business
  plan](https://dub.co/help/article/business-plan) subscription and above. If
  you'd like to join the beta, [reach out to us via
  email](mailto:support@dub.co?subject=I%20want%20to%20join%20the%20Dub%20Webhooks%20beta).
</Note>

Webhooks allows you to listen to real-time events happening across your Dub workspace. With webhooks, you can build custom integrations with Dub, such as:

*   Triggering a Zap on [Zapier](https://app.dub.co/integrations/zapier) when a new link is created in Dub
*   Send a notification on [Slack](https://app.dub.co/integrations/slack) when a link is clicked
*   For building gamified referral programs with [Dub Conversions](/conversions/quickstart) – e.g. increment usage credits for both the referrer and referee when a conversion event happens

<Warning>
  The following endpoints do not trigger webhook events: [Bulk create
  links](/api-reference/endpoint/bulk-create-links), [Bulk update
  links](/api-reference/endpoint/bulk-update-links), [Bulk delete
  links](/api-reference/endpoint/bulk-delete-links).
</Warning>

In this guide, we'll show you how to configure webhooks for your Dub workspace and a list of available events you can listen to.

## Creating a webhook

To create a webhook for your Dub workspace, you'll need to follow these steps:

Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) in your Dub workspace.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/create-webhook.png" alt="Create Webhook" />
</Frame>

Click on **Create Webhook** to create a new webhook.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/create-webhook-form.png" alt="Create Webhook Form" />
</Frame>

Fill in the required fields in the webhook creation form:

1.  **Name**: Give your webhook a name that helps you identify it.
2.  **URL**: Enter the URL of the endpoint where you want to send the webhook. We recommend using [webhook.site](https://webhook.site/) to test your webhook.
3.  **Signing secret**: This is an auto-generated secret key that you can use to verify the authenticity of the webhook in your application.
4.  **Events**: Select the events you want to listen to. You can select multiple events. Refer to the [Available Events](#available-events) section to see the list of available events.

Finally, click on **Create webhook** to create the webhook.

## Available webhook events

When setting up a webhook, you can select the events you want to listen to across your Dub workspace. There are two types of events you can listen to:

*   **Workspace-level events**: These events are triggered in the context of your entire workspace.
*   **Link-level events**: These events are triggered in the context of a specific link.

### Workspace-level events

These events are triggered in the context of your entire workspace:

<AccordionGroup>
  <Accordion title="link.created">
    This event is triggered when a [new link is created](/api-reference/endpoint/create-a-new-link) in your Dub workspace. The event payload contains the created link's details.

    Here's an example payload:

    ```json link.created
    {
      "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
      "event": "link.created",
      "createdAt": "2024-08-26T16:41:52.346Z",
      "data": {
        "id": "cm0b87844000dismqhkviju54",
        "domain": "dub.sh",
        "key": "sOvvXDT",
        "externalId": null,
        "url": "https://github.com/stack-auth/stack",
        "trackConversion": false,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "tagId": null,
        "tags": [],
        "comments": null,
        "shortLink": "https://dub.sh/sOvvXDT",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 0,
        "lastClicked": null,
        "leads": 0,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-26T16:41:52.084Z",
        "updatedAt": "2024-08-26T16:41:52.084Z",
        "projectId": "cm022sis60003ikt1syy7kfhl"
      }
    }
    ```
  </Accordion>

  <Accordion title="link.updated">
    This event is triggered when a link is updated in your Dub workspace. The event payload contains the updated link's details.

    Here's an example payload:

    ```json link.updated
    {
      "id": "event_KleiO4HBwZFbO1vZLWIPZ2AtX",
      "event": "link.updated",
      "createdAt": "2024-08-26T16:41:52.346Z",
      "data": {
        "id": "cm0b87844000dismqhkviju54",
        "domain": "dub.sh",
        "key": "sOvvXDT",
        "externalId": null,
        "url": "https://github.com/stack-auth/stack",
        "trackConversion": false,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "tagId": null,
        "tags": [],
        "comments": null,
        "shortLink": "https://dub.sh/sOvvXDT",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 0,
        "lastClicked": null,
        "leads": 0,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-26T16:41:52.084Z",
        "updatedAt": "2024-08-26T16:41:52.084Z",
        "projectId": "cm022sis60003ikt1syy7kfhl"
      }
    }
    ```
  </Accordion>

  <Accordion title="link.deleted">
    This event is triggered when a link is deleted in your Dub workspace. The event payload contains the deleted link's details.

    Here's an example payload:

    ```json link.deleted
    {
      "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX",
      "event": "link.deleted",
      "createdAt": "2024-08-26T16:41:52.346Z",
      "data": {
        "id": "cm0b87844000dismqhkviju54",
        "domain": "dub.sh",
        "key": "sOvvXDT",
        "externalId": null,
        "url": "https://github.com/stack-auth/stack",
        "trackConversion": false,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "tagId": null,
        "tags": [],
        "comments": null,
        "shortLink": "https://dub.sh/sOvvXDT",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 0,
        "lastClicked": null,
        "leads": 0,
        "sales": 0,
        "saleAmount": 0,
        "createdAt": "2024-08-26T16:41:52.084Z",
        "updatedAt": "2024-08-26T16:41:52.084Z",
        "projectId": "cm022sis60003ikt1syy7kfhl"
      }
    }
    ```
  </Accordion>

  <Accordion title="lead.created">
    This event is triggered when a [new lead is created](/api-reference/endpoint/track-lead) via [Dub Conversions](/conversions/quickstart). The event payload contains the following:

    *   `eventName`: The name of the event that was tracked.
    *   `customer`: Details about the customer that signed up.
    *   `click`: Details about the click event that led to the lead event.
    *   `link`: Details about the referral link that the lead event is associated with.

    Here's an example payload:

    ```json lead.created
    {
      "id": "evt_P343bmyae40ALQYr5HT4vRXRd",
      "event": "lead.created",
      "createdAt": "2024-08-30T09:53:50.343Z",
      "data": {
        "eventName": "Sign up",
        "customer": {
          "id": "oU5P0SqI8fpwx5bxw1",
          "name": "John",
          "email": "john@example.com",
          "avatar": "https://example.com/john.jpeg"
        },
        "click": {
          "id": "d0UtZqE0BZuBPrJS",
          "url": "https://github.com/dubinc/dub",
          "ip": "63.141.57.109",
          "continent": "NA",
          "country": "US",
          "city": "San Francisco",
          "device": "Desktop",
          "browser": "Chrome",
          "os": "Mac OS",
          "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
          "bot": false,
          "qr": false,
          "referer": "(direct)"
        },
        "link": {
          "id": "cm0faqkyn0001txvfwjfeq7gl",
          "domain": "dub.sh",
          "key": "79ys3WA",
          "externalId": null,
          "url": "https://github.com/dubinc/dub",
          "trackConversion": true,
          "archived": false,
          "expiresAt": null,
          "expiredUrl": null,
          "password": null,
          "proxy": false,
          "title": null,
          "description": null,
          "image": null,
          "video": null,
          "rewrite": false,
          "doIndex": false,
          "ios": null,
          "android": null,
          "geo": null,
          "publicStats": false,
          "comments": null,
          "shortLink": "https://dub.sh/79ys3WA",
          "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
          "utm_source": null,
          "utm_medium": null,
          "utm_campaign": null,
          "utm_term": null,
          "utm_content": null,
          "userId": "cm022rkcw0000ikt14mscg9sg",
          "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
          "clicks": 10,
          "lastClicked": "2024-08-30T07:45:09.000Z",
          "leads": 5,
          "sales": 0,
          "saleAmount": 0,
          "createdAt": "2024-08-29T13:03:59.098Z",
          "updatedAt": "2024-08-30T09:53:49.505Z"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="sale.created">
    This event is triggered when a [new sale is created](/api-reference/endpoint/create-a-sale) via [Dub Conversions](/conversions/quickstart). The event payload contains the following:

    *   `eventName`: The name of the event that was tracked.
    *   `customer`: Details about the customer that made the purchase.
    *   `click`: Details about the click event that led to the sale event.
    *   `link`: Details about the referral link that the sale event is associated with.

    Here's an example payload:

    ```json sale.created
    {
      "id": "evt_WHjyHhqsfYOrlJOOVJSoHXysD",
      "event": "sale.created",
      "createdAt": "2024-08-30T09:57:51.245Z",
      "data": {
        "eventName": "Purchased",
        "customer": {
          "id": "cm0gjdvr20001dkbha2n9gt2b",
          "name": "John",
          "email": "john@example.com",
          "avatar": "https://example.com/john.jpeg"
        },
        "click": {
          "id": "d0UtZqE0BZuBPrJS",
          "url": "https://github.com/dubinc/dub",
          "ip": "63.141.57.109",
          "continent": "NA",
          "country": "US",
          "city": "San Francisco",
          "device": "Desktop",
          "browser": "Chrome",
          "os": "Mac OS",
          "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
          "bot": false,
          "qr": false,
          "referer": "(direct)"
        },
        "link": {
          "id": "cm0faqkyn0001txvfwjfeq7gl",
          "domain": "dub.sh",
          "key": "79ys3WA",
          "externalId": null,
          "url": "https://github.com/dubinc/dub",
          "trackConversion": true,
          "archived": false,
          "expiresAt": null,
          "expiredUrl": null,
          "password": null,
          "proxy": false,
          "title": null,
          "description": null,
          "image": null,
          "video": null,
          "rewrite": false,
          "doIndex": false,
          "ios": null,
          "android": null,
          "geo": null,
          "publicStats": false,
          "comments": null,
          "shortLink": "https://dub.sh/79ys3WA",
          "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
          "utm_source": null,
          "utm_medium": null,
          "utm_campaign": null,
          "utm_term": null,
          "utm_content": null,
          "userId": "cm022rkcw0000ikt14mscg9sg",
          "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
          "clicks": 10,
          "lastClicked": "2024-08-30T07:45:09.000Z",
          "leads": 5,
          "sales": 1,
          "saleAmount": 20000,
          "createdAt": "2024-08-29T13:03:59.098Z",
          "updatedAt": "2024-08-30T09:57:50.891Z"
        },
        "sale": {
          "amount": 4500,
          "currency": "usd",
          "paymentProcessor": "stripe",
          "invoiceId": null
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Link-level events

Due to the high volume nature of these events, they are scoped to a specific link.

This means that you need to specify the link when creating a webhook – though you can select multiple links for the same webhook if you'd like.

<Accordion title="link.clicked">
  This event is triggered when a user clicks on a link. The event payload contains all the details about the click event.

  Here's an example payload:

  ```json link.clicked
  {
    "id": "evt_b9ywgxWqai2glUpCQjclB17kM",
    "event": "link.clicked",
    "createdAt": "2024-08-30T10:16:13.149Z",
    "data": {
      "click": {
        "timestamp": "2024-08-30T10:16:12.124Z",
        "clickId": "d0UtZqE0BZuBPrJS",
        "url": "https://github.com/dubinc/dub",
        "ip": "63.141.57.109",
        "continent": "NA",
        "country": "US",
        "city": "San Francisco",
        "device": "Desktop",
        "browser": "Chrome",
        "os": "Mac OS",
        "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
        "bot": false,
        "qr": false,
        "referer": "(direct)"
      },
      "link": {
        "id": "cm0faqkyn0001txvfwjfeq7gl",
        "domain": "dub.sh",
        "key": "79ys3WA",
        "externalId": null,
        "url": "https://github.com/dubinc/dub",
        "trackConversion": true,
        "archived": false,
        "expiresAt": null,
        "expiredUrl": null,
        "password": null,
        "proxy": false,
        "title": null,
        "description": null,
        "image": null,
        "video": null,
        "rewrite": false,
        "doIndex": false,
        "ios": null,
        "android": null,
        "geo": null,
        "publicStats": false,
        "comments": null,
        "shortLink": "https://dub.sh/79ys3WA",
        "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1",
        "utm_source": null,
        "utm_medium": null,
        "utm_campaign": null,
        "utm_term": null,
        "utm_content": null,
        "userId": "cm022rkcw0000ikt14mscg9sg",
        "workspaceId": "ws_cm022sis60003ikt1syy7kfhl",
        "clicks": 11,
        "lastClicked": "2024-08-30T07:45:09.000Z",
        "leads": 6,
        "sales": 10,
        "saleAmount": 200000,
        "createdAt": "2024-08-29T13:03:59.098Z",
        "updatedAt": "2024-08-30T10:16:12.126Z"
      }
    }
  }
  ```
</Accordion>

## Sending test events

You can send test events to your webhook URL to ensure that it's working correctly. To do this:

<Steps>
  <Step title="Go to the webhook details page">
    Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) and select the webhook you want to test.

    Click on the **Update Details** tab to open the webhook details page.
  </Step>

  <Step title="Open the test event modal">
    Select the `⋮` icon on the top right of the page, and click on **Send test event**.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/send-test-event.png" alt="Send Test Event" />
    </Frame>

    This will open up a modal where you can select the event you want to send.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/send-test-event-modal.png" alt="Send Test Event Modal" />
    </Frame>
  </Step>

  <Step title="Send the test event">
    Select the event you want to send, and click on **Send test webhook**.

    <Frame>
      <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/send-test-webhook.png" alt="Send Test Webhook" />
    </Frame>

    You'll see a success message and receive the webhook event in the webhook endpoint you specified.
  </Step>
</Steps>

## Viewing webhook event logs

We also provide you with a webhook event logs page where you can view all the webhook events that have been sent to your webhook endpoint in real-time.

To view the webhook event logs, select the webhook from the [**Webhooks** settings page](https://app.dub.co/webhooks) and click on the **Webhook Logs** tab.

Here, you'll see a list of all the webhook events that have been sent to your webhook endpoint:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/webhook-event-logs.png" alt="Webhook Event Logs" />
</Frame>

You can also select on a specific event, which will open up a sheet with more details about the event:

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/webhook-event-logs-details.png" alt="Webhook Event Logs Details" />
</Frame>

## Retry Behaviour

If your webhook endpoint does not respond with a success status code (2XX), we retry the request to ensure every message will be delivered.

Webhooks are retried 3 times with an exponential backoff. You can see all the retry attempts in the webhook event logs.

## Signature Verification

With signature verification, you can determine if the webhook came from Dub, and has not been tampered with in transit.

All webhooks are delivered with a `Dub-Signature` header. Dub generates this header using a secret key that only you and Dub know.

An example header looks like this:

```
Dub-Signature: c9ed6a2abf93f59d761eea69908d8de00f4437b5b6d7cd8b9bf5719cbe61bf46
```

To verify, you can use the secret key to generate your own signature for each webhook. If both signatures match then you can be sure that a received event came from Dub.

The steps required are:

1.  Get the raw body of the request.
2.  Extract the signature from the `Dub-Signature` header.
3.  Calculate the HMAC of the raw body using the `SHA-256` hash function and the secret.
4.  Compare the calculated `HMAC` with the one sent in the `Dub-Signature` header. If they match, the webhook is verified.

<CodeGroup>
  ```javascript Next.js
  export const POST = async (req: Request) => {
    const webhookSignature = req.headers.get("Dub-Signature");
    if (!webhookSignature) {
      return new Response("No signature provided.", { status: 401 });
    }

    // Copy this from the webhook details page
    const secret = process.env.DUB_WEBHOOK_SECRET;
    if (!secret) {
      return new Response("No secret provided.", { status: 401 });
    }

    // Make sure to get the raw body from the request
    const rawBody = await req.text();

    const computedSignature = crypto
      .createHmac("sha256", secret)
      .update(rawBody)
      .digest("hex");

    if (webhookSignature !== computedSignature) {
      return new Response("Invalid signature", { status: 400 });
    }

    # Handle the webhook event
    # ...
  };
  ```

  ```python Python
  import hmac
  import hashlib

  def webhook():
      # Get the signature from the header
      webhook_signature = request.headers.get('Dub-Signature')
      if not webhook_signature:
          abort(401, 'No signature provided.')

      # Copy this from the webhook details page
      secret = os.environ.get('DUB_WEBHOOK_SECRET')
      if not secret:
          abort(401, 'No secret provided.')

      # Get the raw body of the request
      raw_body = request.data

      # Calculate the HMAC
      computed_signature = hmac.new(
          secret.encode('utf-8'),
          raw_body,
          hashlib.sha256
      ).hexdigest()

      if webhook_signature != computed_signature:
          abort(400, 'Invalid signature')

      # Handle the webhook event
      # ...

      return 'OK', 200
  ```

  ```go Go

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"io/ioutil"
  	"net/http"
  	"os"
  )

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	// Get the signature from the header
  	webhookSignature := r.Header.Get("Dub-Signature")
  	if webhookSignature == "" {
  		http.Error(w, "No signature provided.", http.StatusUnauthorized)
  		return
  	}

  	// Copy this from the webhook details page
  	secret := os.Getenv("DUB_WEBHOOK_SECRET")
  	if secret == "" {
  		http.Error(w, "No secret provided.", http.StatusUnauthorized)
  		return
  	}

  	// Read the raw body
  	body, err := ioutil.ReadAll(r.Body)
  	if err != nil {
  		http.Error(w, "Error reading request body", http.StatusInternalServerError)
  		return
  	}

  	// Calculate the HMAC
  	h := hmac.New(sha256.New, []byte(secret))
  	h.Write(body)
  	computedSignature := hex.EncodeToString(h.Sum(nil))

  	if webhookSignature != computedSignature {
  		http.Error(w, "Invalid signature", http.StatusBadRequest)
  		return
  	}

  	// Handle the webhook event
  	// ...

  	w.WriteHeader(http.StatusOK)
  	w.Write([]byte("OK"))
  }
  ```
</CodeGroup>


# Introduction

Dub is the link management platform for modern marketing teams.

<Frame>
  <img className="rounded-lg border border-gray-100" src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/thumbnail.jpg" alt="Dub Thumbnail" width="1200" height="630" />
</Frame>

Our mission is to reimagine the role of links – one of the most foundational pieces of the web – from being a simple **"resource locator"** (URL) to a full **attribution engine** – visualizing the user journey from the first click to the final conversion event.

## Features

Here are some of the features that Dub offers:

<AccordionGroup>
  <Accordion title="Advanced Analytics" icon="chart-line">
    Dub provides powerful analytics for your links, including geolocation,
    device, browser, and referrer information. [Learn
    more](https://dub.co/help/article/dub-analytics)
  </Accordion>

  <Accordion title="Branded Links" icon="link">
    Dub offers free custom domains on all plans for you to create branded links
    that stand out. [Learn more](https://dub.co/features/branded-links)
  </Accordion>

  <Accordion title="QR Codes" icon="qrcode">
    QR codes and short links are like peas in a pod. Dub offers free QR codes
    for every short link you create – both via our dashboard and
    [programmatically](/api-reference/endpoint/retrieve-a-qr-code). [Learn
    more](https://dub.co/features/qr-codes)
  </Accordion>

  <Accordion title="Personalization" icon="user">
    Customize your link's behavior with [device
    targeting](https://dub.co/help/article/device-targeting), [geo
    targeting](https://dub.co/help/article/geo-targeting), [link
    expiration](https://dub.co/help/article/link-expiration), [link
    cloaking](https://dub.co/help/article/link-cloaking), and more. [Learn
    more](https://dub.co/features/personalization)
  </Accordion>

  <Accordion title="Team Collaboration" icon="users">
    Invite your teammates to collaborate on your links. For enterprises, Dub
    offers [SAML SSO](https://dub.co/help/category/saml-sso) for better
    security. [Learn more](https://dub.co/features/collaboration)
  </Accordion>
</AccordionGroup>

## Getting Started

To get started with Dub, you can do any of the following:

<CardGroup cols={2}>
  <Card title="Quickstart Guide" icon="code" href="/quickstart">
    Follow our quickstart guide to get started with Dub
  </Card>

  <Card title="Try our API" icon="code" href="/api-reference/introduction">
    Learn how to use Dub's API to create and manage links programmatically
  </Card>

  <Card title="Self-host Dub" icon="building" href="/self-hosting/guide">
    Learn how to self-host Dub for greater control and privacy
  </Card>

  <Card title="Integrate with Dub" icon="circle-nodes" href="/integrations">
    Learn how to integrate Dub with your favorite tools
  </Card>
</CardGroup>


# Local Development

A guide on how to run Dub.co's codebase locally.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/logo-background-gradient.png" alt="Dub.co Logo on a gradient background" width="1200" height="630" />
</Frame>

## Introduction

Dub's codebase is set up in a monorepo (via [Turborepo](https://turbo.build/repo)) and is fully [open-source on GitHub](https://github.com/dubinc/dub).

Here's the monorepo structure:

```
apps
├── web
packages
├── tailwind-config
├── tinybird
├── tsconfig
├── ui
├── utils
```

The `apps` directory contains the code for:

*   `web`: The entirety of Dub's application ([app.dub.co](https://app.dub.co)) + our link redirect infrastructure.

The `packages` directory contains the code for:

*   `tailwind-config`: The Tailwind CSS configuration for Dub's web app.
*   `tinybird`: Dub's Tinybird configuration.
*   `tsconfig`: The TypeScript configuration for Dub's web app.
*   `ui`: Dub's UI component library.
*   `utils`: A collection of utility functions and constants used across Dub's codebase.

## How `app.dub.co` works

Dub's web app is built with [Next.js](https://nextjs.org) and [TailwindCSS](https://tailwindcss.com).

It also utilizes code from the `packages` directory, specifically the `@dub/ui` and `@dub/utils` packages.

All of the code for the web app is located in here: [`main`/apps/web/app/app.dub.co](https://github.com/dubinc/dub/tree/main/apps/web/app/app.dub.co). This is using the Next.js [route group pattern](https://nextjs.org/docs/app/building-your-application/routing/route-groups).

There's also the API server, which is located in here: [`main`/apps/web/app/api](https://github.com/dubinc/dub/tree/main/apps/web/app/api)

When you run `pnpm dev` to start the development server, the app will be available at [http://localhost:8888](http://localhost:8888). The reason we use `localhost:8888` and not `app.localhost:8888` is because Google OAuth doesn't allow you to use localhost subdomains.

## How link redirects work on Dub

Link redirects on Dub are powered by [Next.js Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware).

To handle high traffic, we use Redis to cache every link's metadata when it's first created. This allows us to serve redirects without hitting our MySQL database.

Here's the code that powers link redirects:

*   Link redirects: [`main`/apps/web/lib/middleware/link.ts](https://github.com/dubinc/dub/blob/main/apps/web/lib/middleware/link.ts).
*   Root domain redirects: [`main`/apps/web/lib/middleware/root.ts](https://github.com/dubinc/dub/blob/main/apps/web/lib/middleware/root.ts).

## Running Dub locally

To run Dub.co locally, you'll need to set up the following:

*   A [Tinybird](https://www.tinybird.co/) account
*   An [Upstash](https://upstash.com/) account
*   A [PlanetScale](https://planetscale.com/)-compatible MySQL database

## Step 1: Local setup

First, you'll need to clone the Dub.co repo and install the dependencies.

<Steps>
  <Step title="Clone the repo">
    First, clone the [Dub.co repo](https://d.to/github) into a public GitHub repository.

    ```bash Terminal
    git clone https://github.com/dubinc/dub.git
    ```
  </Step>

  <Step title="Install dependencies">
    Run the following command to install the dependencies:

    ```bash Terminal
    pnpm i
    ```
  </Step>

  <Step title="Build internal packages">
    Execute the command below to compile all internal packages:

    ```bash Terminal
    pnpm -r --filter "./packages/**" build
    ```
  </Step>

  <Step title="Set up environment variables">
    Copy the `.env.example` file to `.env`:

    ```bash Terminal
    cp .env.example .env
    ```

    You'll be updating this `.env` file with your own values as you progress through the setup.
  </Step>
</Steps>

## Step 2: Set up Tinybird Clickhouse database

Next, you'll need to set up the [Tinybird](https://tinybird.co) Clickhouse database. This will be used to store time-series click events data.

<Steps>
  <Step title="Create Tinybird Workspace">
    In your [Tinybird](https://tinybird.co/) account, create a new Workspace.

    Copy your `admin` [Auth Token](https://www.tinybird.co/docs/concepts/auth-tokens.html). Paste this token as the `TINYBIRD_API_KEY` environment variable in your `.env` file.
  </Step>

  <Step title="Install Tinybird CLI and authenticate">
    In your newly-cloned Dub.co repo, navigate to the `packages/tinybird` directory.

    Install the Tinybird CLI with `pip install tinybird-cli` (requires Python >= 3.8).

    Run `tb auth` and paste your `admin` Auth Token.
  </Step>

  <Step title="Publish Tinybird datasource and endpoints">
    Run `tb push` to publish the datasource and endpoints in the `packages/tinybird` directory. You should see the following output (truncated for brevity):

    ```bash Terminal
    $ tb push

    ** Processing ./datasources/click_events.datasource
    ** Processing ./endpoints/clicks.pipe
    ...
    ** Building dependencies
    ** Running 'click_events'
    ** 'click_events' created
    ** Running 'device'
    ** => Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ** Token device_endpoint_read_8888 not found, creating one
    ** => Test endpoint with:
    ** $ curl https://api.us-east.tinybird.co/v0/pipes/device.json?token=p.ey...NWeaoTLM
    ** 'device' created
    ...
    ```
  </Step>

  <Step title="Set up Tinybird API base URL">
    You will then need to update your [Tinybird API base URL](https://www.tinybird.co/docs/api-reference/api-reference.html#regions-and-endpoints) to match the region of your database.

    From the previous step, take note of the **Test endpoint** URL. It should look something like this:

    ```bash Terminal
    Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ```

    Copy the base URL and paste it as the `TINYBIRD_API_URL` environment variable in your `.env` file.

    ```bash Terminal
    TINYBIRD_API_URL=https://api.us-east.tinybird.co
    ```
  </Step>
</Steps>

## Step 3: Set up Upstash Redis database

Next, you'll need to set up the [Upstash](https://upstash.com) Redis database. This will be used to cache link metadata and serve link redirects.

<Steps>
  <Step title="Create Upstash database">
    In your [Upstash account](https://console.upstash.com/), create a new database.

    For better performance & read times, we recommend setting up a global database with several read regions.

    <Frame>![Upstash Redis database](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-create-db.png)</Frame>
  </Step>

  <Step title="Set up Upstash Redis environment variables">
    Once your database is created, copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the **REST API** section into your `.env` file.

    <Frame>![Upstash Redis tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-redis-tokens.png)</Frame>

    Navigate to the [QStash tab](https://console.upstash.com/qstash) and copy the `QSTASH_TOKEN`, `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY` from the **Request Builder** section into your `.env` file.

    <Frame>![Upstash QStash tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-qstash-tokens.png)</Frame>
  </Step>

  <Step title="Optional: Set up Ngrok tunnel">
    If you're planning to run Qstash-powered background jobs locally, you'll need to set up an Ngrok tunnel to expose your local server to the internet.

    Follow [these steps](https://ngrok.com/docs/getting-started/) to setup `ngrok`, and then run the following command to start an Ngrok tunnel at port `8888`:

    ```bash Terminal
    ngrok http 8888
    ```

    Copy the `https` URL and paste it as the `NEXT_PUBLIC_NGROK_URL` environment variable in your `.env` file.
  </Step>
</Steps>

## Step 4: Set up PlanetScale MySQL database

Next, you'll need to set up a [PlanetScale](https://planetscale.com/)-compatible MySQL database. This will be used to store user data and link metadata. There are two options:

### Option 1: Local MySQL database with PlanetScale simulator (recommended)

You can use a local MySQL database with a PlanetScale simulator. This is the recommended option for local development since it's 100% free.

Prerequisites:

*   [Docker](https://www.docker.com/products/docker-desktop)
*   [Docker Compose](https://docs.docker.com/compose/install/)

<Steps>
  <Step title="Spin up the docker-compose stack">
    In the terminal, navigate to the `apps/web` directory and run the following command to start the Docker Compose stack:

    ```bash Terminal
    docker-compose up
    ```

    This will start two containers: one for the MySQL database and another for the PlanetScale simulator.
  </Step>

  <Step title="Set up database environment variables">
    Add the following credentials to your `.env` file:

    ```
    PLANETSCALE_DATABASE_URL="http://root:unused@localhost:3900"
    DATABASE_URL="mysql://root:@localhost:3306/planetscale"
    ```

    Here, we are using the open-source [PlanetScale simulator](https://github.com/mattrobenolt/ps-http-sim) so the application can continue to use the `@planetscale/database` SDK.

    <Tip>
      While we're using two different values in local development, in production or staging environments, you'll only need the `DATABASE_URL` value.
    </Tip>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal
    npx prisma generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal
    npx prisma db push
    ```
  </Step>
</Steps>

<Tip>
  The docker-compose setup includes Mailhog, which acts as a mock SMTP server
  and shows received emails in a web UI. You can access the Mailhog web
  interface at [http://localhost:8025](http://localhost:8025). This is useful
  for testing email functionality without sending real emails during local
  development.
</Tip>

### Option 2: PlanetScale hosted database

<Note>
  PlanetScale recently [removed their free
  tier](https://planetscale.com/blog/planetscale-forever), so you'll need to pay
  for this option. A cheaper alternative is to use a [MySQL database on
  Railway](https://railway.app/template/mysql) (\$5/month).
</Note>

<Steps>
  <Step title="Create PlanetScale database">
    In your [PlanetScale account](https://app.planetscale.com/), create a new database.

    Once your database is created, you'll be prompted to select your language or Framework. Select **Prisma**.

    <Frame>
      ![PlanetScale choose framework](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-choose-framework.png)
    </Frame>
  </Step>

  <Step title="Set up PlanetScale environment variables">
    Then, you'll have to create a new password for your database. Once the password is created, scroll down to the **Add credentials to .env** section and copy the `DATABASE_URL` into your `.env` file.

    <Frame>
      ![PlanetScale add credentials](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-add-credentials.png)
    </Frame>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal
    npx prisma generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal
    npx prisma db push
    ```
  </Step>
</Steps>

## Step 5: Set up Mailhog

To view emails sent from your application during local development, you'll need to set up [Mailhog](https://github.com/mailhog/MailHog).

<Note>
  If you've already run `docker compose up` as part of the database setup, you
  can skip this step. Mailhog is included in the Docker Compose configuration
  and should already be running.
</Note>

<Steps>
  <Step title="Pull Mailhog Docker image">
    Run the following command to pull the Mailhog Docker image:

    ```bash Terminal
    docker pull mailhog/mailhog
    ```
  </Step>

  <Step title="Start Mailhog container">
    Start the Mailhog container with the following command:

    ```bash Terminal
    docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
    ```

    This will run Mailhog in the background, and the web interface will be available at [http://localhost:8025](http://localhost:8025).
  </Step>
</Steps>

## Step 6: Start the development server

Finally, you can start the development server. This will build the packages + start the app servers.

```bash Terminal
pnpm dev
```

The web app (`apps/web`) will be available at [localhost:8888](http://localhost:8888).


# Django

Learn how to integrate Dub with Django.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Python SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Python SDK](https://dub.co/solutions/python), run the following command:

    ```bash pip
    pip install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Python SDK by creating a new instance of the `Dub` class.

    ```python
    import os
    import dub
    from dub.models import operations
    from django.http import JsonResponse

    d = dub.Dub(
      token=os.environ['DUB_API_KEY'],
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Python SDK](https://dub.co/solutions/python).

```python views.py
def create_link(request):
  res = d.links.create(request={
    "url": "https://google.com",
  })

  return JsonResponse({
    "short_link": res.short_link
  })
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```python views.py
def create_link(request):
  res = d.links.create(request={
    "url": "https://google.com",
    "external_id": "12345",
  })

  return JsonResponse({
    "short_link": res.short_link
  })
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Python SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```python views.py
def upsert_link(request):
  res = d.links.upsert(request={
    "url": "https://google.com",
  })

  return JsonResponse({
    "short_link": res.short_link
  })
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Python SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```python views.py
# Update a link by its linkId
def update_link(request):
  res = d.links.update(link_id="clx1gvi9o0005hf5momm6f7hj", request_body={
    "url": "https://google.uk",
  })

  return JsonResponse({
    "short_link": res.short_link
  })

# Update a link by its externalId
def update_link(request):
  res = d.links.update(external_id="ext_12345", request_body={
    "url": "https://google.uk",
  })

  return JsonResponse({
    "short_link": res.short_link
  })
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub Python SDK.

```python views.py
# Retrieve the timeseries analytics for the last 7 days for a link
def analytics(request):
  res = d.analytics.retrieve(request={
    "link_id": "clx1gvi9o0005hf5momm6f7hj",
    "interval": "7d",
    "group_by": "timeseries",
  })

  return JsonResponse(res)
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```python views.py
# Retrieve the timeseries analytics for the last 7 days for a link
def analytics(request):
  res = d.analytics.retrieve(request={
    "external_id": "ext_12345",
    "interval": "7d",
    "group_by": "timeseries",
  })

  return JsonResponse(res)
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Python Example" icon="github" href="https://github.com/dubinc/examples/tree/main/python/django" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Express

Learn how to integrate Dub with Express.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub TypeScript SDK

<Steps titleSize="h3">
  <Step title="Install">
    Install the [Dub TypeScript SDK](/sdks/typescript/overview) using your preferred package manager:

    <Tabs>
      <Tab title="npm">
        ```bash
        npm install dub
        ```
      </Tab>

      <Tab title="yarn">
        ```bash
        yarn add dub zod # zod is a peer dependency
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash
        pnpm add dub 
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Initialize">
    Then, initialize the Dub TypeScript SDK with your API key.

    ```typescript lib/dub.ts
    import { Dub } from "dub";

    export const dub = new Dub({
      token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY env variable
    });
    ```

    You can now use the `dub` object to interact with the Dub API.

    ```typescript
    import { dub } from "./lib/dub";
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub TypeScript SDK](/sdks/typescript/overview).

```typescript index.ts
app.post("/create-link", async (req: Request, res: Response) => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
    });

    res.status(200).json(result);
  } catch (error: any) {
    res.status(400).json(error);
  }
});
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```typescript index.ts
app.post("/create-link", async (req: Request, res: Response) => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
      externalId: "12345",
    });

    res.status(200).json(result);
  } catch (error: any) {
    res.status(400).json(error);
  }
});
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub TypeScript SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```typescript index.ts
app.post("/upsert-link", async (req: Request, res: Response) => {
  try {
    // Update the link if same URL already exists or create a new link
    const result = await dub.links.upsert({
      url: "https://www.google.com",
    });

    res.status(200).json(result);
  } catch (error: any) {
    res.status(400).json(error);
  }
});
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub TypeScript SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```typescript index.ts
app.post("/update-link", async (req: Request, res: Response) => {
  try {
    // Update a link by its linkId
    const { shortLink } = await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
      url: "https://www.google.uk", // new URL
    });

    // Update a link by its externalId
    const { shortLink } = await dub.links.update("ext_12345", {
      url: "https://www.google.uk", // new URL
    });

    res.status(200).json({ shortLink });
  } catch (error: any) {
    res.status(400).json(error);
  }
});
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub TypeScript SDK.

```typescript index.ts
import { ClicksTimeseries } from "dub/models/components";

app.get("/analytics", async (req: Request, res: Response) => {
  try {
    // Retrieve the timeseries analytics for the last 7 days for a link
    const response = await dub.analytics.retrieve({
      linkId: "clv3o9p9q000au1h0mc7r6l63",
      interval: "7d",
      groupBy: "timeseries",
    });

    const timeseries = response as ClicksTimeseries[];

    res.status(200).json(timeseries);
  } catch (error: any) {
    res.status(400).json(error);
  }
});
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```typescript index.ts
// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  externalId: "ext_12345",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Express Example" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript/express" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Flask

Learn how to integrate Dub with Flask.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Python SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Python SDK](https://dub.co/solutions/python), run the following command:

    ```bash pip
    pip install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Python SDK by creating a new instance of the `Dub` class.

    ```python
    import os
    import dub
    from dub.models import operations

    d = dub.Dub(
      token=os.environ['DUB_API_KEY'],
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Python SDK](https://dub.co/solutions/python).

```python app.py
def create_link():
  res = d.links.create(request={
    "url": "https://google.com",
  })

  return res.short_link
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```python app.py
def create_link(request):
  res = d.links.create(request={
    "url": "https://google.com",
    "external_id": "12345",
  })

  return res.short_link
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Python SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```python app.py
def upsert_link(request):
  res = d.links.upsert(request={
    "url": "https://google.com",
  })

  return res.short_link
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Python SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```python app.py
# Update a link by its linkId
def update_link(request):
  res = d.links.update(link_id="clx1gvi9o0005hf5momm6f7hj", request_body={
    "url": "https://google.uk",
  })

  return res.short_link

# Update a link by its externalId
def update_link(request):
  res = d.links.update(external_id="ext_12345", request_body={
    "url": "https://google.uk",
  })

  return res.short_link
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub Python SDK.

```python app.py
# Retrieve the timeseries analytics for the last 7 days for a link
def analytics(request):
  res = d.analytics.retrieve(request={
    "link_id": "clx1gvi9o0005hf5momm6f7hj",
    "interval": "7d",
    "group_by": "timeseries",
  })

  return str(res)
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```python views.py
# Retrieve the timeseries analytics for the last 7 days for a link
def analytics(request):
  res = d.analytics.retrieve(request={
    "external_id": "ext_12345",
    "interval": "7d",
    "group_by": "timeseries",
  })

  return str(res)
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Python Example" icon="github" href="https://github.com/dubinc/examples/tree/main/python/flask" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction

Learn how to integrate Dub with Go.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Go SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Go SDK](https://dub.co/solutions/go), run the following command:

    ```bash bash
    go get github.com/dubinc/dub-go
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Go SDK by creating a new instance of the `Dub` struct.

    ```go
    package main

    import (
    	"log"
    	"os"
    	"context"
    	dub "github.com/dubinc/dub-go"
    )

    d := dub.New(
    	dub.WithSecurity(os.Getenv("DUB_API_KEY")),
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Go SDK](https://dub.co/solutions/go).

```go main.go
func main() {
	request := &operations.CreateLinkRequestBody{
		URL: "https://google.com",
	}

	ctx := context.Background()
	res, err := d.Links.Create(ctx, request)
	if err != nil {
		log.Fatal(err)
	}
	if res.LinkSchema != nil {
		fmt.Println(res.LinkSchema.ShortLink)
	}
}
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```go main.go
func main() {
	request := &operations.CreateLinkRequestBody{
		URL: "https://google.com",
		ExternalId: "12345"
	}

	ctx := context.Background()
	res, err := d.Links.Create(ctx, request)
	if err != nil {
		log.Fatal(err)
	}
	if res.LinkSchema != nil {
		fmt.Println(res.LinkSchema.ShortLink)
	}
}
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Go SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```go main.go
func main() {
	// Update the link if same URL already exists or create a new link
	request := &operations.UpsertLinkRequestBody{
		URL: "https://google.com",
	}

	ctx := context.Background()
	res, err := d.Links.Upsert(ctx, request)
	if err != nil {
		log.Fatal(err)
	}
	if res.LinkSchema != nil {
		fmt.Println(res.LinkSchema.ShortLink)
	}
}
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Go SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```go main.go
func main() {
	request := &operations.UpdateLinkRequestBody{
		URL: "https://google.us",
	}

	// Update a link by its linkId
	ctx := context.Background()
	res, err := d.Links.Update(ctx, "clv3o9p9q000au1h0mc7r6l63", request)
	if err != nil {
		log.Fatal(err)
	}
	if res.LinkSchema != nil {
		fmt.Println(res.LinkSchema.ShortLink)
	}
}
```

## 6. Retrieve analytics for link

Let's retrieve analytics for a link using the Dub Go SDK.

```go main.go
func main() {
	// Retrieve the timeseries analytics for the last 7 days for a link
	request := operations.RetrieveAnalyticsRequest{
		LinkId: "clv3o9p9q000au1h0mc7r6l63",
		Interval: "7d",
		GroupBy: "timeseries"
	}

	ctx := context.Background()
	res, err := d.Analytics.Retrieve(ctx, request)
	if err != nil {
		log.Fatal(err)
	}
	if res.OneOf != nil {
		// handle response
	}
}
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Go Example" icon="github" href="https://github.com/dubinc/examples/tree/main/go" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Laravel

Learn how to integrate Dub with Laravel.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub PHP SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub PHP SDK](https://dub.co/solutions/php), run the following command:

    ```bash
    composer require dub/dub-php
    ```
  </Step>

  <Step title="Configuration">
    In your `.env` file, add your Dub API key:

    ```bash
    DUB_API_KEY=your_api_key
    ```

    In your `config/services.php` file, add the following:

    ```php
    'dub' => [
      'api_key' => env('DUB_API_KEY'),
    ],
    ```
  </Step>

  <Step title="Initialize">
    You can now create an instance of the `Dub` class and pass in your API key:

    ```php index.php
    use Dub\Dub;
    use Dub\Components\Security;

    $dub = Dub::builder()->setSecurity(config('services.dub.api_key'))->build();

    // create a link
    $dub->links->create(...);
    ```
  </Step>

  <Step title="Service Container (Optional)">
    If you want to be able to inject the `Dub` class via the service container, add this to the `register` method of your `AppServiceProvider.php`:

    ```php index.php
    $this->app->bind(Dub::class, function ($app) {
      return Dub::builder()->setSecurity($app['config']->get('services.dub.api_key'))->build();
    });
    ```

    You can then inject the authenticated `Dub` instance throughout your application:

    ```php index.php
    use Dub\Laravel\Dub;

    class LinkController extends Controller {
      public function createLink(Dub $dub) {
        // Now you can use the SDK instance
        $dub->links->create(...);
      }
    }
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the Dub Laravel SDK.

```php index.php
use Dub\Models\Operations;

class LinkController extends Controller {
  public function createLink() {
    $dub = new Dub();

    try {
      $request = new Operations\CreateLinkRequestBody(
        url: 'https://google.com'
      );

      $response = $dub->links->create($request);

      if ($response->linkSchema !== null) {
        // handle response
      }
  } catch (Throwable $e) {
    // handle exception
  }
  }
}
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field to associate the link with a unique identifier in your own system.

```php index.php
use Dub\Models\Operations;

class LinkController extends Controller
{
    public function createLinkWithExternalId()
    {
        $dub = new Dub();

        try {
            $request = new Operations\CreateLinkRequestBody(
                url: 'https://google.com',
                externalId: '12345'
            );

            $response = $dub->links->create($request);

            if ($response->linkSchema !== null) {
                // handle response
            }
        } catch (Throwable $e) {
            // handle exception
        }
    }
}
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

The Dub Laravel SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't.

```php index.php
use Dub\Models\Operations;

class LinkController extends Controller
{
    public function upsertLink()
    {
        $dub = new Dub();

        try {
            $request = new Operations\UpsertLinkRequestBody(
                url: 'https://google.com'
            );

            $response = $dub->links->upsert($request);

            if ($response->linkSchema !== null) {
                // handle response
            }
        } catch (Throwable $e) {
            // handle exception
        }
    }
}
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

To update an existing link using the Dub Laravel SDK, you can either use the link's `linkId` or `externalId`.

```php index.php
use Dub\Models\Operations;

class LinkController extends Controller
{
    public function updateLink()
    {
        $dub = new Dub();

        try {
            $request = new Operations\UpdateLinkRequest();
            $request->linkId = 'cly2p8onm000cym8200nfay7l';
            $request->requestBody = new Operations\UpdateLinkRequestBody();
            $request->requestBody->url = 'https://google.us';

            $response = $dub->links->update($request);

            if ($response->linkSchema !== null) {
                echo $response->linkSchema->shortLink;
            }
        } catch (Throwable $e) {
            // handle exception
        }
    }
}
```

## 6. Retrieve analytics for link

You can also retrieve analytics for a link using the Dub Laravel SDK.

```php index.php
use Dub\Models\Operations;

class LinkController extends Controller
{
    public function retrieveAnalytics()
    {
        $dub = new Dub();

        try {
            $request = new Operations\RetrieveAnalyticsRequest();
            $request->linkId = 'clmnr6jcc0005l308q9v56uz1';
            $request->interval = Operations\Interval::SevenD;
            $request->groupBy = Operations\GroupBy::Timeseries;

            $response = $dub->analytics->retrieve($request);

            if ($response->oneOf !== null) {
                // Handle the response
                print_r($response->oneOf);
            }
        } catch (Throwable $e) {
            // handle exception
        }
    }
}
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Laravel Example" icon="github" href="https://github.com/dubinc/examples/tree/main/laravel" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Next.js

Learn how to integrate Dub with Next.js.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub TypeScript SDK

<Steps titleSize="h3">
  <Step title="Install">
    Install the [Dub TypeScript SDK](/sdks/typescript/overview) using your preferred package manager:

    <Tabs>
      <Tab title="npm">
        ```bash
        npm install dub
        ```
      </Tab>

      <Tab title="yarn">
        ```bash
        yarn add dub zod # zod is a peer dependency
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash
        pnpm add dub 
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Initialize">
    Then, initialize the Dub TypeScript SDK with your API key.

    ```typescript lib/dub.ts
    import { Dub } from "dub";

    export const dub = new Dub({
      token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY env variable
    });
    ```

    You can now use the `dub` object to interact with the Dub API.

    ```typescript
    import { dub } from "./lib/dub";
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub TypeScript SDK](/sdks/typescript/overview).

```typescript app/api/links/route.ts
export async function POST() {
  try {
    const link = await dub.links.create({
      url: "https://google.com",
    });

    return Response.json(link);
  } catch (error) {
    return Response.json({ error }, { status: 500 });
  }
}
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```typescript app/api/links/route.ts
export async function POST() {
  try {
    const link = await dub.links.create({
      url: "https://google.com",
      externalId: "12345",
    });

    return Response.json(link);
  } catch (error) {
    return Response.json({ error }, { status: 500 });
  }
}
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub TypeScript SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```typescript app/api/links/route.ts
export async function PATCH() {
  try {
    const { shortLink } = await dub.links.upsert({
      url: "https://google.com",
    });

    return Response.json({ shortLink }); // will always be the same short link
  } catch (error) {
    return Response.json({ error }, { status: 500 });
  }
}
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub TypeScript SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```typescript app/api/links/route.ts
export async function PATCH() {
  try {
    // Update a link by its linkId
    const { shortLink } = await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
      url: "https://www.google.uk", // new URL
    });

    // Update a link by its externalId
    const { shortLink } = await dub.links.update("ext_12345", {
      url: "https://www.google.uk", // new URL
    });

    return Response.json({ shortLink });
  } catch (error) {
    return Response.json({ error }, { status: 500 });
  }
}
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub TypeScript SDK.

```typescript app/api/analytics/route.ts
import { ClicksTimeseries } from "dub/models/components";

export async function GET() {
  try {
    // Retrieve the timeseries analytics for the last 7 days for a link
    const response = await dub.analytics.retrieve({
      linkId: "clv3o9p9q000au1h0mc7r6l63",
      interval: "7d",
      groupBy: "timeseries",
    });

    const timeseries = response as ClicksTimeseries[];

    return Response.json(timeseries);
  } catch (error) {
    return Response.json({ error }, { status: 500 });
  }
}
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```typescript app/api/analytics/route.ts
// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  externalId: "ext_12345",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Next.js Example" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript/next" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Nuxt

Learn how to integrate Dub with Nuxt.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub TypeScript SDK

<Steps titleSize="h3">
  <Step title="Install">
    Install the [Dub TypeScript SDK](/sdks/typescript/overview) using your preferred package manager:

    <Tabs>
      <Tab title="npm">
        ```bash
        npm install dub
        ```
      </Tab>

      <Tab title="yarn">
        ```bash
        yarn add dub zod # zod is a peer dependency
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash
        pnpm add dub 
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Initialize">
    Then, initialize the Dub TypeScript SDK with your API key.

    ```typescript lib/dub.ts
    import { Dub } from "dub";

    export const dub = new Dub({
      token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY env variable
    });
    ```

    You can now use the `dub` object to interact with the Dub API.

    ```typescript
    import { dub } from "./lib/dub";
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub TypeScript SDK](/sdks/typescript/overview).

```typescript server/api/links.post.ts
export default defineEventHandler(async () => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
    });

    return result;
  } catch (error) {
    console.error(error);
    return error;
  }
});
```

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```typescript server/api/links.post.ts
export default defineEventHandler(async () => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
      externalId: "12345",
    });

    return result;
  } catch (error) {
    console.error(error);
    return error;
  }
});
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub TypeScript SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```typescript server/api/links.put.ts
export default defineEventHandler(async () => {
  try {
    const result = await dub.links.upsert({
      url: "https://www.google.com",
    });

    return result;
  } catch (error) {
    console.error(error);
    return error;
  }
});
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub TypeScript SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```typescript server/api/links.patch.ts
export default defineEventHandler(async () => {
  try {
    // Update a link by its linkId
    const { shortLink } = await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
      url: "https://www.google.uk", // new URL
    });

    // Update a link by its externalId
    const { shortLink } = await dub.links.update("ext_12345", {
      url: "https://www.google.uk", // new URL
    });

    return { shortLink };
  } catch (error) {
    console.error(error);
    return error;
  }
});
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub TypeScript SDK.

```typescript server/api/analytics.get.ts
import { ClicksTimeseries } from "dub/models/components";

export default defineEventHandler(async () => {
  try {
    // Retrieve the timeseries analytics for the last 7 days for a link
    const response = await dub.analytics.retrieve({
      linkId: "clv3o9p9q000au1h0mc7r6l63",
      interval: "7d",
      groupBy: "timeseries",
    });

    const timeseries = response as ClicksTimeseries[];

    return timeseries;
  } catch (error) {
    console.error(error);
    return error;
  }
});
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```typescript server/api/analytics.get.ts
// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  externalId: "ext_12345",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Nuxt.js Example" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript/nuxt" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction

Learn how to integrate Dub with PHP.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub PHP SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub PHP SDK](https://dub.co/solutions/php), run the following command:

    ```bash
    composer require dub/dub-php
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub PHP SDK by creating a new instance of the `Dub` class.

    ```php index.php
    declare(strict_types=1);

    require 'vendor/autoload.php';

    use Dub\Dub;
    use Dub\Models\Components;

    $dub = Dub::builder()->setSecurity("DUB_API_KEY")->build();
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the Dub PHP SDK.

```php index.php
use Dub\Models\Operations;

try {
  $request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
  );

  $response = $dub->links->create($request);

  if ($response->linkSchema !== null) {
    // handle response
  }
} catch (Throwable $e) {
  // handle exception
}
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```php index.php
use Dub\Models\Operations;

try {
  $request = new Operations\CreateLinkRequestBody(
    url: 'https://google.com',
    externalId: '12345',
  );

  $response = $dub->links->create($request);

  if ($response->linkSchema !== null) {
    // handle response
  }
} catch (Throwable $e) {
  // handle exception
}
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub PHP SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```php index.php
use Dub\Models\Operations;

try {
  $request = new Operations\UpsertLinkRequestBody(
    url: 'https://google.com',
  );

  $response = $dub->links->upsert($request);

  if ($response->linkSchema !== null) {
    // handle response
  }
} catch (Throwable $e) {
  // handle exception
}
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub PHP SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with ext\_).

```php index.php
use Dub\Models\Operations;

try {
    $request = new Operations\UpdateLinkRequest();
    $request->linkId = 'cly2p8onm000cym8200nfay7l';
    $request->requestBody = new Operations\UpdateLinkRequestBody();
    $request->requestBody->url = 'https://google.us';

    $response = $dub->links->update($request);

    if ($response->linkSchema !== null) {
        echo $response->linkSchema->shortLink;
    }
} catch (Throwable $e) {
    // handle exception
}
```

## 6. Retrieve analytics for link

Let's retrieve analytics for a link using the Dub PHP SDK.

```php index.php
use Dub\Models\Operations;

try {
    $request = new Operations\RetrieveAnalyticsRequest();
    $request->linkId = 'clmnr6jcc0005l308q9v56uz1';
    $request->interval = Operations\Interval::SevenD;
    $request->groupBy = Operations\GroupBy::Timeseries;

    $response = $dub->analytics->retrieve($request);

    if ($response->oneOf !== null) {
        // Handle the response
        print_r($response->oneOf);
    }
} catch (Throwable $e) {
    // handle exception
}
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="PHP Example" icon="github" href="https://github.com/dubinc/examples/tree/main/php" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction

Learn how to integrate Dub with Python.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Python SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Python SDK](https://dub.co/solutions/python), run the following command:

    ```bash pip
    pip install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Python SDK by creating a new instance of the `Dub` class.

    ```python
    import os
    import dub
    from dub.models import operations

    d = dub.Dub(
      token=os.environ['DUB_API_KEY'],
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Python SDK](https://dub.co/solutions/python).

```python index.py
res = d.links.create(request={
  "url": "https://google.com",
})

print(res.short_link)
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```python index.py
res = d.links.create(request={
  "url": "https://google.com",
  "external_id": "12345",
})

print(res.short_link)
```

## 4. Upsert link

Dub Python SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```python index.py
res = d.links.upsert(request={
  "url": "https://google.com",
})

print(res.short_link)
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Python SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```python index.py
# Update a link by its linkId
res = d.links.update(link_id="clx1gvi9o0005hf5momm6f7hj", request_body={
  "url": "https://google.uk",
})

print(res.short_link)

# Update a link by its externalId
res = d.links.update(external_id="ext_12345", request_body={
  "url": "https://google.uk",
})

print(res.short_link)
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub Python SDK.

```python index.py
# Retrieve the timeseries analytics for the last 7 days for a link
res = d.analytics.retrieve(request={
  "link_id": "clx1gvi9o0005hf5momm6f7hj",
  "interval": "7d",
  "group_by": "timeseries",
})

print(res)
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```python index.py
# Retrieve the timeseries analytics for the last 7 days for a link
res = d.analytics.retrieve(request={
  "external_id": "ext_12345",
  "interval": "7d",
  "group_by": "timeseries",
})

print(res)
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Python Example" icon="github" href="https://github.com/dubinc/examples/tree/main/python/flask" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Ruby on Rails

Learn how to integrate Dub with Ruby on Rails.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Ruby SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Ruby SDK](https://dub.co/solutions/ruby), run the following command:

    ```bash bash
    gem install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Ruby SDK by creating a new instance of the `Dub` struct.

    ```ruby
    class LinksController < ApplicationController
      require 'dub'

      before_action :initialize_dub

      def initialize_dub
        @dub = ::OpenApiSDK::Dub.new
        @dub.config_security(
          ::OpenApiSDK::Shared::Security.new(
            token: ENV['DUB_API_KEY']
          )
        )
      end
    end
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Ruby SDK](https://dub.co/solutions/ruby).

```ruby links_controller.rb
def create
  req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
      url: 'https://google.com'
    )
  )

  res = @dub.links.create(req)

  render json: res.raw_response.body
end
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```ruby links_controller.rb
def create
  req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
      url: 'https://google.com',
      external_id: '12345'
    )
  )

  res = @dub.links.create(req)

  render json: res.raw_response.body
end
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Ruby SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```ruby links_controller.rb
def upsert
  req = ::OpenApiSDK::Operations::UpsertLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::UpsertLinkRequestBody.new(
      url: "https://google.com",
    ),
  )

  res = @dub.links.upsert(req)

  render json: res.raw_response.body
end
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Ruby SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```ruby links_controller.rb
def update
  req = ::OpenApiSDK::Operations::UpdateLinkRequest.new(
    link_id: 'clx1gvi9o0005hf5momm6f7hj',
    request_body: ::OpenApiSDK::Operations::UpdateLinkRequestBody.new(
      url: 'https://google.uk'
    )
  )

  res = @dub.links.update(req)

  render json: res.raw_response.body
end
```

## 6. Retrieve analytics for link

Let's retrieve analytics for a link using the Dub Ruby SDK.

```ruby index.rb
def analytics
  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    link_id: "clx1gvi9o0005hf5momm6f7hj",
    interval: ::OpenApiSDK::Operations::Interval::SEVEND,
    group_by: ::OpenApiSDK::Operations::GroupBy::TIMESERIES
  )

  res = @dub.analytics.retrieve(req)

  render json: res.raw_response.body
end
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Rails Example" icon="github" href="https://github.com/dubinc/examples/tree/main/ruby/rails/example" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Remix

Learn how to integrate Dub with Remix.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub TypeScript SDK

<Steps titleSize="h3">
  <Step title="Install">
    Install the [Dub TypeScript SDK](/sdks/typescript/overview) using your preferred package manager:

    <Tabs>
      <Tab title="npm">
        ```bash
        npm install dub
        ```
      </Tab>

      <Tab title="yarn">
        ```bash
        yarn add dub zod # zod is a peer dependency
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash
        pnpm add dub 
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Initialize">
    Then, initialize the Dub TypeScript SDK with your API key.

    ```typescript lib/dub.ts
    import { Dub } from "dub";

    export const dub = new Dub({
      token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY env variable
    });
    ```

    You can now use the `dub` object to interact with the Dub API.

    ```typescript
    import { dub } from "./lib/dub";
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub TypeScript SDK](/sdks/typescript/overview).

```typescript app/routes/create-link.ts
export const loader = async () => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
    });

    return json(result, 200);
  } catch (error: any) {
    console.error(error);
    return json(error, 400);
  }
};
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```typescript app/routes/create-link.ts
export const loader = async () => {
  try {
    const result = await dub.links.create({
      url: "https://www.google.com",
      externalId: "12345",
    });

    return json(result, 200);
  } catch (error: any) {
    console.error(error);
    return json(error, 400);
  }
};
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub TypeScript SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```typescript app/routes/upsert-link.ts
export const loader = async () => {
  try {
    const result = await dub.links.upsert({
      url: "https://www.google.com",
    });

    return json(result, 200);
  } catch (error: any) {
    console.error(error);
    return json(error, 400);
  }
};
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub TypeScript SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```typescript app/routes/update-link.ts
export const loader = async () => {
  try {
    // Update a link by its linkId
    const { shortLink } = await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
      url: "https://www.google.uk", // new URL
    });

    // Update a link by its externalId
    const { shortLink } = await dub.links.update("ext_12345", {
      url: "https://www.google.uk", // new URL
    });

    return json({ shortLink }, 200);
  } catch (error: any) {
    console.error(error);
    return json(error, 400);
  }
};
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub TypeScript SDK.

```typescript app/routes/analytics.ts
export const loader = async () => {
  try {
    // Retrieve the timeseries analytics for the last 7 days for a link
    const result = await dub.analytics.retrieve({
      linkId: "clv3o9p9q000au1h0mc7r6l63",
      interval: "7d",
      groupBy: "timeseries",
    });

    const timeseries = response as ClicksTimeseries[];

    return json(timeseries, 200);
  } catch (error: any) {
    console.error(error);
    return json(error, 400);
  }
};
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```typescript app/routes/analytics.ts
// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  externalId: "ext_12345",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Remix Example" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript/remix" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction

Learn how to integrate Dub with Ruby.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Ruby SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Ruby SDK](https://dub.co/solutions/ruby), run the following command:

    ```bash bash
    gem install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Ruby SDK by creating a new instance of the `Dub` struct.

    ```ruby
    require 'dub'

    dub = ::OpenApiSDK::Dub.new
    dub.config_security(
      ::OpenApiSDK::Shared::Security.new(
        token: ENV['DUB_API_KEY'],
      )
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Ruby SDK](https://dub.co/solutions/ruby).

```ruby index.rb
req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
  request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com"
  )
)

res = dub.links.create(req)

puts res.raw_response.body
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```ruby index.rb
req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
  request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
    url: "https://google.com",
    external_id: "12345"
  )
)

res = dub.links.create(req)

puts res.raw_response.body
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Ruby SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```ruby index.rb
req = ::OpenApiSDK::Operations::UpsertLinkRequest.new(
  request_body: ::OpenApiSDK::Operations::UpsertLinkRequestBody.new(
    url: "https://google.com",
  ),
)

res = dub.links.upsert(req)

puts res.raw_response.body
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Ruby SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```ruby index.rb
req = ::OpenApiSDK::Operations::UpdateLinkRequest.new(
  link_id: "cly2p8onm000cym8200nfay7l",
  request_body: ::OpenApiSDK::Operations::UpdateLinkRequestBody.new(
    url: "https://google.us",
  ),
)

res = dub.links.update(req)

puts res.raw_response.body
```

## 6. Retrieve analytics for link

Let's retrieve analytics for a link using the Dub Ruby SDK.

```ruby index.rb
req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
  link_id: "clmnr6jcc0005l308q9v56uz1",
  interval: ::OpenApiSDK::Operations::Interval::SEVEND,
  group_by: ::OpenApiSDK::Operations::GroupBy::TIMESERIES
)

res = dub.analytics.retrieve(req)

puts res.raw_response.body
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Ruby Example" icon="github" href="https://github.com/dubinc/examples/tree/main/ruby" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Sinatra

Learn how to integrate Dub with Sinatra.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub Ruby SDK

<Steps titleSize="h3">
  <Step title="Install">
    To install the [Dub Ruby SDK](https://dub.co/solutions/ruby), run the following command:

    ```bash bash
    gem install dub
    ```
  </Step>

  <Step title="Initialize">
    Initialize the Dub Ruby SDK by creating a new instance of the `Dub` struct.

    ```ruby index.rb
    require "sinatra"
    require "dub"

    dub = ::OpenApiSDK::Dub.new
    dub.config_security(
      ::OpenApiSDK::Shared::Security.new(
        token: ENV['DUB_API_KEY'],
      )
    )
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub Ruby SDK](https://dub.co/solutions/ruby).

```ruby index.rb
post "/links" do
  req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
      url: 'https://google.com'
    )
  )

  res = dub.links.create(req)

  content_type :json
  res.raw_response.body
end
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```ruby index.rb
post "/links" do
  req = ::OpenApiSDK::Operations::CreateLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::CreateLinkRequestBody.new(
      url: 'https://google.com',
      external_id: '12345'
    )
  )

  res = dub.links.create(req)

  content_type :json
  res.raw_response.body
end
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub Ruby SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```ruby index.rb
put "/links" do
  req = ::OpenApiSDK::Operations::UpsertLinkRequest.new(
    request_body: ::OpenApiSDK::Operations::UpsertLinkRequestBody.new(
      url: "https://google.com",
    ),
  )

  res = dub.links.upsert(req)

  content_type :json
  res.raw_response.body
end
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub Ruby SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```ruby index.rb
patch "/links" do
  req = ::OpenApiSDK::Operations::UpdateLinkRequest.new(
    link_id: "clyci5h0w000511sjmu0tyjv9",
    request_body: ::OpenApiSDK::Operations::UpdateLinkRequestBody.new(
      url: 'https://google.uk'
    )
  )

  res = dub.links.update(req)

  content_type :json
  res.raw_response.body
end
```

## 6. Retrieve analytics for link

Let's retrieve analytics for a link using the Dub Ruby SDK.

```ruby index.rb
get "/analytics" do
  req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new(
    link_id: "clx1gvi9o0005hf5momm6f7hj",
    interval: ::OpenApiSDK::Operations::Interval::SEVEND,
    group_by: ::OpenApiSDK::Operations::GroupBy::TIMESERIES
  )

  res = dub.analytics.retrieve(req)

  content_type :json
  res.raw_response.body
end
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="Sinatra Example" icon="github" href="https://github.com/dubinc/examples/tree/main/ruby/sinatra" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Introduction

Learn how to integrate Dub with TypeScript.

## 1. Prerequisites

To follow this guide, you will need to:

*   [Create a Dub account](https://d.to/try)
*   [Create an API key](https://app.dub.co/settings/tokens)

## 2. Install and initialize the Dub TypeScript SDK

<Steps titleSize="h3">
  <Step title="Install">
    Install the [Dub TypeScript SDK](/sdks/typescript/overview) using your preferred package manager:

    <Tabs>
      <Tab title="npm">
        ```bash
        npm install dub
        ```
      </Tab>

      <Tab title="yarn">
        ```bash
        yarn add dub zod # zod is a peer dependency
        ```
      </Tab>

      <Tab title="pnpm">
        ```bash
        pnpm add dub 
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Initialize">
    Then, initialize the Dub TypeScript SDK with your API key.

    ```typescript lib/dub.ts
    import { Dub } from "dub";

    export const dub = new Dub({
      token: process.env.DUB_API_KEY, // optional, defaults to DUB_API_KEY env variable
    });
    ```

    You can now use the `dub` object to interact with the Dub API.

    ```typescript
    import { dub } from "./lib/dub";
    ```
  </Step>
</Steps>

## 3. Create link

Let's create a short link using the [Dub TypeScript SDK](/sdks/typescript/overview).

```typescript create-link.ts
const { shortLink } = await dub.links.create({
  url: "https://google.com",
});

console.log(shortLink); // e.g. https://dub.sh/abc123
```

<Accordion title="Full list of available attributes for the Link model">
  <ParamField body="url" type="string" required>
    The destination URL of the short link.
  </ParamField>

  <ParamField body="domain" type="string">
    The domain of the short link. If not provided, the primary domain for the
    workspace will be used (or `dub.sh` if the workspace has no domains).
  </ParamField>

  <ParamField body="key" type="string">
    The short link slug. If not provided, a random 7-character slug will be
    generated.
  </ParamField>

  <ParamField body="externalId" type="string | null">
    This is the ID of the link in your database. If set, it can be used to
    identify the link in the future.
  </ParamField>

  <ParamField body="prefix" type="string">
    The prefix of the short link slug for randomly-generated keys (e.g. if prefix
    is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
    key is provided.
  </ParamField>

  <ParamField body="archived" type="boolean" default={false}>
    Whether the short link is archived.
  </ParamField>

  <ParamField body="publicStats" type="boolean" default={false}>
    Whether the short link's stats are publicly accessible.
  </ParamField>

  <ParamField body="tagId" type="string | null">
    The unique ID of the tag assigned to the short link. This field is deprecated
    – use `tagIds` instead.
  </ParamField>

  <ParamField body="tagIds" type="string">
    The unique IDs of the tags assigned to the short link.
  </ParamField>

  <ParamField body="comments" type="string | null">
    The comments for the short link.
  </ParamField>

  <ParamField body="expiresAt" type="string | null">
    The date and time when the short link will expire at.
  </ParamField>

  <ParamField body="expiredUrl" type="string | null">
    The URL to redirect to when the short link has expired.
  </ParamField>

  <ParamField body="password" type="string | null">
    The password required to access the destination URL of the short link.
  </ParamField>

  <ParamField body="proxy" type="boolean" default={false}>
    Whether the short link uses Custom Social Media Cards feature.
  </ParamField>

  <ParamField body="title" type="string | null">
    The title of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="description" type="string | null">
    The description of the short link generated via `api.dub.co/metatags`. Will be
    used for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="image" type="string | null">
    The image of the short link generated via `api.dub.co/metatags`. Will be used
    for Custom Social Media Cards if proxy is true.
  </ParamField>

  <ParamField body="rewrite" type="boolean" default={false}>
    Whether the short link uses link cloaking.
  </ParamField>

  <ParamField body="ios" type="string | null">
    The iOS destination URL for the short link for iOS device targeting.
  </ParamField>

  <ParamField body="android" type="string | null">
    The Android destination URL for the short link for Android device targeting.
  </ParamField>

  <ParamField body="utm_source" type="string | null">
    The UTM source of the short link. If set, this will populate or override the
    UTM source in the destination URL.
  </ParamField>

  <ParamField body="utm_medium" type="string | null">
    The UTM medium of the short link. If set, this will populate or override the
    UTM medium in the destination URL.
  </ParamField>

  <ParamField body="utm_campaign" type="string | null">
    The UTM campaign of the short link. If set, this will populate or override the
    UTM campaign in the destination URL.
  </ParamField>

  <ParamField body="utm_term" type="string | null">
    The UTM term of the short link. If set, this will populate or override the UTM
    term in the destination URL.
  </ParamField>

  <ParamField body="utm_content" type="string | null">
    The UTM content of the short link. If set, this will populate or override the
    UTM content in the destination URL.
  </ParamField>
</Accordion>

Optionally, you can also pass an `externalId` field which is a unique identifier for the link in your own database to associate it with the link in Dub's system.

```typescript create-link-external-id.ts
await dub.links.create({
  url: "https://google.com",
  externalId: "12345",
});
```

This will let you easily [update the link](#5-update-link) or [retrieve analytics](#6-retrieve-analytics-for-link) for it later on using the `externalId` instead of the Dub `linkId`.

## 4. Upsert link

Dub TypeScript SDK provides a method to upsert a link – where an existing link is updated if it exists, or a new link is created if it doesn't. so you don't have to worry about checking if the link already exists.

```typescript upsert-link.ts
const { shortLink } = await dub.links.upsert({
  url: "https://www.google.com",
});

console.log(shortLink); // will always be the same short link
```

This way, you won't have to worry about checking if the link already exists when you're creating it.

## 5. Update link

Let's update an existing link using the Dub TypeScript SDK.

You can do that in two ways:

*   Using the link's `linkId` in Dub's system.
*   Using the link's `externalId` in your own database (prefixed with `ext_`).

```typescript update-link.ts
// Update a link by its linkId
await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
  url: "https://www.google.uk", // new URL
});

// Update a link by its externalId
await dub.links.update("ext_12345", {
  url: "https://www.google.uk", // new URL
});
```

## 6. Retrieve analytics for link

Dub allows you to retrieve analytics for a link using the Dub TypeScript SDK.

```typescript get-analytics.ts
import { ClicksTimeseries } from "dub/models/components";

// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  linkId: "clv3o9p9q000au1h0mc7r6l63",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

Similarly, you can retrieve analytics for a link using the `externalId` field.

```typescript get-analytics-external-id.ts
import { ClicksTimeseries } from "dub/models/components";

// Retrieve the timeseries analytics for the last 7 days for a link
const response = await dub.analytics.retrieve({
  externalId: "ext_12345",
  interval: "7d",
  groupBy: "timeseries",
});

const timeseries = response as ClicksTimeseries[];
```

## 7. Examples

<CardGroup cols={2}>
  <Card title="TypeScript Example" icon="github" href="https://github.com/dubinc/examples/tree/main/typescript" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Overview

Dub CLI for easily shortening URLs

## Install

<Tabs>
  <Tab title="npm">
    ```bash
    npm i -g dub-cli@latest
    ```
  </Tab>

  <Tab title="yarn">
    ```bash
    yarn global add dub-cli@latest
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash
    pnpm i -g dub-cli@latest
    ```
  </Tab>
</Tabs>

<Info>To update an existing installation, run the same command.</Info>

## Version

Check your installed version of Dub CLI:

```bash
dub --version
# => 0.0.12
```

## Commands

Below is a list of available commands in the Dub CLI:

<AccordionGroup>
  <Accordion title="dub login">
    Log into the Dub platform:

    ```bash
    dub login
    ```
  </Accordion>

  <Accordion title="dub config">
    View your workspace credentials:

    ```bash
    dub config
    ```
  </Accordion>

  <Accordion title="dub domains">
    Configure a domain for URL shortening:

    ```bash
    dub domains
    ```
  </Accordion>

  <Accordion title="dub shorten">
    Create a short link:

    ```bash
    dub shorten
    ```

    You can preemptively pass the URL and the generated short link key, or go through the CLI prompts.

    ```bash
    dub shorten [url] [key]
    ```
  </Accordion>

  <Accordion title="dub links">
    Search for links in your Dub workspace:

    ```bash
    dub links
    ```

    Here are the available options:

    | Option                  | Description                                 |
    | ----------------------- | ------------------------------------------- |
    | `-s, --search <search>` | Search by name                              |
    | `-l, --limit <limit>`   | Limit the number of results (default is 10) |

    ```bash
    dub links [options]
    ```
  </Accordion>

  <Accordion title="dub help">
    Get help for a specific command:

    ```bash
    dub help [command]
    ```
  </Accordion>
</AccordionGroup>

## Demo

Watch the Dub CLI in action:

<video src="https://github.com/user-attachments/assets/2ce9fe51-68ab-4e6d-b08d-4da09c17f90e" loop autoPlay muted playsInline />


# Framer

How to add Dub Analytics to your Framer site

With Dub Analytics, you can track leads and sales conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

You can add Dub Analytics script to your Framer website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

*   Go to your Framer project and open the **Project Settings** menu.
*   Open the **General** tab and scroll down to the **Custom Code** section.
*   Paste the Dub analytics script in the **Start of head tag** section.
*   Click on the **Save** button to save the changes.

```html
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```


# React

How to add Dub Analytics to your React or Next.js site

With Dub Analytics, you can track leads and sales conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

## Quickstart

This quick start guide will show you how to get started with Dub Analytics on your website.

<Steps titleSize="h3">
  <Step title="Install package">
    Using the package manager of your choice, add the `@dub/analytics` to your project.

    <CodeGroup>
      ```bash npm
      npm install @dub/analytics
      ```

      ```bash pnpm
      pnpm add @dub/analytics
      ```

      ```bash yarn
      yarn add @dub/analytics
      ```

      ```bash bun
      bun add @dub/analytics
      ```
    </CodeGroup>
  </Step>

  <Step title="Initialize package in your code">
    If you are using a React framework, you can use the `<Analytics />` component to track conversions on your website.

    E.g. if you're using Next.js, you can add the `<Analytics />` component to your root layout component or any other pages where you want to track conversions.

    ```jsx app/layout.tsx
    import { Analytics as DubAnalytics } from '@dub/analytics/react';

    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <body>{children}</body>
          <DubAnalytics />
        </html>
      );
    }
    ```
  </Step>
</Steps>

## Concepts

You can pass the following props to the `<Analytics />` component to customize the tracking script:

<ParamField body="apiHost" type="url" default="https://api.dub.co">
  The base URL for the Dub API. This is useful for setting up reverse proxies to
  avoid adblockers.
</ParamField>

<ParamField body="apiKey" type="string">
  The publishable API key to use for client-side click tracking. Get your
  publishable API key from your [Dub workspace's token settings
  page](https://app.dub.co/settings/tokens).
</ParamField>

<ParamField body="attributionModel" type="first-click | last-click" default="last-click">
  The attribution model to use for the analytics event. The following
  attribution models are available:

  *   `first-click`: The first click model
      gives all the credit to the first touchpoint in the customer journey.
  *   `last-click`: The last click model gives all the credit to the last
      touchpoint in the customer journey.
</ParamField>

<ParamField body="cookieOptions" type="CookieOption Object">
  <Expandable title="properties">
    <ParamField body="domain" type="string">
      Specifies the value for the `Domain` Set-Cookie attribute. This is useful
      for cross-domain tracking. Example: `.example.com`
    </ParamField>

    <ParamField body="expires" type="integer" default="90">
      Specifies the `Date` object to be the value for the `Expires` Set-Cookie
      attribute. Example: `new Date('2024-12-31')`
    </ParamField>

    <ParamField body="expiresInDays" type="integer" default="90">
      Specifies the number (in days) to be the value for the `Expires`
      Set-Cookie attribute. Example: `90`
    </ParamField>

    <ParamField body="path" type="string" default="/">
      Specifies the value for the `Path` Set-Cookie attribute. By default, the
      path is considered the "default path". Example: `/`
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="queryParam" type="string" default="via">
  The query parameter to listen to for client-side click-tracking (e.g.
  `?via=abc123`).
</ParamField>

<ParamField body="scriptProps" type="HTMLScriptElement Object">
  Custom properties to pass to the script tag. Refer to
  [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement) for
  all available options.
</ParamField>

## Examples

We also have some advanced examples on GitHub:

<CardGroup cols={2}>
  <Card title="Dub Analytics with Geolocation" icon="github" href="https://github.com/dubinc/analytics/tree/main/apps/nextjs-geolocation-script" color="#333333">
    See the full example on GitHub.
  </Card>
</CardGroup>


# Webflow

How to add Dub Analytics to your Webflow site

With Dub Analytics, you can track leads and sales conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

You can add Dub Analytics script to your Webflow website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

*   On your project's page, click on the **Webflow logo** in the left-hand side menu and choose **Project Settings**.
*   Choose **[Custom Code](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags?topics=site-settings)** from the menu and paste the Dub analytics script in the **Head Code** section.
*   Click on the **Save Changes** button and then **Publish** your changes.

```html
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```


# WordPress

How to add Dub Analytics to your WordPress site

With Dub Analytics, you can track leads and sales conversions on your website, enabling you to measure the effectiveness of your marketing campaigns.

You can add Dub Analytics script to your WordPress website same way you would add Google Analytics script or any other JavaScript code.

Follow these steps to add the script to your site:

*   On your WordPress dashboard, navigate to the **Theme Editor** section under the **Appearance** menu.
*   Open the **Theme Header (header.php)** file on the right column.
*   Paste the Dub analytics script in the header area.
*   Click on the **Update File** button to save the changes.

```html
<script src="https://www.dubcdn.com/analytics/script.js" defer></script>
```


# Introduction

Client-side JavaScript SDK for for Dub Conversions

The following guides show you how to add [Dub Analytics](https://github.com/dubinc/analytics) to your website.

With Dub Analytics, you can [track leads and sales conversions](/conversions/quickstart) on your website, enabling you to measure the effectiveness of your marketing campaigns.

Based on the framework or platform you're using, you can install the script in different ways:

<CardGroup>
  <Card title="React" icon="react" href="/sdks/client-side/installation-guides/react" horizontal>
    Add Dub Analytics to your React or Next.js app.
  </Card>

  <Card title="WordPress" icon="wordpress" href="/sdks/client-side/installation-guides/wordpress" horizontal>
    Add Dub Analytics to your WordPress site.
  </Card>

  <Card title="Webflow" icon="webflow" href="/sdks/client-side/installation-guides/webflow" horizontal>
    Add Dub Analytics to your Webflow site.
  </Card>

  <Card title="Framer" icon="framer" href="/sdks/client-side/installation-guides/framer" horizontal>
    Add Dub Analytics to your Framer site.
  </Card>
</CardGroup>


# Overview

Open-source client libraries for the Dub.co API

## Server-side SDKs

<CardGroup cols={2}>
  <Card title="TypeScript" icon="npm" href="/sdks/typescript/overview">
    TypeScript library for the Dub API
  </Card>

  <Card title="Go" icon="golang" href="https://dub.co/solutions/go" iconType="brands">
    Go library for the Dub API
  </Card>

  <Card title="Python" icon="python" href="https://dub.co/solutions/python">
    Python library for the Dub API
  </Card>

  <Card title="Ruby" icon="gem" href="https://dub.co/solutions/ruby">
    Ruby library for the Dub API
  </Card>

  <Card title="PHP" icon="php" href="https://dub.co/solutions/php">
    PHP library for the Dub API
  </Card>
</CardGroup>

## Client-side SDKs

<CardGroup cols={2}>
  <Card title="@dub/analytics" icon="js" href="/sdks/client-side/introduction">
    Dub analytics SDK
  </Card>
</CardGroup>


# dub.analytics.retrieve

Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters.

<Note>
  Analytics endpoints require a [Pro plan](https://d.to/pro) subscription or
  higher.
</Note>

## Arguments

<ParamField body="event" type="enum" default="clicks">
  The type of event to retrieve analytics for. Available options: `clicks`,
  `leads`, and `sales`.
</ParamField>

<ParamField body="groupBy" type="enum">
  The parameter to group the analytics data points by. Defaults to 'count' if
  undefined. Available options: `count`, `timeseries`, `countries`, `cities`,
  `devices`, `browsers`, `os`, `referers`, `referer_urls`, `top_links`,
  `top_urls`, and `trigger`.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link.
</ParamField>

<ParamField body="key" type="string">
  The short link slug.
</ParamField>

<ParamField body="linkId" type="string">
  The unique ID of the short link on Dub.
</ParamField>

<ParamField body="externalId" type="string">
  This is the ID of the link in the your database. Must be prefixed with 'ext\_'
  when passed as a query parameter.
</ParamField>

<ParamField body="interval" type="enum<string>">
  The interval to retrieve analytics for.

  Available options are `1h`, `24h`, `7d`, `30d`, `90d`, `ytd`, `1y`, and `all`.

  Takes precedence over `start` and `end`.
</ParamField>

<ParamField body="start" type="string">
  The start date to retrieve analytics for.

  Cannot be earlier than September 22, 2022.
</ParamField>

<ParamField body="end" type="string">
  The start date to retrieve analytics for.

  Cannot be in the future. If not provided, defaults to the current date.
</ParamField>

<ParamField body="timezone" type="string">
  The IANA time zone code for aligning timeseries granularity (e.g.
  America/New\_York). Defaults to UTC.
</ParamField>

<ParamField body="continent" type="string">
  The continent to retrieve analytics for.

  Available options are `AF`, `AN`, `AS`, `EU`, `NA`, `OC`, and `SA`.
</ParamField>

<ParamField body="country" type="enum<string>">
  The country to retrieve analytics for.

  Available options are `AF`, `AL`, `DZ`,
  `AS`, `AD`, `AO`, `AI`, `AQ`, `AG`, `AR`, `AM`, `AW`, `AU`, `AT`, `AZ`, `BS`,
  `BH`, `BD`, `BB`, `BY`, `BE`, `BZ`, `BJ`, `BM`, `BT`, `BO`, `BA`, `BW`, `BV`,
  `BR`, `IO`, `BN`, `BG`, `BF`, `BI`, `KH`, `CM`, `CA`, `CV`, `KY`, `CF`, `TD`,
  `CL`, `CN`, `CX`, `CC`, `CO`, `KM`, `CG`, `CD`, `CK`, `CR`, `CI`, `HR`, `CU`,
  `CY`, `CZ`, `DK`, `DJ`, `DM`, `DO`, `EC`, `EG`, `SV`, `GQ`, `ER`, `EE`, `ET`,
  `FK`, `FO`, `FJ`, `FI`, `FR`, `GF`, `PF`, `TF`, `GA`, `GM`, `GE`, `DE`, `GH`,
  `GI`, `GR`, `GL`, `GD`, `GP`, `GU`, `GT`, `GN`, `GW`, `GY`, `HT`, `HM`, `VA`,
  `HN`, `HK`, `HU`, `IS`, `IN`, `ID`, `IR`, `IQ`, `IE`, `IL`, `IT`, `JM`, `JP`,
  `JO`, `KZ`, `KE`, `KI`, `KP`, `KR`, `KW`, `KG`, `LA`, `LV`, `LB`, `LS`, `LR`,
  `LY`, `LI`, `LT`, `LU`, `MO`, `MG`, `MW`, `MY`, `MV`, `ML`, `MT`, `MH`, `MQ`,
  `MR`, `MU`, `YT`, `MX`, `FM`, `MD`, `MC`, `MN`, `MS`, `MA`, `MZ`, `MM`, `NA`,
  `NR`, `NP`, `NL`, `NC`, `NZ`, `NI`, `NE`, `NG`, `NU`, `NF`, `MK`, `MP`, `NO`,
  `OM`, `PK`, `PW`, `PS`, `PA`, `PG`, `PY`, `PE`, `PH`, `PN`, `PL`, `PT`, `PR`,
  `QA`, `RE`, `RO`, `RU`, `RW`, `SH`, `KN`, `LC`, `PM`, `VC`, `WS`, `SM`, `ST`,
  `SA`, `SN`, `SC`, `SL`, `SG`, `SK`, `SI`, `SB`, `SO`, `ZA`, `GS`, `ES`, `LK`,
  `SD`, `SR`, `SJ`, `SZ`, `SE`, `CH`, `SY`, `TW`, `TJ`, `TZ`, `TH`, `TL`, `TG`,
  `TK`, `TO`, `TT`, `TN`, `TR`, `TM`, `TC`, `TV`, `UG`, `UA`, `AE`, `GB`, `US`,
  `UM`, `UY`, `UZ`, `VU`, `VE`, `VN`, `VG`, `VI`, `WF`, `EH`, `YE`, `ZM`, `ZW`,
  `AX`, `BQ`, `CW`, `GG`, `IM`, `JE`, `ME`, `BL`, `MF`, `RS`, `SX`, `SS`, `XK`
</ParamField>

<ParamField body="city" type="string">
  The city to retrieve analytics for.
</ParamField>

<ParamField body="device" type="string">
  The device to retrieve analytics for.
</ParamField>

<ParamField body="browser" type="string">
  The browser to retrieve analytics for.
</ParamField>

<ParamField body="os" type="string">
  The OS to retrieve analytics for.
</ParamField>

<ParamField body="referer" type="string">
  The referer to retrieve analytics for.
</ParamField>

<ParamField body="refererUrl" type="string">
  The full referer URL to retrieve analytics for.
</ParamField>

<ParamField body="url" type="string">
  The URL to retrieve analytics for.
</ParamField>

<ParamField body="tagId" type="string">
  The tag ID to retrieve analytics for.
</ParamField>

<ParamField body="qr" type="boolean">
  Filter for QR code scans. If true, filter for QR code scans only. If false,
  filter for link clicks only. If undefined, return both.
</ParamField>

<ParamField body="root" type="boolean">
  Filter for root domains. If true, filter for domains only. If false, filter
  for links only. If undefined, return both.
</ParamField>

<RequestExample>
  ```typescript
  // Retrieve total clicks for a link
  await dub.analytics.retrieve({
    event: "clicks",
    domain: "dub.sh",
    key: "dummy",
    groupBy: "count",
  });

  // Retrieve timeseries analytics for a link
  await dub.analytics.retrieve({
    event: "clicks",
    linkId: "abc123",
    start: "30 days ago", // we support natural language for start and end dates
    end: "now",
    groupBy: "timeseries",
  });

  // Retrieve analytics by external ID
  await dub.analytics.retrieve({
    event: "clicks",
    externalId: "ext_123", // ID of the link in your database – must be prefixed with 'ext_'
    groupBy: "referer_urls", // we support grouping by the top referer URLs
  });
  ```
</RequestExample>


# dub.domains.create

Create a domain for the authenticated workspace.

## Arguments

<ParamField body="slug" type="string" required>
  Name of the domain. Eg. `acme.com`.
</ParamField>

<ParamField body="type" type="enum<string>" default="redirect">
  The type of redirect to use for this domain.

  <enum>
    <value>redirect</value>
    <value>rewrite</value>
  </enum>
</ParamField>

<ParamField body="target" type="string | null">
  The page your users will get redirected to when they visit your domain.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  Redirect users to a specific URL when any link under this domain has expired.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether to archive this domain. false will unarchive a previously archived
  domain.
</ParamField>

<ParamField body="placeholder" type="string | null" default="https://dub.co/help/article/what-is-dub">
  Provide context to your teammates in the link creation modal by showing them
  an example of a link to be shortened.
</ParamField>

## Response

<ParamField body="id" type="string" required>
  The unique identifier of the domain.
</ParamField>

<ParamField body="slug" type="string" required>
  The domain name.
</ParamField>

<ParamField body="verified" type="boolean" default={false} required>
  Whether the domain is verified.
</ParamField>

<ParamField body="primary" type="boolean" default={false} required>
  Whether the domain is the primary domain for the workspace.
</ParamField>

<ParamField body="archived" type="boolean" default={false} required>
  Whether the domain is archived.
</ParamField>

<ParamField body="placeholder" type="string" default="https://dub.co/help/article/what-is-dub" required>
  Provide context to your teammates in the link creation modal by showing them
  an example of a link to be shortened.
</ParamField>

<ParamField body="expiredUrl" type="string | null" required>
  The URL to redirect to when a link under this domain has expired.
</ParamField>

<ParamField body="target" type="string | null" required>
  The page your users will get redirected to when they visit your domain.
</ParamField>

<ParamField body="type" type="enum<string>" required>
  The type of redirect to use for this domain. Available values are `redirect`
  and `rewrite`.
</ParamField>

<ParamField body="clicks" type="number" default={0} required>
  The number of clicks on the domain.
</ParamField>

<RequestExample>
  ```ts
  await dub.domains.create({
    slug: "acme.com",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: "clvcep2sn87jh8nf808x00005",
    slug: "acme.com",
    verified: false,
    primary: true,
    archived: false,
    placeholder: "https://dub.co/help/article/what-is-dub",
    expiredUrl: null,
    target: null,
    type: "redirect",
    clicks: 0
  }
  ```
</ResponseExample>


# dub.domains.delete

Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain.

## Arguments

<ParamField body="slug" type="string" required>
  Name of the domain. Eg. `acme.com`.
</ParamField>

## Response

<ResponseField name="slug" type="string" required>
  Name of the domain that was deleted.
</ResponseField>

<RequestExample>
  ```ts
  await dub.domains.delete("acme.com");
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    slug: "acme.com";
  }
  ```
</ResponseExample>


# dub.domains.list

Retrieve a list of domains associated with the authenticated workspace.

## Arguments

No arguments.

## Response

<ParamField body="id" type="string" required>
  The unique identifier of the domain.
</ParamField>

<ParamField body="slug" type="string" required>
  The domain name.
</ParamField>

<ParamField body="verified" type="boolean" default={false} required>
  Whether the domain is verified.
</ParamField>

<ParamField body="primary" type="boolean" default={false} required>
  Whether the domain is the primary domain for the workspace.
</ParamField>

<ParamField body="archived" type="boolean" default={false} required>
  Whether the domain is archived.
</ParamField>

<ParamField body="placeholder" type="string" default="https://dub.co/help/article/what-is-dub" required>
  Provide context to your teammates in the link creation modal by showing them
  an example of a link to be shortened.
</ParamField>

<ParamField body="expiredUrl" type="string | null" required>
  The URL to redirect to when a link under this domain has expired.
</ParamField>

<ParamField body="target" type="string | null" required>
  The page your users will get redirected to when they visit your domain.
</ParamField>

<ParamField body="type" type="enum<string>" required>
  The type of redirect to use for this domain. Available values are `redirect`
  and `rewrite`.
</ParamField>

<ParamField body="clicks" type="number" default={0} required>
  The number of clicks on the domain.
</ParamField>

<RequestExample>
  ```ts
  const { result } = await dub.domains.list();
  ```
</RequestExample>

<ResponseExample>
  ```ts
  [
    {
      id: "clvcep2sn87jh8nf808x00005",
      slug: "acme.com",
      verified: false,
      primary: true,
      archived: false,
      placeholder: "https://dub.co/help/article/what-is-dub",
      expiredUrl: "https://acme.com/expired",
      target: "https://acme.com/home",
      type: "redirect",
      clicks: 0,
    },
  ];
  ```
</ResponseExample>


# dub.domains.update

Edit a domain for the authenticated workspace.

## Arguments

<ParamField body="slug" type="string">
  Name of the domain. If provided, the existing domain will be updated with the
  new domain. Eg. `acme.com`.
</ParamField>

<ParamField body="type" type="enum<string>" default="redirect">
  The type of redirect to use for this domain.

  <enum>
    <value>redirect</value>
    <value>rewrite</value>
  </enum>
</ParamField>

<ParamField body="target" type="string | null">
  The page your users will get redirected to when they visit your domain.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  Redirect users to a specific URL when any link under this domain has expired.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether to archive this domain. false will unarchive a previously archived
  domain.
</ParamField>

<ParamField body="placeholder" type="string | null" default="https://dub.co/help/article/what-is-dub">
  Provide context to your teammates in the link creation modal by showing them
  an example of a link to be shortened.
</ParamField>

## Response

<ParamField body="id" type="string" required>
  The unique identifier of the domain.
</ParamField>

<ParamField body="slug" type="string" required>
  The domain name.
</ParamField>

<ParamField body="verified" type="boolean" default={false} required>
  Whether the domain is verified.
</ParamField>

<ParamField body="primary" type="boolean" default={false} required>
  Whether the domain is the primary domain for the workspace.
</ParamField>

<ParamField body="archived" type="boolean" default={false} required>
  Whether the domain is archived.
</ParamField>

<ParamField body="placeholder" type="string" default="https://dub.co/help/article/what-is-dub" required>
  Provide context to your teammates in the link creation modal by showing them
  an example of a link to be shortened.
</ParamField>

<ParamField body="expiredUrl" type="string | null" required>
  The URL to redirect to when a link under this domain has expired.
</ParamField>

<ParamField body="target" type="string | null" required>
  The page your users will get redirected to when they visit your domain.
</ParamField>

<ParamField body="type" type="enum<string>" required>
  The type of redirect to use for this domain. Available values are `redirect`
  and `rewrite`.
</ParamField>

<ParamField body="clicks" type="number" default={0} required>
  The number of clicks on the domain.
</ParamField>

<RequestExample>
  ```ts
  await dub.domains.update("acme.com", {
    expiredUrl: "https://acme.com/expired",
    target: "https://acme.com/home",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: "clvcep2sn87jh8nf808x00005",
    slug: "acme.com",
    verified: false,
    primary: true,
    archived: false,
    placeholder: "https://dub.co/help/article/what-is-dub",
    expiredUrl: "https://acme.com/expired",
    target: "https://acme.com/home",
    type: "redirect",
    clicks: 0
  }
  ```
</ResponseExample>


# dub.events.list

Retrieve a list of events for the authenticated workspace. The list will be paginated and the provided query parameters allow filtering the returned events.

<Note>
  Events endpoints require a [Business plan](https://d.to/business) subscription
  or higher.
</Note>

## Arguments

<ParamField body="event" type="enum" default="clicks">
  The type of event to retrieve events for. Available options: `clicks`,
  `leads`, and `sales`.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link.
</ParamField>

<ParamField body="key" type="string">
  The short link slug.
</ParamField>

<ParamField body="linkId" type="string">
  The unique ID of the short link on Dub.
</ParamField>

<ParamField body="externalId" type="string">
  This is the ID of the link in the your database. Must be prefixed with 'ext\_'
  when passed as a query parameter.
</ParamField>

<ParamField body="interval" type="enum<string>">
  The interval to retrieve events for.

  Available options are `1h`, `24h`, `7d`, `30d`, `90d`, `ytd`, `1y`, and `all`.

  Takes precedence over `start` and `end`.
</ParamField>

<ParamField body="start" type="string">
  The start date to retrieve events for.

  Cannot be earlier than September 22, 2022.
</ParamField>

<ParamField body="end" type="string">
  The start date to retrieve events for.

  Cannot be in the future. If not provided, defaults to the current date.
</ParamField>

<ParamField body="timezone" type="string">
  The IANA time zone code for aligning timeseries granularity (e.g.
  America/New\_York). Defaults to UTC.
</ParamField>

<ParamField body="continent" type="string">
  The continent to retrieve events for.

  Available options are `AF`, `AN`, `AS`, `EU`, `NA`, `OC`, and `SA`.
</ParamField>

<ParamField body="country" type="enum<string>">
  The country to retrieve events for.

  Available options are `AF`, `AL`, `DZ`,
  `AS`, `AD`, `AO`, `AI`, `AQ`, `AG`, `AR`, `AM`, `AW`, `AU`, `AT`, `AZ`, `BS`,
  `BH`, `BD`, `BB`, `BY`, `BE`, `BZ`, `BJ`, `BM`, `BT`, `BO`, `BA`, `BW`, `BV`,
  `BR`, `IO`, `BN`, `BG`, `BF`, `BI`, `KH`, `CM`, `CA`, `CV`, `KY`, `CF`, `TD`,
  `CL`, `CN`, `CX`, `CC`, `CO`, `KM`, `CG`, `CD`, `CK`, `CR`, `CI`, `HR`, `CU`,
  `CY`, `CZ`, `DK`, `DJ`, `DM`, `DO`, `EC`, `EG`, `SV`, `GQ`, `ER`, `EE`, `ET`,
  `FK`, `FO`, `FJ`, `FI`, `FR`, `GF`, `PF`, `TF`, `GA`, `GM`, `GE`, `DE`, `GH`,
  `GI`, `GR`, `GL`, `GD`, `GP`, `GU`, `GT`, `GN`, `GW`, `GY`, `HT`, `HM`, `VA`,
  `HN`, `HK`, `HU`, `IS`, `IN`, `ID`, `IR`, `IQ`, `IE`, `IL`, `IT`, `JM`, `JP`,
  `JO`, `KZ`, `KE`, `KI`, `KP`, `KR`, `KW`, `KG`, `LA`, `LV`, `LB`, `LS`, `LR`,
  `LY`, `LI`, `LT`, `LU`, `MO`, `MG`, `MW`, `MY`, `MV`, `ML`, `MT`, `MH`, `MQ`,
  `MR`, `MU`, `YT`, `MX`, `FM`, `MD`, `MC`, `MN`, `MS`, `MA`, `MZ`, `MM`, `NA`,
  `NR`, `NP`, `NL`, `NC`, `NZ`, `NI`, `NE`, `NG`, `NU`, `NF`, `MK`, `MP`, `NO`,
  `OM`, `PK`, `PW`, `PS`, `PA`, `PG`, `PY`, `PE`, `PH`, `PN`, `PL`, `PT`, `PR`,
  `QA`, `RE`, `RO`, `RU`, `RW`, `SH`, `KN`, `LC`, `PM`, `VC`, `WS`, `SM`, `ST`,
  `SA`, `SN`, `SC`, `SL`, `SG`, `SK`, `SI`, `SB`, `SO`, `ZA`, `GS`, `ES`, `LK`,
  `SD`, `SR`, `SJ`, `SZ`, `SE`, `CH`, `SY`, `TW`, `TJ`, `TZ`, `TH`, `TL`, `TG`,
  `TK`, `TO`, `TT`, `TN`, `TR`, `TM`, `TC`, `TV`, `UG`, `UA`, `AE`, `GB`, `US`,
  `UM`, `UY`, `UZ`, `VU`, `VE`, `VN`, `VG`, `VI`, `WF`, `EH`, `YE`, `ZM`, `ZW`,
  `AX`, `BQ`, `CW`, `GG`, `IM`, `JE`, `ME`, `BL`, `MF`, `RS`, `SX`, `SS`, `XK`
</ParamField>

<ParamField body="city" type="string">
  The city to retrieve events for.
</ParamField>

<ParamField body="device" type="string">
  The device to retrieve events for.
</ParamField>

<ParamField body="browser" type="string">
  The browser to retrieve events for.
</ParamField>

<ParamField body="os" type="string">
  The OS to retrieve events for.
</ParamField>

<ParamField body="referer" type="string">
  The referer to retrieve events for.
</ParamField>

<ParamField body="refererUrl" type="string">
  The full referer URL to retrieve events for.
</ParamField>

<ParamField body="url" type="string">
  The URL to retrieve events for.
</ParamField>

<ParamField body="tagId" type="string">
  The tag ID to retrieve events for.
</ParamField>

<ParamField body="qr" type="boolean">
  Filter for QR code scans. If true, filter for QR code scans only. If false,
  filter for link clicks only. If undefined, return both.
</ParamField>

<ParamField body="root" type="boolean">
  Filter for root domains. If true, filter for domains only. If false, filter
  for links only. If undefined, return both.
</ParamField>

<ParamField body="limit" type="number">
  The number of records to retrieve.
</ParamField>

<ParamField body="offset" type="number">
  Number of records to offset the results by.
</ParamField>

<ParamField body="sortBy" type="enum<'timestamp'>">
  The field to sort the results by.
</ParamField>

<ParamField body="order" type="enum<'asc' | 'desc'>">
  The order to sort the results by.
</ParamField>

<RequestExample>
  ```ts
  await dub.events.list({
    event: "clicks",
    linkId: "abc123",
    start: "a week ago", // we support relative dates like "a week ago"
    end: "today", // we support relative dates like "today"
  });
  ```
</RequestExample>


# Count

Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links.

## Arguments

<ParamField body="domain" type="string">
  The domain to filter the links by. E.g. `ac.me`. If not provided, all links
  for the workspace will be returned.
</ParamField>

<ParamField body="tagId" type="string">
  The tag ID to filter the links by. This field is deprecated – use `tagIds`
  instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The tag IDs to filter the links by.
</ParamField>

<ParamField body="search" type="string">
  The search term to filter the links by. The search term will be matched
  against the short link slug and the destination URL.
</ParamField>

<ParamField body="userId" type="string">
  The user ID to filter the links by.
</ParamField>

<ParamField body="showArchived" type="boolean" default={false}>
  Whether to include archived links in the response. Defaults to `false` if not
  provided.
</ParamField>

<ParamField body="withTags" type="boolean" default={false}>
  Whether to include tags in the response. Defaults to `false` if not provided.
</ParamField>

<ParamField body="groupBy" type="string">
  The field to group the links by. Available options are `domain` and `tagId`.
</ParamField>

## Response

The number of links matching the query.

<RequestExample>
  ```ts
  // Count links by domain
  await dub.links.count({
    domain: "dub.sh",
  });

  // Count links by tagIds
  await dub.links.count({
    tagIds: ["clv3mewk30001mq0rxl3j3frn"],
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  2;
  ```
</ResponseExample>


# Create

Create a new link for the authenticated workspace.

## Arguments

<ParamField body="url" type="string" required>
  The destination URL of the short link.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or `dub.sh` if the workspace has no domains).
</ParamField>

<ParamField body="key" type="string">
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ParamField>

<ParamField body="externalId" type="string | null">
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future.
</ParamField>

<ParamField body="prefix" type="string">
  The prefix of the short link slug for randomly-generated keys (e.g. if prefix
  is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
  key is provided.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether the short link is archived.
</ParamField>

<ParamField body="publicStats" type="boolean" default={false}>
  Whether the short link's stats are publicly accessible.
</ParamField>

<ParamField body="tagId" type="string | null">
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tagIds` instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The unique IDs of the tags assigned to the short link.
</ParamField>

<ParamField body="comments" type="string | null">
  The comments for the short link.
</ParamField>

<ParamField body="expiresAt" type="string | null">
  The date and time when the short link will expire at.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  The URL to redirect to when the short link has expired.
</ParamField>

<ParamField body="password" type="string | null">
  The password required to access the destination URL of the short link.
</ParamField>

<ParamField body="proxy" type="boolean" default={false}>
  Whether the short link uses Custom Social Media Cards feature.
</ParamField>

<ParamField body="title" type="string | null">
  The title of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="description" type="string | null">
  The description of the short link generated via `api.dub.co/metatags`. Will be
  used for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="image" type="string | null">
  The image of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="rewrite" type="boolean" default={false}>
  Whether the short link uses link cloaking.
</ParamField>

<ParamField body="ios" type="string | null">
  The iOS destination URL for the short link for iOS device targeting.
</ParamField>

<ParamField body="android" type="string | null">
  The Android destination URL for the short link for Android device targeting.
</ParamField>

<ParamField body="utm_source" type="string | null">
  The UTM source of the short link. If set, this will populate or override the
  UTM source in the destination URL.
</ParamField>

<ParamField body="utm_medium" type="string | null">
  The UTM medium of the short link. If set, this will populate or override the
  UTM medium in the destination URL.
</ParamField>

<ParamField body="utm_campaign" type="string | null">
  The UTM campaign of the short link. If set, this will populate or override the
  UTM campaign in the destination URL.
</ParamField>

<ParamField body="utm_term" type="string | null">
  The UTM term of the short link. If set, this will populate or override the UTM
  term in the destination URL.
</ParamField>

<ParamField body="utm_content" type="string | null">
  The UTM content of the short link. If set, this will populate or override the
  UTM content in the destination URL.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  await dub.links.create({
    url: "https://www.google.com",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3o9p9q000au1h0mc7r6l63',
    domain: 'dub.sh',
    key: 'e7qzMuI',
    externalId: "123",
    url: 'https://www.google.com',
    archived: false,
    expiresAt: null,
    expiredUrl: null,
    password: null,
    proxy: false,
    title: null,
    description: null,
    image: null,
    rewrite: false,
    ios: null,
    android: null,
    geo: null,
    publicStats: false,
    tagId: null,
    tags: [],
    comments: null,
    shortLink: 'https://dub.sh/e7qzMuI',
    qrCode: 'https://api.dub.co/qr?url=https://dub.sh/e7qzMuI?qr=1',
    utmSource: null,
    utmMedium: null,
    utmCampaign: null,
    utmTerm: null,
    utmContent: null,
    userId: 'cludszk1h0000wmd2e0ea2b0p',
    workspaceId: 'ws_clugls3tn000lwfotbpy30304',
    clicks: 0,
    lastClicked: null,
    createdAt: '2024-04-17T10:31:00.398Z',
    updatedAt: '2024-04-17T10:31:00.398Z',
    projectId: 'clugls3tn000lwfotbpy30304'
  }
  ```
</ResponseExample>


# Create Many

Bulk create up to 100 links for the authenticated workspace.

## Arguments

<ParamField body="url" type="string" required>
  The destination URL of the short link.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or `dub.sh` if the workspace has no domains).
</ParamField>

<ParamField body="key" type="string">
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ParamField>

<ParamField body="externalId" type="string | null">
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future.
</ParamField>

<ParamField body="prefix" type="string">
  The prefix of the short link slug for randomly-generated keys (e.g. if prefix
  is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
  key is provided.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether the short link is archived.
</ParamField>

<ParamField body="publicStats" type="boolean" default={false}>
  Whether the short link's stats are publicly accessible.
</ParamField>

<ParamField body="tagId" type="string | null">
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tagIds` instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The unique IDs of the tags assigned to the short link.
</ParamField>

<ParamField body="comments" type="string | null">
  The comments for the short link.
</ParamField>

<ParamField body="expiresAt" type="string | null">
  The date and time when the short link will expire at.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  The URL to redirect to when the short link has expired.
</ParamField>

<ParamField body="password" type="string | null">
  The password required to access the destination URL of the short link.
</ParamField>

<ParamField body="proxy" type="boolean" default={false}>
  Whether the short link uses Custom Social Media Cards feature.
</ParamField>

<ParamField body="title" type="string | null">
  The title of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="description" type="string | null">
  The description of the short link generated via `api.dub.co/metatags`. Will be
  used for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="image" type="string | null">
  The image of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="rewrite" type="boolean" default={false}>
  Whether the short link uses link cloaking.
</ParamField>

<ParamField body="ios" type="string | null">
  The iOS destination URL for the short link for iOS device targeting.
</ParamField>

<ParamField body="android" type="string | null">
  The Android destination URL for the short link for Android device targeting.
</ParamField>

<ParamField body="utm_source" type="string | null">
  The UTM source of the short link. If set, this will populate or override the
  UTM source in the destination URL.
</ParamField>

<ParamField body="utm_medium" type="string | null">
  The UTM medium of the short link. If set, this will populate or override the
  UTM medium in the destination URL.
</ParamField>

<ParamField body="utm_campaign" type="string | null">
  The UTM campaign of the short link. If set, this will populate or override the
  UTM campaign in the destination URL.
</ParamField>

<ParamField body="utm_term" type="string | null">
  The UTM term of the short link. If set, this will populate or override the UTM
  term in the destination URL.
</ParamField>

<ParamField body="utm_content" type="string | null">
  The UTM content of the short link. If set, this will populate or override the
  UTM content in the destination URL.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  await dub.links.createMany([
    {
      url: "https://www.google.com",
    },
    {
      url: "https://github.com",
    },
  ]);
  ```
</RequestExample>

<ResponseExample>
  ```ts
  [
    {
      id: "clv3u86ej0008y9nm1f1yto2w",
      domain: "dub.sh",
      key: "FTC1AXj",
      externalId: "123",
      url: "https://www.google.com",
      archived: false,
      expiresAt: null,
      expiredUrl: null,
      password: null,
      proxy: false,
      title: null,
      description: null,
      image: null,
      rewrite: false,
      ios: null,
      android: null,
      geo: null,
      ...
    },
    {
      id: "clv3u86ej000ay9nm55qgbh5r",
      domain: "dub.sh",
      key: "B6qpMch",
      externalId: "123",
      url: "https://github.com",
      archived: false,
      expiresAt: null,
      expiredUrl: null,
      password: null,
      proxy: false,
      title: null,
      description: null,
      image: null,
      rewrite: false,
      ios: null,
      android: null,
      geo: null,
      ...
    },
  ];
  ```
</ResponseExample>


# Delete

Delete a link for the authenticated workspace.

## Arguments

<ParamField body="linkId" type="string" required>
  The id of the link to delete. You can get this via the `links.get` method.
  Alternatively, you can use your `externalId` prefixed with `ext_`.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The id of the link that was deleted.
</ResponseField>

<RequestExample>
  ```ts
  // Delete a link by linkId
  await dub.links.delete("clv3o9p9q000au1h0mc7r6l63");

  // Delete a link by externalId
  await dub.links.delete("ext_123");
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: "clv3o9p9q000au1h0mc7r6l63";
  }
  ```
</ResponseExample>


# Get

Retrieve info for a link by specifying either the domain and key or the linkId or externalId

## Arguments

<ParamField body="domain" type="string">
  The domain of the link to retrieve. E.g. for `d.to/github`, the domain is
  `d.to`.
</ParamField>

<ParamField body="key" type="string">
  The key of the link to retrieve. E.g. for `d.to/github`, the key is `github`.
</ParamField>

<ParamField body="linkId" type="string">
  The unique ID of the short link.
</ParamField>

<ParamField body="externalId" type="string">
  This is the ID of the link in the your database. Must be prefixed with `ext_`
  when provided.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  // Retrieve a link by domain and key
  await dub.links.get({
    domain: "dub.sh",
    key: "e7qzMuI",
  });

  // Retrieve a link by linkId
  await dub.links.get({
    linkId: "clv3o9p9q000au1h0mc7r6l63",
  });

  // Retrieve a link by externalId
  await dub.links.get({
    externalId: "ext_123",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3o9p9q000au1h0mc7r6l63',
    domain: 'dub.sh',
    key: 'e7qzMuI',
    externalId: "123",
    url: 'https://www.google.com',
    archived: false,
    expiresAt: null,
    expiredUrl: null,
    password: null,
    proxy: false,
    title: null,
    description: null,
    image: null,
    rewrite: false,
    ios: null,
    android: null,
    geo: null,
    publicStats: false,
    tagId: null,
    tags: [],
    comments: null,
    shortLink: 'https://dub.sh/e7qzMuI',
    qrCode: 'https://api.dub.co/qr?url=https://dub.sh/e7qzMuI?qr=1',
    utmSource: null,
    utmMedium: null,
    utmCampaign: null,
    utmTerm: null,
    utmContent: null,
    userId: 'cludszk1h0000wmd2e0ea2b0p',
    workspaceId: 'ws_clugls3tn000lwfotbpy30304',
    clicks: 0,
    lastClicked: null,
    createdAt: '2024-04-17T10:31:00.398Z',
    updatedAt: '2024-04-17T10:31:00.398Z',
    projectId: 'clugls3tn000lwfotbpy30304'
  }
  ```
</ResponseExample>


# List

Retrieve a list of links for the authenticated workspace. The list will be paginated and the provided query parameters allow filtering the returned links.

## Arguments

<ParamField body="domain" type="string">
  The domain to filter the links by. E.g. `ac.me`. If not provided, all links
  for the workspace will be returned.
</ParamField>

<ParamField body="tagId" type="string">
  The tag ID to filter the links by. This field is deprecated – use `tagIds`
  instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The tag IDs to filter the links by.
</ParamField>

<ParamField body="search" type="string">
  The search term to filter the links by. The search term will be matched
  against the short link slug and the destination URL.
</ParamField>

<ParamField body="userId" type="string">
  The user ID to filter the links by.
</ParamField>

<ParamField body="showArchived" type="boolean" default={false}>
  Whether to include archived links in the response. Defaults to `false` if not
  provided.
</ParamField>

<ParamField body="withTags" type="boolean" default={false}>
  Whether to include tags in the response. Defaults to `false` if not provided.
</ParamField>

<ParamField body="sort" type="string" default="createdAt">
  The field to sort the links by. The default is `createdAt`, and sort order is
  always descending. Available options are `createdAt`, `clicks`, and
  `lastClicked`.
</ParamField>

<ParamField body="page" type="number">
  The page number for pagination (each page contains `100` links).
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  // Find links by domain
  const { result } = await dub.links.list({
    domain: "dub.sh",
  });

  // Find links by tagIds
  const { result } = await dub.links.list({
    tagIds: ["clv3mewk30001mq0rxl3j3frn"],
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  [
    {
      id: "clv3o9p9q000au1h0mc7r6l63",
      domain: "dub.sh",
      key: "e7qzMuI",
      externalId: "123",
      url: "https://www.google.com",
      archived: false,
      expiresAt: null,
      expiredUrl: null,
      password: null,
      proxy: false,
      title: null,
      description: null,
      image: null,
      rewrite: false,
      ios: null,
      android: null,
      geo: null,
      publicStats: false,
      tagId: null,
      tags: [],
      comments: null,
      shortLink: "https://dub.sh/e7qzMuI",
      qrCode: "https://api.dub.co/qr?url=https://dub.sh/e7qzMuI?qr=1",
      utmSource: null,
      utmMedium: null,
      utmCampaign: null,
      utmTerm: null,
      utmContent: null,
      userId: "cludszk1h0000wmd2e0ea2b0p",
      workspaceId: "ws_clugls3tn000lwfotbpy30304",
      clicks: 0,
      lastClicked: null,
      createdAt: "2024-04-17T10:31:00.398Z",
      updatedAt: "2024-04-17T10:31:00.398Z",
      projectId: "clugls3tn000lwfotbpy30304",
    },
  ];
  ```
</ResponseExample>


# Update

Edit a link for the authenticated workspace. Support partial updates.

## Arguments

<ParamField body="url" type="string" required>
  The destination URL of the short link.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or `dub.sh` if the workspace has no domains).
</ParamField>

<ParamField body="key" type="string">
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ParamField>

<ParamField body="externalId" type="string | null">
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future.
</ParamField>

<ParamField body="prefix" type="string">
  The prefix of the short link slug for randomly-generated keys (e.g. if prefix
  is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
  key is provided.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether the short link is archived.
</ParamField>

<ParamField body="publicStats" type="boolean" default={false}>
  Whether the short link's stats are publicly accessible.
</ParamField>

<ParamField body="tagId" type="string | null">
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tagIds` instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The unique IDs of the tags assigned to the short link.
</ParamField>

<ParamField body="comments" type="string | null">
  The comments for the short link.
</ParamField>

<ParamField body="expiresAt" type="string | null">
  The date and time when the short link will expire at.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  The URL to redirect to when the short link has expired.
</ParamField>

<ParamField body="password" type="string | null">
  The password required to access the destination URL of the short link.
</ParamField>

<ParamField body="proxy" type="boolean" default={false}>
  Whether the short link uses Custom Social Media Cards feature.
</ParamField>

<ParamField body="title" type="string | null">
  The title of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="description" type="string | null">
  The description of the short link generated via `api.dub.co/metatags`. Will be
  used for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="image" type="string | null">
  The image of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="rewrite" type="boolean" default={false}>
  Whether the short link uses link cloaking.
</ParamField>

<ParamField body="ios" type="string | null">
  The iOS destination URL for the short link for iOS device targeting.
</ParamField>

<ParamField body="android" type="string | null">
  The Android destination URL for the short link for Android device targeting.
</ParamField>

<ParamField body="utm_source" type="string | null">
  The UTM source of the short link. If set, this will populate or override the
  UTM source in the destination URL.
</ParamField>

<ParamField body="utm_medium" type="string | null">
  The UTM medium of the short link. If set, this will populate or override the
  UTM medium in the destination URL.
</ParamField>

<ParamField body="utm_campaign" type="string | null">
  The UTM campaign of the short link. If set, this will populate or override the
  UTM campaign in the destination URL.
</ParamField>

<ParamField body="utm_term" type="string | null">
  The UTM term of the short link. If set, this will populate or override the UTM
  term in the destination URL.
</ParamField>

<ParamField body="utm_content" type="string | null">
  The UTM content of the short link. If set, this will populate or override the
  UTM content in the destination URL.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  // Update a link by its linkId
  await dub.links.update("clv3o9p9q000au1h0mc7r6l63", {
    url: "https://www.google.com",
  });

  // Update a link by its externalId
  await dub.links.update("ext_123", {
    url: "https://www.google.com",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3o9p9q000au1h0mc7r6l63',
    domain: 'dub.sh',
    key: 'e7qzMuI',
    externalId: "123",
    url: 'https://www.google.com',
    archived: false,
    expiresAt: null,
    expiredUrl: null,
    password: null,
    proxy: false,
    title: null,
    description: null,
    image: null,
    rewrite: false,
    ios: null,
    android: null,
    geo: null,
    publicStats: false,
    tagId: 'clv3j1h7o00032u1ngo8luv84',
    tags: [
      {
        id: 'clv3j1h7o00032u1ngo8luv84',
        name: 'news',
        color: 'blue'
      }
    ],
    comments: null,
    shortLink: 'https://dub.sh/e7qzMuI',
    qrCode: 'https://api.dub.co/qr?url=https://dub.sh/e7qzMuI?qr=1',
    utmSource: null,
    utmMedium: null,
    utmCampaign: null,
    utmTerm: null,
    utmContent: null,
    userId: 'cludszk1h0000wmd2e0ea2b0p',
    workspaceId: 'ws_clugls3tn000lwfotbpy30304',
    clicks: 0,
    lastClicked: null,
    createdAt: '2024-04-17T10:31:00.398Z',
    updatedAt: '2024-04-17T10:31:00.398Z',
    projectId: 'clugls3tn000lwfotbpy30304'
  }
  ```
</ResponseExample>


# Upsert

Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created.

## Arguments

<ParamField body="url" type="string" required>
  The destination URL of the short link.
</ParamField>

<ParamField body="domain" type="string">
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or `dub.sh` if the workspace has no domains).
</ParamField>

<ParamField body="key" type="string">
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ParamField>

<ParamField body="externalId" type="string | null">
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future.
</ParamField>

<ParamField body="prefix" type="string">
  The prefix of the short link slug for randomly-generated keys (e.g. if prefix
  is `/c/`, generated keys will be in the `/c/:key` format). Will be ignored if
  key is provided.
</ParamField>

<ParamField body="archived" type="boolean" default={false}>
  Whether the short link is archived.
</ParamField>

<ParamField body="publicStats" type="boolean" default={false}>
  Whether the short link's stats are publicly accessible.
</ParamField>

<ParamField body="tagId" type="string | null">
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tagIds` instead.
</ParamField>

<ParamField body="tagIds" type="string">
  The unique IDs of the tags assigned to the short link.
</ParamField>

<ParamField body="comments" type="string | null">
  The comments for the short link.
</ParamField>

<ParamField body="expiresAt" type="string | null">
  The date and time when the short link will expire at.
</ParamField>

<ParamField body="expiredUrl" type="string | null">
  The URL to redirect to when the short link has expired.
</ParamField>

<ParamField body="password" type="string | null">
  The password required to access the destination URL of the short link.
</ParamField>

<ParamField body="proxy" type="boolean" default={false}>
  Whether the short link uses Custom Social Media Cards feature.
</ParamField>

<ParamField body="title" type="string | null">
  The title of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="description" type="string | null">
  The description of the short link generated via `api.dub.co/metatags`. Will be
  used for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="image" type="string | null">
  The image of the short link generated via `api.dub.co/metatags`. Will be used
  for Custom Social Media Cards if proxy is true.
</ParamField>

<ParamField body="rewrite" type="boolean" default={false}>
  Whether the short link uses link cloaking.
</ParamField>

<ParamField body="ios" type="string | null">
  The iOS destination URL for the short link for iOS device targeting.
</ParamField>

<ParamField body="android" type="string | null">
  The Android destination URL for the short link for Android device targeting.
</ParamField>

<ParamField body="utm_source" type="string | null">
  The UTM source of the short link. If set, this will populate or override the
  UTM source in the destination URL.
</ParamField>

<ParamField body="utm_medium" type="string | null">
  The UTM medium of the short link. If set, this will populate or override the
  UTM medium in the destination URL.
</ParamField>

<ParamField body="utm_campaign" type="string | null">
  The UTM campaign of the short link. If set, this will populate or override the
  UTM campaign in the destination URL.
</ParamField>

<ParamField body="utm_term" type="string | null">
  The UTM term of the short link. If set, this will populate or override the UTM
  term in the destination URL.
</ParamField>

<ParamField body="utm_content" type="string | null">
  The UTM content of the short link. If set, this will populate or override the
  UTM content in the destination URL.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the short link.
</ResponseField>

<ResponseField name="domain" type="string" required>
  The domain of the short link. If not provided, the primary domain for the
  workspace will be used (or dub.sh if the workspace has no domains).
</ResponseField>

<ResponseField name="key" type="string" required>
  The short link slug. If not provided, a random 7-character slug will be
  generated.
</ResponseField>

<ResponseField name="externalId" type="string | null" required>
  This is the ID of the link in your database. If set, it can be used to
  identify the link in the future. Must be prefixed with `ext_` when provided to
  `links.get`, `links.update`, and `links.delete` methods.
</ResponseField>

<ResponseField name="url" type="string" required>
  The destination URL of the short link.
</ResponseField>

<ResponseField name="archived" type="boolean" required default="false">
  Whether the short link is archived.
</ResponseField>

<ResponseField name="expiresAt" type="string | null" required>
  The date and time when the short link will expire in ISO-8601 format.
</ResponseField>

<ResponseField name="expiredUrl" type="string | null" required>
  The URL to redirect to when the short link has expired.
</ResponseField>

<ResponseField name="password" type="string | null" required>
  The password required to access the destination URL of the short link.
</ResponseField>

<ResponseField name="proxy" type="boolean" required default="false">
  Whether the short link uses Custom Social Media Cards feature.
</ResponseField>

<ResponseField name="title" type="string | null" required>
  The title of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="description" type="string | null" required>
  The description of the short link generated via api.dub.co/metatags. Will be
  used for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="image" type="string | null" required>
  The image of the short link generated via api.dub.co/metatags. Will be used
  for Custom Social Media Cards if proxy is true.
</ResponseField>

<ResponseField name="rewrite" type="boolean" required default="false">
  Whether the short link uses link cloaking.
</ResponseField>

<ResponseField name="ios" type="string | null" required>
  The iOS destination URL for the short link for iOS device targeting.
</ResponseField>

<ResponseField name="android" type="string | null" required>
  The Android destination URL for the short link for Android device targeting.
</ResponseField>

<ResponseField name="publicStats" type="boolean" required default="false">
  Whether the short link's stats are publicly accessible.
</ResponseField>

<ResponseField name="tagId" type="string | null" required>
  The unique ID of the tag assigned to the short link. This field is deprecated
  – use `tags` instead.
</ResponseField>

<ResponseField name="tags" type="object[] | null" required>
  The tags assigned to the short link.

  <Expandable title="properties">
    <ResponseField name="tags.id" type="string" required>
      The unique ID of the tag.
    </ResponseField>

    <ResponseField name="tags.name" type="string" required>
      The name of the tag.
    </ResponseField>

    <ResponseField name="tags.color" type="string" required>
      The color of the tag.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="comments" type="string | null" required>
  The comments for the short link.
</ResponseField>

<ResponseField name="shortLink" type="string" required>
  The full URL of the short link, including the https protocol (e.g.
  `https://dub.sh/try`).
</ResponseField>

<ResponseField name="qrCode" type="string" required>
  The full URL of the QR code for the short link (e.g.
  `https://api.dub.co/qr?url=https://dub.sh/try`).
</ResponseField>

<ResponseField name="utm_source" type="string | null" required>
  The UTM source of the short link.
</ResponseField>

<ResponseField name="utm_medium" type="string | null" required>
  The UTM medium of the short link.
</ResponseField>

<ResponseField name="utm_campaign" type="string | null" required>
  The UTM campaign of the short link.
</ResponseField>

<ResponseField name="utm_term" type="string | null" required>
  The UTM term of the short link.
</ResponseField>

<ResponseField name="utm_content" type="string | null" required>
  The UTM content of the short link.
</ResponseField>

<ResponseField name="userId" type="string" required>
  The user ID of the creator of the short link.
</ResponseField>

<ResponseField name="workspaceId" type="string" required>
  The workspace ID of the short link.
</ResponseField>

<ResponseField name="clicks" type="number" required default="0">
  The number of clicks on the short link.
</ResponseField>

<ResponseField name="lastClicked" type="string | null" required>
  The date and time when the short link was last clicked.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the short link was created.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  The date and time when the short link was last updated.
</ResponseField>

<ResponseField name="projectId" type="string" required>
  The project ID of the short link. This field is deprecated – use `workspaceId`
  instead.
</ResponseField>

<RequestExample>
  ```ts
  const { shortLink } = await dub.links.upsert({
    url: "https://www.google.com",
  });

  console.log(shortLink); // will always be https://dub.sh/xnjk23d
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3o9p9q000au1h0mc7r6l63',
    domain: 'dub.sh',
    key: 'e7qzMuI',
    externalId: "123",
    url: 'https://www.google.com',
    archived: false,
    expiresAt: null,
    expiredUrl: null,
    password: null,
    proxy: false,
    title: null,
    description: null,
    image: null,
    rewrite: false,
    ios: null,
    android: null,
    geo: null,
    publicStats: false,
    tagId: null,
    tags: [],
    comments: null,
    shortLink: 'https://dub.sh/e7qzMuI',
    qrCode: 'https://api.dub.co/qr?url=https://dub.sh/e7qzMuI?qr=1',
    utmSource: null,
    utmMedium: null,
    utmCampaign: null,
    utmTerm: null,
    utmContent: null,
    userId: 'cludszk1h0000wmd2e0ea2b0p',
    workspaceId: 'ws_clugls3tn000lwfotbpy30304',
    clicks: 0,
    lastClicked: null,
    createdAt: '2024-04-17T10:31:00.398Z',
    updatedAt: '2024-04-17T10:31:00.398Z',
    projectId: 'clugls3tn000lwfotbpy30304'
  }
  ```
</ResponseExample>


# dub.metatags.get

Retrieve the metatags for a URL.

## Arguments

<ParamField body="url" type="string" required>
  The URL to retrieve metatags for.
</ParamField>

## Response

<ResponseField name="title" type="string | null" required>
  The meta title tag for the URL.
</ResponseField>

<ResponseField name="id" type="string | null" required>
  The meta description tag for the URL.
</ResponseField>

<ResponseField name="id" type="string | null" required>
  The OpenGraph image for the URL.
</ResponseField>

<RequestExample>
  ```ts
  await dub.metatags.get({
    url: "https://dub.co",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    title: 'Dub.co - Link Management for Modern Marketing Teams',
    description: 'Dub.co is the open-source link management infrastructure ...',
    image: 'https://assets.dub.co/thumbnail.jpg'
  }
  ```
</ResponseExample>


# Overview

TypeScript SDK for Dub.co

## Install

<Tabs>
  <Tab title="npm">
    ```bash
    npm install dub
    ```
  </Tab>

  <Tab title="yarn">
    ```bash
    yarn add dub zod # zod is a peer dependency
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash
    pnpm add dub 
    ```
  </Tab>
</Tabs>

## Options

The SDK constructor accepts an options object with the following properties:

<ParamField body="token" type="string" required>
  The API token for the Dub.co API. You can create your token from the [Dub.co
  dashboard](https://app.dub.co/settings/tokens).
</ParamField>

## Usage

```typescript
import { Dub } from "dub";

const dub = new Dub({
  token: "DUB_API_KEY",
});

await dub.links.create({
  url: "https://google.com",
});
```


# dub.tags.create

Create a new tag for the authenticated workspace.

## Arguments

<ParamField body="tag" type="string" required>
  The name of the tag to create.
</ParamField>

<ParamField body="color" type="string">
  The color of the tag. If not provided, a random color will be used from the
  list: `red`, `yellow`, `green`, `blue`, `purple`, `pink`, `brown`.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the tag.
</ResponseField>

<ResponseField name="name" type="string" required>
  The name of the tag.
</ResponseField>

<ResponseField name="color" type="string" required>
  The color of the tag.
</ResponseField>

<RequestExample>
  ```ts
  await dub.tags.create({
    tag: "news",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: "clv3j1h7o00032u1ngo8luv84",
    name: "news",
    color: "blue"
  }
  ```
</ResponseExample>


# dub.tags.list

Retrieve a list of tags for the authenticated workspace.

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the tag.
</ResponseField>

<ResponseField name="name" type="string" required>
  The name of the tag.
</ResponseField>

<ResponseField name="color" type="string" required>
  The color of the tag.
</ResponseField>

<RequestExample>
  ```ts
  await dub.tags.list();
  ```
</RequestExample>

<ResponseExample>
  ```ts
  [
    {
      id: "clv3j1h7o00032u1ngo8luv84",
      name: "news",
      color: "blue",
    },
    {
      id: "cluibmjj5000975qol4hnx0t7",
      name: "work",
      color: "red",
    },
  ];
  ```
</ResponseExample>


# dub.tags.update

Update a tag for the authenticated user.

## Arguments

<ParamField body="name" type="string">
  The name of the tag to update.
</ParamField>

<ParamField body="color" type="string">
  The color of the tag.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the tag.
</ResponseField>

<ResponseField name="name" type="string" required>
  The name of the tag.
</ResponseField>

<ResponseField name="color" type="string" required>
  The color of the tag.
</ResponseField>

<RequestExample>
  ```ts
  await dub.tags.update("cluibmjj5000975qol4hnx0t7", {
    name: "Jobs",
    color: "red",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: "clv3j1h7o00032u1ngo8luv84",
    name: "news",
    color: "blue"
  }
  ```
</ResponseExample>


# dub.workspaces.get

Retrieve a workspace for the authenticated user.

## Arguments

<ParamField body="idOrSlug" type="string" required>
  The unique ID or slug of the workspace.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the workspace.
</ResponseField>

<ResponseField name="name" type="string" required>
  The name of the workspace.
</ResponseField>

<ResponseField name="slug" type="string" required>
  The slug of the workspace.
</ResponseField>

<ResponseField name="logo" type="string | null" required>
  The logo of the workspace.
</ResponseField>

<ResponseField name="usage" type="number" required>
  The usage of the workspace.
</ResponseField>

<ResponseField name="usageLimit" type="number" required>
  The usage limit of the workspace.
</ResponseField>

<ResponseField name="linksUsage" type="number" required>
  The links usage of the workspace.
</ResponseField>

<ResponseField name="linksLimit" type="number" required>
  The links limit of the workspace.
</ResponseField>

<ResponseField name="domainsLimit" type="number" required>
  The domains limit of the workspace.
</ResponseField>

<ResponseField name="tagsLimit" type="number" required>
  The tags limit of the workspace.
</ResponseField>

<ResponseField name="usersLimit" type="number" required>
  The users limit of the workspace.
</ResponseField>

<ResponseField name="plan" type="string" required>
  The plan of the workspace.
</ResponseField>

<ResponseField name="stripeId" type="string | null" required>
  The Stripe ID of the workspace.
</ResponseField>

<ResponseField name="billingCycleStart" type="number" required>
  The date and time when the billing cycle starts for the workspace.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the workspace was created.
</ResponseField>

<ResponseField name="users" type="object[]" required>
  The role of the authenticated user in the workspace.

  <Expandable title="properties">
    <ResponseField name="users.role" type="string" required>
      The role of the authenticated user in the workspace.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="domains" type="object[]" required>
  The domains of the workspace.

  <Expandable title="properties">
    <ResponseField name="domains.slug" type="string">
      The domain of the workspace.
    </ResponseField>

    <ResponseField name="domains.primary" type="string">
      Indicates if the domain is the primary domain.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```ts
  // Retrieve a workspace by ID
  await dub.workspaces.get({
    idOrSlug: "clv3mw4qb00008tvuw86c53db,
  });

  // Retrieve a workspace by slug
  await dub.workspaces.get({
    idOrSlug: "acme",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3mw4qb00008tvuw86c53db',
    name: 'Acme',
    slug: 'acme',
    logo: null,
    usage: 0,
    usageLimit: 1000,
    linksUsage: 0,
    linksLimit: 25,
    domainsLimit: 3,
    tagsLimit: 5,
    usersLimit: 1,
    plan: 'free',
    stripeId: null,
    billingCycleStart: 17,
    createdAt: '2024-04-17T09:52:27.635Z',
    users: [ { role: 'owner' } ],
    domains: []
  }
  ```
</ResponseExample>


# dub.workspaces.update

Update a workspace for the authenticated user.

## Arguments

<ParamField body="idOrSlug" type="string" required>
  The unique ID or slug of the workspace.
</ParamField>

<ParamField body="name" type="string">
  The name of the workspace.
</ParamField>

<ParamField body="slug" type="string">
  The slug of the workspace.
</ParamField>

## Response

<ResponseField name="id" type="string" required>
  The unique ID of the workspace.
</ResponseField>

<ResponseField name="name" type="string" required>
  The name of the workspace.
</ResponseField>

<ResponseField name="slug" type="string" required>
  The slug of the workspace.
</ResponseField>

<ResponseField name="logo" type="string | null" required>
  The logo of the workspace.
</ResponseField>

<ResponseField name="usage" type="number" required>
  The usage of the workspace.
</ResponseField>

<ResponseField name="usageLimit" type="number" required>
  The usage limit of the workspace.
</ResponseField>

<ResponseField name="linksUsage" type="number" required>
  The links usage of the workspace.
</ResponseField>

<ResponseField name="linksLimit" type="number" required>
  The links limit of the workspace.
</ResponseField>

<ResponseField name="domainsLimit" type="number" required>
  The domains limit of the workspace.
</ResponseField>

<ResponseField name="tagsLimit" type="number" required>
  The tags limit of the workspace.
</ResponseField>

<ResponseField name="usersLimit" type="number" required>
  The users limit of the workspace.
</ResponseField>

<ResponseField name="plan" type="string" required>
  The plan of the workspace.
</ResponseField>

<ResponseField name="stripeId" type="string | null" required>
  The Stripe ID of the workspace.
</ResponseField>

<ResponseField name="billingCycleStart" type="number" required>
  The date and time when the billing cycle starts for the workspace.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  The date and time when the workspace was created.
</ResponseField>

<ResponseField name="users" type="object[]" required>
  The role of the authenticated user in the workspace.

  <Expandable title="properties">
    <ResponseField name="users.role" type="string" required>
      The role of the authenticated user in the workspace.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="domains" type="object[]" required>
  The domains of the workspace.

  <Expandable title="properties">
    <ResponseField name="domains.slug" type="string">
      The domain of the workspace.
    </ResponseField>

    <ResponseField name="domains.primary" type="string">
      Indicates if the domain is the primary domain.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```ts
  // Update a workspace by its slug
  await dub.workspaces.update("acme", {
    name: "Acme Inc",
  });

  // Update a workspace by its ID
  await dub.workspaces.update("ws_clv3mw4qb00008tvuw86c53db", {
    name: "Acme Inc",
  });
  ```
</RequestExample>

<ResponseExample>
  ```ts
  {
    id: 'clv3mw4qb00008tvuw86c53db',
    name: 'Acme',
    slug: 'acme',
    logo: null,
    usage: 0,
    usageLimit: 1000,
    linksUsage: 0,
    linksLimit: 25,
    domainsLimit: 3,
    tagsLimit: 5,
    usersLimit: 1,
    plan: 'free',
    stripeId: null,
    billingCycleStart: 17,
    createdAt: '2024-04-17T09:52:27.635Z',
    users: [ { role: 'owner' } ],
    domains: []
  }
  ```
</ResponseExample>


# Self-hosting Dub.co

An end-to-end guide on how to self-host Dub.co – the open-source link management platform.

<Frame>
  <img src="https://mintlify.s3-us-west-1.amazonaws.com/dub/images/logo-background-gradient.png" alt="Dub.co Logo on a gradient background" width="1200" height="630" />
</Frame>

You can self-host Dub.co on your own servers and cloud infrastructure for greater control over your data and design. This guide will walk you through the entire process of setting up Dub.co on your own servers.

## Prerequisites

Before you begin, make sure you have the following:

*   A [GitHub](https://github.com/) account
*   A [Tinybird](https://www.tinybird.co/) account
*   An [Upstash](https://upstash.com/) account
*   A [PlanetScale](https://planetscale.com/) account
*   A [Vercel](https://vercel.com/) account
*   Either a [Cloudflare](https://www.cloudflare.com/) or [AWS](https://aws.com) account

You'll also need a custom domain that you will be using for your Dub.co instance, with an optional custom short domain for your links.

In this guide, we'll use `acme.com` as a placeholder for your custom domain, and `ac.me` as a placeholder for your custom short domain.

## Step 1: Local setup

First, you'll need to clone the Dub.co repo and install the dependencies.

<Steps>
  <Step title="Clone the repo">
    First, clone the [Dub repo](https://d.to/github) into a public GitHub repository. If you are planning to distribute the code or allow users to interact with the code remotely (e.g., as part of a hosted application), make sure to provide source access (including modifications) as required by the [AGPLv3 license](https://d.to/license).

    ```bash Terminal
    git clone https://github.com/dubinc/dub.git
    ```
  </Step>

  <Step title="Install dependencies">
    Run the following command to install the dependencies:

    ```bash Terminal
    pnpm i
    ```
  </Step>

  <Step title="Remove unnecessary files">
    Delete the `apps/web/vercel.json` file since cron jobs are not required for the self-hosted version:

    ```bash Terminal
    rm apps/web/vercel.json
    ```
  </Step>

  <Step title="Set up environment variables">
    Convert the `.env.example` file to `.env`. You can start filling in the first few environment variables:

    ```bash Terminal
    # The domain that your app will be hosted on
    NEXT_PUBLIC_APP_DOMAIN=acme.com
    # The short domain that your app will be using (could be the same as the above)
    NEXT_PUBLIC_APP_SHORT_DOMAIN=ac.me
    # The ID of the Vercel team that your app will be deployed to: https://vercel.com/docs/accounts/create-a-team#find-your-team-id
    TEAM_ID_VERCEL=
    # The unique access token for your Vercel account: https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token
    AUTH_BEARER_TOKEN=
    ```

    You will fill in the remaining environment variables in the following steps.
  </Step>
</Steps>

## Step 2: Set up Tinybird Clickhouse database

Next, you'll need to set up the [Tinybird](https://tinybird.co) Clickhouse database. This will be used to store time-series click events data.

<Steps>
  <Step title="Create Tinybird Workspace">
    In your [Tinybird](https://tinybird.co/) account, create a new Workspace.

    Copy your `admin` [Auth Token](https://www.tinybird.co/docs/concepts/auth-tokens.html). Paste this token as the `TINYBIRD_API_KEY` environment variable in your `.env` file.
  </Step>

  <Step title="Install Tinybird CLI and authenticate">
    In your newly-cloned Dub.co repo, navigate to the `packages/tinybird` directory.

    Install the Tinybird CLI with `pip install tinybird-cli` (requires Python >= 3.8).

    Run `tb auth` and paste your `admin` Auth Token.
  </Step>

  <Step title="Publish Tinybird datasource and endpoints">
    Run `tb push` to publish the datasource and endpoints in the `packages/tinybird` directory. You should see the following output (truncated for brevity):

    ```bash Terminal
    $ tb push

    ** Processing ./datasources/click_events.datasource
    ** Processing ./endpoints/clicks.pipe
    ...
    ** Building dependencies
    ** Running 'click_events'
    ** 'click_events' created
    ** Running 'device'
    ** => Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ** Token device_endpoint_read_8888 not found, creating one
    ** => Test endpoint with:
    ** $ curl https://api.us-east.tinybird.co/v0/pipes/device.json?token=p.ey...NWeaoTLM
    ** 'device' created
    ...
    ```
  </Step>

  <Step title="Set up Tinybird API base URL">
    You will then need to update your [Tinybird API base URL](https://www.tinybird.co/docs/api-reference/api-reference.html#regions-and-endpoints) to match the region of your database.

    From the previous step, take note of the **Test endpoint** URL. It should look something like this:

    ```bash Terminal
    Test endpoint at https://api.us-east.tinybird.co/v0/pipes/device.json
    ```

    Copy the base URL and paste it as the `TINYBIRD_API_URL` environment variable in your `.env` file.

    ```bash Terminal
    TINYBIRD_API_URL=https://api.us-east.tinybird.co
    ```
  </Step>
</Steps>

## Step 3: Set up Upstash Redis database

Next, you'll need to set up the [Upstash](https://upstash.com) Redis database. This will be used to cache link metadata and serve link redirects.

<Steps>
  <Step title="Create Upstash database">
    In your [Upstash account](https://console.upstash.com/), create a new database.

    For better performance & read times, we recommend setting up a global database with several read regions.

    <Frame>![Upstash Redis database](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-create-db.png)</Frame>
  </Step>

  <Step title="Set up Upstash environment variables">
    Once your database is created, copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the **REST API** section into your `.env` file.

    <Frame>![Upstash Redis tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-redis-tokens.png)</Frame>

    Navigate to the [QStash tab](https://console.upstash.com/qstash) and copy the `QSTASH_TOKEN`, `QSTASH_CURRENT_SIGNING_KEY`, and `QSTASH_NEXT_SIGNING_KEY` from the **Request Builder** section into your `.env` file.

    <Frame>
      ![Upstash QStash tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-qstash-tokens.png)
    </Frame>
  </Step>
</Steps>

## Step 4: Set up PlanetScale MySQL database

Next, you'll need to set up a [PlanetScale](https://planetscale.com/)-compatible MySQL database. This will be used to store user data and link metadata.

{/* prettier-ignore */}

<Note>
  PlanetScale recently [removed their free
  tier](https://planetscale.com/blog/planetscale-forever), so you'll need to pay
  for this option. A cheaper alternative is to use a [MySQL database on
  Railway](https://railway.app/template/mysql) (\$5/month).

  For [local development](local-development), we recommend using a [local MySQL database
  with PlanetScale simulator](local-development#option-1-local-mysql-database-with-planetscale-simulator-recommended) (100% free).
</Note>

<Steps>
  <Step title="Create PlanetScale database">
    In your [PlanetScale account](https://app.planetscale.com/), create a new database.

    Once your database is created, you'll be prompted to select your language or Framework. Select **Prisma**.

    <Frame>
      ![PlanetScale choose framework](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-choose-framework.png)
    </Frame>
  </Step>

  <Step title="Set up PlanetScale environment variables">
    Then, you'll have to create a new password for your database. Once the password is created, scroll down to the **Add credentials to .env** section and copy the `DATABASE_URL` into your `.env` file.

    <Frame>
      ![PlanetScale add credentials](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-add-credentials.png)
    </Frame>
  </Step>

  <Step title="Generate Prisma client and create database tables">
    In your Dub.co codebase, navigate to `apps/web/prisma/schema.prisma` and replace all the columns in the `DefaultDomains` model to the normalized version of your custom short domain (removing the `.` character).

    For example, if your custom short domain is `ac.me`, your `DefaultDomains` model should look like this:

    ```prisma apps/web/prisma/schema.prisma
    model DefaultDomains {
      id          String   @id @default(cuid())
      acme        Boolean  @default(true)
      projectId   String   @unique
      project     Project  @relation(fields: [projectId], references: [id], onDelete: Cascade)
    }
    ```

    In the terminal, navigate to the `apps/web` directory and run the following command to generate the Prisma client:

    ```bash Terminal
    npx prisma generate
    ```

    Then, create the database tables with the following command:

    ```bash Terminal
    npx prisma db push
    ```
  </Step>
</Steps>

## Step 5: Set up GitHub OAuth

Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to Dub.co with your GitHub account.

Don't forget to set the following Callback URLs:

*   `https://app.acme.com/api/auth/callback/github`
*   `http://localhost:8888/api/auth/callback/github` for local development.

<Info>
  Optional: Set the "Email addresses" account permission to **read-only** in
  order to access private email addresses on GitHub.
</Info>

Once your GitHub App is created, copy the `Client ID` and `Client Secret` into your `.env` file as the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` environment variables.

## Step 6: Set up Cloudflare R2

Dub stores user-generated assets in either S3 or S3-compatible services like [Cloudflare R2](https://cloudflare.com/r2). These include:

*   Project logos
*   User avatars
*   [Custom Social Media Cards](https://dub.co/help/article/custom-social-media-cards) images

We recommend using [Cloudflare R2](https://cloudflare.com/r2) for self-hosting Dub.co, as it's a more cost-effective solution compared to AWS S3. Here's how you can set it up:

<Steps>
  <Step title="Create R2 bucket">
    <Note>You'll need to subscribe to the R2 service if you haven't already.</Note>

    In your [Cloudflare account](https://dash.cloudflare.com/), create a new R2 bucket. We recommend giving your bucket a descriptive name (e.g. `dubassets`) and leaving the remaining settings as is.

    <Frame>![Cloudflare R2 bucket](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-create-bucket.png)</Frame>

    In your bucket settings, copy the **S3 API** value – you'll need it in Step 3.
  </Step>

  <Step title="Set up access to R2">
    From the R2 main page, click **Manage R2 API Tokens** on the right-hand column.

    <Frame>
      ![Cloudflare manage API tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-manage-api-tokens.png)
    </Frame>

    Then, click **Create API Token**.

    <Frame>
      ![Cloudflare R2 API token](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-create-api-token.png)
    </Frame>

    Make sure to name your API token something relevant to the service that will be using the token.

    Give it "Object Read & Write" permissions, and we recommend only applying ito to a single bucket.

    You can leave the remaining settings (TTL, Client IP Address Filtering) as is, and click **Create API Token**.

    After you create you token, copy the `Access Key ID` and `Secret Access Key` values – you'll need them in the next step.
  </Step>

  <Step title="Set up R2 environment variables">
    Once you have your credentials, set them in your `.env` file:

    ```TypeScript .env
    STORAGE_ACCESS_KEY_ID= // this is the Access Key ID value from Step 2
    STORAGE_SECRET_ACCESS_KEY= // this is the Secret Access Key value from Step 2
    STORAGE_ENDPOINT= // this is the S3 API value from Step 1
    ```
  </Step>

  <Step title="Set up R2 domain">
    In order for your images to be publically accessible in R2 you need to setup a domain. You can either use your own domain or an R2.dev subdomain.

    To use your own domain, you'll need to create a CNAME record in your DNS settings that points to your R2 bucket.

    In you plan to use an R2.dev subdomain, make sure you "Allow Access".

    Then set the `STORAGE_BASE_URL` in your `.env` file to the domain you chose.

    ```bash
    STORAGE_BASE_URL={URL your assets as available at} # https://static.example.com
    ```

    <Frame>![Cloudflare R2 domain](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-public-domain.png)</Frame>
  </Step>
</Steps>

## Step 7: Set up Resend (optional)

<Note>
  Note that if you want to use magic link sign-in, this is a required step.
</Note>

Next, you'll need to set up Resend for transactional emails (e.g. magic link emails):

1.  Sign up for Resend and [create your API key here](https://resend.com/api-keys).
2.  Copy the API key into your `.env` file as the `RESEND_API_KEY` environment variable.
3.  You'll then need to set up and verify your domain by [following this guide here](https://resend.com/docs/dashboard/domains/introduction).

## Step 8: Set up Unsplash (optional)

Dub uses Unsplash's API for the [Custom Social Media Cards](https://dub.co/help/article/custom-social-media-cards) feature. You'll need to set up an Unsplash application to get an access key.

<Frame>
  ![Custom social media
  cards](https://assets.dub.co/changelog/custom-social-cards.png)
</Frame>

Check out Unsplash's [official documentation](https://unsplash.com/documentation#creating-a-developer-account) to learn how you can set up the `UNSPLASH_ACCESS_KEY` env var.

## Step 9: Deploy to Vercel

Once you've set up all of the above services, you can now deploy your app to Vercel.

<Steps>
  <Step title="Deploy code to GitHub">
    If you haven't already, push up your cloned repository to GitHub by running the following commands:

    ```bash Terminal
    git add .
    git commit -m "Initial commit"
    git push origin main
    ```
  </Step>

  <Step title="Create a new Vercel project">
    In your [Vercel account](https://vercel.com/), create a new project. Then, select your GitHub repository and click **Import**.

    Make sure that your **Framework Preset** is set to **Next.js** and the **Root Directory** is set to `apps/web`.

    <Frame>
      ![Vercel Framework Preset and Root Directory](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/vercel-framework-preset.png)
    </Frame>

    In the **Environment Variables** section, add all of the environment variables from your `.env` file by copying all of them and pasting it into the first input field. A few notes:

    *   Remove the `PROJECT_ID_VERCEL` environment variable for now since we will only get the project ID after deploying the project.
    *   Replace the `NEXTAUTH_URL` environment variable with the app domain that you will be using (e.g. `https://app.acme.com`).

    Click on **Deploy** to deploy your project.

    <Tip>
      If you get a `No Output Directory called "public" was found after the build
            completed` error, make sure that your [Vercel deployment
      settings](https://vercel.com/docs/deployments/configure-a-build) to make sure that they match the following:

      <Frame>
        ![Vercel Deploy settings](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/vercel-deploy-settings.png)
      </Frame>
    </Tip>
  </Step>

  <Step title="Add required environment variables">
    Once the project deploys, retrieve your [Vercel project ID](https://vercel.com/docs/projects/overview#project-id) and add it as the `PROJECT_ID_VERCEL` environment variable – both in your `.env` file and in your newly created Vercel project's settings (under **Settings > Environment Variables**)

    Add both the `NEXT_PUBLIC_APP_DOMAIN` and `NEXT_PUBLIC_APP_SHORT_DOMAIN` as domains in your Vercel project's settings (under **Settings** > **Domains**). You can follow this guide to learn [how to set up a custom domain on Vercel](https://vercel.com/docs/projects/domains/add-a-domain).
  </Step>

  <Step title="Redeploy your Vercel project">
    Go back to the **Deployments** page and redeploy your project.

    Once the deployment is complete, you should be able to visit your app domain (e.g. `https://app.acme.com`) and see the following login page:

    <Frame>![Whitelabeled Login](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/whitelabeled-login.png)</Frame>
  </Step>
</Steps>

## Caveats

This guide is meant to be a starting point for self-hosting Dub.co. It currently depends on the following services to work:

*   [Tinybird](https://www.tinybird.co/) for the analytics database
*   [Upstash](https://upstash.com/) for the Redis database
*   [PlanetScale](https://planetscale.com/) for the MySQL database
*   [Vercel](https://vercel.com/) for hosting & [Edge Middleware](https://vercel.com/docs/functions/edge-middleware)

In the future, we plan to make it easier to self-host Dub.co by making these dependencies optional by swapping them out for native databases (e.g. mysql, redis, clickhouse, [GeoLite2](https://github.com/GitSquared/node-geolite2-redist) etc.)

Also, Docker is currently not supported, but we have a few [open](https://github.com/dubinc/dub/issues/25) [issues](https://github.com/dubinc/dub/issues/378) and [PRs](https://github.com/dubinc/dub/pull/391) for it.