API reference
HTTPS API for links, slug checks, expirations, password protection, and bulk creation.
API reference
This page documents the current Nimriz customer-facing API contract for link creation and link management.
Base rules
- All write operations are server-side calls. Do not expose workspace API keys or internal control-plane keys to browsers.
- Link uniqueness is domain-bound: the same slug can exist on different domains, but not twice on the same
domain_id. - Redirects default to
302. Send301only when you explicitly want a permanent redirect. - Host matching is strict.
example.comandwww.example.comare different domains.
Canonical hosts
- Browser-authenticated dashboard and session routes live on
https://app.nimriz.com. - Machine-facing Nimriz API traffic should use
https://api.nimriz.com. - Short-link hosts such as
rix.to,riz.to,nim.lu, or customer-branded link domains are redirect surfaces, not the preferred long-term machine API host. short_urlfields returned by Nimriz can still use those redirect domains because they represent the redirect surface, not the API surface.
Authentication
Supported customer-facing API routes use a workspace API key generated from the dashboard API access screen. Send one of:
Authorization: Bearer <WORKSPACE_API_KEY>
or:
X-Nim-Api-Key: <WORKSPACE_API_KEY>
Public domains are intentionally lower-trust and can allow unauthenticated create and slug-check flows, but quotas and expiration policy still apply.
CONTROL_PLANE_API_KEY remains an internal first-party credential for Nimriz app-to-Worker traffic. It is not the customer-facing key you should hand to workspace users.
You do not need a workspace API key for conversion tracking. The conversion API uses the workspace conversion API signing secret instead.
For more about domain access tiers and readiness, see Domains and DNS.
Conversion API authentication
Conversion API requests do not use workspace API keys or CONTROL_PLANE_API_KEY.
Instead, each workspace generates its own conversion API signing secret from the dashboard integrations screen. Your backend uses that secret to sign conversion API requests sent to:
POST https://api.nimriz.com/api/conversions/callback/:workspace_id
That conversion API flow is intentionally separate from the link-management API because it is for post-click conversion events, not link-management mutations.
Trusted actor context
The first-party Nimriz app can forward trusted actor context to the Worker. This is mainly for internal or tightly controlled server-to-server flows, not for untrusted clients.
Optional headers:
X-Nim-Actor-User-IdX-Nim-Actor-Account-IdX-Nim-Actor-RoleX-Nim-Creation-Source
X-Nim-Creation-Source currently accepts values such as dashboard, import, admin, webhook, and system.
These headers are only trusted when internal control-plane auth is valid. When present and valid, Nimriz can persist provenance such as who created a link and from which flow.
Resource identifiers
domain_id: UUID of the target short-link domainurl_id: UUID of the created short linkshort_code: the slug path segment for the linkshort_url: the full short URL returned after creation or slug update
POST /api/conversions/callback/:workspace_id
Send a signed server-to-server conversion API request for an existing workspace.
Canonical request URL:
https://api.nimriz.com/api/conversions/callback/:workspace_id
Authentication
Required headers:
Content-Type: application/json
X-Nim-Timestamp: <unix-seconds>
X-Nim-Signature: v1=<hex-hmac-sha256>
Idempotency-Key: <stable-key>
Signature input:
${X-Nim-Timestamp}.${raw_request_body}
Common request fields
event_name: required; one oflead,sale,refund,cancellation,reversalevent_time: optional ISO timestampevent_id: your upstream event identifier; strongly recommended as the business dedupe keyuser_data.click_id: the original click identifier, usually captured fromnim_cton the landing URLuser_data.external_id: your stable customer or lead identifiercustom_data.order_id: your order, invoice, or payment identifiercustom_data.related_order_idorcustom_data.related_event_id: required forrefund,cancellation, andreversalcustom_data.value,custom_data.currency,custom_data.quantity, andcustom_data.properties: optional business context
Example request
{
"event_name": "lead",
"event_time": "2026-03-26T12:00:00.000Z",
"event_id": "evt_lead_123",
"user_data": {
"click_id": "nimct_exampletoken",
"external_id": "cust_123"
},
"custom_data": {
"properties": {
"source_system": "crm"
}
}
}
Successful response
{
"ok": true,
"duplicate": false,
"conversion_id": "conversion-event-1",
"event_name": "lead",
"event_time": "2026-03-26T12:00:00.000Z",
"event_id": "evt_lead_123",
"idempotency_key": "crm-lead-123",
"attributed": false,
"attribution_reason": null
}
Notes
- Send conversion API requests from your backend, not from the browser.
- Preserve
nim_ctfrom the landing URL if you want direct deterministic click attribution. - Replaying the same business event with the same idempotency key is safe; changing the payload while reusing the same key is rejected.
- Negative follow-on events are stored as new immutable events and must point back to the earlier sale or transaction they adjust.
POST /api/shorten
Create a single short link.
Canonical request URL:
https://api.nimriz.com/api/shorten
Request body
{
"domain_id": "00000000-0000-0000-0000-000000000000",
"long_url": "https://example.com/landing?utm_source=twitter&utm_medium=social&utm_campaign=spring_launch",
"custom_slug": "spring-launch",
"expires_at": "2026-12-31T23:59:59Z",
"password": "optional-password",
"redirect_status_code": 302,
"account_id": "11111111-1111-1111-1111-111111111111"
}
Fields
domain_id: requiredlong_url: required destination URLcustom_slug: optional custom short codeexpires_at: optional ISO timestamp; may be required by policypassword: optional plaintext password for a protected linkredirect_status_code: optional,302default,301only when explicitly requestedaccount_id: optional delegated-account context for shared-domain or mapped-domain flows; requires authenticated workspace access and valid domain access
Successful response
{
"url_id": "22222222-2222-2222-2222-222222222222",
"short_code": "spring-launch",
"short_url": "https://rix.to/spring-launch",
"expires_at": "2026-12-31T23:59:59.000Z",
"password_protected": true
}
Notes
- Custom domains can be blocked until DNS verification and Cloudflare readiness are complete.
- Public-domain create flows can apply a default expiration when
expires_atis omitted. - Password-protected links are plan-gated and require server-side handling.
POST /api/shorten/bulk
Create multiple links in one request. This is the current best fit for import or automation jobs that need idempotent retries.
Request body
{
"domain_id": "00000000-0000-0000-0000-000000000000",
"account_id": "11111111-1111-1111-1111-111111111111",
"items": [
{
"client_row_id": "row-1",
"idempotency_key": "import-2026-03-08-row-1",
"long_url": "https://example.com/a",
"custom_slug": "campaign-a"
},
{
"client_row_id": "row-2",
"idempotency_key": "import-2026-03-08-row-2",
"long_url": "https://example.com/b"
}
]
}
Bulk rules
- Maximum batch size is
25items. - Each item needs both
client_row_idandidempotency_key. - Validation failures are returned per row instead of failing the entire batch.
- Replaying the same
idempotency_keyreturns the stored result withidempotent: true.
Successful response
{
"results": [
{
"client_row_id": "row-1",
"ok": true,
"idempotent": false,
"short_code": "campaign-a",
"short_url": "https://rix.to/campaign-a"
},
{
"client_row_id": "row-2",
"ok": true,
"idempotent": true,
"short_code": "campaign-b",
"short_url": "https://rix.to/campaign-b"
}
]
}
POST /api/check-slug
Validate a candidate slug for a specific domain and check availability.
Request body
{
"domain_id": "00000000-0000-0000-0000-000000000000",
"slug": "spring-launch"
}
Responses
Available:
{
"available": true
}
Taken or tombstoned:
{
"available": false,
"error": "Slug is reserved",
"code": "slug_tombstoned"
}
PUT /api/update-slug
Update the slug for an existing link.
Request body
{
"url_id": "22222222-2222-2222-2222-222222222222",
"new_slug": "spring-launch-v2"
}
Successful response
{
"short_code": "spring-launch-v2",
"short_url": "https://rix.to/spring-launch-v2"
}
PUT /api/update-expiration
Set, change, or remove link expiration.
Request body
{
"url_id": "22222222-2222-2222-2222-222222222222",
"expires_at": "2026-12-31T23:59:59Z"
}
Use null to remove expiration when the current domain and plan policy allow it.
Successful response
{
"url_id": "22222222-2222-2222-2222-222222222222",
"expires_at": "2026-12-31T23:59:59.000Z",
"was_expired": false
}
PUT /api/update-password
Set, replace, or remove password protection for a link.
Request body
Set or replace:
{
"url_id": "22222222-2222-2222-2222-222222222222",
"password": "new-password"
}
Remove:
{
"url_id": "22222222-2222-2222-2222-222222222222",
"password": null
}
Successful response
{
"url_id": "22222222-2222-2222-2222-222222222222",
"password_protected": true
}
POST /api/links/routing-rules
Save the full ordered routing ruleset for one link from the dashboard.
Request body
{
"url_id": "22222222-2222-2222-2222-222222222222",
"operation": "save",
"rules": [
{
"name": "US mobile override",
"enabled": true,
"actionType": "destination_override",
"conditions": {
"countryIn": ["US"],
"deviceOsIn": [],
"deviceTypeIn": ["mobile"],
"timeWindows": []
},
"destinationUrl": "https://example.com/us-mobile"
}
]
}
Notes
- The payload is ordered. The first enabled matching rule wins.
- The dashboard create flow may author these rules from separate capability tabs such as
Geo & Device Targeting,Deep links, andA/B Testing, but the API persists them as one ordered Route/Experiment decision list through this adapter payload. - Supported action types are
destination_override,ab_split, anddeep_link. - Routing-rule saves require both the relevant Pro entitlement and the workspace rollout flag.
- Deep-link actions are only accepted on ready branded domains in v1.
- Deep-link saves also require the deep-link rollout flag for the workspace.
- Routing remains per-link only in v1. Domain-level templates/defaults are intentionally deferred.
- Older
redirect_rulescompatibility remains server-side only during rollout; clients should treat this endpoint as the canonical dashboard mutation surface.
POST /api/links/routing-preview
Preview how the current draft rules would resolve for a specific request shape.
Request body
{
"url_id": "22222222-2222-2222-2222-222222222222",
"host": "links.example.com",
"path": "/hello77",
"country": "US",
"device_os": "ios",
"device_type": "mobile",
"preview_at": "2026-03-18T12:00:00.000Z",
"rules": []
}
Successful response
{
"ok": true,
"preview": {
"destination_url": "https://example.com/us-mobile",
"decision_id": "rule-country-first",
"decision_kind": "route",
"destination_type": "web",
"outcome": "rule_web_redirect",
"match_type": "country",
"fallback_reason": null,
"variant_key": null,
"experiment_key": null,
"experiment_run_key": null,
"routing_rule_name": "US mobile override"
}
}
Notes
- This route proxies to a Worker preview endpoint so routing preview stays aligned with the live evaluator.
- Preview uses the same workspace rollout gates as live routing.
- The preview uses a first-visit posture for A/B routing. Sticky cookies are not replayed.
- Host and path are included for realism, but v1 rule matching is still driven by the selected link plus request context.
- Canonical preview identifiers are
decision_id,decision_kind,destination_type,experiment_key,experiment_run_key, andvariant_key.
Common error codes
| Code | Meaning |
| --- | --- |
| monthly_quota_exceeded | The request exceeded a monthly create quota for the current scope. |
| slug_tombstoned | The slug cannot be reused yet after cleanup or purge. |
| domain_disabled | The target domain is disabled. |
| account_suspended | The effective account cannot create or mutate links. |
| feature_flag_disabled | The requested delegated/shared-domain behavior is disabled for that account. |
| routing_rules_not_allowed | Routing rules are not enabled for the current workspace yet. |
| ab_routing_not_allowed | A/B routing is not enabled for the current workspace yet. |
| deep_link_domain_not_ready | Deep-link routing is only available on ready branded domains that can serve app-link trust files. |
| domain_access_denied | The account does not have access to the requested mapped domain. |
| custom_domain_not_ready | The custom domain is not ready for traffic yet. |
| invalid_expires_at | The expiration timestamp is malformed. |
| expires_in_past | The expiration timestamp is already in the past. |
| expires_too_soon | The expiration violates the minimum TTL policy. |
| expires_too_far | The expiration violates the maximum TTL policy. |
| expiration_required | The domain policy requires an expiration. |
| expiration_update_not_allowed | The current domain policy does not allow expiration changes. |
| reactivation_not_allowed | The link is already expired and the current tier does not allow extending it back to active. |
| feature_not_enabled | The requested feature, such as password protection, is not enabled for the current plan or account. |
Retry guidance
- Do not blindly retry
4xxresponses. They usually indicate invalid input or policy failure. - Retry
429,502,503, and504with exponential backoff. - For import or job-style automation, prefer
POST /api/shorten/bulkplus stableidempotency_keyvalues.