API v1
Read-only JSON endpoints for prompt search, public collections and automation. No authentication required; requests are limited by IP.
Quick start
Use the curl executable. In Windows PowerShell, use curl.exe if curl is an alias. Quoted URLs keep query characters intact.
Read official model references
curl "https://prompt.luvai.net/api/v1/models?lang=en&limit=1"Discover tool keys and public counts
curl "https://prompt.luvai.net/api/v1/tools?lang=en&limit=1"Search published newsletter titles
curl "https://prompt.luvai.net/api/v1/newsletters?lang=en&limit=1"Search public tutorials
curl "https://prompt.luvai.net/api/v1/tutorials?limit=1"Search stored engineering terms
curl "https://prompt.luvai.net/api/v1/keywords?q=cache&limit=1"Find a free prompt
curl "https://prompt.luvai.net/api/v1/prompts?free=1&lang=en&limit=1"Discover topic keys
curl "https://prompt.luvai.net/api/v1/tags?q=code-review&lang=en"Browse public collections
curl "https://prompt.luvai.net/api/v1/collections?lang=en&limit=20"Read one result using its actual slug (Node.js 18+)
Save this code as promptcraft-read.mjs and run node promptcraft-read.mjs. It makes at most two public requests with a ten-second timeout per request. No API key or extra package is needed.
const base = "https://prompt.luvai.net/api/v1";
async function read(url) {
const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
if (data.ok !== true) throw new Error("Unexpected API response");
return data;
}
const page = await read(base + "/prompts?free=1&limit=1&lang=en");
const selected = page.prompts[0];
if (selected) {
const detail = await read(base + "/prompts/" + encodeURIComponent(selected.slug) + "?lang=en");
console.log(JSON.stringify(detail, null, 2));
} else {
console.log("No published free prompts were found.");
}Paged responses include next_offset. Keep the same filters, use that offset for the next request, and stop when it is null. Collection items are summaries; request /prompts/{slug} for available detail.
Stored engineering glossary
Read public definitions, when-to-use notes and how_to_ask text directly. This performs stored-data lookup, not AI inference, and uses no AI credits. content_language is zh-Hant; English terms are included but definitions remain Traditional Chinese. Returned URLs point to the existing source-language entry.
| GET | Query / response |
|---|---|
| /api/v1/tutorials | q (≤200), limit (1–50; 20), offset; total / showing / next_offset / tutorials |
| /api/v1/tutorials/{slug} | tutorial: full stored Markdown, content_language=zh-Hant, content_format=markdown, source URL and dates |
| /api/v1/keywords | q (≤200), category, limit (1–50; 20), offset; total / showing / next_offset / keywords |
| /api/v1/keywords/{slug} | keyword: definition, when_to_use, how_to_ask, gotcha, related, tags, updated_at |
Search is literal: % and _ have no wildcard meaning. category uses the exact values in OpenAPI. Keep filters while following next_offset until null. Search is limited to 30/min/IP and detail to 60/min/IP. Invalid or repeated parameters return 400, missing entries 404, rate limits 429 with Retry-After, and unavailable data 503. No lang parameter or generated translation is provided.
Node.js: discover then read a tutorial
Stored titles, summaries and complete Markdown are Traditional Chinese. Search is literal; no lang option or AI generation. Read through next_offset until null. Returned URLs point to the real source.
const base = "https://prompt.luvai.net/api/v1";
async function read(path) {
const response = await fetch(base + path, { signal: AbortSignal.timeout(10000) });
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
if (data.ok !== true) throw new Error("Unexpected response");
return data;
}
const page = await read("/tutorials?limit=1");
const selected = page.tutorials[0];
if (selected) {
const detail = await read("/tutorials/" + encodeURIComponent(selected.slug));
console.log(JSON.stringify(detail.tutorial, null, 2));
} else {
console.log("No stored public tutorials were found.");
}Node.js: search then read a definition
const base = "https://prompt.luvai.net/api/v1";
async function read(path) {
const response = await fetch(base + path, { signal: AbortSignal.timeout(10000) });
if (!response.ok) throw new Error("HTTP " + response.status);
const data = await response.json();
if (data.ok !== true) throw new Error("Unexpected response");
return data;
}
const page = await read("/keywords?limit=1");
const selected = page.keywords[0];
if (selected) {
const detail = await read("/keywords/" + encodeURIComponent(selected.slug));
console.log(JSON.stringify(detail.keyword, null, 2));
} else {
console.log("No stored glossary entries were found.");
}Endpoint reference
GET/api/v1/collectionsPublished collection summaries. count is the total before pagination; follow next_offset until it is null.+
| Name | Values | Description |
|---|---|---|
| lang | zh | en | Default zh. English fields fall back to the source; translation_status is complete, partial or source. |
| limit | 1-50 | Default 20 |
| offset | integer ≥ 0 | Default 0; use next_offset from the previous response. |
{
"ok": true,
"count": 1,
"limit": 20,
"offset": 0,
"next_offset": null,
"collections": [{
"slug": "example-collection",
"title": "Example collection",
"summary": "Public collection metadata",
"kicker": null,
"unlock_cost": null,
"updated_at": "2026-10-12",
"translation_status": "complete",
"url": "https://prompt.luvai.net/en/collections/example-collection"
}]
}GET/api/v1/collections/[slug]Collection metadata and ordered public prompt summaries, notes and links. items_count precedes pagination; position preserves the original order. Unpublished collections return 404.+
| Name | Values | Description |
|---|---|---|
| lang | zh | en | Default zh; untranslated notes keep their source text. |
| limit | 1-50 | Default 25 |
| offset | integer ≥ 0 | Default 0; follow next_offset until null. |
{
"ok": true,
"limit": 25,
"offset": 0,
"next_offset": null,
"collection": {
"slug": "example-collection",
"title": "Example collection",
"items_count": 1,
"items": [{
"position": 1,
"note": "Editorial note",
"prompt": {
"slug": "example-prompt",
"title": "Example prompt",
"is_pro": false,
"url": "https://prompt.luvai.net/en/prompts/example-prompt"
}
}]
}
}GET/api/v1/tagsDiscover every public topic, its unique prompt count, slug and locale URL. Use slug as the prompts API tag filter or MCP search_prompts tag.+
| Name | Values | Description |
|---|---|---|
| q | string ≤ 200 | Search topic labels and aliases. |
| min_count | 1–100 | Minimum unique prompt count, default 1. |
| lang | zh | en | Locale for topic links, default zh. |
| limit | 1–50 | Default 20; count precedes pagination. |
| offset | safe integer ≥ 0 | Follow next_offset until null. |
{ "ok": true, "count": 1, "limit": 20, "offset": 0, "next_offset": null, "tags": [{ "slug": "code-review", "label": "Code Review", "prompt_count": 2, "url": "https://prompt.luvai.net/tags/code-review" }] }GET/api/v1/promptsList / search prompts. Count is complete before pagination; follow next_offset until null.+
| Name | Values | Description |
|---|---|---|
| q | string ≤ 500 | Literal search across public metadata/examples and free body/variables. PRO body/variables are excluded from matching. Count includes every published match before pagination. |
| tag | string ≤ 200 | Exact topic label or normalized slug from /tags. Aliases match the same topic; literal percent text must be URL encoded. Empty/invalid values return 400; unknown topics return zero results. Combines with all other filters. |
| tool | string ≤ 100 | Use a known key from /tools or tool_key from /integrations (Midjourney uses mj); recognized legacy aliases also work. Unknown keys return 400. |
| category | string? | Filter by medium: image / video / music / tutorial |
| lang | zh | en | Metadata language (default zh; missing translations fall back to Chinese) |
| free | 0 | 1 | Default 0. Use 1 for free entries only; unsupported values return 400. |
| sort | recommended | updated | rating | Default recommended. updated uses UTC update time; rating uses average, then rating count, with unrated entries last. Invalid values return 400. Sort applies before pagination; MCP search_prompts accepts the same sort values. |
| limit | 1-50 | Default 20. Invalid integers return 400. |
| offset | safe integer ≥ 0 | Default 0. Invalid integers return 400. |
{
"ok": true,
"count": 237,
"limit": 20,
"offset": 0,
"next_offset": 20,
"sort": "recommended",
"tag": null,
"prompts": [
{
"slug": "midjourney-portrait-cinematic",
"title": "...",
"summary": "...",
"tool": "mj",
"category": "image",
"tags": ["portrait", "cinematic"],
"difficulty": "intermediate",
"url": "https://prompt.luvai.net/prompts/..."
}
]
}GET/api/v1/prompts/[slug]Published prompt detail: free entries include full text; Pro entries return preview only. Draft and scheduled entries return 404.+
| Name | Values | Description |
|---|---|---|
| lang | zh | en | Metadata language (default zh) |
{
"ok": true,
"prompt": {
"slug": "...",
"title": "...",
"is_pro": false,
"access": "full",
"preview": "first-line preview",
"full_prompt": "complete prompt text",
"context": "when to use / why",
"variables": [{"name": "subject", "description": "..."}],
"sample_output": "...",
"rating": { "avg": 4.6, "count": 32 },
"updated_at": "2026-05-13"
}
}GET/api/v1/modelsSourced reference versions, official URLs and review dates; explicit unverified and retirement notes. No generation or provider routing.+
| Name | Values | Description |
|---|---|---|
| q | string ≤ 200 | Literal reference metadata search |
| category | enum | image / video / music / tutorial / audio |
| lang | enum | zh / en |
| limit / offset | integer | limit 1–50; offset ≥ 0; follow next_offset |
{
"ok": true,
"total": 1,
"showing": 1,
"limit": 1,
"offset": 0,
"next_offset": null,
"models": [
{
"key": "midjourney",
"name": "Midjourney",
"category": "image",
"version": "V8.2",
"vendor": "Midjourney",
"notes": "The version guide lists V8.2 as the default. Check reference-image features and version flags when adapting older prompts.",
"source_url": "https://docs.midjourney.com/hc/en-us/articles/32199405667853-Version",
"checked_on": "2026-10-15",
"verified_on": "2026-10-15",
"verification": "documented",
"status": "reference",
"tool_key": "mj",
"url": "https://prompt.luvai.net/en/models?q=midjourney#midjourney",
"search_url": "https://prompt.luvai.net/en/search?q=Midjourney",
"generator_url": "https://prompt.luvai.net/en/tools/midjourney/generator",
"record_type": "model_reference"
}
]
}GET/api/v1/toolsPublic tool definitions and library counts. No query retains the legacy complete list; explicit query parameters return a localized page. Counts may overlap and do not establish model execution.+
| Name | Values | Description |
|---|---|---|
| q | string ≤ 200 | Literal tool metadata search |
| category | enum | image / video / music / tutorial |
| lang | enum | zh / en |
| limit / offset | integer | Explicit pagination; limit 1–50, offset ≥ 0 |
{
"ok": true,
"tools": [
{
"key": "mj",
"label": "Midjourney",
"label_en": "Midjourney",
"medium": "image",
"description": "...",
"prompt_count": 42,
"url": "https://prompt.luvai.net/tools/mj"
}
]
}GET/api/v1/integrationsPlatform directory with search keys and working local builder links. Media generation happens on the external platform; audio-tool requirements are shown on its page.+
{
"ok": true,
"total": 19,
"categories": { "image": 7, "video": 6, "music": 2, "audio": 1, "text": 3 },
"with_generator": 15,
"with_prompts": 18,
"integrations": [{
"key": "midjourney",
"tool_key": "mj",
"name": "Midjourney",
"vendor": "Midjourney Inc",
"category": "image",
"has_generator": true,
"has_prompts": true,
"generator_kind": "prompt_builder",
"prompts_url": "https://prompt.luvai.net/midjourney-prompts",
"generator_url": "https://prompt.luvai.net/tools/midjourney/generator"
}]
}GET/api/v1/changelogRecently updated prompts (default last 30 days). Polling-friendly.+
| Name | Values | Description |
|---|---|---|
| days | 1-365 | Lookback window, default 30 |
| limit | 1-100 | Max items returned, default 50 |
{
"ok": true,
"days": 30,
"count": 28,
"prompts": [
{
"slug": "midjourney-portrait-cinematic",
"title": "...",
"tool": "mj",
"category": "image",
"difficulty": "intermediate",
"updated_at": "2026-05-13",
"url": "https://prompt.luvai.net/prompts/..."
}
]
}Limits and public access
List and search endpoints allow 30 requests per minute per IP; single prompt and collection detail allow 60. Limits are shown with each endpoint. HTTP 429 includes Retry-After in seconds. Respect that delay and avoid simultaneous retries.
Free prompt detail includes full_prompt and variables. PRO entries return is_pro: true and access: preview; full_prompt and variables are omitted. The public API cannot unlock paid content.
Collections expose public summaries only, including for free items. Draft and scheduled prompts are excluded. English fields fall back to source text when a translation is missing; translation_status identifies collection translation coverage.
Error handling
- 400
- Invalid query value or unsupported tool key. Read error and correct the request; repeating the same invalid request will not help.
- 404
- The requested slug is unavailable or unpublished. Discover a current slug from a list response.
- 429
- The IP request limit was reached. Wait for the Retry-After delay before trying again.
- Network / 5xx
- Use a request timeout and bounded retries with backoff. Do not treat a failed request as an empty result.
Endpoint response blocks are abridged illustrations. Use the OpenAPI schemas for the complete response contract.