The TRIMS
API Reference.
A predictable, resource-oriented REST API. JSON everywhere, bearer-token auth with granular scopes, and a machine-readable OpenAPI spec. Everything below maps one-to-one to a live endpoint under/api/v1.
Introduction
The TRIMS API lets you manage links, pull analytics, track conversions, and generate QR codes programmatically. It follows REST conventions: resource-based URLs, standard HTTP verbs, JSON request and response bodies, and conventional status codes.
- All requests must be made over HTTPS.
- All requests are authenticated with a workspace-scoped bearer token.
- All responses are JSON, except binary endpoints like QR image export.
- Every token action is logged and visible in your workspace API logs.
Base URL & versioning
All public API endpoints live under a single versioned base path on your TRIMS host:
https://app.trims.app/api/v1The version is pinned in the path (/v1). Backwards-incompatible changes ship under a new version; additive changes (new fields, new endpoints) may arrive within v1, so write clients that ignore unknown fields.
/api/v1 path is identical. During local development the base is http://localhost:8080/api/v1.Authentication
Authenticate every request with a bearer token in the Authorization header. Create tokens in the dashboard under Settings → API tokens — you choose the workspace and the scopes each token carries.
Authorization: Bearer trims_9f8a7b6c5d4e3f2a1b0c8d7e6f5a4b3c| Field | Type | Description |
|---|---|---|
| Authorization* | header | Bearer <token>. Tokens begin with the trims_ prefix and are bound to one workspace. |
| Content-Type | header | application/json for POST/PATCH requests carrying a body. |
A token resolves to its workspace automatically — you never pass a workspace id. Requests with a missing, malformed, or revoked token return 401; a valid token lacking the required scope returns 403.
curl https://app.trims.app/api/v1/links \
-H "Authorization: Bearer $TRIMS_TOKEN"Scopes
Tokens are least-privilege by design. Each endpoint requires a specific scope; grant a token only the scopes its integration needs.
| Field | Type | Description |
|---|---|---|
| links.read | scope | List and read links; generate QR codes. |
| links.write | scope | Create, update, and delete links. |
| tags.read | scope | Read workspace tags. |
| domains.read | scope | Read verified custom domains. |
| folders.read | scope | Read folders. |
| analytics.read | scope | Read analytics summaries and breakdowns. |
| conversions.read | scope | List customers and conversion events. |
| conversions.write | scope | Record lead and sale events. |
403 with {"error":"Insufficient permissions. Required scope: <scope>"}.Rate limits
Limits are enforced per workspace across all of its tokens. Exceeding a limit returns 429 with a Retry-After header (in seconds).
| Field | Type | Description |
|---|---|---|
| Pro | 1,200 / min | Standard developer throughput. |
| Business | 3,000 / min | For higher-volume automation. |
| Premium | Custom | Negotiated limits and dedicated capacity. |
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{ "error": "Rate limit exceeded" }Retry-After with exponential backoff rather than tight retry loops.Errors
TRIMS uses conventional HTTP status codes. 2xx means success, 4xx indicates a client problem (the JSON body explains it), and 5xx indicates a server error.
| Field | Type | Description |
|---|---|---|
| 200 / 201 | Success | The request succeeded (201 for resource creation). |
| 400 | Bad Request | Malformed JSON or failed validation. |
| 401 | Unauthorized | Missing, malformed, or revoked token. |
| 403 | Forbidden | Valid token, but missing scope or an upgrade-required plan gate. |
| 404 | Not Found | Resource doesn't exist or isn't in your workspace. |
| 409 | Conflict | Duplicate alias or domain. |
| 429 | Too Many Requests | Rate limit exceeded; retry after the given delay. |
| 500 | Server Error | Something went wrong on our end — safe to retry. |
Plan-gated errors
When an endpoint needs a higher plan, the response is a structured 403 your client can detect programmatically:
{
"error": "Advanced analytics require the Pro plan.",
"code": "upgrade_required",
"feature": "advanced_analytics",
"requiredPlan": "pro"
}Pagination
List endpoints are paginated with page and pageSize query parameters. pageSize defaults to 50 and is capped at 100.
curl "https://app.trims.app/api/v1/links?page=2&pageSize=50" \
-H "Authorization: Bearer $TRIMS_TOKEN"{
"links": [ /* … */ ],
"page": 2,
"pageSize": 50,
"total": 3842
}OpenAPI spec
A machine-readable OpenAPI 3 document describes the entire public surface. Point Postman, Insomnia, or an OpenAPI code generator at it to scaffold a client in seconds.
/api/v1/openapi.jsoncurl https://app.trims.app/api/v1/openapi.json --output trims-openapi.jsonLinks
The core resource. List your workspace links or create new ones. Each link carries its rules and live click counters.
/api/v1/links links.read/api/v1/links links.writeThe link object
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier for the link. |
| shortCode | string | The alias / key after the domain (e.g. q4-launch). |
| shortUrl | string | The full short URL, including the domain. |
| url | string | The destination the link redirects to. |
| title | string | Human-readable title. |
| tags | string[] | Tag names attached to the link. |
| clickCount | integer | Total resolved clicks (after bot filtering). |
| createdAt | datetime | When the link was created (ISO 8601). |
List links
Supports search, tag, folder, and pagination.
curl "https://app.trims.app/api/v1/links?search=q4&tag=campaign&page=1&pageSize=50" \
-H "Authorization: Bearer $TRIMS_TOKEN"{
"links": [
{
"id": "lnk_8f2a",
"shortCode": "q4-launch",
"shortUrl": "https://go.northlight.co/q4-launch",
"url": "https://northlight.co/products/launch-2026",
"title": "Q4 Launch",
"tags": ["campaign", "q4"],
"clickCount": 12483,
"createdAt": "2026-02-01T10:22:00Z"
}
],
"page": 1,
"pageSize": 50,
"total": 128
}Create a link
| Field | Type | Description |
|---|---|---|
| url* | string | Destination URL. Must be absolute and valid. |
| key | string | Custom alias. If omitted, a random code is generated. 409 if taken. |
| domain | string | Custom domain to mint on. Defaults to the workspace's primary domain. |
| title | string | Title for the dashboard and social previews. |
| tags | string[] | Tag names; unknown tags are auto-created. |
| utmSource… | string | Optional utmSource, utmMedium, utmCampaign, utmContent, utmTerm. |
| expiresAt | datetime | Optional expiry timestamp (ISO 8601). |
| password | string | Optional password to gate the destination. |
curl -X POST https://app.trims.app/api/v1/links \
-H "Authorization: Bearer $TRIMS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://northlight.co/pricing", "key": "pricing", "tags": ["q4"] }'const res = await fetch("https://app.trims.app/api/v1/links", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TRIMS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://northlight.co/pricing", key: "pricing" }),
});
const link = await res.json();
console.log(link.shortUrl);Domains
List the verified custom domains available for minting links in this workspace.
/api/v1/domains domains.read{
"domains": [
{ "id": "dom_1", "domain": "go.northlight.co", "verified": true, "ssl_status": "active" }
]
}Folders
Folders group links for organization. Read them to power navigation or filtered creation.
/api/v1/folders folders.read{
"folders": [
{ "id": "fld_1", "name": "Campaigns", "linkCount": 42 }
]
}Analytics
Read analytics for a link or your workspace. Requires the analytics.read scope (advanced metrics require the Pro plan).
/api/v1/analytics analytics.readPass an alias for a single link, or omit it for workspace-wide aggregates. Control the window with period (24h, 7d, 30d, 90d, all).
curl "https://app.trims.app/api/v1/analytics?alias=q4-launch&period=30d" \
-H "Authorization: Bearer $TRIMS_TOKEN"{
"total_clicks": 12483,
"unique_visitors": 8121,
"top_country": "US",
"top_referrer": "linkedin.com",
"period": "30d"
}Conversion tracking
Record leads and sales so TRIMS can attribute revenue back to the link and customer that drove it. Requires conversions.write.
/api/v1/track/lead conversions.write/api/v1/track/sale conversions.write| Field | Type | Description |
|---|---|---|
| customer_id* | string | The customer the event belongs to (create/identify via your app). |
| event_name | string | A label for the conversion, shown in reports. |
| amount | integer | Sale value in the smallest currency unit (e.g. cents). Sales only. |
| currency | string | ISO 4217 code. Defaults to usd. Sales only. |
curl -X POST https://app.trims.app/api/v1/track/sale \
-H "Authorization: Bearer $TRIMS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_8f2a",
"amount": 4900,
"currency": "usd",
"event_name": "Pro subscription"
}'Customers
List the customers in your workspace with their attributed leads, sales, and lifetime revenue. Requires conversions.read.
/api/v1/customers conversions.read{
"customers": [
{
"id": "cus_8f2a",
"email": "nadia@company.com",
"first_link": "q4-launch",
"leads": 3,
"sales": 2,
"revenue": 124000
}
]
}QR codes
Generate a QR code image for any URL. Requires links.read.
/api/v1/qr?url={url} links.readcurl "https://app.trims.app/api/v1/qr?url=https://go.northlight.co/q4-launch" \
-H "Authorization: Bearer $TRIMS_TOKEN" --output qr.pngOAuth 2.0
Building an app that acts on behalf of other TRIMS users? Use the OAuth 2.0 authorization-code flow instead of a static token, so each user grants your app scoped access to their workspace.
/oauth/authorize/oauth/token- 1Redirect the user to /oauth/authorize with your client_id, redirect_uri, response_type=code, and requested scopes.
- 2The user reviews the scopes and approves; TRIMS redirects back to your redirect_uri with a short-lived authorization code.
- 3Your server exchanges the code at /oauth/token for an access token and a refresh token.
- 4Call /api/v1/* with the access token; use the refresh token to obtain a new one when it expires.
curl -X POST https://app.trims.app/oauth/token \
-d grant_type=authorization_code \
-d code=$AUTH_CODE \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET \
-d redirect_uri=$REDIRECT_URIWebhooks
Instead of polling, subscribe to events and TRIMS will POST a signed JSON payload to your endpoint whenever they occur. Manage webhook subscriptions in Settings → Webhooks (API access / Pro required).
Events
link.created,link.updated,link.deletedlink.clicked— emitted on every redirect (high volume).lead.created,sale.created— conversion events.spike.detected— anomalous-traffic alert.
{
"event": "sale.created",
"created_at": "2026-03-02T12:04:11Z",
"data": {
"customer_id": "cus_8f2a",
"link": "q4-launch",
"amount": 4900,
"currency": "usd"
}
}Verifying signatures
Each delivery includes an X-Trims-Signature header — an HMAC-SHA256 of the raw request body using your webhook secret. Recompute it and compare in constant time to reject spoofed or tampered requests.
import crypto from "node:crypto";
export function verify(rawBody, signature, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}SDKs & tooling
Use the raw REST API from any language, or generate a typed client from the OpenAPI spec.
- Point Postman or Insomnia at
https://app.trims.app/api/v1/openapi.jsonto import every endpoint. - Run openapi-generator to scaffold a client in your language of choice.
- Store your token in an environment variable (
TRIMS_TOKEN) — never in source control.
npx @openapitools/openapi-generator-cli generate \
-i https://app.trims.app/api/v1/openapi.json \
-g typescript-fetch \
-o ./trims-clientChangelog
Support
Stuck on an integration? We're happy to help.
Build something great
Grab a scoped token from your workspace settings and make your first API call in minutes.