{
  "schema": "nlo.agent-tools.v0",
  "status": "preview: not switched on at neverlogoff.com yet",
  "note": "Tool list in the common tool format (name, description, inputSchema, annotations). x_nlo says which HTTP call each tool makes. Tools with status not_available_yet have no endpoint. ask_human_first = the agent must get the human's yes for this exact call. discover_* tools: status planned = not built yet, with x_nlo.spec saying what it will do.",
  "base_url": "https://neverlogoff.com",
  "auth_kinds": {
    "none": "no sign-in",
    "cookie": "the human's sign-in cookie, only in their browser (an agent without the browser cannot make this call)",
    "upload_pass": "Authorization: Bearer nlou1.… (from the wallet challenge; 15 minutes; one site or one post)",
    "storage_pass": "Authorization: NLO-Pass <pass> to the owner's own gatekeeper",
    "bridge_pairing": "a pairing on the human's own machine (127.0.0.1:3847)",
    "page_token": "Authorization: Bearer nlou1.… with typ nlo-page-push. The human makes it at /me/page (\"Make a key for my AI\"); 60 minutes; reads and changes THIS person's page only. It cannot upload posts or site files.",
    "read_token": "Authorization: Bearer <read token> for /api/v1/* (the person makes it at /lens; 60 minutes; scopes items · lenses · people). Read-only: answers as that person, item by item. Without it you see what a signed-out stranger sees."
  },
  "tools": [
    {
      "name": "sites_check_name",
      "description": "Check whether a page name is free.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-z0-9-]{3,30}$"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/sites/check",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "sites_me",
      "description": "The human's page: name, wallet, current version, limits.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/sites/me",
        "auth": "cookie",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "sites_claim",
      "description": "Claim a page name. One per person; cannot be changed or given up in v0.",
      "inputSchema": {
        "$ref": "/schemas/nlo-sites-v0.json#/$defs/claim"
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/sites/claim",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "sites_upload_file",
      "description": "Upload one file (4 MB max) to the staging version named in the upload pass. Body = the raw bytes.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "path"
        ],
        "properties": {
          "path": {
            "type": "string",
            "maxLength": 1600
          }
        }
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "PUT",
        "path": "/api/sites/upload",
        "auth": "upload_pass",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "sites_publish",
      "description": "Switch visitors to the uploaded version. Makes it public to anyone with the link.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": false,
        "openWorldHint": true
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/sites/publish",
        "auth": "upload_pass",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "sites_rollback",
      "description": "Swap the current and previous versions of the page.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/sites/rollback",
        "auth": "upload_pass",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "sites_report",
      "description": "Report a user page.",
      "inputSchema": {
        "$ref": "/schemas/nlo-sites-v0.json#/$defs/report"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/sites/report",
        "auth": "none",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "get_page",
      "description": "Read the human's page: the full page JSON (custom HTML inline), its address and the current version number. The full page-builder tool list (add, move, hide blocks, undo, preview link) is /agents/tools-pages.json.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/page?op=get",
        "auth": "page_token",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "put_page",
      "description": "REPLACE the whole page with a new version (the last 10 are kept; undo puts one back). Call get_page first, change its page, and send its ver as base_version: without it, or if the page changed since, the answer is 409 stale_page {current_version} and nothing is saved (a first page needs none). Validated server-side: unknown block types or keys are refused. The full page-builder tool list (add, move, hide blocks, undo, preview link) is /agents/tools-pages.json.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "page"
        ],
        "properties": {
          "page": {
            "$ref": "/schemas/nlo-page-v0.json"
          },
          "base_version": {
            "type": "integer",
            "minimum": 1,
            "description": "the ver from get_page (or the header If-Match: \"<ver>\"); required once the person has a page"
          },
          "confirm_public": {
            "type": "boolean",
            "description": "only after the human said yes to the blocks named in needs_confirm"
          }
        }
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/page?op=put",
        "auth": "page_token",
        "ask_human_first": true,
        "status": "preview",
        "needs_confirm": "409 {error:'needs_confirm', blocks:[ids]} when a block that was private or hidden would become public"
      }
    },
    {
      "name": "posts_search",
      "description": "Search posts the caller may see, by words that resolve to places and topics. Newest first.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "q": {
            "type": "string",
            "maxLength": 300
          },
          "format": {
            "type": "string"
          },
          "place": {
            "type": "string"
          },
          "topic": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          },
          "limit": {
            "type": "integer",
            "maximum": 200
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/posts?op=search",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_get",
      "description": "One post. Missing and private give the same 404.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^p_[a-f0-9]{20}$"
          },
          "lang": {
            "type": "string"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/posts?op=get",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_vocab",
      "description": "Autocomplete place and topic tags.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "q"
        ],
        "properties": {
          "q": {
            "type": "string",
            "maxLength": 60
          },
          "kinds": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/posts?op=vocab",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_resolve",
      "description": "Turn free words into place and topic ids.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "q"
        ],
        "properties": {
          "q": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/posts?op=resolve",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_create",
      "description": "Create a post. Defaults to only_me. Nothing posts without the human.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/create"
      },
      "annotations": {
        "readOnlyHint": false,
        "openWorldHint": true
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=create",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "posts_edit",
      "description": "Change a post's text, tags or who may see it.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/edit"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=edit",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "posts_byoa_request",
      "description": "The tag-suggest request (contract nlo.posts.tag-suggest.v0) for one of the human's posts.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^p_[a-f0-9]{20}$"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/posts?op=byoa-request",
        "auth": "cookie",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_byoa_submit",
      "description": "Send the agent's tag suggestions. They stay pending until the human accepts them.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/byoaSubmit"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=byoa-submit",
        "auth": "cookie",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "posts_decide",
      "description": "Accept or reject tag suggestions.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/decide"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=decide",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "posts_approve",
      "description": "Approve (or un-approve) a viewer for one post or all approved posts.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/approve"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=approve",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "posts_report",
      "description": "Report a post.",
      "inputSchema": {
        "$ref": "/schemas/nlo-posts-v0.json#/$defs/report"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/posts?op=report",
        "auth": "none",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "storage_pass",
      "description": "Get a short-lived pass (or the public address) to view one file in someone's own storage.",
      "inputSchema": {
        "$ref": "/schemas/nlo-storage-v0.json#/$defs/passRequest"
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/storage/pass",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "storage_search",
      "description": "Search linked storage; only what the caller may see.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "q": {
            "type": "string",
            "maxLength": 64
          }
        }
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/storage/search",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "storage_link",
      "description": "Link the human's own storage. Never send keys or passwords.",
      "inputSchema": {
        "$ref": "/schemas/nlo-storage-v0.json#/$defs/link"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/storage/link",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "storage_collection",
      "description": "Make a collection with one audience.",
      "inputSchema": {
        "$ref": "/schemas/nlo-storage-v0.json#/$defs/collection"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/storage/collection",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "storage_decide",
      "description": "Approve or deny an Ask to see request.",
      "inputSchema": {
        "$ref": "/schemas/nlo-storage-v0.json#/$defs/decide"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/storage/decide",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "storage_revoke",
      "description": "Take an approval away. New passes stop at once; old ones end within 5 minutes.",
      "inputSchema": {
        "$ref": "/schemas/nlo-storage-v0.json#/$defs/revoke"
      },
      "annotations": {
        "readOnlyHint": false,
        "destructiveHint": true
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/storage/revoke",
        "auth": "cookie",
        "ask_human_first": true,
        "status": "preview"
      }
    },
    {
      "name": "desk_bot_ask",
      "description": "Ask the human's own bot on their own machine (bridge, not neverlogoff.com).",
      "inputSchema": {
        "$ref": "/schemas/nlo-desk-v0.json#/$defs/botAsk"
      },
      "annotations": {
        "readOnlyHint": false
      },
      "x_nlo": {
        "method": "POST",
        "path": "/api/nlo/bot/ask",
        "auth": "bridge_pairing",
        "ask_human_first": false,
        "status": "preview"
      }
    },
    {
      "name": "share_card",
      "description": "Make a share card for a page or post.",
      "inputSchema": {
        "type": "object"
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": null,
        "path": null,
        "auth": "none",
        "ask_human_first": true,
        "status": "not_available_yet"
      }
    },
    {
      "name": "clips_queue",
      "description": "List clips waiting for the human's approval.",
      "inputSchema": {
        "type": "object"
      },
      "annotations": {
        "readOnlyHint": true
      },
      "x_nlo": {
        "method": null,
        "path": null,
        "auth": "cookie",
        "ask_human_first": false,
        "status": "not_available_yet"
      }
    },
    {
      "name": "discover_lens",
      "description": "Run a lens spec: a small JSON search anyone can write (filters over tags, places, kinds, creators, people, formats, languages, a time window; one open sort; a limit and a cursor). The same spec drives the site wall and any other frontend. Unknown fields and sorts are refused.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "spec"
        ],
        "properties": {
          "spec": {
            "type": "object",
            "description": "The lens spec. Format: GET /api/v1/specs. Example: {\"v\":1,\"query\":{\"tags\":[\"topic:ramen\"],\"date\":{\"last_days\":30}},\"ranking\":{\"recipe\":\"newest\"},\"limit\":10}"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/search",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "spec=<the spec as JSON, URL-encoded>",
        "returns": "{ ok, audience: \"18+\", data: [Item + provenance {key, source, signed, rank_why, example:false}], total, next_cursor, ranking, lens_rule {recipe, line, how, spec_hash}, spec, examples: [] }",
        "lens_rule": "the spec's ranking.recipe"
      }
    },
    {
      "name": "discover_search",
      "description": "Find posts and published items by tags, places, kind, creator and a time window, with one open sort. A shorthand for discover_lens: the agent folds these parameters into a spec.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tags": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string",
              "pattern": "^topic:[a-z0-9-]+$"
            },
            "description": "Topic ids from the vocabulary (tags.topics in /schemas/nlo-meta-v0.json). Turn words into ids with discover_normalise_tags first.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/topics"
          },
          "places": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string"
            },
            "description": "Place ids from the vocabulary (tags.places), e.g. gn:2643123.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/places"
          },
          "kinds": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "enum": [
                "post",
                "page",
                "subpage",
                "block",
                "widget",
                "dashboard",
                "custom",
                "app",
                "game",
                "stream",
                "lobby",
                "lens",
                "space",
                "template"
              ]
            },
            "description": "post, or a kind of published item (app, game, template, lens, …).",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/kind"
          },
          "creator": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "pattern": "^@?[a-z0-9][a-z0-9-]{1,29}$"
            },
            "description": "Page names, as in neverlogoff.com/@name."
          },
          "last_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "Time window: only things from the last N days."
          },
          "recipe": {
            "enum": [
              "newest",
              "nearest",
              "saved_by_people_i_follow",
              "random_daily",
              "verified_first",
              "vouched",
              "trending"
            ],
            "default": "newest",
            "description": "An open sort. Each says how it works at /api/v1/recipes."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          },
          "cursor": {
            "type": "string",
            "maxLength": 200
          },
          "include_examples": {
            "type": "boolean",
            "default": false,
            "description": "Also return a pointer (example: true) to the made-up examples at /examples. Example items are never returned as results; never cite them as real."
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/search",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "spec={\"v\":1,\"query\":{\"tags\":tags,\"places\":places,\"date\":{\"last_days\":last_days}},\"kinds\":kinds,\"creators\":creator,\"ranking\":{\"recipe\":recipe},\"limit\":limit,\"cursor\":cursor,\"include_examples\":include_examples} (leave out what you do not use)",
        "returns": "{ ok, audience: \"18+\", data: [Item + provenance {key, source, signed, rank_why, example:false}], total, next_cursor, ranking, lens_rule {recipe, line, how, spec_hash}, spec, examples: [] }",
        "lens_rule": "the recipe you pass (default newest)"
      }
    },
    {
      "name": "discover_trending",
      "description": "What is trending: Things more different people saved in public in the last 7 days come first. Each person counts once per thing, and the maker saving their own thing does not count. Then newest first. Examples, held things and pages behind the region gate are never listed by this rule. No view counts, no watch time, no paid boost, no private saves.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "tags": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string",
              "pattern": "^topic:[a-z0-9-]+$"
            },
            "description": "Topic ids from the vocabulary (tags.topics in /schemas/nlo-meta-v0.json). Turn words into ids with discover_normalise_tags first.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/topics"
          },
          "places": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string"
            },
            "description": "Place ids from the vocabulary (tags.places), e.g. gn:2643123.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/places"
          },
          "kinds": {
            "type": "array",
            "maxItems": 8,
            "items": {
              "enum": [
                "post",
                "page",
                "subpage",
                "block",
                "widget",
                "dashboard",
                "custom",
                "app",
                "game",
                "stream",
                "lobby",
                "lens",
                "space",
                "template"
              ]
            },
            "description": "post, or a kind of published item (app, game, template, lens, …).",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/kind"
          },
          "creator": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "pattern": "^@?[a-z0-9][a-z0-9-]{1,29}$"
            },
            "description": "Page names, as in neverlogoff.com/@name."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          },
          "cursor": {
            "type": "string",
            "maxLength": 200
          },
          "include_examples": {
            "type": "boolean",
            "default": false,
            "description": "Also return a pointer (example: true) to the made-up examples at /examples. Example items are never returned as results; never cite them as real."
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/trending",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "query string: tags, places, kinds and creator comma-separated; include_examples → examples=1",
        "returns": "{ ok, audience: \"18+\", data: [Item + provenance {key, source, signed, rank_why, example:false}], total, next_cursor, ranking, lens_rule {recipe, line, how, spec_hash}, spec, examples: [] }",
        "lens_rule": "trending"
      }
    },
    {
      "name": "discover_new",
      "description": "What is new: the built-in New lens (the last 2 days, newest first, nothing else weighed). named=this_week for 7 days, named=all_time for everything.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          },
          "cursor": {
            "type": "string",
            "maxLength": 200
          },
          "include_examples": {
            "type": "boolean",
            "default": false,
            "description": "Also return a pointer (example: true) to the made-up examples at /examples. Example items are never returned as results; never cite them as real."
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/search",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "named=new (or this_week, all_time); include_examples → examples=1",
        "returns": "{ ok, audience: \"18+\", data: [Item + provenance {key, source, signed, rank_why, example:false}], total, next_cursor, ranking, lens_rule {recipe, line, how, spec_hash}, spec, examples: [] }",
        "lens_rule": "newest"
      }
    },
    {
      "name": "discover_by_tag",
      "description": "Things tagged with a topic or place. Narrower topics count (\"food\" finds ramen). Each result says which tag matched.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tags"
        ],
        "properties": {
          "tags": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string",
              "pattern": "^topic:[a-z0-9-]+$"
            },
            "description": "Topic ids from the vocabulary (tags.topics in /schemas/nlo-meta-v0.json). Turn words into ids with discover_normalise_tags first.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/topics"
          },
          "places": {
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "string"
            },
            "description": "Place ids from the vocabulary (tags.places), e.g. gn:2643123.",
            "x_nlo_facet": "/schemas/nlo-meta-v0.json#/properties/tags/properties/places"
          },
          "last_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "Time window: only things from the last N days."
          },
          "recipe": {
            "enum": [
              "newest",
              "nearest",
              "saved_by_people_i_follow",
              "random_daily",
              "verified_first",
              "vouched",
              "trending"
            ],
            "default": "newest",
            "description": "An open sort. Each says how it works at /api/v1/recipes."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          },
          "cursor": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/items",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "query string: tags and places comma-separated",
        "returns": "{ ok, audience, data:[Item], total, next_cursor, ranking:{recipe, line, how}, query }",
        "lens_rule": "the recipe you pass (default newest)"
      }
    },
    {
      "name": "discover_normalise_tags",
      "description": "Turn words a person typed into vocabulary ids: case, \"#\", plurals and the vocabulary's own synonyms fold; anything unknown or ambiguous comes back under suggested (ask the person), never as a tag.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "tags"
        ],
        "properties": {
          "tags": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "kind": {
            "enum": [
              "topic",
              "place",
              "any"
            ],
            "default": "topic"
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/normalise",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "tags comma-separated",
        "returns": "{ ok, data: { tags: [id], suggested: [{input, candidates}] } }",
        "lens_rule": "none (a lookup)"
      }
    },
    {
      "name": "discover_specs",
      "description": "The lens spec format: fields, cost caps, kinds, every open sort in plain words, the built-in named lenses and the trending rule.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/specs",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "returns": "{ ok, data: { format, caps, kinds, recipes, named, trending } }",
        "lens_rule": "none (a description)"
      }
    },
    {
      "name": "discover_live_now",
      "description": "Who is live right now on their own channel (public streams only, newest start first, examples never listed).",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {}
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/live?op=live-now",
        "auth": "none",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "returns": "{ ok, live: [{ name, title, started_at }] }",
        "lens_rule": "live now, newest start first",
        "note": "A same-site API: browsers on other sites are refused; an agent calling from its own machine sends no Origin and is answered."
      }
    },
    {
      "name": "discover_people_like",
      "description": "People who post about the same things as a given person. Planned: not built.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "creator"
        ],
        "properties": {
          "creator": {
            "type": "string",
            "pattern": "^@?[a-z0-9][a-z0-9-]{1,29}$"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "auth": "none | read_token",
        "ask_human_first": false,
        "status": "planned",
        "doc": "/agents/discover.md",
        "planned_path": "/api/v1/people-like",
        "spec": "Vocabulary only, never behaviour: take the topics the person tags most on their PUBLIC items, then list other people whose public items carry the same topics, most shared topics first, ties newest. Today: discover_search with those tags (discover_by_tag), and read /api/v1/people/{id}/public."
      }
    },
    {
      "name": "discover_following",
      "description": "New things from the people the READER follows (the Following lens), newest first. Needs the reader's own read token: it answers as that person, reads only their own follows, and never returns or shares the list. Without a token: 501.",
      "inputSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 20
          },
          "cursor": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "annotations": {
        "readOnlyHint": true,
        "openWorldHint": false
      },
      "x_nlo": {
        "method": "GET",
        "path": "/api/v1/search",
        "auth": "read_token",
        "ask_human_first": false,
        "status": "preview",
        "doc": "/agents/discover.md",
        "encode": "named=following (limit, cursor)",
        "returns": "{ ok, audience: \"18+\", data: [Item + provenance {key, source, signed, rank_why, example:false}], total, next_cursor, ranking, lens_rule {recipe, line, how, spec_hash}, spec, examples: [] }",
        "lens_rule": "newest"
      }
    }
  ]
}
