# GenLink API GenLink runs AI agents that find sites and pitch them for backlinks. This file is the short guide for scripts and LLM agents. The machine-readable spec is https://backend.genlink.so/openapi.json. ## Auth - Base URL: https://backend.genlink.so/api - Create a token in the app: Settings → API access → Create token. It is shown once. Copy it then. - Send it on every call: `Authorization: Bearer ` - Every call acts as you, in your current workspace. ## Talk to your manager agent The manager is the main entry point. Tell it what you want in plain words. POST /manager/messages body: {"message": "Create an agent targeting seed-stage founders and find 25 leads"} optional: "campaign_uuid" (pin the turn to one agent), "table_uuid" message is required, at most 6000 characters. 200: {"success": true, "reply": "", "turn_messages": []} 422: {"error": "No active workspace"} or a validation error. A turn can chain several tool calls and take 60-180 seconds. Use a client timeout of at least 300 seconds. GET /manager/messages?limit=100 The newest messages (limit clamped to 10-500), returned oldest first: {"success": true, "data": {"workspace_id": 12, "messages": [...]}} Each message: {"id", "role" (user|assistant|tool), "type", "content", "metadata", "created_at" (ISO 8601), "time" (unix seconds)}. Example: curl -X POST https://backend.genlink.so/api/manager/messages \ -H "Authorization: Bearer $GENLINK_API_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ --max-time 300 \ -d '{"message":"how are my agents doing this week?"}' ## Manage tokens GET /tokens your live API tokens: {"tokens": [{"id", "name", "last_used" (always null), "created_at", "expires_at"}]} POST /tokens {"name": "my script"} (optional, max 80 chars) 201: {"ok": true, "token": "", "name", "token_id", "note"} 422 "Revoke an old token first" at 20 live tokens. DELETE /tokens/{id} revokes it at once. 404 if it is not yours. Sign-in sessions never appear in this list and cannot be revoked here. ## Check your account, agents and results (read-only) Send "Accept: application/json" on every call. Without it a validation error comes back as a redirect instead of JSON. GET /workspaces {"success": true, "data": {"current_workspace_id": 12, "workspaces": [{"id", "name", ...}]}} Your token acts in current_workspace_id. Switch workspace in the app. GET /campaigns?page=1 Your agents, 20 a page, in your sidebar order: {"campaigns": {"data": [{"uuid", "name", "status" (running|paused), "is_active", "kind", "leads_count", "sent_count", "reply_count", "success_count", "daily_send_cap", "mandate"}], "current_page", "last_page", "total"}} A row carries other keys. They are internal and can change. GET /results {"domain", "metrics", "totals": {"won", "attributed", "domains", "value_usd"}, "backlinks": [...], "activity": {"days": 90, "series": [{"date", "leads", "sends", "replies", "backlinks"}]}, "ai_visibility"} 404 {"message": "No workspace"} until onboarding is finished. ## Set up a coding agent https://genlink.so/agent-setup.md is a prompt for Claude Code, Cursor, Codex or any agent that can make HTTP calls. It walks the agent through a token, these docs and a check. ## Receive drafts on your own site (drafts API) If your site is not on a platform we publish to, you can run a small endpoint that receives drafts. Your agents push articles and link-insertion proposals into it. A person on your side reviews and publishes them. GenLink never publishes anything itself. Setup: in Settings → Integrations, add a key with service: drafts_api secret: the bearer token your endpoint expects meta: {"base": "https://your-site.example/api/genlink"} (https only; optional "language": en|fr|de|es|pt, default en) What we send: POST {base}/drafts Authorization: Bearer Idempotency-Key: <1-120 chars, equal to external_id> Content-Type: application/json { "kind": "article" | "link_insertion", "title": "...", "content": "...", article: the post body (markdown or HTML) link_insertion: what to add and where "language": "en" | "fr" | "de" | "es" | "pt", "external_id": "genlink-ws12-", "target_url": "...", link_insertion only: the page to edit "source_url": "..." link_insertion only, when we have it } We never send keys other than these. Missing optional keys are left out, never sent as null, so a strict schema (additionalProperties: false) works. Idempotency: the same proposal always carries the same Idempotency-Key. A retry after a timeout sends the identical request again. A different proposal never reuses a key. How we read your answer: 201 stored for review. Success. Return {"id": "", "status_url": ""}; we show status_url to the agent. 200 replay of a key you already stored with the same content. Success. 409 same key, different content. Reported to the agent as a conflict. 401 / 403 key rejected. We tell the user to save a new key. anything else 4xx/5xx a failure; we relay your "error" or "message" (first 400 characters). Status: GET {base}/drafts/{id} should return the draft and its review status. Agents call it to check on a proposal. Optional: GET {base}/health (no auth), for example {"status": "ok", "mode": "drafts_only", "publishing_enabled": false} Your endpoint lists no posts. When an agent needs a page to propose a link insertion on, it picks from your sitemap, which GenLink already reads.