# Error handling ## Error Codes | Error Code | Error Class | Http Status Code | Description | | ------------------ | ----------------- | ---------------- | ------------------------------------------------------------------------------------ | | BAD\_REQUEST | BadRequestError | 400 | The request was invalid or cannot be processed. | | UNAUTHORIZED | UnauthorizedError | 401 | Authentication is required and has failed or has not been provided. | | FORBIDDEN | ForbiddenError | 403 | The request is valid but the server is refusing action. | | NOT\_FOUND | NotFoundError | 404 | The requested resource could not be found. | | VALIDATION\_FAILED | ValidationError | 422 | The request was well-formed but contained invalid data. | | RATE\_LIMITED | RateLimitError | 429 | Too many requests have been made in a given amount of time. | | NETWORK\_ERROR | NetworkError | 0 | No HTTP response status code was received or could be reported for a network request | | SERVER\_ERROR | ServerError | >= 500 | The server encountered an error while processing the request. | | UNKNOWN | | Other | An unknown error occurred. | When a matching Error Code is encountered, the u301 library throws the corresponding error class. Use `isU301Error` to determine whether an error originated from the u301 library. Supplying an Error Code also narrows the TypeScript type. ```typescript import { isU301Error, U301 } from 'u301' try { const u301 = new U301({ apiKey: '', workspaceId: '', debug: true, }) await u301.links.create('https://example.com') } catch (error) { if (isU301Error(error, 'RATE_LIMITED')) { // [!code highlight] console.log('Rate limited, please try again later') } } ``` # Introduction ## Key Features * Shorten long URLs into clean, shareable links. * Use your own custom domain for branded links. * Built-in analytics for clicks, referrers, geography, and devices. * REST API and JavaScript SDK for automation and integrations. * Companion utilities to streamline everyday operations. ## Get Started U301 is a modern URL shortening platform. Beyond the default `u301.co` domain, you can connect your own branded domain to create links that look and feel like your brand. * Create your first short link: 1. Paste a long URL. 2. Choose a domain (`u301.co` or your custom domain). 3. Optionally set a custom slug. 4. Save and share your short link. * Connect a custom domain: * Add a CNAME record that provided in dashboard * Verify ownership and enable the domain in U301. * Use the domain when creating links. * Explore the SDK: * Read the [JavaScript SDK](/u301-js) guide for usage examples. ## Analytics * Track total clicks and trends over time. * See referrers and top traffic sources. * Understand audience geography and device breakdown. * Export data or access analytics via the API. ## API & SDK * Automate link creation, updates, and retrieval of analytics. * JavaScript SDK: see the [guide](/u301-js). * REST endpoints: documentation will be published soon. ## Support Questions or issues? Email `fry@u301.com`. # AI & LLMs.txt import { LLMCopyButton, ViewOptions } from "@/components/page-actions"; LLMs.txt is a small, plain‑text companion to your docs. It gives AI assistants a single, clean source of truth about your library. Point tools like Cursor, ChatGPT, Claude, or Copilot to this text and they will understand your concepts, APIs, and examples much more reliably. ```text title="u301-llms-full.txt" https://u301.com/docs/llms-full.txt ``` ## Ask Bender Bender is our AI assistant listed on the GPT Store. It loads LLMs.txt, pulls answers from our documentation, and responds based on that context. We recommend using a thinking‑capable model for best results; Instant makes more mistakes. Each page includes a button at the top. Click it to use Bender with this page’s content. You can also open Bender directly on ChatGPT: [https://chatgpt.com/g/g-69118c1d8974819196445be466120f24-bender-from-u301](https://chatgpt.com/g/g-69118c1d8974819196445be466120f24-bender-from-u301) ## What is LLMs.txt? [LLMs.txt](https://llmstxt.org) is just text. It’s structured and predictable so models can scan it quickly. * Plain text: easy to fetch, paste, and index. * Stable URL: a single link you can reference in prompts and tools. * Headings first: page titles and sections come before content for fast retrieval. * No styling: content only, which reduces token waste. In this documentation, LLMs.txt is automatically generated from the same source, so it stays in sync when page content changes. ## How to use it * [https://u301.com/docs/llms-full.txt](/llms-full.txt) returns the full docs as plain Markdown. ### Use it in Cursor Add U301 docs as context in Cursor with the `@Docs` feature. See the Cursor documentation: [Cursor Docs](https://cursor.com/docs/context/symbols#adding-your-own-documentation) # Timezone Time zones are hard (the fun kind). Let’s make them easy. No matter where you live, which language you speak, or which time zone you use, U301 works hard to localize your experience. Correct time handling is a big part of that. On the server, we store every timestamp in UTC. This prevents confusion across regions and systems. A typical UTC string looks like: `2025-05-17T13:30:18.602Z`. Here’s how to read it: * `2025-05-17`: the date part (May 17, 2025) * `T`: the separator between date and time * `13:30:18.602`: the time part (13:30:18 and 602 milliseconds) * `Z`: the time zone indicator for Coordinated Universal Time (UTC) ## TL;DR * Store times in `UTC`. * Display times in the viewer’s local time (or a chosen target zone). * Never add hours by hand; use proper time zone tools. ## Show UTC as local time Usually, UTC is not your local time. In your app, convert UTC to the user’s local time when displaying. In JavaScript: ```javascript const utcTime = '2025-05-17T13:30:18.602Z'; const date = new Date(utcTime); console.log(date); ``` That converts the UTC value to local time on the machine where the code runs. To format for humans (and control the target zone), use `Intl.DateTimeFormat`: ```javascript const utcTime = '2025-05-17T13:30:18.602Z'; const localText = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short', }).format(new Date(utcTime)); const kolkataText = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short', timeZone: 'Asia/Kolkata', }).format(new Date(utcTime)); console.log(localText); // formatted in the viewer’s current zone console.log(kolkataText); // formatted as if viewed in Asia/Kolkata ``` Why bother? Because your users live everywhere. Offsets aren’t uniform, and daylight saving rules vary. Some regions use half‑hour offsets (like Newfoundland and India), and Nepal even uses UTC+5:45. Naive math breaks quickly. ## Store vs. Present * Store in UTC. It’s portable, unambiguous, and safe for computation. * Present in a time zone that makes sense for the viewer or report. * Keep raw UTC in your database; convert only at display or reporting time. ## Why time zones matter in Analytics Our analytics offer rich queries like top browser and top country. Two time dimensions shape those results: * Query window (for example, the last 3 days) * Aggregation grain (by day, by hour) Different time zones shift boundaries and affect counts. Say you want “today’s clicks.” In London, “today” starts at UTC+1. In New York, it starts at UTC−5. Those six hours in between fall on different calendar days depending on the zone. ## How U301 helps U301’s API accepts a `timezone` parameter and aggregates with respect to that zone. You can query in your base time zone and get correctly localized results. ## Common gotchas * Daylight saving transitions create 23-hour or 25-hour days. Don’t assume 24. * Not all offsets are whole hours (for example, `UTC+05:30`, `UTC+05:45`). * “Add N hours” is fragile. Always use libraries or platform APIs. ## Beginner’s checklist * Use `UTC` for storage and computation. * Use `Intl.DateTimeFormat` (or a reliable library) for display. * Decide the presentation zone: viewer’s local, fixed report zone, or account-specific zone. * Include the zone in labels (for example, “All times shown in UTC+1”). If time zones still feel thorny, drop us a note. We’ll help you pick the right zone for your use case. # Install SDK Currently, we only provide a TypeScript SDK for Node.js and Bun. For other environments, you can use the REST API directly. [Run in Postman](https://god.gw.postman.com/run-collection/29773502-12b109dc-9ad4-4aa9-a5c8-38062563c256?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D29773502-12b109dc-9ad4-4aa9-a5c8-38062563c256%26entityType%3Dcollection%26workspaceId%3D61afe648-47cf-4b66-80de-79d6fd8a14b0#?env%5Bproduction%5D=W3sia2V5IjoiYmFzZVVSTCIsInZhbHVlIjoiaHR0cHM6Ly9hcGkudTMwMS5jb20iLCJlbmFibGVkIjp0cnVlLCJ0eXBlIjoiZGVmYXVsdCIsInNlc3Npb25WYWx1ZSI6Imh0dHBzOi8vYXBpLnUzMDEuY29tIiwiY29tcGxldGVTZXNzaW9uVmFsdWUiOiJodHRwczovL2FwaS51MzAxLmNvbSIsInNlc3Npb25JbmRleCI6MH0seyJrZXkiOiJ0b2tlbiIsInZhbHVlIjoiIiwiZW5hYmxlZCI6dHJ1ZSwidHlwZSI6ImRlZmF1bHQiLCJzZXNzaW9uVmFsdWUiOiJ1YXRfQVpwbkhneEFmYjJKdDlKLVJXTG1yUS5OakV4WkZWellXRmZNVEp3VFc4NFNGOVVVbk5SVmtVelRrTlNSQzEzY25oNVQyRnBTbk5qTVRNeE1ERXdPRFkxTmpRIiwiY29tcGxldGVTZXNzaW9uVmFsdWUiOiJ1YXRfQVpwbkhneEFmYjJKdDlKLVJXTG1yUS5OakV4WkZWellXRmZNVEp3VFc4NFNGOVVVbk5SVmtVelRrTlNSQzEzY25oNVQyRnBTbk5qTVRNeE1ERXdPRFkxTmpRIiwic2Vzc2lvbkluZGV4IjoxfV0=) ## Install TypeScript SDK npm pnpm yarn bun ```bash npm install u301 ``` ```bash pnpm add u301 ``` ```bash yarn add u301 ``` ```bash bun add u301 ``` The TypeScript SDK is still in development. We value your feedbacks. ## Quick Start
Node.js cURL ```typescript title="quick-start.ts" import { U301 } from 'u301'; const apiKey = ''; const workspaceId = ''; const u301 = new U301({ apiKey, workspaceId, }); const link = await u301.links.create({ url: 'https://example.com', domain: 'u301.co', slug: 'launch', }); ``` ```ts // url is encoded to https%3A%2F%2Fexample.com by `encodeURIComponent(url)` [!code word:https%3A%2F%2Fexample.com] curl --location 'https://api.u301.com/v3/shorten?url=https%3A%2F%2Fexample.com&workspaceId=' \ --header 'Authorization: Bearer ' ```
## Analytics
Node.js cURL ```typescript title="analytics.ts" import { U301 } from 'u301'; const apiKey = 'YOUR_API_KEY'; const workspaceId = 'YOUR_WORKSPACE_ID'; const u301 = new U301({ apiKey, workspaceId, }); const report = await u301.analytics.getClicks({ range: '1m', granularity: 'day', timezone: 'Europe/London' }) ``` ```bash curl --location 'https://api.u301.com/v3/shorten/analytics/clicks?timezone=Europe%2FLondon&granularity=day&range=1m&workspaceId=' \ --header 'Authorization: Bearer ' ```
Our JavaScript SDK source code is available on Github. Learn more about the npm package. You can use the Postman collection to test the API. # Create Link ## Create a link Creating a short link in U301 is simple. Here's a minimal example: Node.js cURL ```typescript title="create-link.ts" import { U301 } from 'u301'; const apiKey = ''; const workspaceId = ''; const u301 = new U301({ apiKey, workspaceId, }); const link = await u301.links.create('https://example.com'); // [!code highlight] ``` ```bash curl --location 'https://api.u301.com/v3/shorten/bulk?workspaceId=' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '[ { "url": "https://example.com", } ] ' ``` Example response: ```json { "id": "019a7d8c-4a85-7aa4-9862-ee2008f54854", "url": "https://example.com", "slug": "jtyZ", "isCustomSlug": false, "domain": "u301.co", "isReused": false, "shortLink": "u301.co/jtyZ", "comment": "" } ``` ## Bulk create links Create multiple links in one call by passing an array to `createMany`: Node.js cURL ```typescript title="create-link.ts" const link = await u301.links.createMany([ 'https://example.com', 'https://example.org', ]); ``` ```bash curl --location 'https://api.u301.com/v3/shorten/bulk?workspaceId=' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '[ { "url": "https://example.com", }, { "url": "https://example.org", } ] ' ``` ## Custom domain and path (slug) Want a branded short link, like a fixed path `https://u301.co/launch`, or your own domain instead of `u301.co`? Provide `domain` and `slug`: Node.js cURL ```typescript title="create-link.ts" const link = await u301.links.create({ url: 'https://example.com', domain: 'u301.co', // [!code highlight] slug: 'launch', // [!code highlight] }); ``` ```bash curl --location 'https://api.u301.com/v3/shorten/bulk?workspaceId=' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '[ { "url": "https://example.com", "domain": "u301.co", // [!code highlight] "slug": "launch", // [!code highlight] } ] ' ``` ## Handle duplicate URLs If the target URL already exists, you may prefer to reuse the existing short link instead of creating a new one. Set `reuseExisting` to `true`: Node.js cURL ```typescript title="create-link.ts" const link = await u301.links.create({ url: 'https://example.com', reuseExisting: true, // [!code highlight] }); ``` ```bash curl --location 'https://api.u301.com/v3/shorten/bulk?workspaceId=' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '[ { "url": "https://example.com", "reuseExisting": true, // [!code highlight] } ] ' ``` The response marks `isReused` as `true`: ```json { "id": "019a7d8c-4a85-7aa4-9862-ee2008f54854", "url": "https://example.com", "slug": "jtyZ", "isCustomSlug": false, "domain": "u301.co", "isReused": true, // [!code highlight] "shortLink": "u301.co/jtyZ", "comment": "" } ``` ## Password-protect a short link Require a password before resolving to the target URL by setting `password`: Node.js cURL ```typescript title="create-link.ts" const link = await u301.links.create({ url: 'https://example.com', password: 'example-password', // [!code highlight] }); ``` ```bash curl --location 'https://api.u301.com/v3/shorten/bulk?workspaceId=' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '[ { "url": "https://example.com", "password": "example-password", // [!code highlight] } ] ' ``` Try [https://u301.co/4jnB](https://u301.co/4jnB) with password `example-password`. # Fry import { AuthorCard } from "@/components/author-card";