Overview
Two schemes, one API
Inksong accepts two authentication schemes against the same set of endpoints. Long-lived API keys are the recommended path for server-to-server integrations — back-end workers, cron jobs, internal tools, anything running outside a browser. Short-lived Bearer access tokens are what the web app uses, and they’re a reasonable fit for OAuth-style flows when those ship.
Pick API keys for back-end integrations. Pick Bearer tokens when you have a real user session in front of a browser. The endpoints don’t care which header you send — they only care that it’s valid.
API keys
Long-lived, server-side
Every Inksong account is provisioned with one API key. View or rotate it from your dashboard. Keys are prefixed ink_followed by 48 hexadecimal characters — 52 characters total, generated from a cryptographically strong source.
Authenticate by sending the key in the X-API-Key header. API keys do not expire on a schedule. If a key is compromised, regenerate it yourself from the dashboard — the old key stops working immediately and a new one is shown to you once for copying.
curl
curl https://api.inksong.app/api/v1/documents/ \ -H "X-API-Key: ink_YOUR_KEY_HERE"
Python
import os
import requests
response = requests.get(
"https://api.inksong.app/api/v1/documents/",
headers={"X-API-Key": os.environ["INKSONG_API_KEY"]},
)
response.raise_for_status()
print(response.json())JavaScript
const response = await fetch("https://api.inksong.app/api/v1/documents/", {
headers: { "X-API-Key": process.env.INKSONG_API_KEY },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log(data);Bearer tokens
Short-lived, browser-context
Bearer access tokens are issued by POST /api/v1/auth/login in exchange for an email + password, and refreshed via POST /api/v1/auth/refresh. Access tokens expire after 15 minutes. Refresh tokens are issued as httpOnly cookies with the Secure and SameSite=Strictflags — they’re not exposed to JavaScript on purpose.
Authenticate by sending the access token in the Authorization: Bearer <token>header. This is what the web app uses, and it’s the reasonable choice for any integration that already has a user signed in via the dashboard.
curl
# 1. Log in
curl -X POST https://api.inksong.app/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"..."}'
# → {"access_token":"eyJ...","token_type":"bearer"}
# 2. Authed request
curl https://api.inksong.app/api/v1/documents/ \
-H "Authorization: Bearer eyJ..."Python
import requests
session = requests.Session()
login = session.post(
"https://api.inksong.app/api/v1/auth/login",
json={"email": "you@example.com", "password": "..."},
)
login.raise_for_status()
token = login.json()["access_token"]
response = session.get(
"https://api.inksong.app/api/v1/documents/",
headers={"Authorization": f"Bearer {token}"},
)
print(response.json())JavaScript
const login = await fetch("https://api.inksong.app/api/v1/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "you@example.com", password: "..." }),
credentials: "include",
});
const { access_token } = await login.json();
const response = await fetch("https://api.inksong.app/api/v1/documents/", {
headers: { Authorization: `Bearer ${access_token}` },
});
console.log(await response.json());Choosing
Which scheme to pick
If your code runs on a server you control — a Lambda, a Rails worker, a Django management command, a bash script in a CI pipeline — use an API key. It’s simpler, doesn’t expire, and doesn’t require a login round-trip.
If your code runs in a browser tab, use a Bearer token. The token is short-lived, the refresh token is httpOnly, and the user is in front of the browser to recover from any edge case.
Most external integrations want API keys. Reach for Bearer tokens only when you specifically need a user session.
Security
Keep keys out of client code
Never embed an API key in client-side JavaScript, mobile binaries, or any artifact shipped to a user’s machine. Never commit a key to git. Keys belong in environment variables or a secrets manager (1Password, AWS Secrets Manager, HashiCorp Vault, Doppler — whatever your stack uses).
If a key leaks — pushed to a public repo, pasted into a Slack channel, captured in a screenshot — rotate it immediately from the API keys page. Rotation is instant: the old key starts returning 401 Unauthorized on the next request, and the new key is shown to you once for copying.
IP allowlisting on a per-key basis is something we want to add — you’ll be able to scope a key to a CIDR range and reject everything else at the edge. It’s not shipped yet.