Shortnd Docs

Mint from CI

Exchange an API key for a JWT, then mint a short URL from GitHub Actions or any CI shell — the one distribution recipe (not Zapier).

This is the one distribution asset for Phase 3: Postman for humans, this recipe for machines. Non-goal: Zapier, Slack bots, or browser extensions.

Prerequisites

  1. Mint an API key in the dashboard (Authentication) — format snd_….
  2. Store it as a CI secret named SHORTND_API_KEY (never commit it).
  3. Optional: import the Postman collection for the same flow interactively.

Contract (two HTTP calls)

1) POST /api/v1/auth/token
   Authorization: ApiKey $SHORTND_API_KEY
   → data.accessToken  (JWT, ~15 minutes)

2) POST /api/v1/urls
   Authorization: Bearer $SHORTND_ACCESS_TOKEN
   Body: { "targetUrl": "https://…" }
   → data.shortUrl / data.id / data.shortCode

Do not send the long-lived key as Bearer.

Shell recipe (any CI)

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="${SHORTND_BASE_URL:-https://shortnd.com}"
TARGET_URL="${1:?usage: $0 <targetUrl> [customSlug]}"
CUSTOM_SLUG="${2:-}"

: "${SHORTND_API_KEY:?set SHORTND_API_KEY}"

TOKEN_JSON=$(curl -sS -X POST "${BASE_URL}/api/v1/auth/token" \
  -H "Authorization: ApiKey ${SHORTND_API_KEY}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{}')

ACCESS_TOKEN=$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["data"]["accessToken"])' <<<"$TOKEN_JSON")

if [[ -n "$CUSTOM_SLUG" ]]; then
  BODY=$(python3 -c 'import json,sys; print(json.dumps({"targetUrl":sys.argv[1],"customSlug":sys.argv[2]}))' "$TARGET_URL" "$CUSTOM_SLUG")
else
  BODY=$(python3 -c 'import json,sys; print(json.dumps({"targetUrl":sys.argv[1]}))' "$TARGET_URL")
fi

CREATE_JSON=$(curl -sS -X POST "${BASE_URL}/api/v1/urls" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d "$BODY")

python3 -c 'import json,sys; d=json.load(sys.stdin); data=d["data"]; print(data["shortUrl"]); print("id="+str(data["id"])); print("shortCode="+data.get("shortCode",""))' <<<"$CREATE_JSON"

Repo copy (same logic): scripts/mint-url-from-ci.sh.

export SHORTND_API_KEY=snd_…   # from CI secrets
./scripts/mint-url-from-ci.sh 'https://example.com/launch' 'launch-2026'

GitHub Actions

name: Mint Shortnd link
on:
  workflow_dispatch:
    inputs:
      target_url:
        description: Destination URL
        required: true
      custom_slug:
        description: Optional custom slug
        required: false
        default: ''

jobs:
  mint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Exchange key and mint URL
        env:
          SHORTND_API_KEY: ${{ secrets.SHORTND_API_KEY }}
          SHORTND_BASE_URL: https://shortnd.com
        run: |
          chmod +x scripts/mint-url-from-ci.sh
          ./scripts/mint-url-from-ci.sh \
            "${{ inputs.target_url }}" \
            "${{ inputs.custom_slug }}"

Use a dedicated key with only the scopes you need (urls:write). Rotate if it leaks.

Postman (same flow)

  1. Download public-api.postman_collection.json.
  2. Set collection variable apiKey.
  3. Run Auth → POST /api/v1/auth/tokenaccessToken is saved automatically.
  4. Run URLs → POST /api/v1/urlsid / shortUrl / shortCode are saved automatically.

Details: Postman collection.

Ops note

Production Contabo runs an hourly token → create → delete smoke (scripts/smoke-public-api.sh). That proves the same auth path; it is not a substitute for your product CI mint step. See docs/ops/PUBLIC_API_SMOKE_CRON.md.