REST API reference
Every endpoint with its scope, parameters, request example and a curl command to copy. Also as OpenAPI 3.1 and a Postman collection.
Developers
Every organization on c1k.me can be driven from code: a REST API for your own systems, and an MCP endpoint for AI assistants such as Claude, ChatGPT, Cursor and Codex. Both follow the same rules as the web interface, so a link made by a script or an assistant behaves exactly like one made by hand.
Every endpoint with its scope, parameters, request example and a curl command to copy. Also as OpenAPI 3.1 and a Postman collection.
Setup for Claude Code, Claude, ChatGPT, Cursor, VS Code, Codex and Gemini CLI, with an organization token or OAuth sign-in.
Small, complete programs in curl, JavaScript, Python and PHP. Each one runs unchanged in our test pipeline before every release.
export C1K_API_KEY='c1k_rest_...'
export C1K_ORG_ID='org_...'
/me answers for any valid key and lists what it may do.curl -sS "https://c1k.me/api/v1/orgs/$C1K_ORG_ID/me" \
-H "Authorization: Bearer $C1K_API_KEY"
destination_url is required. The Idempotency-Key makes a retry after a lost response safe.curl -sS -X POST "https://c1k.me/api/v1/orgs/$C1K_ORG_ID/links" \
-H "Authorization: Bearer $C1K_API_KEY" \
-H "Idempotency-Key: c1k-$(date +%s)-$RANDOM" \
-H 'Content-Type: application/json' \
-d '{"destination_url":"https://venture.example/launch","title":"Launch page"}'
The answer carries data.short_url, for example https://c1k.me/Ab3xY9k, and the link's version.
| Base URL | https://c1k.me/api/v1/orgs/{org_id}. A key works for exactly one organization; anything else answers 404. |
|---|---|
| Authentication | Authorization: Bearer c1k_rest_... only. Keys in URLs, query strings or cookies are refused. |
| Scopes | links:read, links:write, links:delete, analytics:read, analytics:delete, exports:read, events:read, members:manage, integrations:manage. An operation outside the key's scopes answers 403. |
| Format | UTF-8 JSON, at most 65,536 bytes per request. Success: {"data": ..., "meta": {"request_id": ...}}. Times are RFC 3339 in UTC. |
| Safe retries | Creating links, bulk changes and import apply take an Idempotency-Key (16 to 128 characters). The same key with the same body returns the first result for 24 hours; a different body answers 409. |
| Concurrent edits | Edits and deletions need If-Match with the current version (the ETag). Missing answers 428, outdated answers 412. |
| Pages | Lists return meta.next_cursor; pass it back as cursor. Up to 100 items per page. |
| Rate limits | 60 requests per minute per key, 300 per organization, 10 changes per minute per key. A 429 says how long to wait in Retry-After. |
Every error has the same shape, with a stable code to branch on and a request_id to quote when you ask for help:
{"error": {"code": "invalid_input", "message": "Some fields are not valid.",
"fields": [{"field": "destination_url", "code": "required", "message": "..."}],
"request_id": "req_..."}}
| Status and code | What to do |
|---|---|
| 400 malformed_json, invalid_input | Fix the request; fields names each problem. |
| 401 unauthenticated | The key is missing, wrong, expired or revoked. |
| 403 forbidden | The key lacks the scope, or the organization is suspended. |
| 404 not_found | No such object in this key's organization. |
| 409 conflict | A taken slug, a reused Idempotency-Key with another body, or a stale import receipt. |
| 410 cursor_expired | The event cursor fell out of the retained history; take a fresh snapshot. |
| 412 version_conflict, 428 precondition_required | Read the link again and send its current version in If-Match. |
| 413, 415, 422 | Body too large, not JSON, or fields not valid. |
| 429 rate_limited | Wait for Retry-After seconds, then retry. |
| 503, 507 storage_unavailable | Busy or full; retry later. Short links keep redirecting. |
orgId and apiKey, then send.