Skip to content
Developer

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.

GETQuery / response
/api/v1/tutorialsq (≤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/keywordsq (≤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.");
}

Public newsletter archive

Search stored published issue titles, then read the complete source HTML. lang chooses the source URL interface only; subjects and bodies are unchanged. No subscriber or delivery-operation fields, email sends, subscription changes or AI calls.

GETQuery / response
/api/v1/newslettersq (≤200 Unicode code points), lang (zh|en), limit (1–50; 20), offset; total / showing / next_offset / newsletters
/api/v1/newsletters/{slug}lang (zh|en); newsletter: subject, published_at, url, content_format=html, content

q matches titles literally (ASCII case-insensitive); % and _ are not wildcards. Keep the same filters and follow next_offset until null. URL-encode each returned slug once in REST paths; use it literally in MCP. Search: 30/min/IP; detail: 60/min/IP. Invalid, unknown or repeated parameters return 400; missing or private issues 404; limits 429 with Retry-After; unavailable data 503. Responses use no-store so publication changes are read again.

Node.js: find then read a published issue
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("/newsletters?limit=1&lang=en");
const selected = page.newsletters[0];
if (selected) {
  const detail = await read("/newsletters/" + encodeURIComponent(selected.slug) + "?lang=en");
  console.log(JSON.stringify(detail.newsletter, null, 2));
} else {
  console.log("No published newsletter issues were found.");
}

Endpoint reference

GET/api/v1/collectionsPublished collection summaries. count is the total before pagination; follow next_offset until it is null.+
Query parameters
NameValuesDescription
langzh | enDefault zh. English fields fall back to the source; translation_status is complete, partial or source.
limit1-50Default 20
offsetinteger ≥ 0Default 0; use next_offset from the previous response.
200 response · abridged illustration
{
  "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"
  }]
}
Rate limit: 30/min/IP
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.+
Query parameters
NameValuesDescription
langzh | enDefault zh; untranslated notes keep their source text.
limit1-50Default 25
offsetinteger ≥ 0Default 0; follow next_offset until null.
200 response · abridged illustration
{
  "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"
      }
    }]
  }
}
Rate limit: 60/min/IP
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.+
Query parameters
NameValuesDescription
qstring ≤ 200Search topic labels and aliases.
min_count1–100Minimum unique prompt count, default 1.
langzh | enLocale for topic links, default zh.
limit1–50Default 20; count precedes pagination.
offsetsafe integer ≥ 0Follow next_offset until null.
200 response · abridged illustration
{ "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" }] }
Rate limit: 30/min/IP
GET/api/v1/promptsList / search prompts. Count is complete before pagination; follow next_offset until null.+
Query parameters
NameValuesDescription
qstring ≤ 500Literal search across public metadata/examples and free body/variables. PRO body/variables are excluded from matching. Count includes every published match before pagination.
tagstring ≤ 200Exact 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.
toolstring ≤ 100Use a known key from /tools or tool_key from /integrations (Midjourney uses mj); recognized legacy aliases also work. Unknown keys return 400.
categorystring?Filter by medium: image / video / music / tutorial
langzh | enMetadata language (default zh; missing translations fall back to Chinese)
free0 | 1Default 0. Use 1 for free entries only; unsupported values return 400.
sortrecommended | updated | ratingDefault 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.
limit1-50Default 20. Invalid integers return 400.
offsetsafe integer ≥ 0Default 0. Invalid integers return 400.
200 response · abridged illustration
{
  "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/..."
    }
  ]
}
Rate limit: 30/min/IP
GET/api/v1/prompts/[slug]Published prompt detail: free entries include full text; Pro entries return preview only. Draft and scheduled entries return 404.+
Query parameters
NameValuesDescription
langzh | enMetadata language (default zh)
200 response · abridged illustration
{
  "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"
  }
}
Rate limit: 60/min/IP
GET/api/v1/modelsSourced reference versions, official URLs and review dates; explicit unverified and retirement notes. No generation or provider routing.+
Query parameters
NameValuesDescription
qstring ≤ 200Literal reference metadata search
categoryenumimage / video / music / tutorial / audio
langenumzh / en
limit / offsetintegerlimit 1–50; offset ≥ 0; follow next_offset
200 response · abridged illustration
{
  "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"
    }
  ]
}
Rate limit: 30/min/IP
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.+
Query parameters
NameValuesDescription
qstring ≤ 200Literal tool metadata search
categoryenumimage / video / music / tutorial
langenumzh / en
limit / offsetintegerExplicit pagination; limit 1–50, offset ≥ 0
200 response · abridged illustration
{
  "ok": true,
  "tools": [
    {
      "key": "mj",
      "label": "Midjourney",
      "label_en": "Midjourney",
      "medium": "image",
      "description": "...",
      "prompt_count": 42,
      "url": "https://prompt.luvai.net/tools/mj"
    }
  ]
}
Rate limit: 30/min/IP
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.+
200 response · abridged illustration
{
  "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"
  }]
}
Rate limit: 30/min/IP
GET/api/v1/changelogRecently updated prompts (default last 30 days). Polling-friendly.+
Query parameters
NameValuesDescription
days1-365Lookback window, default 30
limit1-100Max items returned, default 50
200 response · abridged illustration
{
  "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/..."
    }
  ]
}
Rate limit: 30/min/IP

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.

MORE