# Bulk create links post /links/bulk Bulk create up to 100 links for the authenticated workspace. 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. # Bulk delete links delete /links/bulk Bulk delete up to 100 links for the authenticated workspace. 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. # 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 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. # 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. Events endpoints require a [Business plan](https://d.to/business) subscription or higher. # 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. Analytics endpoints require a [Pro plan](https://d.to/pro) subscription or higher. # 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. Dub.co's API is now generally available. [Read the announcement](https://dub.co/blog/announcing-dub-api). ## 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 ``` 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 The page number to retrieve. By default, the first page is returned. The number of items to retrieve per page. The default value varies by endpoint. Maximum value is 100. ## Example The following example demonstrates how to retrieve the first page of 10 links: ```bash cURL curl --request GET \ --url https://api.dub.co/links?page=1&pageSize=10 \ --header 'Authorization: Bearer ' ``` ```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) ``` # 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) ```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", ), ] ) ) ``` ### 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. ```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 ``` 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. ```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 ``` # 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: Go to **Settings** > [**API Keys**](https://app.dub.co/settings/tokens) in your workspace. Workspace API keys on Dub 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. Add new API key on Dub 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. 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 ``` 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. ## 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`. 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). ## 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: | 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. You can only set permissions on Secret keys. Publishable keys only have access to certain endpoints, and cannot be restricted. ## 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. Creating an API key associated with a machine user on Dub These machine users will show up on your workspace's **People** tab, but will not contribute to your workspace's user count. Machine user on Dub 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. 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. # 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: 1. Inside the Dub Link Builder, click on the **More options** button and select **Advanced Settings**. You can also use the `A` keyboard shortcut. Dub Link Builder Advanced Settings 2. Under the **Identifier** section, enter a unique value that corresponds to the query parameter you want to use for the click-tracking. Dub Link Builder Identifier 3. Click on the **Save** button to save the changes. 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`: ```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) ``` ### 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. ```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 ( {/* Your app code here */} ); } ``` ```javascript Other Frameworks // include this script tag in your HTML Head tag ``` # 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. Using the package manager of your choice, add the `@dub/analytics` to your project. ```bash npm npm install @dub/analytics ``` ```bash pnpm pnpm add @dub/analytics ``` ```bash yarn yarn add @dub/analytics ``` ```bash bun bun add @dub/analytics ``` If you are using a React framework, you can use the `` component to track conversions on your website. E.g. if you're using Next.js, you can add the `` 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 ( {children} ); } ``` ## Concepts You can pass the following props to the `` component to customize the tracking script: The base URL for the Dub API. This is useful for setting up reverse proxies to avoid adblockers. 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). 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. Specifies the value for the `Domain` Set-Cookie attribute. This is useful for cross-domain tracking. Example: `.example.com` Specifies the `Date` object to be the value for the `Expires` Set-Cookie attribute. Example: `new Date('2024-12-31')` Specifies the number (in days) to be the value for the `Expires` Set-Cookie attribute. Example: `90` Specifies the value for the `Path` Set-Cookie attribute. By default, the path is considered the "default path". Example: `/` The query parameter to listen to for client-side click-tracking (e.g. `?via=abc123`). 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. ## Examples We also have some advanced examples on GitHub: See the full example on GitHub. # 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 ``` # 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 ``` # 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: Add Dub Analytics to your React or Next.js app. Add Dub Analytics to your WordPress site. Add Dub Analytics to your Webflow site. Add Dub Analytics to your Framer site. # Overview Open-source client libraries for the Dub.co API ## Server-side SDKs TypeScript library for the Dub API Go library for the Dub API Python library for the Dub API Ruby library for the Dub API PHP library for the Dub API ## Client-side SDKs Dub analytics SDK # 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. Analytics endpoints require a [Pro plan](https://d.to/pro) subscription or higher. ## Arguments The type of event to retrieve analytics for. Available options: `clicks`, `leads`, and `sales`. 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`. The domain of the short link. The short link slug. The unique ID of the short link on Dub. This is the ID of the link in the your database. Must be prefixed with 'ext\_' when passed as a query parameter. The interval to retrieve analytics for. Available options are `1h`, `24h`, `7d`, `30d`, `90d`, `ytd`, `1y`, and `all`. Takes precedence over `start` and `end`. The start date to retrieve analytics for. Cannot be earlier than September 22, 2022. The start date to retrieve analytics for. Cannot be in the future. If not provided, defaults to the current date. The IANA time zone code for aligning timeseries granularity (e.g. America/New\_York). Defaults to UTC. The continent to retrieve analytics for. Available options are `AF`, `AN`, `AS`, `EU`, `NA`, `OC`, and `SA`. 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` The city to retrieve analytics for. The device to retrieve analytics for. The browser to retrieve analytics for. The OS to retrieve analytics for. The referer to retrieve analytics for. The full referer URL to retrieve analytics for. The URL to retrieve analytics for. The tag ID to retrieve analytics for. Filter for QR code scans. If true, filter for QR code scans only. If false, filter for link clicks only. If undefined, return both. Filter for root domains. If true, filter for domains only. If false, filter for links only. If undefined, return both. ```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 }); ``` # dub.domains.create Create a domain for the authenticated workspace. ## Arguments Name of the domain. Eg. `acme.com`. The type of redirect to use for this domain. redirect rewrite The page your users will get redirected to when they visit your domain. Redirect users to a specific URL when any link under this domain has expired. Whether to archive this domain. false will unarchive a previously archived domain. Provide context to your teammates in the link creation modal by showing them an example of a link to be shortened. ## Response The unique identifier of the domain. The domain name. Whether the domain is verified. Whether the domain is the primary domain for the workspace. Whether the domain is archived. Provide context to your teammates in the link creation modal by showing them an example of a link to be shortened. The URL to redirect to when a link under this domain has expired. The page your users will get redirected to when they visit your domain. The type of redirect to use for this domain. Available values are `redirect` and `rewrite`. The number of clicks on the domain. ```ts await dub.domains.create({ slug: "acme.com", }); ``` ```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 } ``` # 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 Name of the domain. Eg. `acme.com`. ## Response Name of the domain that was deleted. ```ts await dub.domains.delete("acme.com"); ``` ```ts { slug: "acme.com"; } ``` # dub.domains.list Retrieve a list of domains associated with the authenticated workspace. ## Arguments No arguments. ## Response The unique identifier of the domain. The domain name. Whether the domain is verified. Whether the domain is the primary domain for the workspace. Whether the domain is archived. Provide context to your teammates in the link creation modal by showing them an example of a link to be shortened. The URL to redirect to when a link under this domain has expired. The page your users will get redirected to when they visit your domain. The type of redirect to use for this domain. Available values are `redirect` and `rewrite`. The number of clicks on the domain. ```ts const { result } = await dub.domains.list(); ``` ```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, }, ]; ``` # dub.domains.update Edit a domain for the authenticated workspace. ## Arguments Name of the domain. If provided, the existing domain will be updated with the new domain. Eg. `acme.com`. The type of redirect to use for this domain. redirect rewrite The page your users will get redirected to when they visit your domain. Redirect users to a specific URL when any link under this domain has expired. Whether to archive this domain. false will unarchive a previously archived domain. Provide context to your teammates in the link creation modal by showing them an example of a link to be shortened. ## Response The unique identifier of the domain. The domain name. Whether the domain is verified. Whether the domain is the primary domain for the workspace. Whether the domain is archived. Provide context to your teammates in the link creation modal by showing them an example of a link to be shortened. The URL to redirect to when a link under this domain has expired. The page your users will get redirected to when they visit your domain. The type of redirect to use for this domain. Available values are `redirect` and `rewrite`. The number of clicks on the domain. ```ts await dub.domains.update("acme.com", { expiredUrl: "https://acme.com/expired", target: "https://acme.com/home", }); ``` ```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 } ``` # 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. Events endpoints require a [Business plan](https://d.to/business) subscription or higher. ## Arguments The type of event to retrieve events for. Available options: `clicks`, `leads`, and `sales`. The domain of the short link. The short link slug. The unique ID of the short link on Dub. This is the ID of the link in the your database. Must be prefixed with 'ext\_' when passed as a query parameter. The interval to retrieve events for. Available options are `1h`, `24h`, `7d`, `30d`, `90d`, `ytd`, `1y`, and `all`. Takes precedence over `start` and `end`. The start date to retrieve events for. Cannot be earlier than September 22, 2022. The start date to retrieve events for. Cannot be in the future. If not provided, defaults to the current date. The IANA time zone code for aligning timeseries granularity (e.g. America/New\_York). Defaults to UTC. The continent to retrieve events for. Available options are `AF`, `AN`, `AS`, `EU`, `NA`, `OC`, and `SA`. 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` The city to retrieve events for. The device to retrieve events for. The browser to retrieve events for. The OS to retrieve events for. The referer to retrieve events for. The full referer URL to retrieve events for. The URL to retrieve events for. The tag ID to retrieve events for. Filter for QR code scans. If true, filter for QR code scans only. If false, filter for link clicks only. If undefined, return both. Filter for root domains. If true, filter for domains only. If false, filter for links only. If undefined, return both. The number of records to retrieve. Number of records to offset the results by. The field to sort the results by. The order to sort the results by. ```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" }); ``` # Count Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links. ## Arguments The domain to filter the links by. E.g. `ac.me`. If not provided, all links for the workspace will be returned. The tag ID to filter the links by. This field is deprecated – use `tagIds` instead. The tag IDs to filter the links by. The search term to filter the links by. The search term will be matched against the short link slug and the destination URL. The user ID to filter the links by. Whether to include archived links in the response. Defaults to `false` if not provided. Whether to include tags in the response. Defaults to `false` if not provided. The field to group the links by. Available options are `domain` and `tagId`. ## Response The number of links matching the query. ```ts // Count links by domain await dub.links.count({ domain: "dub.sh", }); // Count links by tagIds await dub.links.count({ tagIds: ["clv3mewk30001mq0rxl3j3frn"], }); ``` ```ts 2; ``` # Create Create a new link for the authenticated workspace. ## Arguments The destination URL of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. This is the ID of the link in your database. If set, it can be used to identify the link in the future. 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. Whether the short link is archived. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tagIds` instead. The unique IDs of the tags assigned to the short link. The comments for the short link. The date and time when the short link will expire at. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. The UTM source of the short link. If set, this will populate or override the UTM source in the destination URL. The UTM medium of the short link. If set, this will populate or override the UTM medium in the destination URL. The UTM campaign of the short link. If set, this will populate or override the UTM campaign in the destination URL. The UTM term of the short link. If set, this will populate or override the UTM term in the destination URL. The UTM content of the short link. If set, this will populate or override the UTM content in the destination URL. ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```ts await dub.links.create({ url: "https://www.google.com", }); ``` ```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' } ``` # Create Many Bulk create up to 100 links for the authenticated workspace. ## Arguments The destination URL of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. This is the ID of the link in your database. If set, it can be used to identify the link in the future. 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. Whether the short link is archived. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tagIds` instead. The unique IDs of the tags assigned to the short link. The comments for the short link. The date and time when the short link will expire at. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. The UTM source of the short link. If set, this will populate or override the UTM source in the destination URL. The UTM medium of the short link. If set, this will populate or override the UTM medium in the destination URL. The UTM campaign of the short link. If set, this will populate or override the UTM campaign in the destination URL. The UTM term of the short link. If set, this will populate or override the UTM term in the destination URL. The UTM content of the short link. If set, this will populate or override the UTM content in the destination URL. ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```ts await dub.links.createMany([ { url: "https://www.google.com", }, { url: "https://github.com", }, ]); ``` ```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, ... }, ]; ``` # Delete Delete a link for the authenticated workspace. ## Arguments 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_`. ## Response The id of the link that was deleted. ```ts // Delete a link by linkId await dub.links.delete("clv3o9p9q000au1h0mc7r6l63"); // Delete a link by externalId await dub.links.delete("ext_123"); ``` ```ts { id: "clv3o9p9q000au1h0mc7r6l63"; } ``` # Get Retrieve info for a link by specifying either the domain and key or the linkId or externalId ## Arguments The domain of the link to retrieve. E.g. for `d.to/github`, the domain is `d.to`. The key of the link to retrieve. E.g. for `d.to/github`, the key is `github`. The unique ID of the short link. This is the ID of the link in the your database. Must be prefixed with `ext_` when provided. ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```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", }); ``` ```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' } ``` # 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 The domain to filter the links by. E.g. `ac.me`. If not provided, all links for the workspace will be returned. The tag ID to filter the links by. This field is deprecated – use `tagIds` instead. The tag IDs to filter the links by. The search term to filter the links by. The search term will be matched against the short link slug and the destination URL. The user ID to filter the links by. Whether to include archived links in the response. Defaults to `false` if not provided. Whether to include tags in the response. Defaults to `false` if not provided. The field to sort the links by. The default is `createdAt`, and sort order is always descending. Available options are `createdAt`, `clicks`, and `lastClicked`. The page number for pagination (each page contains `100` links). ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```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"], }); ``` ```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", }, ]; ``` # Update Edit a link for the authenticated workspace. Support partial updates. ## Arguments The destination URL of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. This is the ID of the link in your database. If set, it can be used to identify the link in the future. 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. Whether the short link is archived. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tagIds` instead. The unique IDs of the tags assigned to the short link. The comments for the short link. The date and time when the short link will expire at. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. The UTM source of the short link. If set, this will populate or override the UTM source in the destination URL. The UTM medium of the short link. If set, this will populate or override the UTM medium in the destination URL. The UTM campaign of the short link. If set, this will populate or override the UTM campaign in the destination URL. The UTM term of the short link. If set, this will populate or override the UTM term in the destination URL. The UTM content of the short link. If set, this will populate or override the UTM content in the destination URL. ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```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", }); ``` ```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' } ``` # 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 The destination URL of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. This is the ID of the link in your database. If set, it can be used to identify the link in the future. 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. Whether the short link is archived. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tagIds` instead. The unique IDs of the tags assigned to the short link. The comments for the short link. The date and time when the short link will expire at. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via `api.dub.co/metatags`. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. The UTM source of the short link. If set, this will populate or override the UTM source in the destination URL. The UTM medium of the short link. If set, this will populate or override the UTM medium in the destination URL. The UTM campaign of the short link. If set, this will populate or override the UTM campaign in the destination URL. The UTM term of the short link. If set, this will populate or override the UTM term in the destination URL. The UTM content of the short link. If set, this will populate or override the UTM content in the destination URL. ## Response The unique ID of the short link. 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). The short link slug. If not provided, a random 7-character slug will be generated. 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. The destination URL of the short link. Whether the short link is archived. The date and time when the short link will expire in ISO-8601 format. The URL to redirect to when the short link has expired. The password required to access the destination URL of the short link. Whether the short link uses Custom Social Media Cards feature. The title of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The description of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. The image of the short link generated via api.dub.co/metatags. Will be used for Custom Social Media Cards if proxy is true. Whether the short link uses link cloaking. The iOS destination URL for the short link for iOS device targeting. The Android destination URL for the short link for Android device targeting. Whether the short link's stats are publicly accessible. The unique ID of the tag assigned to the short link. This field is deprecated – use `tags` instead. The tags assigned to the short link. The unique ID of the tag. The name of the tag. The color of the tag. The comments for the short link. The full URL of the short link, including the https protocol (e.g. `https://dub.sh/try`). The full URL of the QR code for the short link (e.g. `https://api.dub.co/qr?url=https://dub.sh/try`). The UTM source of the short link. The UTM medium of the short link. The UTM campaign of the short link. The UTM term of the short link. The UTM content of the short link. The user ID of the creator of the short link. The workspace ID of the short link. The number of clicks on the short link. The date and time when the short link was last clicked. The date and time when the short link was created. The date and time when the short link was last updated. The project ID of the short link. This field is deprecated – use `workspaceId` instead. ```ts const { shortLink } = await dub.links.upsert({ url: "https://www.google.com", }); console.log(shortLink); // will always be https://dub.sh/xnjk23d ``` ```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' } ``` # dub.metatags.get Retrieve the metatags for a URL. ## Arguments The URL to retrieve metatags for. ## Response The meta title tag for the URL. The meta description tag for the URL. The OpenGraph image for the URL. ```ts await dub.metatags.get({ url: "https://dub.co", }); ``` ```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' } ``` # Overview TypeScript SDK for Dub.co ## Install ```bash npm install dub ``` ```bash yarn add dub zod # zod is a peer dependency ``` ```bash pnpm add dub ``` ## Options The SDK constructor accepts an options object with the following properties: The API token for the Dub.co API. You can create your token from the [Dub.co dashboard](https://app.dub.co/settings/tokens). ## 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 The name of the tag to create. The color of the tag. If not provided, a random color will be used from the list: `red`, `yellow`, `green`, `blue`, `purple`, `pink`, `brown`. ## Response The unique ID of the tag. The name of the tag. The color of the tag. ```ts await dub.tags.create({ tag: "news", }); ``` ```ts { id: "clv3j1h7o00032u1ngo8luv84", name: "news", color: "blue" } ``` # dub.tags.list Retrieve a list of tags for the authenticated workspace. ## Response The unique ID of the tag. The name of the tag. The color of the tag. ```ts await dub.tags.list(); ``` ```ts [ { id: "clv3j1h7o00032u1ngo8luv84", name: "news", color: "blue", }, { id: "cluibmjj5000975qol4hnx0t7", name: "work", color: "red", }, ]; ``` # dub.tags.update Update a tag for the authenticated user. ## Arguments The name of the tag to update. The color of the tag. ## Response The unique ID of the tag. The name of the tag. The color of the tag. ```ts await dub.tags.update("cluibmjj5000975qol4hnx0t7", { name: "Jobs", color: "red", }); ``` ```ts { id: "clv3j1h7o00032u1ngo8luv84", name: "news", color: "blue" } ``` # dub.workspaces.get Retrieve a workspace for the authenticated user. ## Arguments The unique ID or slug of the workspace. ## Response The unique ID of the workspace. The name of the workspace. The slug of the workspace. The logo of the workspace. The usage of the workspace. The usage limit of the workspace. The links usage of the workspace. The links limit of the workspace. The domains limit of the workspace. The tags limit of the workspace. The users limit of the workspace. The plan of the workspace. The Stripe ID of the workspace. The date and time when the billing cycle starts for the workspace. The date and time when the workspace was created. The role of the authenticated user in the workspace. The role of the authenticated user in the workspace. The domains of the workspace. The domain of the workspace. Indicates if the domain is the primary domain. ```ts // Retrieve a workspace by ID await dub.workspaces.get({ idOrSlug: "clv3mw4qb00008tvuw86c53db, }); // Retrieve a workspace by slug await dub.workspaces.get({ idOrSlug: "acme", }); ``` ```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: [] } ``` # dub.workspaces.update Update a workspace for the authenticated user. ## Arguments The unique ID or slug of the workspace. The name of the workspace. The slug of the workspace. ## Response The unique ID of the workspace. The name of the workspace. The slug of the workspace. The logo of the workspace. The usage of the workspace. The usage limit of the workspace. The links usage of the workspace. The links limit of the workspace. The domains limit of the workspace. The tags limit of the workspace. The users limit of the workspace. The plan of the workspace. The Stripe ID of the workspace. The date and time when the billing cycle starts for the workspace. The date and time when the workspace was created. The role of the authenticated user in the workspace. The role of the authenticated user in the workspace. The domains of the workspace. The domain of the workspace. Indicates if the domain is the primary domain. ```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", }); ``` ```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: [] } ``` # Self-hosting Dub.co An end-to-end guide on how to self-host Dub.co – the open-source link management platform. Dub.co Logo on a gradient background 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. 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 ``` Run the following command to install the dependencies: ```bash Terminal pnpm i ``` 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 ``` 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 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. 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. 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. 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 ... ``` 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 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. 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. ![Upstash Redis database](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-create-db.png) 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. ![Upstash Redis tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-redis-tokens.png) 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. ![Upstash QStash tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/upstash-qstash-tokens.png) ## 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 */} 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). 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**. ![PlanetScale choose framework](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-choose-framework.png) 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. ![PlanetScale add credentials](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/planetscale-add-credentials.png) 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 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. Optional: Set the "Email addresses" account permission to **read-only** in order to access private email addresses on GitHub. 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: You'll need to subscribe to the R2 service if you haven't already. 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. ![Cloudflare R2 bucket](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-create-bucket.png) In your bucket settings, copy the **S3 API** value – you'll need it in Step 3. From the R2 main page, click **Manage R2 API Tokens** on the right-hand column. ![Cloudflare manage API tokens](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-manage-api-tokens.png) Then, click **Create API Token**. ![Cloudflare R2 API token](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-create-api-token.png) 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. 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 ``` 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 ``` ![Cloudflare R2 domain](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/cloudflare-r2-public-domain.png) ## Step 7: Set up Resend (optional) Note that if you want to use magic link sign-in, this is a required step. 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. ![Custom social media cards](https://assets.dub.co/changelog/custom-social-cards.png) 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. 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 ``` 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`. ![Vercel Framework Preset and Root Directory](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/vercel-framework-preset.png) 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. 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: ![Vercel Deploy settings](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/vercel-deploy-settings.png) 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). 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: ![Whitelabeled Login](https://mintlify.s3-us-west-1.amazonaws.com/dub/images/whitelabeled-login.png) ## 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.