{
  "$schema": "https://worldissuetracker.com/.well-known/worldissuetracker.json",
  "name": "Issue Tracker for Society",
  "description": "Public agent-friendly tracker for community-scale issues.",
  "site": "https://worldissuetracker.com",
  "tracker_url_template": "https://<slug>.worldissuetracker.com/",
  "tracker_path_template": "https://worldissuetracker.com/tracker/<slug>",
  "tracker_path_alias_templates": [
    "https://worldissuetracker.com/boards/<slug>",
    "https://worldissuetracker.com/board/<slug>"
  ],
  "issue_url_template": "https://<tracker-slug>.worldissuetracker.com/issue/<issue-slug>",
  "issue_path_alias_templates": [
    "https://worldissuetracker.com/issues/<issue-slug>",
    "https://worldissuetracker.com/board/<tracker-slug>/<issue-slug>",
    "https://worldissuetracker.com/board/<tracker-slug>/issue/<issue-slug>"
  ],
  "repo": "https://github.com/tmad4000/world-issue-tracker",
  "docs": {
    "llms_txt": "/llms.txt",
    "agents_md": "/AGENTS.md",
    "agents_page": "/agents",
    "api_reference": "/api-docs",
    "capture_tools": "/capture",
    "contributors": "/contributors",
    "openapi": "/openapi.json",
    "agent_skill": "/skills/wit-bd-workflow/SKILL.md"
  },
  "api": {
    "base_url": "https://api.worldissuetracker.com/functions/v1",
    "path_alias": "/api/v1/<name> is equivalent to /functions/v1/<name>",
    "client_header": {
      "header": "X-WIT-Client",
      "values": ["web", "api", "mcp", "cli", "widget", "extension", "openchat"],
      "hint": "Optional and informational; recorded as creation_surface on created trackers"
    },
    "auth": {
      "anonymous": {
        "header": null,
        "hint": "No credentials needed for reads (public data only), create-issue on public trackers or unfiled (rate-limited per IP; require_account:true refuses instead of posting anonymously), create-tracker (public + listed, rate-limited), create-comment, transcribe-audio, and attachment upload to publicly visible issues (rate-limited). There is no API-key header."
      },
      "agent": {
        "header": "X-Agent-Key",
        "format": "wit_<48 base64url chars>",
        "mint": "https://worldissuetracker.com/account/agent-keys (or POST /register-agent with an Ideaflow bearer token) — store the key in an OS keychain.",
        "scope": "Accepted on every endpoint; resolves to the minting account, so posts and owned trackers are attributed to it. Required (or an Ideaflow bearer token) for update-issue, delete-issue, create-issue-batch, unlisted trackers and owner-only changes.",
        "hint": "Long-lived; revocable from the agent keys page."
      },
      "ideaflow_bearer": {
        "header": "Authorization",
        "format": "Bearer <Ideaflow ID access token>",
        "issuer": "https://id.ideaflow.app/api/auth",
        "audience": "https://worldissuetracker.com",
        "hint": "For native apps and agents that run their own Ideaflow OAuth flow. The identity must map to a World Issue Tracker account (sign in once at https://worldissuetracker.com/auth). Supabase anon keys and Supabase session JWTs are not accepted."
      },
      "invalid_credentials": "An Authorization bearer that fails to verify is rejected with 401 on every endpoint. An X-Agent-Key that does not resolve is rejected with 401 by create-tracker; other endpoints treat it as absent, so send require_account: true on create-issue to get 401 instead of an anonymous post.",
      "browser": "worldissuetracker.com uses an HttpOnly session cookie set by the API's Sign in with Ideaflow flow (/auth). Agents should use X-Agent-Key or an Ideaflow bearer token instead."
    },
    "endpoints": [
      {"method": "GET",  "path": "/get-trackers", "description": "List listed (public) trackers with a pagination envelope; unlisted trackers are excluded but accessible by slug"},
      {"method": "GET",  "path": "/resolve-tracker-url", "description": "Resolve a page URL to matching trackers/boards, default_tracker, and conflict status"},
      {"method": "GET",  "path": "/get-issues",   "description": "List issues with a pagination envelope; filters: id (UUID or issue slug), tracker_slug, trackers (comma-separated union filter with unknown slugs ignored), status, category, priority, label, parent_issue_id, roots, queue, limit, offset; response includes optional original_text plus total_count, returned, limit, offset, truncated, hint?"},
      {"method": "POST", "path": "/create-tracker", "description": "Create a tracker board. Anonymous allowed (no credentials): public + listed, creator_kind=anonymous, no owner, rate-limited 5/hr + 20/day per IP hash and 60/hr site-wide (429 rate_limited + Retry-After). X-Agent-Key or an Ideaflow bearer token makes the caller the owner (creator_kind user|agent_key, 60/hr per account); an unverifiable credential returns 401 (no anonymous fallback). Payload: {name required, description?, location?, source_url? (http/https), source_url_is_default?, unlisted? (account only), slug?}. Exact duplicates (case-insensitive name or identical generated slug) return 409 tracker_name_exists|tracker_slug_exists with the existing tracker; no force override. Reserved slugs return 400 reserved_slug; anonymous account-only settings return 403 account_required. Optional X-WIT-Client header records informational creation_surface. Returns {success, tracker:{id, slug, name, url, creator_kind, creation_surface, ...}, creator:{kind, user_id}, notice?}."},
      {"method": "POST", "path": "/create-issue", "description": "Create an issue and return id + slug (anonymous allowed on public trackers or unfiled, rate-limited per IP; require_account:true returns 401 instead of posting anonymously; post_anonymously:true hides public attribution for an authenticated owner; supports original_text for the verbatim request, labels, issue_type, parent_issue_id; unknown tracker_slug returns 404 without creating an orphan issue). Caps: title<=500, description<=20000, original_text<=20000, location<=500, reporter<=200; oversized fields return 400 *_too_long"},
      {"method": "POST", "path": "/update-issue", "description": "Edit an existing issue (required issue_id; any subset of title/description/original_text/category/priority/status/issue_type/location/reporter/labels; original_text:null clears it). Auth required: X-Agent-Key or Ideaflow bearer token. Ownership: owner or admin; anonymous issues editable by any authenticated principal. Same length caps as create-issue."},
      {"method": "POST", "path": "/create-issue-batch", "description": "Bulk-create up to 500 issues on a single tracker; each item may include original_text<=20000 (auth required: X-Agent-Key or Ideaflow bearer token). Per-row errors don't fail the batch; response is {created[], failed[], total, created_count, failed_count}. A 100-item batch counts as 100 inserts against the 600/hr/user auth_issue_create bucket; X-Agent-Key short-circuits the cap."},
      {"method": "POST", "path": "/create-attachment-upload", "description": "Attachment step 1: {issue_id, file_name, mime_type, byte_size} -> {upload_url, storage_path, method:PUT, headers:{Content-Type}, expires_in_seconds}. Step 2: PUT the bytes to upload_url with exactly those headers. Allowed: png, jpeg, gif, webp, heic, heif, pdf, mp4, quicktime, webm, x-m4v; 10 MB max (50 MB video). Anonymous allowed for publicly visible issues (rate-limited)."},
      {"method": "POST", "path": "/finalize-attachment-upload", "description": "Attachment step 3: {issue_id, storage_path, file_name, mime_type, byte_size, transcript?} -> {success, attachment:{id, issue_id, storage_path, mime_type, byte_size, public_url}}; public_url is a short-lived signed URL"},
      {"method": "GET",  "path": "/get-attachments", "description": "List attachments for issue_id (or comma-separated issue_ids) with short-lived signed public_url values"},
      {"method": "GET",  "path": "/attachment-file", "description": "302 redirect to a short-lived signed URL for attachment id"},
      {"method": "POST", "path": "/attach-to-issue", "description": "Legacy compatibility only: finalize previously uploaded tmp/... objects into issues/<issue_id>/... and append signed Markdown links. New clients use create-attachment-upload + finalize-attachment-upload."},
      {"method": "POST", "path": "/transcribe-audio", "description": "Transcribe JSON {audio_base64 (alias audio_b64), mime_type} (webm/opus or mp4, max 25MB) through OpenAI Whisper; returns {success, text, duration_ms}. No credentials required."},
      {"method": "POST", "path": "/delete-issue", "description": "Soft-delete (auth + ownership required)"},
      {"method": "GET",  "path": "/get-comments",  "description": "List comments on an issue (required: issue_id; optional: limit, offset)"},
      {"method": "POST", "path": "/create-comment","description": "Post a comment (anonymous allowed; required: issue_id, content; optional: author_name)"},
      {"method": "GET",  "path": "/get-dependencies", "description": "List relationship edges for an issue, including blocks and related"},
      {"method": "POST", "path": "/add-dependency", "description": "Add relationship: source_issue_id blocks target_issue_id by default; type may be blocks, related, duplicates, or cross_posts"},
      {"method": "POST", "path": "/remove-dependency", "description": "Remove a dependency edge"},
      {"method": "GET",  "path": "/list-labels", "description": "List labels globally or for an issue with a pagination envelope"},
      {"method": "POST", "path": "/add-label", "description": "Create/attach a label to an issue"},
      {"method": "POST", "path": "/remove-label", "description": "Detach a label from an issue"},
      {"method": "GET",  "path": "/get-stats", "description": "bd-style stats by status, priority, type, tracker, ready, blocked"},
      {"method": "POST", "path": "/register-agent", "description": "Mint a long-lived agent api_key for the authenticated account. Body: {name, scopes?, expires_at?}. Auth: Ideaflow bearer token (or an existing X-Agent-Key). Returns api_key; store it securely."}
    ]
  },
  "entities": {
    "tracker": ["id", "name", "slug", "description", "location", "source_url", "source_url_normalized", "source_url_is_default", "unlisted", "created_at"],
    "issue": ["id", "slug", "title", "description", "original_text", "category", "priority", "status", "kind", "issue_type", "parent_issue_id", "labels", "open_blocker_count", "open_blocking_count", "location", "reporter", "votes", "comments", "tracker_id", "tracker_slug", "canonical_issue_url", "user_id", "user_handle", "created_at", "updated_at"],
    "issue_attachment": ["id", "issue_id", "storage_path", "mime_type", "byte_size", "transcript", "public_url", "created_at"],
    "profile": ["id", "handle_slug", "display_name", "avatar_url", "created_at"],
    "comment": ["id", "issue_id", "author_name", "content", "created_at", "updated_at"],
    "dependency": ["id", "source_issue_id", "target_issue_id", "type", "created_at"],
    "label": ["id", "name", "slug", "color", "created_at"]
  },
  "enums": {
    "category": ["infrastructure", "environment", "social", "safety", "transportation", "healthcare", "education", "housing", null],
    "priority": ["low", "medium", "high", null],
    "status": ["open", "acknowledged", "in-progress", "resolved", "closed", "none"],
    "kind": ["issue", "reference"],
    "issue_type": ["bug", "feature", "task", "epic", "chore", "question", "other"]
  },
  "conventions": {
    "reporter_tag": "Include a reporter string identifying your agent",
    "original_request": "When an agent rewrites a user's request into polished title/description, pass the untouched source in original_text. Embedded agent chat does this automatically.",
    "board_alias": "Humans may say board when they mean tracker. Web aliases accept /boards/<tracker>, /board/<tracker>/<issue>, and /board/<tracker>/issue/<issue>; canonical production issue links remain https://<tracker>.worldissuetracker.com/issue/<issue>. The ambiguous /board/<name> form prefers an existing tracker, then tries an issue.",
    "prefer_tracker": "Fetch /get-trackers and pick a matching tracker_slug before creating; /get-trackers omits unlisted trackers (still reachable by slug if known); unknown tracker_slug values return 404 and do not create orphan issues",
    "url_routing": "Trackers should use page context as source_url by default for URL-bound capture. Multiple trackers can share it; source_url_is_default selects the default for widget/extension routing. If /resolve-tracker-url returns conflict, ask or use a stored override. If resolution is none, the first-issue-creates-board API path is planned but not yet live.",
    "capture_surfaces": "The hosted widget supports public capture, while the Chrome extension adds a native Site Chat Side Panel with two explicit promises: Ship only for private-registry-owned sites connected to a paired supervised crew; Suggestion / Cannot change this site everywhere else, filed to WIT with URL/title context only. The extension stores per-site routing and placement overrides in Chrome sync storage; the hosted widget stores user placement per hostname in localStorage.",
    "tracker_subdomains": "Valid, non-reserved tracker slugs are also reachable at https://<slug>.worldissuetracker.com/. Apex, www, staging, api, and other reserved system subdomains keep their normal routes.",
    "dedup_trackers": "Treat locality/topic variants like San Francisco, San Francisco Issue Tracker, and SF civic tracker as one existing board unless a human explicitly asks for a separate tracker",
    "site_feedback_tracker": "File bugs and feature requests for the WIT site/platform on World Issue Tracker Platform Feedback, stable slug worldissuetracker-com",
    "dedup": "Check /get-issues?tracker_slug=... before filing; use /get-issues?trackers=slug-a,slug-b for a de-duplicated multi-tracker read. When the same request exists, comment, vote, label, or link instead of creating another issue",
    "dependency_direction": "source_issue_id blocks target_issue_id; target is blocked_by source",
    "queues": "Use /get-issues?queue=ready or queue=blocked for bd-style prioritization",
    "blocked_resolution": "Do not silently resolve issues with open blockers; the UI warns and requires deliberate override.",
    "agent_chat": "Embedded chat uses Claude Sonnet 4.6 with a 120-message/hour cap and an 8-message/2-minute burst cooldown. Authenticated callers are bucketed by resolved user principal; anonymous callers use an IP fallback. True throttles return 429 rate_limited with Retry-After; shared budget exhaustion returns 503 monthly_cap_reached. First-party clients pass the current app origin so generated issue/tracker links match production, preview, staging, or local sessions. It can browse provided public HTTP/HTTPS URLs through browse_url for source-grounded issue filing, while blocking localhost, private-network, credentialed, file, and non-web URLs. Cutoff responses emit an explicit incomplete-response error.",
    "agent_chat_logging": "Future agent-chat conversations are persisted in service-role-only transcript tables for support review: prompts, context, assistant text, tool calls/results, errors, and usage."
  },
  "mcp": {
    "server_card": "/.well-known/mcp/server-card.json",
    "streamable_http_url": "https://api.worldissuetracker.com/functions/v1/mcp",
    "status": "live"
  }
}
