# kanukko.json reference A product on Kanukko is described by one JSON file on its own domain. Kanukko reads it, checks it, and after a human review uses it to call the product for ChatGPT and Claude users. ## Where it lives Serve it at exactly `https:///.well-known/kanukko.json`, as raw JSON with status 200 and no HTML wrapper. On Next.js, put it at `public/.well-known/kanukko.json`. On Vite, Lovable, Bolt or similar, also use `public/.well-known/kanukko.json`. On Express or Flask, add a static route. JSON Schema: https://docs.kanukko.introex.in/schema/kanukko.v1.json (set `"$schema"` to that URL so editors validate it). ```json { "$schema": "https://docs.kanukko.introex.in/schema/kanukko.v1.json", "schema_version": 1, "product": { "id": "internhub", "name": "InternHub", "description": "Paid internships at Indian startups for students and freshers, updated daily.", "homepage": "https://internhub.example.in", "categories": ["jobs", "internships"], "tags": ["internship", "freshers", "startups", "remote"], "regions": ["india"], "contact_email": "founder@internhub.example.in" }, "integration": { "type": "rest", "base_url": "https://internhub.example.in/api" }, "auth": { "type": "none" }, "actions": [ { "name": "search_internships", "title": "Search internships", "description": "Search open internships by keyword and city.", "input_schema": { "type": "object", "additionalProperties": false, "properties": { "query": { "type": "string", "description": "Role or skill, e.g. data science" }, "city": { "type": "string" }, "limit": { "type": "integer", "minimum": 1, "maximum": 50 } } }, "example_input": { "query": "data" }, "read_only": true, "rest": { "method": "GET", "path": "/kanukko/search", "query": { "q": "$query", "city": "$city", "limit": "$limit" } }, "output": { "kind": "jobs", "items_path": "results", "fields": { "id": "id", "title": "title", "company": "company.name", "location": "city", "url": "apply_url", "posted_at": "posted_at", "job_type": "type", "tags": "skills", "summary": { "path": "description", "transform": "strip_html" } } } } ], "jobs_search": { "action": "search_internships", "input": { "query": "$query", "city": "$location", "limit": "$limit" }, "filters_locally": ["job_type"] }, "attribution": "Internships from InternHub" } ``` ## Field rules - `product.id`: lowercase letters, digits and dashes, 2 to 40 characters. Keep it stable. - `product.description`: 20 to 500 characters of plain words. Say what it does and for whom, with no marketing claims and no instructions to AI assistants. Kanukko shows it to assistants marked as untrusted text, and a reviewer reads it. - `categories`: start with what fits: `jobs`, `internships`, `courses`, `events`, `housing`, `tools`. - `integration`: either `{"type": "rest", "base_url": "https://..."}` or `{"type": "mcp", "url": "https://.../mcp"}` for an existing remote MCP server (Streamable HTTP). Every URL must use **https** and sit on the verified domain or a subdomain of it. - `actions[].name`: `snake_case`, starting with a letter. - `actions[].input_schema`: a JSON Schema object. Kanukko validates every call against it before anything reaches the app. Use `"additionalProperties": false`. - `actions[].example_input`: an input Kanukko uses to test the action. Required in practice for any read-only action whose schema has required fields. - `actions[].read_only`: `true` if the action changes nothing. Kanukko caches read-only results and only test-calls read-only actions. - `actions[].personal_data_fields`: list any input fields that carry personal data (`email`, `phone`, `resume_url`, `name`). Kanukko makes the assistant ask the user before sending them. Each one must also appear in `input_schema.properties`. - `rest`: `method` is `GET` or `POST`. `path` is joined to `base_url` and can't leave it. In `query` (and `body` for POST), a value starting with `$` takes that input field. Missing inputs are dropped. Any other value is sent as written. - `mcp`: `{"tool": ""}`. - `output`: `{"kind": "raw"}` returns the JSON as is. For job-like listings use `{"kind": "jobs", ...}` so results merge with other boards: - `items_path`: dot path to the array (`"results"`, `"data.jobs"`, or `""` if the response is the array). - `fields`: dot paths into one item. `title` and `url` are required. Use `"a.b"` for nested values and `"tags[].name"` to collect a field from an array. A field can also be `{"path": "...", "transform": "unix_to_iso" | "strip_html" | "join" | "lower"}`. - `require_field`: skip items without this field. - `jobs_search` (optional, for job and internship products): lets Kanukko's `search_jobs` include you. In `input`, map Kanukko's `$query`, `$location`, `$job_type` (`internship`, `full_time`, `part_time`, `contract`) and `$limit` to your action's inputs, or use `{"from": "job_type", "map": {"internship": "Internship"}}` to translate values. `filters_locally` lists what your API can't filter, so Kanukko filters your results itself. - `cache_ttl_seconds` (default 900) and `rate_limit_per_minute` (default 30): how gently Kanukko should call you. - `auth`: `{"type": "none"}` for public endpoints. If the endpoint needs a key, use `{"type": "api_key", "header": "Authorization", "prefix": "Bearer "}`. The developer pastes the key into the kanukko.introex.in dashboard. **Never put a key in kanukko.json.** See also: [agent setup guide](agent-setup.md), [JSON Schema](schema/kanukko.v1.json), and examples for a [REST job board](examples/rest-jobs.json) and a [remote MCP server](examples/mcp-server.json).