The Link API.
Zero friction.

Link exposes a small REST API so your code can create and measure short links. Same rules as everything we make: simple, documented, free.

Base URL

https://zerosoft.ai/api/v1

Authentication

Every request needs an API key, sent as a Bearer token:

Authorization: Bearer zs_live_9f3k...

Create keys in Dashboard → Settings → API keys. A key is shown exactly once — copy it and store it like a password.

Each key allows 100 requests per minute. Go over and you get a 429 with a Retry-After header. Need more? Self-host and rate-limit yourself.

Endpoints

Create a link

Creates a short link. slug is optional — leave it out and we generate one.

POST/api/v1/links

Request

curl -X POST https://zerosoft.ai/api/v1/links \
  -H "Authorization: Bearer zs_live_9f3k..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://zerosoft.ai/blog/software-is-dead",
    "slug": "manifesto",
    "title": "The manifesto",
    "tags": ["launch", "blog"]
  }'

Response · 201

{
  "id": "lnk_9f3ka2",
  "slug": "manifesto",
  "short_url": "https://zs.ai/manifesto",
  "url": "https://zerosoft.ai/blog/software-is-dead",
  "title": "The manifesto",
  "tags": ["launch", "blog"],
  "created_at": "2026-07-14T18:22:41Z"
}

List links

Your links, newest first. Paginate with page and pageSize (max 100).

GET/api/v1/links?page=1&pageSize=10

Request

curl "https://zerosoft.ai/api/v1/links?page=1&pageSize=10" \
  -H "Authorization: Bearer zs_live_9f3k..."

Response · 200

{
  "links": [
    {
      "id": "lnk_9f3ka2",
      "slug": "manifesto",
      "short_url": "https://zs.ai/manifesto",
      "url": "https://zerosoft.ai/blog/software-is-dead",
      "title": "The manifesto",
      "tags": ["launch", "blog"],
      "created_at": "2026-07-14T18:22:41Z"
    }
  ],
  "total": 48
}

Link stats

Click totals and a per-day breakdown for one link.

GET/api/v1/links/{id}/stats

Request

curl "https://zerosoft.ai/api/v1/links/lnk_9f3ka2/stats" \
  -H "Authorization: Bearer zs_live_9f3k..."

Response · 200

{
  "slug": "manifesto",
  "total": 4812,
  "byDay": [
    { "date": "2026-07-15", "count": 412 },
    { "date": "2026-07-16", "count": 388 },
    { "date": "2026-07-17", "count": 441 }
  ]
}

The CRM API

The CRM ships with the same API: same keys, same rate limit, same rules. Contacts, companies, deals and tasks — everything the dashboard can do, your code can do.

Create a contact

POST/api/v1/crm/contacts

Request

curl -X POST https://zerosoft.ai/api/v1/crm/contacts \
  -H "Authorization: Bearer zs_live_9f3k..." \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Ana",
    "last_name": "Garcia",
    "email": "ana@kallpa.co",
    "lifecycle_stage": "sql"
  }'

Response · 201

{
  "id": "b1f47ac2-…",
  "first_name": "Ana",
  "last_name": "Garcia",
  "email": "ana@kallpa.co",
  "lifecycle_stage": "sql",
  "created_at": "2026-07-18 10:04:11"
}

Every CRM endpoint

MethodEndpointWhat it does
GET/api/v1/crm/contactsList contacts. Filters: q, page, pageSize (max 100).
POST/api/v1/crm/contactsCreate a contact.
GET/api/v1/crm/contacts/:idRead one contact.
PATCH/api/v1/crm/contacts/:idUpdate any property.
GET/api/v1/crm/companiesList companies.
POST/api/v1/crm/companiesCreate a company.
GET/api/v1/crm/companies/:idRead one company.
PATCH/api/v1/crm/companies/:idUpdate any property.
GET/api/v1/crm/dealsList deals. Filter: pipelineId.
POST/api/v1/crm/dealsCreate a deal in a pipeline stage.
GET/api/v1/crm/deals/:idRead one deal.
PATCH/api/v1/crm/deals/:idUpdate amount, stage, dates.
GET/api/v1/crm/tasksList tasks. bucket: overdue, today, upcoming, done.
POST/api/v1/crm/tasksCreate a task, optionally linked to a record.
PATCH/api/v1/crm/tasks/:idEdit or complete a task.

Errors

Errors are always JSON with the same shape:

{
  "error": {
    "code": "slug_taken",
    "message": "That slug is already in use."
  }
}
CodeHTTPMeaning
invalid_key401The key is missing, wrong or revoked.
invalid_url400The url field is missing or is not a valid http(s) URL.
slug_taken400That custom slug already exists. Pick another.
not_found404No link with that id belongs to your account.
rate_limited429Over 100 requests in a minute. Wait for Retry-After.

Good to know

  • Slugs match [A-Za-z0-9_-]{2,32} — letters, numbers, dash and underscore, 2 to 32 characters.
  • Redirects are public 302s at the root — https://<your-domain>/<slug>. No interstitials, no ads, no tracking pages.
  • Analytics are cookie-less: referrer, country and device come from request headers. Nothing personal is stored.

Coming soon

Track and Cal will ship with the same style of API — REST, JSON, boring in the best way. These docs grow as each product is set free.