YAS.SH API
Build, manage, redirect and analyze short links programmatically. Create and manage short links, generate QR codes, access click analytics, manage API keys, and automate supported tools.
Introduction
The yas.sh API lets you programmatically shorten URLs, generate QR codes, read click analytics, manage API keys and run developer tools. It is RESTful, JSON-based, and follows RFC 9457 for errors.
Predictable resource URLs and consistent JSON responses. OpenAPI 3.1 contract.
Public redirect and QR endpoints need no authentication.
Every endpoint ships copyable cURL, JS, TS and Python examples.
Quick Start
Five steps from zero to your first working short link. Assume nothing — every step is spelled out.
- 1
Create an API key
Log in to your dashboard and open Account → API Keys. Click Create Key, give it a name, choose its scopes, and create it. - 2
Store the key securely
The raw key is shown exactly once. Store it in a server-side environment variable. Send it asAuthorization: Bearer yas_live_…. Never expose a key in frontend/browser code. - 3
Create your first link
curl -X POST https://yas.sh/api/v1/links \ -H "Authorization: Bearer yas_live_abc123..." \ -H "Content-Type: application/json" \ -d '{"originalUrl":"https://example.com","customAlias":"launch"}' - 4
Read the response
201 Created{ "shortCode": "launch", "url": "https://yas.sh/launch", "link": { "id": "clx1abc2def3", "shortCode": "launch", "customAlias": "launch", "originalUrl": "https://example.com", "clicks": 0, "expiresAt": null, "createdAt": "2026-08-08T10:00:00.000Z" } } - 5
Open your short link
Visithttps://yas.sh/launchin your browser — it redirects (302) tohttps://example.com. Done! 🎉
Authentication
There are two ways to authenticate: a session cookie (browser/dashboard) and a Bearer API key (server-to-server). Choose based on where your code runs.
Bearer token
For server-side automation. Create a key in the dashboard or via POST /api/v1/api-keys.
curl https://yas.sh/api/v1/links \ -H "Authorization: Bearer yas_live_abc123..."
Keys are scoped. A key can only call endpoints matching its granted scopes — links:read, links:write, analytics:read. Missing scope returns 403.
Session cookie
For the dashboard/browser. Logging in sets an httpOnly yas_session cookie (30 days, Lax). Sends it automatically. Sessions hold all scopes.
# Login sets the yas_session cookie for subsequent requests
curl -X POST https://yas.sh/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"correct-horse-battery-staple"}' \
-c cookies.txt
curl https://yas.sh/api/v1/links -b cookies.txtAPI Keys
A step-by-step key lifecycle: create, copy, store, use, rotate and revoke.
- 1Create. Dashboard → API Keys → Create, or POST /api/v1/api-keys (session required).
- 2Copy. The raw key is returned exactly once in the response — copy it immediately.
- 3Store. Save it in a server-side environment variable. Never in client-side code or git.
- 4Use. Send Authorization: Bearer yas_live_... on every protected request.
- 5Rotate. Create a new key, switch your app over, then revoke the old one.
- 6Revoke. DELETE /api/v1/api-keys/{id} — immediate and permanent.
Errors
All error responses use RFC 9457 (application/problem+json). Every error carries type, title, status, code and a human-readable detail.
{
"type": "https://yas.sh/docs/errors#validation_error",
"title": "Validation failed",
"status": 400,
"code": "VALIDATION_ERROR",
"detail": "customAlias: 3-30 chars: a-z 0-9 - _"
}type is a stable URI that resolves to the error’s documentation. code is the machine-readable key — match on it in your error handling.
| Status | Code | Meaning | Common cause | How to fix |
|---|---|---|---|---|
| 400 | VALIDATION_ERROR | The request body or query is invalid — a field failed validation. | Missing required field, malformed URL, invalid alias pattern, expiresAt in the past, or bad JSON. | Check the detail message for the exact field and constraint, fix it, and retry. |
| 401 | UNAUTHORIZED | You are not authenticated, or the credential is invalid. | Missing/invalid Bearer token, expired API key, or no session cookie on a protected endpoint. | Attach a valid Authorization: Bearer yas_live_... header (or a session cookie), or log in first. |
| 403 | FORBIDDEN | Authenticated but not allowed to perform the action. | The API key lacks the required scope, the request came from an untrusted origin (CSRF), or a plan/key limit was reached (LIMIT_REACHED). | Grant the required scope, call from a trusted origin, or reduce usage / upgrade the plan. |
| 404 | NOT_FOUND | The resource does not exist, or you do not own it. | Wrong id, deleted resource, or an id owned by another user (returned as 404 to avoid leaking existence). | Verify the id and that the resource belongs to your account. |
| 409 | CONFLICT | The request conflicts with the current state. | Alias/short code already taken, or another unique-constraint conflict. | Choose a different alias/email. Create Link may include suggestions in the response. |
| 413 | PAYLOAD_TOO_LARGE | The request body exceeds the allowed size. | Body over the endpoint limit (e.g. links 20 KB, tools 64 KiB). | Reduce the payload size and retry. |
| 429 | RATE_LIMITED | You have exceeded a rate limit. | Too many requests in the window (see Rate Limits). | Wait for the Retry-After period, respect the RateLimit-* headers, and use exponential backoff. |
| 500 | INTERNAL_ERROR | Something went wrong on the server. | An unexpected server error. Details are never leaked to the client. | Retry with backoff; if it persists, contact support with the request details. |
Rate Limits
Limits are enforced per-user and/or per-IP depending on the endpoint. On a 429, honor the Retry-After header and back off exponentially.
| Endpoint | Scope | Limit | Headers |
|---|---|---|---|
| POST /api/v1/links | per-user | 60 / min | X-RateLimit-* |
| POST /api/v1/links | per-IP | 120 / min | X-RateLimit-* |
| POST /api/v1/auth/login | per-IP | 15 / min | Retry-After |
| POST /api/v1/auth/register | per-IP | 20 / min | Retry-After |
| POST /api/v1/api-keys | per-user | 10 / min | Retry-After |
| POST /api/v1/tools/:slug | per-IP | 60 / min | Retry-After |
| /{code} password attempts | per-IP | 10 / min | Retry-After |
Plan-specific API request limits (daily)
API-key authenticated requests are counted against your plan's daily API quota. Session (cookie) requests from the dashboard are not counted.
| Plan | API Requests |
|---|---|
| Free | 100 / day |
| Starter | 1,000 / day |
| Pro | 10,000 / day |
| Business | 100,000 / day |
| Enterprise | Unlimited |
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
{
"type": "https://yas.sh/docs/errors#rate_limited",
"title": "Rate Limited",
"status": 429,
"code": "RATE_LIMITED",
"detail": "Too many requests",
"retryAfter": 60
}Pagination
List endpoints (e.g. GET /api/v1/links) use cursor pagination. The response returns a nextCursor — pass it to get the next page.
GET /api/v1/links?limit=20
{
"data": [ /* up to 20 links */ ],
"nextCursor": "clx1abc2def3"
}GET /api/v1/links?limit=20&cursor=clx1abc2def3
{
"data": [ /* the next page */ ],
"nextCursor": "clx9zyx8wvu7" // null when there are no more pages
}Idempotency
POST /api/v1/links supports an optional Idempotency-Key header so safe retries never create duplicate links.
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-launch" \
-d '{"originalUrl":"https://example.com","customAlias":"launch"}'# Same request, same Idempotency-Key — returns the existing link, no duplicate.
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-launch" \
-d '{"originalUrl":"https://example.com","customAlias":"launch"}'Security
Keep your keys safe. These practices prevent the most common leaks.
- Never commit API keys to version control.
- Never expose secret API keys in client-side JavaScript.
- Use environment variables for all secrets.
- Rotate compromised keys immediately.
- Revoke unused keys.
- Use least-privilege scopes (e.g. links:read only).
Type Generation & SDK
Generate typed clients from the canonical OpenAPI 3.1 document.
# Generate TypeScript types from the live spec npx openapi-typescript https://yas.sh/api/v1/openapi -o sdk.ts
The spec is a single authoritative document, so code generation stays in sync with the API. Any OpenAPI 3.x code generator (openapi-typescript, openapi-generator, etc.) works against /api/v1/openapi.
Real-world examples
Task-oriented recipes. Each links to the relevant endpoint for the full parameter reference.
Create a short link with a custom alias
Set customAlias to control the short code.
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"originalUrl":"https://example.com","customAlias":"launch"}'Create an expiring link
Set expiresAt to a future ISO 8601 datetime. The link stops redirecting after that time (returns 410).
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"originalUrl":"https://example.com/sale","expiresAt":"2026-12-31T23:59:59Z"}'Create a password-protected link
Set password (8–128 chars). Visitors must submit it to be redirected.
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"originalUrl":"https://example.com/private","password":"super-secret"}'Update a link's destination
PATCH the link with a new originalUrl — the short code stays the same.
curl -X PATCH https://yas.sh/api/v1/links/clx1abc2def3 \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"originalUrl":"https://example.com/new-destination"}'List links with search
Search matches originalUrl, shortCode, customAlias and title.
curl "https://yas.sh/api/v1/links?search=launch&limit=20" \ -H "Authorization: Bearer yas_live_abc123..."
Get account analytics
Totals, daily series, and top breakdowns for the last 30 days.
curl "https://yas.sh/api/v1/analytics/overview?days=30" \ -H "Authorization: Bearer yas_live_abc123..."
Get analytics for one link
Lifetime click count and recent events for a single link.
curl "https://yas.sh/api/v1/analytics/clx1abc2def3?days=30" \ -H "Authorization: Bearer yas_live_abc123..."
Generate a QR code
Public endpoint — returns PNG (or SVG) image bytes, no auth needed.
curl "https://yas.sh/api/v1/qr?url=https://yas.sh/demo&size=512" -o qr.png
Create an API key
Requires a session cookie. The raw key is returned exactly once.
curl -X POST https://yas.sh/api/v1/api-keys \
-H "Cookie: yas_session=..." \
-H "Content-Type: application/json" \
-d '{"name":"ci","scopes":["links:read","links:write","analytics:read"]}'Revoke an API key
Permanent and immediate — requests using the key start failing with 401.
curl -X DELETE "https://yas.sh/api/v1/api-keys/clk1abc" \ -H "Cookie: yas_session=..."
API Reference
Every endpoint, verified against the live implementation. Use the sidebar or search to jump to any endpoint.
Public · no auth Public · Bearer token Bearer · Session cookie Cookie ·Session only Session only
links
Create Link
Shorten a URL into a yas.sh short link.
https://yas.sh/api/v1/linksWhat this endpoint does
Creates a new short link for the authenticated user. The destination URL is required. You may optionally supply a custom alias, a title, an expiry time, or a per-link password. The request is idempotent when you send an Idempotency-Key header, and rate limited (60/min per user, 120/min per IP).
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| Idempotency-Key | header | string | No | Optional. If set, retrying the same key returns the existing link instead of creating a duplicate. · e.g. req-001 |
Request body
Request examples
curl -X POST https://yas.sh/api/v1/links \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: req-001" \
-d '{"originalUrl":"https://example.com?utm_source=newsletter","customAlias":"launch","title":"Product Launch"}'Response 201 Created
Link created successfully.
{
"shortCode": "launch",
"url": "https://yas.sh/launch",
"link": {
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch",
"originalUrl": "https://example.com?utm_source=newsletter",
"title": "Product Launch",
"clicks": 0,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| shortCode | string | No | Yes | The public short code used in the URL path. e.g. launch |
| url | string | No | Yes | The fully-formed short URL. e.g. https://yas.sh/launch |
| link | object | No | Yes | The full Link resource. |
| link.id | string | No | Yes | Unique link identifier (CUID). |
| link.shortCode | string | No | Yes | Public short code. |
| link.customAlias | string | Yes | No | Custom alias, or null for random codes. |
| link.originalUrl | string | No | Yes | The destination URL. |
| link.title | string | Yes | No | Optional display title. |
| link.clicks | integer | No | Yes | Number of tracked (non-bot) clicks. |
| link.expiresAt | string | Yes | No | When the link stops redirecting, or null. |
| link.createdAt | string | No | Yes | Creation timestamp. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing/invalid originalUrl, invalid alias, invalid expiresAt, or bad JSON. |
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
| 403 | LIMIT_REACHED | Free plan link limit (50) reached. |
| 403 | EXPIRATION_LIMIT_EXCEEDED | Requested expiresAt exceeds the plan's maximum active link lifetime (Free: 5 days, Business: 30 days). Response includes maximum_lifetime_days. |
| 409 | CONFLICT | Alias or short code already taken. |
| 429 | RATE_LIMITED | Exceeded 60/min per user or 120/min per IP. |
List Links
List the authenticated user's links with search and cursor pagination.
https://yas.sh/api/v1/linksWhat this endpoint does
Returns a paginated, searchable list of links owned by the current user. Results are ordered newest-first. Search matches originalUrl, shortCode, customAlias and title.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| search | query | string | No | Filter by originalUrl, shortCode, customAlias or title. · e.g. example |
| limit | query | integer | No default: 20 | Page size. · 1–100 · e.g. 20 |
| cursor | query | string | No | Opaque pagination cursor — pass the previous response's nextCursor to get the next page. |
Request examples
curl "https://yas.sh/api/v1/links?search=example&limit=20" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
Paginated list of links.
{
"data": [
{
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch",
"originalUrl": "https://example.com",
"title": "Product Launch",
"clicks": 12,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z",
"_count": { "clickEvents": 12 }
}
],
"nextCursor": "clx1abc2def3"
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| data | array | No | Yes | Array of Link objects for this page. |
| data[]._count | object | No | Yes | Relation counts, including clickEvents. |
| data[]._count.clickEvents | integer | No | Yes | Total click events for the link (not bot-filtered). |
| nextCursor | string | Yes | Yes | Opaque cursor for the next page, or null when there are no more results. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
Get Link
Fetch a single link owned by the current user.
https://yas.sh/api/v1/links/{id}What this endpoint does
Returns one link by its id. Only the owning user can read a link — otherwise the API returns 404 to avoid leaking existence.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Link id (CUID). |
Request examples
curl "https://yas.sh/api/v1/links/clx1abc2def3" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
The requested link.
{
"link": {
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch",
"originalUrl": "https://example.com",
"title": "Product Launch",
"clicks": 42,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| link | object | No | Yes | The Link resource. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
| 404 | NOT_FOUND | Link does not exist or is not owned by you. |
Update Link
Update the destination, title, or custom alias of a link.
https://yas.sh/api/v1/links/{id}What this endpoint does
Partially updates a link. Only fields you send are changed. Alias changes are checked atomically and return 409 if the alias is already taken.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Link id (CUID). |
Request body
Request examples
curl -X PATCH https://yas.sh/api/v1/links/clx1abc2def3 \
-H "Authorization: Bearer yas_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"customAlias":"launch-2026","title":"Launch 2026"}'Response 200 OK
The updated link.
{
"link": {
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch-2026",
"originalUrl": "https://example.com",
"title": "Launch 2026",
"clicks": 42,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| link | object | No | Yes | The updated Link resource. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid alias or protocol, or bad JSON. |
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
| 404 | NOT_FOUND | Link does not exist or is not owned by you. |
| 409 | CONFLICT | Alias already taken. |
Delete Link
Permanently delete a link and its click events.
https://yas.sh/api/v1/links/{id}What this endpoint does
Deletes a link and cascades its click events. This is permanent and cannot be undone.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Link id (CUID). |
Request examples
curl -X DELETE "https://yas.sh/api/v1/links/clx1abc2def3" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
The link was deleted.
{
"ok": true
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| ok | boolean | No | Yes | Always true on success. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
| 404 | NOT_FOUND | Link does not exist or is not owned by you. |
Duplicate Check
Check whether a URL is already shortened by the current user.
https://yas.sh/api/v1/links/duplicateWhat this endpoint does
Lets the UI (or your app) detect that a URL has already been shortened, so you can offer to reuse the existing link instead of creating a duplicate.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| url | query | string | Yes | The URL to check. |
Request examples
curl "https://yas.sh/api/v1/links/duplicate?url=https://example.com" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
Duplicate status.
{
"duplicate": true,
"link": {
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch",
"originalUrl": "https://example.com",
"title": null,
"clicks": 4,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| duplicate | boolean | No | Yes | True when the URL is already shortened by this user. |
| link | object | Yes | No | The existing link when duplicate is true, otherwise null. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | url query parameter is required. |
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
Export Links (CSV)
Download all of your links as a CSV file.
https://yas.sh/api/v1/links/export.csvWhat this endpoint does
Streams every link owned by the current user as a CSV attachment. Cell values are escaped to prevent CSV formula injection.
Request examples
curl "https://yas.sh/api/v1/links/export.csv" \ -H "Authorization: Bearer yas_live_abc123..." \ -o links.csv
Response 200 OK
CSV file attachment (text/csv).
shortCode,url,originalUrl,title,clicks,createdAt launch,https://yas.sh/launch,https://example.com,Product Launch,42,2026-08-08T10:00:00.000Z
Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| body | string | No | Yes | The CSV document as the response body. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
redirect
Redirect
Resolve a short code to its destination with an HTTP 302.
https://yas.sh/{code}What this endpoint does
This is the public redirect endpoint — the core of yas.sh. Visiting a short code (or custom alias) returns a 302 to the destination. It is public and requires no authentication. Expiry, per-link passwords and bot filtering are enforced here. Click events are recorded asynchronously.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| code | path | string | Yes | The short code or custom alias. · 3–30 · e.g. launch |
| password | query | string | No | Required when the link is password-protected. |
Request examples
# A plain redirect: curl -i https://yas.sh/launch # A password-protected link: curl -i "https://yas.sh/secret?password=MyPass123"
Response 302 Found
Redirect to the destination URL.
HTTP/1.1 302 Found Location: https://example.com?utm_source=newsletter Content-Type: text/plain; charset=utf-8
Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| Location | header | No | Yes | The destination URL to follow. |
| body | string | No | No | A short text body. Click events are recorded asynchronously and bot clicks are filtered. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | PASSWORD_REQUIRED | The link is password-protected and no/incorrect password was provided (an HTML form is returned). |
| 404 | NOT_FOUND | No link exists for this code. |
| 410 | GONE | The link has expired or its click limit was reached. |
| 429 | RATE_LIMITED | Too many password attempts (10/min). |
Unlock Protected Link
Submit a link password to follow a protected redirect.
https://yas.sh/{code}What this endpoint does
Submits the password for a protected link (form-encoded). On success the server responds with a 302 to the destination, same as the plain GET.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| code | path | string | Yes | The short code or custom alias. · 3–30 |
Request body
Request examples
curl -X POST https://yas.sh/secret \ -d "password=MyPass123"
Response 302 Found
Redirect to the destination.
HTTP/1.1 302 Found Location: https://example.com
Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| Location | header | No | Yes | The destination URL. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | PASSWORD_REQUIRED | Incorrect password. |
| 404 | NOT_FOUND | No link exists for this code. |
| 410 | GONE | The link has expired. |
| 429 | RATE_LIMITED | Too many password attempts (10/min). |
analytics
Analytics Overview
Aggregate click analytics for the current user.
https://yas.sh/api/v1/analytics/overviewWhat this endpoint does
Returns bot-filtered click totals, a daily click series, and top countries, devices, browsers and referrers for the last N days. This is the same data shown on the dashboard. Breakdowns run as database-side aggregations.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| days | query | integer | No default: 30 | Window in days. · 1–90 · e.g. 30 |
Request examples
curl "https://yas.sh/api/v1/analytics/overview?days=30" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
Aggregate analytics for the requested window.
{
"totalLinks": 42,
"totalClicks": 1248,
"series": [
{ "date": "2026-07-10", "clicks": 41 },
{ "date": "2026-07-11", "clicks": 63 }
],
"breakdowns": {
"country": [{ "name": "US", "value": 320 }],
"device": [{ "name": "mobile", "value": 800 }],
"browser": [{ "name": "chrome", "value": 700 }],
"referrer": [{ "name": "google", "value": 410 }]
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| totalLinks | integer | No | Yes | Number of links owned by the user. |
| totalClicks | integer | No | Yes | Bot-filtered clicks in the window. |
| series | array | No | Yes | One entry per day in the window (zero-filled). |
| series[].date | string | No | Yes | Day (YYYY-MM-DD). |
| series[].clicks | integer | No | Yes | Clicks on that day. |
| breakdowns | object | No | Yes | Top countries/devices/browsers/referrers. |
| breakdowns.country | array | No | Yes | Top countries as { name, value }. |
| breakdowns.device | array | No | Yes | Top devices as { name, value }. |
| breakdowns.browser | array | No | Yes | Top browsers as { name, value }. |
| breakdowns.referrer | array | No | Yes | Top referrers as { name, value }. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
Per-Link Analytics
Recent click events and lifetime totals for one link.
https://yas.sh/api/v1/analytics/{id}What this endpoint does
Returns a single link plus its lifetime bot-filtered click count and recent click events for the requested window.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Link id (CUID). |
| days | query | integer | No default: 30 | Window in days for recent events. · 1–90 |
Request examples
curl "https://yas.sh/api/v1/analytics/clx1abc2def3?days=30" \ -H "Authorization: Bearer yas_live_abc123..."
Response 200 OK
Per-link analytics.
{
"link": {
"id": "clx1abc2def3",
"shortCode": "launch",
"customAlias": "launch",
"originalUrl": "https://example.com",
"title": "Product Launch",
"clicks": 42,
"expiresAt": null,
"createdAt": "2026-08-08T10:00:00.000Z"
},
"totalClicks": 42,
"recent": [
{
"id": "clk1xyz",
"country": "US",
"device": "mobile",
"createdAt": "2026-08-08T09:59:00.000Z"
}
]
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| link | object | No | Yes | The link resource. |
| totalClicks | integer | No | Yes | Lifetime bot-filtered click count. |
| recent | array | No | Yes | Recent bot-filtered click events in the window. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No valid Bearer token or session cookie. |
| 404 | NOT_FOUND | Link does not exist or is not owned by you. |
qr
Generate QR Code
Render a QR code as PNG or SVG.
https://yas.sh/api/v1/qrWhat this endpoint does
Generates a QR code image from a URL. This endpoint is public and requires no authentication. It returns raw binary image data (PNG) or an SVG string — it does not return JSON. Responses are cached for 1 hour.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| url | query | string | Yes | Content to encode (http/https only). · e.g. https://yas.sh/demo |
| size | query | integer | No default: 512 | Output size in pixels. Allowed: 128 | 256 | 512 | 1024 |
| format | query | string | No | Output format. Allowed: png (default) | svg |
Request examples
# PNG (default): curl "https://yas.sh/api/v1/qr?url=https://yas.sh/demo&size=512" -o qr.png # SVG: curl "https://yas.sh/api/v1/qr?url=https://yas.sh/demo&format=svg" -o qr.svg
Response 200 OK
Image bytes (image/png) or SVG document (image/svg+xml).
HTTP/1.1 200 OK Content-Type: image/png Cache-Control: public, max-age=3600 <binary PNG data> <!-- or, with &format=svg --> <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512">...</svg>
Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| body | binary | No | Yes | Raw PNG image or SVG string. Never JSON. |
| Content-Type | header | No | Yes | image/png or image/svg+xml. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | url is missing or not an http/https URL. |
auth
Register
Create an account and start a session.
https://yas.sh/api/v1/auth/registerWhat this endpoint does
Creates a user account and sets a yas_session cookie (httpOnly, 30 days, Lax). Rate limited to 20/min per IP. Passwords must be at least 8 characters and not on the common-password denylist.
Request body
Request examples
curl -X POST https://yas.sh/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"correct-horse-battery-staple","name":"You"}' \
-c cookies.txtResponse 201 Created
Account created and session started.
{
"user": {
"id": "clxuser1",
"email": "you@example.com",
"name": "You",
"role": "user"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| user | object | No | Yes | The created user. |
| user.id | string | No | Yes | User id. |
| user.email | string | No | Yes | Email address. |
| user.name | string | No | Yes | Display name. |
| user.role | string | No | Yes | Role (user). |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid email, weak/too-common password, or missing name. |
| 201 | CREATED | Same success shape whether the address is new or already registered (anti-enumeration). |
| 429 | RATE_LIMITED | Exceeded 20/min per IP. |
Login
Authenticate with email and password and start a session.
https://yas.sh/api/v1/auth/loginWhat this endpoint does
Authenticates a user and sets a yas_session cookie. Uses anti-enumeration timing so the response time does not reveal whether an email exists. Rate limited to 15/min per IP.
Request body
Request examples
curl -X POST https://yas.sh/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"correct-horse-battery-staple"}' \
-c cookies.txtResponse 200 OK
Authenticated and session cookie set.
{
"user": {
"id": "clxuser1",
"email": "you@example.com",
"name": "You",
"role": "user"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| user | object | No | Yes | The authenticated user. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Invalid credentials. |
| 429 | RATE_LIMITED | Exceeded 15/min per IP. |
Get Current User
Return the authenticated user.
https://yas.sh/api/v1/auth/meWhat this endpoint does
Returns the currently authenticated user. When logged out it returns { user: null } with a 200 — useful as a lightweight probe without triggering 401 noise.
Request examples
curl "https://yas.sh/api/v1/auth/me" \ -H "Cookie: yas_session=..."
Response 200 OK
The authenticated user, or user: null when logged out.
{
"user": {
"id": "clxuser1",
"email": "you@example.com",
"name": "You",
"role": "user"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| user | object | Yes | Yes | The user, or null when not authenticated. |
Update Profile
Update the signed-in user's name and/or email.
https://yas.sh/api/v1/auth/meWhat this endpoint does
Updates the current user's name and/or email. Requires a session cookie (not an API key).
Request body
Request examples
curl -X PATCH https://yas.sh/api/v1/auth/me \
-H "Cookie: yas_session=..." \
-H "Content-Type: application/json" \
-d '{"name":"Your New Name"}'Response 200 OK
The updated user.
{
"user": {
"id": "clxuser1",
"email": "you@example.com",
"name": "Your New Name",
"role": "user"
}
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| user | object | No | Yes | The updated user. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Empty name or invalid email. |
| 401 | UNAUTHORIZED | Not authenticated. |
| 409 | CONFLICT | Email already in use. |
Change Password
Verify the current password and set a new one.
https://yas.sh/api/v1/auth/change-passwordWhat this endpoint does
Verifies the current password, sets a new password, and signs out all other sessions. Requires a session cookie.
Request body
Request examples
curl -X POST https://yas.sh/api/v1/auth/change-password \
-H "Cookie: yas_session=..." \
-H "Content-Type: application/json" \
-d '{"currentPassword":"old-password","newPassword":"new-password"}'Response 200 OK
Password changed.
{
"ok": true
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| ok | boolean | No | Yes | Always true on success. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Wrong current password or invalid input. |
| 401 | UNAUTHORIZED | Not authenticated. |
| 429 | RATE_LIMITED | Rate limited. |
Logout
Destroy the session and clear the session cookie.
https://yas.sh/api/v1/auth/logoutWhat this endpoint does
Destroys the current session and clears the yas_session cookie.
Request examples
curl -X POST https://yas.sh/api/v1/auth/logout \ -H "Cookie: yas_session=..."
Response 200 OK
Session destroyed.
{
"ok": true
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| ok | boolean | No | Yes | Always true on success. |
keys
List API Keys
List the current user's API keys.
https://yas.sh/api/v1/api-keysWhat this endpoint does
Lists the user's API keys. The raw key is never returned — only a prefix, scopes and metadata. This endpoint requires a browser session (an API key cannot list keys).
Request examples
curl "https://yas.sh/api/v1/api-keys" \ -H "Cookie: yas_session=..."
Response 200 OK
List of API keys.
{
"data": [
{
"id": "clk1abc",
"name": "production",
"prefix": "yas_live_abc1...",
"scopes": "links:read,links:write,analytics:read",
"lastUsedAt": "2026-08-08T09:00:00.000Z",
"createdAt": "2026-08-01T12:00:00.000Z"
}
]
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| data | array | No | Yes | Array of API key metadata objects. |
| data[].id | string | No | Yes | Key id. |
| data[].name | string | No | Yes | Human-friendly key name. |
| data[].prefix | string | No | Yes | Prefix used for identification. The full key is never returned. |
| data[].scopes | string | No | Yes | Comma-separated scopes granted to the key. |
| data[].lastUsedAt | string | Yes | No | Last use timestamp, or null. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Session authentication required. |
Create API Key
Create a yas_live_ key. The raw key is shown exactly once.
https://yas.sh/api/v1/api-keysWhat this endpoint does
Creates a new API key. Only the SHA-256 hash is stored server-side; the raw key (yas_live_...) is returned in this response exactly once and cannot be retrieved again. Maximum 10 keys per user. Requires a browser session.
Request body
Request examples
curl -X POST https://yas.sh/api/v1/api-keys \
-H "Cookie: yas_session=..." \
-H "Content-Type: application/json" \
-d '{"name":"production","scopes":["links:read","links:write"]}'Response 201 Created
Key created. The raw key is returned once.
{
"id": "clk1abc",
"key": "yas_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"prefix": "yas_live_a1b2...",
"name": "production",
"scopes": "links:read,links:write",
"warning": "Copy now — shown once"
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| id | string | No | Yes | Key id. |
| key | string | No | Yes | The raw API key. Returned exactly once — store it immediately. |
| prefix | string | No | Yes | Prefix for identification. |
| name | string | No | Yes | Key name. |
| scopes | string | No | Yes | Comma-separated granted scopes. |
| warning | string | No | Yes | Reminder that the key is shown once. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | name is required or invalid. |
| 401 | UNAUTHORIZED | Session authentication required. |
| 403 | LIMIT_REACHED | API key limit (10) reached. |
Revoke API Key
Permanently revoke an API key.
https://yas.sh/api/v1/api-keys/{id}What this endpoint does
Permanently revokes an API key, making it invalid immediately. This cannot be undone. Requires a browser session and ownership of the key.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | Yes | Key id (CUID). |
Request examples
curl -X DELETE "https://yas.sh/api/v1/api-keys/clk1abc" \ -H "Cookie: yas_session=..."
Response 200 OK
The key was revoked.
{
"ok": true
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| ok | boolean | No | Yes | Always true on success. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Session authentication required. |
| 404 | NOT_FOUND | Key does not exist or is not owned by you. |
tools
Run Tool
Execute a developer utility server-side.
https://yas.sh/api/v1/tools/{slug}What this endpoint does
Executes one of the utility tools server-side (e.g. hash-calculator, base64-text, uuid-generator, url-encoder, case-converter). Each tool has its own contract. Tools that are browser-only by design for security (e.g. link-checker — SSRF risk) return HTTP 409 with code CLIENT_ONLY_TOOL; they can never be executed through the API. Public and rate limited to 60/min per IP.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| slug | path | string | Yes | The tool slug. |
Request body
Request examples
curl -X POST https://yas.sh/api/v1/tools/hash-calculator \
-H "Content-Type: application/json" \
-d '{"input":"hello","algo":"sha256"}'Response 200 OK
Tool output.
{
"slug": "hash-calculator",
"algo": "sha256",
"result": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| slug | string | No | Yes | The tool slug that was run. |
| result | string | No | Yes | The tool's output. |
| algo | string | No | No | Present for hashing tools. |
| code | string | No | No | CLIENT_ONLY_TOOL when the tool is browser-only. |
| tool | string | No | No | The tool slug that cannot be executed. |
| executionMode | string | No | No | 'client' for browser-only tools. |
| apiAvailable | boolean | No | No | false for browser-only tools. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid input for the tool. |
| 404 | NOT_FOUND | Unknown tool slug. |
| 409 | CLIENT_ONLY_TOOL | The tool is browser-only (SSRF / File API) and cannot run server-side. GET metadata reports executionMode "client" and apiAvailable false. |
| 429 | RATE_LIMITED | Exceeded 60/min per IP. |
Get Tool Metadata
Inspect a tool's contract.
https://yas.sh/api/v1/tools/{slug}What this endpoint does
Returns machine-readable metadata about a tool: executionMode (client/server/hybrid), apiAvailable (false for browser-only tools, with reason), methods, inputLimit, level, worksOffline and relatedTools.
Parameters
| Name | Location | Type | Required | Description |
|---|---|---|---|---|
| slug | path | string | Yes | The tool slug. |
Request examples
curl "https://yas.sh/api/v1/tools/hash-calculator"
Response 200 OK
Tool metadata.
{
"slug": "hash-calculator",
"methods": ["POST"],
"inputLimit": 65536,
"hashAlgorithms": ["md5", "sha1", "sha256", "sha384", "sha512"],
"docs": "https://yas.sh/docs/api#tools"
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| slug | string | No | Yes | The tool slug. |
| methods | array | No | Yes | Supported HTTP methods. |
| inputLimit | integer | No | Yes | Maximum input size in bytes (65536). |
| hashAlgorithms | array | No | No | Allowed hash algorithms (hashing tools). |
System
Liveness Check
Public liveness probe.
https://yas.sh/api/healthWhat this endpoint does
Public liveness probe. Returns minimal { status: "ok" } only and discloses no internal details. Use /api/health/ready for a readiness check that also verifies the database.
Request examples
curl -i https://yas.sh/api/health
Response 200 OK
Service is alive.
{
"status": "ok"
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| status | string | No | Yes | Always "ok" when the process is up. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 500 | INTERNAL_ERROR | Service unavailable. |
Readiness Check
Liveness plus database readiness.
https://yas.sh/api/health/readyWhat this endpoint does
Readiness probe that verifies the database and configuration, returning a status and a map of per-check results.
Request examples
curl -i https://yas.sh/api/health/ready
Response 200 OK
Service is ready (database reachable).
{
"status": "ready",
"checks": { "database": "ok" }
}Response fields
| Field | Type | Nullable | Always returned | Description |
|---|---|---|---|---|
| status | string | No | Yes | "ready" or "not_ready". |
| checks | object | No | Yes | Per-dependency check results. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 503 | INTERNAL_ERROR | A dependency (e.g. database) is unavailable. |
Resources
The OpenAPI 3.1 specification is the source of truth for this documentation.