{"ok": true, "service": "Skar AI Agent API", "description": "JSON API for AI agents to read and create app data. Two categories of endpoints exist \u2014 read \"prompt_routing\" to understand which to use.", "prompt_routing": {"how_to_decide": "Before calling any endpoint, classify the user prompt into one of the two categories below. Use ONLY the endpoints in that category \u2014 do not mix them.", "categories": {"topics": {"description": "Social media platform \u2014 topics (interest channels), events, and user data collection. Use these endpoints when the prompt is about: creating or finding topics/channels, scheduling or listing events, or managing user profile/lookup data.", "prompt_signals": ["topic", "channel", "community", "post", "event", "schedule", "user data", "profile", "form", "follow", "feed"], "endpoints": ["topics_list", "topic_create", "events_list", "event_create", "posts_list", "post_create", "userdata_categories_list", "userdata_fields_search", "userdata_category_create", "userdata_category_update"]}, "skarmall": {"description": "Shop and product marketplace \u2014 categories, products, suppliers. Use these endpoints when the prompt is about: creating or listing products, managing a shop, product categories, pricing, suppliers, or inventory.", "prompt_signals": ["product", "shop", "store", "price", "buy", "sell", "category", "supplier", "inventory", "mrp", "discount", "item", "listing"], "endpoints": ["skarmall_categories_list", "skarmall_category_create", "skarmall_products_search", "skarmall_attribute_fields", "skarmall_attribute_values", "skarmall_category_fields", "skarmall_product_create", "skarmall_product_images", "skarmall_suppliers_search", "skarmall_supplier_create"]}}}, "authentication": {"how_to_get_a_key": "Two ways. (1) Self-serve: as a logged-in user, POST /aiapi/key/ to mint your own key (valid 24h); GET /aiapi/key/ to check the current one. (2) Admin-issued: a long-lived service key created in Django admin > AI Agent API > AI Agent Keys.", "where_to_put_the_key": "Send it on every endpoint except this home page and /aiapi/key/, in the HTTP request header \"Authorization: Bearer <key>\" (or alternatively \"X-API-Key: <key>\").", "example_header": "Authorization: Bearer sk_ai_xxxxxxxxxxxxxxxx", "expiry": "Self-serve keys expire 24h after creation; an expired or invalid key returns HTTP 401 \u2014 request a fresh one at /aiapi/key/.", "scopes": "A read key can call the GET endpoints; a write key (can_write) can also POST to the */create/ endpoints. GET on a */create/ endpoint returns its field schema."}, "endpoints": [{"name": "home", "path": "/aiapi/", "methods": ["GET"], "auth": "none (public) \u2014 discover the API and how to authenticate before you have a key", "summary": "API catalog \u2014 every AI endpoint with a short description of its usage."}, {"name": "ping", "path": "/aiapi/ping/", "methods": ["GET"], "auth": "agent key (read)", "summary": "Auth/health check; echoes your agent name, scope and actas user."}, {"name": "topics_list", "category": "topics", "path": "/aiapi/topics/", "methods": ["GET"], "auth": "agent key (read)", "summary": "List topics, newest-first. Search one title (?q=) or several at once (repeat ?q= or comma-separate ?q=a,b) to batch-probe candidate titles.", "query": {"q": "optional \u2014 case-insensitive title \"contains\" filter. Repeat the param (?q=a&q=b) or comma-separate (?q=a,b) to search several terms at once; the response then groups matches per query under \"matches\".", "followed": "optional \u2014 true: only topics the actas user follows; false: all topics in the actas user's languages. Omit for all topics.", "limit": "optional int 1-200 (default 50), applied per query"}}, {"name": "topic_create", "category": "topics", "path": "/aiapi/topics/create/", "methods": ["GET", "POST"], "auth": "agent key (write); GET schema needs only read", "summary": "GET returns the fields to send; POST creates one or many topics. Before creating, each title is checked for related existing topics (the same case-insensitive search as /aiapi/topics/); any matches are returned under `available` and skipped unless force_create is set.", "fields": {"title": "required string (single-topic form)", "description": "required string (single-topic form)", "language": "optional string \u2014 a language name; defaults to the first configured language", "image_url": "required url \u2014 fetched and stored as the topic image", "topics": "optional list of {title, description, image_url, language?} to create many at once (use instead of the single title/description/image_url fields)", "force_create": "optional bool \u2014 create even when a related topic already exists (default false: matching topics are returned under `available` and skipped)"}, "protected": "isTopic, followers \u2014 server/admin-controlled; ignored if sent. New topics start unapproved."}, {"name": "events_list", "category": "topics", "path": "/aiapi/events/", "methods": ["GET"], "auth": "agent key (read)", "summary": "List events for the actas user's feed (followed topics + isAll), soonest-first. Falls back to isAll-only when the key has no actas user.", "query": {"upcoming": "optional '1' to limit to events from today onward", "limit": "optional int 1-200 (default 50)"}}, {"name": "event_create", "category": "topics", "path": "/aiapi/events/create/", "methods": ["GET", "POST"], "auth": "agent key (write); GET schema needs only read", "summary": "GET returns the fields to send; POST creates one or many events. Before creating, each title is checked for related existing events (case-insensitive title search); any matches are returned under `available` and skipped unless force_create is set \u2014 so a caller can confirm with the user before re-posting the wanted ones with force_create.", "fields": {"title": "required string (single-event form)", "description": "required string (single-event form)", "date": "required ISO-8601 datetime, e.g. '2026-07-31T18:00' (single-event form)", "topic_ids": "optional list of existing topic ids to attach", "topics": "optional list of existing topic titles to attach", "events": "optional list of {title, description, date, topic_ids?, topics?} to create many at once (use instead of the single title/description/date fields)", "force_create": "optional bool \u2014 create even when a related event already exists (default false: matching events are returned under `available` and skipped)"}, "protected": "isAll \u2014 server/admin-controlled (broadcast to all users); ignored if sent."}, {"name": "posts_list", "category": "topics", "path": "/aiapi/posts/", "methods": ["GET"], "auth": "agent key (read)", "summary": "List posts, newest-first (the actas user's own by default). Filters combine, e.g. ?mine=false&topic=12&date=2026-10-02.", "query": {"mine": "optional \u2014 true (default): only the actas user's posts; false: posts by anyone.", "date": "optional \u2014 YYYY-MM-DD; only posts made on that day.", "topic": "optional \u2014 topic id(s) or exact title(s), comma-separated; posts in any of them. Combine with mine=false to see everyone's posts in a topic.", "q": "optional \u2014 case-insensitive \"contains\" search over post title and text.", "limit": "optional int 1-200 (default 50)"}}, {"name": "post_create", "category": "topics", "path": "/aiapi/posts/create/", "methods": ["GET", "POST"], "auth": "agent key (write); GET schema needs only read", "summary": "GET returns the fields to send; POST creates one or many posts for the actas user. Each post must include at least one of youtube/image/video/poll/form and counts against the user's daily posting quota. There is no near-duplicate check (posts are timeline content). A JSON body attaches media by URL (image_urls / video_urls); multipart/form-data may instead upload image/video files.", "fields": {"posttitle": "optional string (max 250)", "posttext": "optional string (max 10000)", "postyoutube": "optional youtube link, e.g. https://youtu.be/VIDEO_ID or https://www.youtube.com/watch?v=VIDEO_ID", "postlink": "optional http(s) URL \u2014 its OpenGraph title/description are scraped server-side into ltitle/ldesc", "postlanguage": "optional language name; defaults to the actas user's first language, else the first configured language", "image_urls": "optional list of public image URLs (fetched & stored server-side, max 10)", "video_urls": "optional list of public video URLs \u2014 mp4/webm/mov (fetched server-side, max 5)", "polls": "optional list of poll option strings", "topics": "optional list of existing topic titles to attach (max 5)", "topic_ids": "optional list of existing topic ids to attach (max 5)", "event_id": "optional existing event id to link the post to", "postform": "optional id of an active user-data category to attach as a form", "taggedusers": "optional list of user ids to tag (max 10; each is notified)", "posts": "optional list of post objects to create many at once (JSON only; use instead of the single-post fields above)"}, "protected": "postuser, postedon, ltitle, ldesc, likedusers \u2014 server-controlled; ignored if sent. The author is always the key's actas user; the link preview (ltitle/ldesc) is scraped from postlink."}, {"name": "userdata_categories_list", "category": "topics", "path": "/aiapi/userdata/categories/", "methods": ["GET"], "auth": "agent key (read)", "summary": "List EXISTING user-data lookup categories (each with its field specs). Call this first to check whether a related category already exists \u2014 if one does, prefer adding your new fields to it (PATCH /aiapi/userdata/<name>/) over creating a near-duplicate category.", "query": {"q": "optional case-insensitive search over category name AND label; omit to list all. Repeat (?q=food&q=diet) or comma-separate to search several at once \u2014 the response then groups matches per query under \"matches\".", "active": "optional '1' to list only active categories", "limit": "optional int 1-200 (default 50)"}}, {"name": "userdata_fields_search", "category": "topics", "path": "/aiapi/userdata/fields/", "methods": ["GET"], "auth": "agent key (read)", "summary": "Search EXISTING user-data field specs across all categories (deduped by key) so you can reuse a field instead of inventing a near-duplicate. Call this first \u2014 before /aiapi/userdata/ \u2014 for each field you plan to add.", "query": {"q": "optional case-insensitive search over field key AND label; omit to list all. Repeat (?q=dob&q=phone) or comma-separate (?q=dob,phone) to look up several at once \u2014 the response then groups matches per query under \"matches\".", "limit": "optional int 1-200 (default 50)"}}, {"name": "userdata_category_create", "category": "topics", "path": "/aiapi/userdata/", "methods": ["GET", "POST"], "auth": "agent key (write) whose actas user has a role ABOVE \"Assistant Leader\" (superusers pass); GET schema needs only read", "summary": "GET returns the fields to send; POST creates a user-data lookup category and its field specs (the schema users then fill in). Before creating, the requested name/label is checked against existing categories: any related ones are returned under `available` and nothing is created unless force_create=true.", "related": "IMPORTANT \u2014 do not blindly create. If `available` comes back non-empty, a related category already exists: you MUST ask the user whether to add the new field(s) to that existing category rather than create a new one. If the user agrees, add them additively via PATCH /aiapi/userdata/<name>/ (each available entry carries its `extend_with` PATCH path). Only if the user prefers a separate category, resend this create with force_create=true.", "reuse": "IMPORTANT \u2014 before posting, search existing fields at GET /aiapi/userdata/fields/?q=<key or label> for each field you intend to add, and REUSE a returned definition (its exact key, type, choices, required, help_text) whenever one matches what you need. Reusing the same key across categories keeps shared fields (e.g. \"dob\", \"phone\", \"email\") consistent AND lets a value the user already gave in another category pre-fill automatically. Only invent a new key when the search returns nothing suitable.", "fields": {"label": "required human label, e.g. \"Food preference\"", "name": "optional slug machine-key (unique). Auto-derived from the label when omitted.", "description": "optional description text shown on the category", "active": "optional bool (default true) \u2014 whether the category is listed to users", "order": "optional int sort order (default 0)", "force_create": "optional bool \u2014 create even when a related category already exists (default false: related categories are returned under `available`).", "fields": "required non-empty list of field specs. Each: {key (or label), label?, type?, choices?, required?, help_text?, order?}. \"type\" is one of: text, textarea, number, select, multiselect, date, bool, email, url (default text). \"choices\" is a required list for select/multiselect and accepts [\"veg\",\"non-veg\"] or [[\"v\",\"Veg\"],[\"nv\",\"Non-veg\"]]. Prefer reusing an existing field key from /aiapi/userdata/fields/ (see \"reuse\")."}}, {"name": "userdata_category_update", "category": "topics", "path": "/aiapi/userdata/<name>/", "methods": ["PATCH", "POST"], "auth": "agent key (write) whose actas user has a role ABOVE \"Assistant Leader\" (superusers pass)", "summary": "Add new fields to an EXISTING category (additive \u2014 existing fields and all collected data are preserved) and optionally update its label/description/active/order. Use this instead of creating a near-duplicate category. Fields whose key already exists on the category are skipped and reported.", "fields": {"fields": "optional list of NEW field specs to add (same shape as create; additive). A field whose key already exists is skipped and listed under `skipped`.", "label": "optional new human label", "description": "optional new description", "active": "optional bool", "order": "optional int sort order"}, "note": "This is additive-only; it never removes or rewrites existing fields, so no collected data is lost. (Full field replacement remains a superuser-only admin API.)"}, {"name": "skarmall_categories_list", "category": "skarmall", "path": "aiapi/skarmall/categories/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "List or search Skarmall product categories.", "query": {"q": "Optional name filter (case-insensitive contains).", "limit": "Max results 1\u2013200, default 50."}}, {"name": "skarmall_category_create", "category": "skarmall", "path": "aiapi/skarmall/categories/create/", "methods": ["GET", "POST"], "auth": "Bearer token (write)", "summary": "Create a new Skarmall category. IMPORTANT: always call skarmall_categories_list first with ?q=<name> \u2014 if a matching category already exists, use it instead of creating a duplicate. Only call this endpoint when no suitable match is found. On duplicate name the endpoint returns the existing category with created=false. Create one only for a product that no existing category fits (a product can have several categories - create just the missing ones).", "fields": {"name": "Category name (required, max 200 chars)."}}, {"name": "skarmall_products_search", "category": "skarmall", "path": "aiapi/skarmall/products/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "Search products across all Skarmall shops, optionally filtered by dynamic attributes. Discover available fields/values first with skarmall_attribute_fields, skarmall_attribute_values or skarmall_category_fields. Each result includes its \"attributes\" as {field: [values]}.", "query": {"q": "Name / description / category keyword.", "shop": "Filter by shop slug (shopid).", "category": "Filter by category name (icontains).", "c": "Multi-select category filter: repeat ?c=<id or exact name> (max 10), e.g. ?c=3&c=7 \u2014 products in ANY of them (OR), AND with the other filters. Pair with skarmall_attribute_fields?c=\u2026 to get the attribute fields those categories use.", "a_<field>": "Attribute filter, e.g. ?a_color=red&a_color=green&a_year=2012 \u2014 AND across different fields, OR within one field (repeat the param).", "sort": "Ordering: newest (default), oldest, price_asc, price_desc, name_asc, name_desc, or a numeric attribute field such as 'mileage kmpl' (ascending) / '-mileage kmpl' (descending); products without that field sort last.", "offset": "Skip this many results (pagination), default 0. Response: {count (TOTAL matches), limit, offset, returned, has_more, next, previous (absolute URLs or null), results}. Each result has \"images\" (absolute URLs), \"image_count\" and \"url\".", "rmin_<field> / rmax_<field>": "Numeric range for number-valued fields, e.g. ?rmin_year=2001&rmax_year=2005 (either bound optional; compared as numbers on the server).", "pmin / pmax": "Buyer-facing price bounds in INR (inclusive, either optional), e.g. ?pmin=500&pmax=2000.", "limit": "Max results 1\u2013100, default 20."}}, {"name": "skarmall_attribute_fields", "category": "skarmall", "path": "aiapi/skarmall/products/attributes/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "Discover the dynamic product attribute fields (human-readable names with spaces, e.g. \"warranty years\", never \"warranty_years\") used across Skarmall (color, model, year, \u2026), each with a sample of its known values. Use before creating a product or before filtering skarmall_products_search by attributes.", "query": {"q": "Optional field-name filter (icontains).", "c": "Optional, repeatable category id or exact name (max 10): only fields (and values) used by products in ANY of those categories.", "limit": "Max fields 1\u2013100, default 30."}}, {"name": "skarmall_attribute_values", "category": "skarmall", "path": "aiapi/skarmall/products/attributes/values/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "List the existing values recorded for one attribute field (e.g. field \"color\" -> red/green/blue) so you can reuse an existing value instead of a new spelling.", "query": {"name": "Attribute field name (required), e.g. color.", "q": "Optional value filter (icontains).", "limit": "Max values 1\u2013100, default 30."}}, {"name": "skarmall_category_fields", "category": "skarmall", "path": "aiapi/skarmall/categories/fields/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "List categories together with the attribute fields their products commonly use (each with sample values), e.g. category \"mobiles\" -> model, ram, year. Best first call to learn which attributes a kind of product should have.", "query": {"q": "Optional category-name filter (icontains).", "limit": "Max categories 1\u201350, default 20."}}, {"name": "skarmall_product_create", "category": "skarmall", "path": "aiapi/skarmall/products/create/", "methods": ["GET", "POST"], "auth": "Bearer token (write)", "summary": "Create a product in the actas user's shop. POST as multipart/form-data when including images. CATEGORY RULE: a product may belong to SEVERAL categories - attach every related one (max 10), e.g. a gaming laptop -> 'Laptops' + 'Electronics & Accessories' + 'Gaming'. Before creating the product, search existing categories with skarmall_categories_list (?q=) (q=<keyword>, try the product type, its broader group and synonyms) and reuse every match, preferably by id. Only when no existing category fits, create the missing one with skarmall_category_create (it returns the existing one on a name clash) and pass its id. Unknown names are NOT auto-created: they come back in 'unmatched_categories', and if nothing matched the product is put in a default category ('category_fallback': true) - then fix the categories instead of leaving it there.", "fields": {"name": "Product name (required, max 200 chars).", "price": "Selling price INR (required, 1\u201310\u202f000\u202f000).", "mrp": "Strike-through / original MRP in INR (optional positive integer, must be greater than price).", "description": "Product description; one feature per line (max 5\u202f000 chars).", "categories": "Category ids and/or exact names (max 10): JSON list (e.g. [3, \"Laptops\"]), a comma-separated string, or a repeated form field. Response reports categories, unmatched_categories, category_fallback.", "attributes": "Dynamic fields as {field: value-or-[values]}, e.g. {\"color\":[\"red\",\"green\"],\"year\":\"2012\"} (JSON body) \u2014 reuse existing fields/values via skarmall_category_fields / skarmall_attribute_values. No limit on fields or values per field. NAMING RULE: field names must be human-readable with spaces, never snake_case/underscores (e.g. \"warranty years\", not \"warranty_years\"); underscores/dashes are converted to spaces and names lowercased.", "ui": "Label shown to buyer when ordering (max 300 chars).", "note": "Private seller note (max 1\u202f000 chars).", "supplier_id": "ID of an existing supplier within this shop (optional).", "images": "Image files (multipart key \u201cimages\u201d, JPEG/PNG/GIF/WebP, \u226410\u202fMB each, max 5).", "image_urls": "JSON list of PUBLIC http(s) image URLs (JPEG/PNG/GIF/WebP, up to 10 MB each; counts toward the max 5). The server downloads and stores them; private/internal hosts and redirects to them are rejected.", "force_create": "Pass true to create even if a product with the same name exists."}, "protected": "shop, addedon, modifiedon, likes, unique"}, {"name": "skarmall_product_images", "category": "skarmall", "path": "aiapi/skarmall/products/<pid>/images/", "methods": ["POST"], "auth": "Bearer token (write)", "summary": "Add images to an existing product in the actas shop. POST multipart/form-data with key \u201cimages\u201d (max 5 files, \u226410\u202fMB each)."}, {"name": "skarmall_suppliers_search", "category": "skarmall", "path": "aiapi/skarmall/suppliers/", "methods": ["GET"], "auth": "Bearer token (read)", "summary": "List or search suppliers within the actas user's shop.", "query": {"q": "Optional name filter (icontains).", "limit": "Max results 1\u2013100, default 50."}}, {"name": "skarmall_supplier_create", "category": "skarmall", "path": "aiapi/skarmall/suppliers/create/", "methods": ["GET", "POST"], "auth": "Bearer token (write)", "summary": "Create a supplier in the actas user's shop.", "fields": {"name": "Supplier name (required, max 100 chars).", "link": "Primary contact URL, http/https only (optional).", "link2": "Secondary contact URL, http/https only (optional).", "phno": "Phone number with country code (optional, max 13 chars).", "cpo": "Profit per order % (optional, 1\u2013100).", "note": "Private note (optional, max 1\u202f000 chars)."}}]}