API and API keys

Moparmeg REST API v1 reference — authentication, scopes, endpoints, rate limits

The v1 API is twenty-six endpoints in twenty-one route files. Eighteen accept an API key and are opened by its scopes, three manage keys and accept nothing but the account owner's browser session, and five are public. This page describes what the server literally does.

العربية

On this page
Moparmeg REST API v1 reference: the endpoint table and how each endpoint authenticates
Moparmeg REST API v1 reference: the endpoint table and how each endpoint authenticates

Overview

Base URL: https://moparmeg-ksa.com. Every path below starts with /api/v1; requests and responses are JSON.

  • API key (Bearer): eighteen endpoints, each opened by its scope — OpenAI-compatible chat, models and code generation; projects with their pages, versions and publishing; the design system and logo; domains; template stage builds; and GET /api/v1/me, which accepts any valid key.
  • Browser session (cookie): three endpoints — list keys, create a key, revoke a key.
  • Public, no authentication: five endpoints — the template list, one template, the section library, the downloads list and a tool download.

That is all of v1: twenty-six endpoints. The platform's other /api/* routes — webhooks included — serve the dashboard and the editor, authenticate with the browser session, and do not accept an API key.

Authentication

MechanismHow it is sentAccepted byOn failure
API keyHeader Authorization: Bearer mop_…Every row whose auth column says "key" in the endpoint table401 {"error":"Invalid API key","code":"API_KEY_INVALID"}; 403 API_KEY_SCOPE_REQUIRED for a valid key without the scope
Browser sessionThe httpOnly access_token cookie set at sign-inGET and POST /api/v1/api-keys, DELETE /api/v1/api-keys/{id}401 {"error":"غير مصرّح"} (Unauthorized)
None—GET /api/v1/templates, GET /api/v1/templates/{id}, GET /api/v1/sections, GET /api/v1/downloads, GET /api/v1/downloads/{id}—

API key

A key is mop_ followed by 32 characters from A-Z a-z 0-9 _ -. The server rejects a malformed header before any lookup, then looks the key up by its fingerprint. The response is the same (401 Invalid API key with the code API_KEY_INVALID) for every reason: unknown, revoked or expired key, deleted account, or malformed header — the response does not reveal which. Create keys on the API keys page.

A key always acts as its owner, with no admin powers even when the owner is an admin, and sees only the projects its owner owns: anyone else's project is 404 «المشروع غير موجود» (Project not found), even when it is published or the key's owner is a collaborator on it.

Browser session

The /api/v1/api-keys routes do not read the Authorization header at all: an API key sent to them gets 401. This is deliberate — a key that could mint keys would turn one leak into permanent, self-renewing access. These routes are built for the dashboard's keys page, i.e. requests from the same browser while signed in.

Scopes

Scopes are chosen when a key is minted (the scopes field) and cannot be edited afterwards. Leaving them out at minting means the default: every scope except publish and domains:write. A key without the required scope is refused with 403:

403
{ "error": "هذا المفتاح لا يحمل صلاحية «…».", "code": "API_KEY_SCOPE_REQUIRED", "scope": "publish" }
ScopeName on the keys pageEndpointsIn the default
ai:chat«محادثة الذكاء الاصطناعي والنماذج» (AI chat and models)POST /api/v1/chat/completions, GET /api/v1/models, POST /api/v1/aiYes
projects:read«قراءة المشاريع وصفحاتها ونسخها» (Read projects, their pages and versions)GET /api/v1/projects, GET /api/v1/projects/{id}, GET /api/v1/projects/{id}/pages, GET /api/v1/projects/{id}/versionsYes
projects:write«إنشاء المشاريع ورفع ملفاتها وإضافة الصفحات وحفظ النسخ» (Create projects, upload their files, add pages and save versions)POST /api/v1/projects, PUT /api/v1/projects/{id}, POST /api/v1/projects/{id}/pages, POST /api/v1/projects/{id}/versionsYes
publish«نشر المشاريع وإلغاء نشرها» (Publish and unpublish projects)POST /api/v1/projects/{id}/publishNo
domains:read«فحص توفّر النطاقات» (Check domain availability)GET /api/v1/domains/availabilityYes
domains:write«طلب شراء النطاقات ونقلها» (Request domain purchases and transfers)POST /api/v1/domains/ordersNo
design«توليد نظام التصميم والشعار» (Generate the design system and logo)POST /api/v1/design/system, POST /api/v1/design/logoYes
templates«بناء مراحل القوالب» (Build template stages)POST /api/v1/templates/buildYes

Legacy keys (minted before scopes, so their scopes are empty) keep working on POST /api/v1/ai, GET and POST /api/v1/projects and GET /api/v1/me, and get 403 API_KEY_SCOPE_REQUIRED everywhere else, with a message asking you to mint a key with scopes.

Endpoint table

MethodPathAuthDescription
GET/api/v1/meKey (any valid key)Account, plan, quota and the key's scopes
POST/api/v1/chat/completionsKey ai:chatOpenAI-compatible chat, with streaming and tools
GET/api/v1/modelsKey ai:chatModel list in OpenAI format
POST/api/v1/aiKey ai:chat (and legacy)Generate code files from a prompt
GET/api/v1/projectsKey projects:read (and legacy)The 50 most recently updated of your projects
POST/api/v1/projectsKey projects:write (and legacy)Create a project with the dashboard's own checks
GET/api/v1/projects/{id}Key projects:readThe full project, files included, plus filesDigest
PUT/api/v1/projects/{id}Key projects:writeSave the project's files from the desktop (Mubarmij Agent app): files and the expectedFilesDigest of the last read; a stale digest is 409, a project over the plan's size is 413, a stack outside the plan is 403 STACK_NOT_IN_PLAN; a version is kept before the write
GET/api/v1/projects/{id}/pagesKey projects:readPages detected in the project's files
POST/api/v1/projects/{id}/pagesKey projects:writeAdd a page
GET/api/v1/projects/{id}/versionsKey projects:readThe version list, or one version with its files
POST/api/v1/projects/{id}/versionsKey projects:writeSave a version of the current files
POST/api/v1/projects/{id}/publishKey publishPublish or unpublish the project
POST/api/v1/design/systemKey designA design system saved into the project (SSE stream)
POST/api/v1/design/logoKey designAn SVG logo
GET/api/v1/domains/availabilityKey domains:readWhether a domain can be registered
POST/api/v1/domains/ordersKey domains:writeRequest a domain purchase or transfer
POST/api/v1/templates/buildKey templatesBuild one stage of a template
GET/api/v1/templatesPublicIndustry templates and their categories
GET/api/v1/templates/{id}PublicOne template with its stages
GET/api/v1/sectionsPublicThe section library and its layouts
GET/api/v1/downloadsPublicTools actually available to download
GET/api/v1/downloads/{id}PublicDownload a tool
GET/api/v1/api-keysBrowser sessionYour active keys, masked
POST/api/v1/api-keysBrowser sessionMint a new key (returned in full once)
DELETE/api/v1/api-keys/{id}Browser sessionRevoke a key

GET /api/v1/me

Accepts any valid key, legacy or scoped, and answers even on the «المجانية» (Free) plan, so your program can show the plan and why AI is refused. It does not count against the plan's hourly cap and has its own cap of 120 calls per hour (429 «كثرة الطلبات — حاول لاحقًا», Too many requests — try later). It does not return the email address.

200
{
  "data": {
    "id": "…",
    "name": "…",
    "plan": { "id": "…", "tier": "…", "label": "…" },
    "ai": {
      "allowed": true,
      "reason": null,
      "quota": { "used": 0, "limit": 0, "remaining": 0, "resetAt": …, "window": "24h-rolling" }
    },
    "api": { "callsPerHour": 0 },
    "key": { "id": "…", "scopes": ["ai:chat", "projects:read"], "legacy": false }
  }
}

When the plan does not include AI, allowed is false, reason carries the reason text, and quota is null.

GET /api/v1/projects

Returns up to 50 of the projects you own, most recently updated first. There is no pagination, and total is the number of items returned (at most 50), not your overall project count. Project files are not returned here; request a single project with GET /api/v1/projects/{id}.

200
{
  "data": [
    {
      "id": "…",
      "title": "…",
      "slug": "…",
      "status": "…",
      "views": 0,
      "forks": 0,
      "createdAt": "2026-09-01T10:00:00.000Z",
      "updatedAt": "2026-09-10T12:00:00.000Z"
    }
  ],
  "total": 1
}

Status codes: 200, 401, 403 (API_REQUIRES_PAID_PLAN or API_KEY_SCOPE_REQUIRED), 429 (hourly cap).

GET /api/v1/projects/{id}

Returns the project record as stored, files included, in {"data": {…}}. Only for projects the key's owner owns: a missing project and anyone else's project — even a published one, or one where you are a collaborator — both return 404 {"error":"المشروع غير موجود"} (Project not found).

POST /api/v1/projects

Creates a project in your name with the same function the dashboard uses, so it passes the same checks: field validation, the abuse check, the plan contract and the project-count limit.

FieldTypeNotes
titlestringRequired, up to 100 characters
descriptionstringOptional, up to 500 characters
framework"html" | "nextjs" | "react"Optional (default html). nextjs and react create a ready project scaffold, and are allowed only on a plan that includes Next.js
template"blank" | "landing" | "portfolio" | "dashboard" | "form"Optional for an HTML project: the starter template files (default blank)
industryIdstringOptional; an industry template id from GET /api/v1/templates. With HTML it generates the full template page; with nextjs or react it is passed to the scaffold

The slug is built from the title: letters in any script (Arabic included) and digits are kept, everything else becomes a hyphen, it is cut to 60 characters, and a random suffix is appended.

POST /api/v1/projects
{ "title": "My Store", "description": "Clothing store", "template": "landing" }

201
{ "data": { "id": "…", "title": "My Store", "slug": "my-store-…", "files": [ … ], … } }

The response carries the full project record as stored. Errors after the key, scope and hourly cap:

  • 400 with the first validation message, such as «عنوان المشروع طويل جداً» (The project title is too long) — a non-JSON body or a missing title included. 400 «قالب المجال المحدد غير موجود» (The selected industry template does not exist) for an unknown industryId.
  • 403 with the reason when the abuse check refuses the title or description.
  • 403 STACK_NOT_IN_PLAN: nextjs or react on a plan without Next.js, including «المجانية» (Free), which includes static HTML only.
  • 403 PROJECT_QUOTA with limit and used: you reached your plan's project-count limit.

Project pages

GET /api/v1/projects/{id}/pages (projects:read) returns {"pages": […]}: the pages detected in the project's files.

POST /api/v1/projects/{id}/pages (projects:write) with the body {"path": "…", "title": "…"} generates the files of a new page and adds them to the project's files: a Next.js page when the project has a next.config file, an HTML page otherwise. It returns {"added": [file names], "totalFiles": …}. A missing field or an empty title: 400 {"error":"path و title مطلوبان"} (path and title are required). A project you do not own: 404.

Project versions

GET /api/v1/projects/{id}/versions (projects:read) returns the array of versions, newest first, each with id, label, createdAt, fileCount and totalSize but no files. With ?versionId=… it returns one version with its files (id, label, createdAt, files), or 404 «النسخة غير موجودة» (Version not found).

POST /api/v1/projects/{id}/versions (projects:write) with an optional body {"label": "…"} saves a snapshot of the project's current files and returns 201 with the version record. The cap is 20 saves per minute (429 «كثرة الطلبات», Too many requests). A project keeps at most 50 versions: at the limit the oldest version is deleted to make room.

Restoring an earlier version with an API key: Not available yet — restoring needs an open editor, so it lives on the session route only.

POST /api/v1/projects/{id}/publish

Needs the publish scope, which is not in the default. It is the same function the editor's publish button uses, with the same checks. See Publishing.

FieldTypeNotes
expectedFilesarray of {name, content, language?}Required to publish: it must equal the project's current files exactly, so only what you reviewed goes live. Read it from GET /api/v1/projects/{id}
unpublishbooleantrue unpublishes: status DRAFT and the project is no longer public

Success returns the project record plus address: the address the site got, or null. It also fires the project.published event to the project's webhooks.

  • 400 PUBLISH_REVISION_REQUIRED: expectedFiles is missing or malformed.
  • 409 PUBLISH_REVISION_CONFLICT: the files changed from what you sent, before or during publishing. 409 PUBLISH_ADDRESS_TAKEN: the address was just taken; retry.
  • 403: refused by the content scan, or PUBLISH_NOT_STATIC when the files include something your plan does not publish, with a refusals list.
  • 404 for a project you do not own, and 429 after 10 publish actions in a minute.

POST /api/v1/ai

Sends your prompt to the AI model and asks for code files as JSON. The request may run for up to 120 seconds.

FieldTypeNotes
promptstringRequired and not blank; missing or not a string gives 400 {"error":"prompt required"}
framework"html" | "nextjs" | "react"Optional. nextjs appends "(Next.js + Tailwind + Prisma)" to the prompt, react appends "(React + Vite + Tailwind)", anything else appends nothing
modelstringOptional; passed to the model layer
200
{
  "data": {
    "files": [ … ],
    "raw": "…",
    "model": "…",
    "tokens": …
  }
}
  • files: the post-processed files when the model's reply is JSON containing files; otherwise null.
  • raw: the model's reply text as is — fall back to it when files is null.
  • model and tokens: the answering model and its usage, as reported by the model layer.

The order of checks matters for your quota: key (401) → scope (403) → plan without AI (402 AI_PAID_ONLY) → hourly cap (403 or 429) → request body: not JSON (400 {"error":"جسم الطلب ليس JSON","code":"invalid_json"}, Request body is not JSON) or no prompt (400) → daily AI quota (429 RATE_LIMITED) → generation (200 or 500). A malformed request or one missing prompt is therefore refused before the daily quota is spent. A generation failure returns 500 {"error":"…"} with the error message, and the quota is given back. For chat and streaming use POST /api/v1/chat/completions.

POST /api/v1/chat/completions

An OpenAI Chat Completions-compatible endpoint, with the ai:chat scope; a legacy key does not open it. A request may run for up to 840 seconds.

FieldNotes
messagesRequired. Roles system, developer, user, assistant and tool. Content is a string or text and image_url parts; images as data:…;base64,… only — an http link is refused with 400 image_url_must_be_data_url
modelOptional; omitted or "auto" means the administrator's preference. Names come from GET /api/v1/models
streamtrue returns an SSE stream: chat.completion.chunk parts, then data: [DONE]
max_tokens or max_completion_tokensAn integer from 1 to 32768
tools, tool_choice, parallel_tool_callsTools are supported: tool_choice is one of none, auto, required or {"type":"function","function":{"name":"…"}}. tool messages carry tool_call_id, and assistant messages may carry tool_calls

A non-streamed chat.completion response carries choices[0].message, with tool_calls and finish_reason: "tool_calls" when the model calls a tool; streaming sends tool_calls deltas. usage values are an estimate, not a provider count, and the field estimated: true says so.

The order of checks: key and scope (401/403) → plan without AI (402 AI_PAID_ONLY) → hourly cap (403/429) → request shape (400) → daily quota (429 RATE_LIMITED) → the model. Shape and provider errors use the OpenAI format: {"error": {"message": "…", "type": "…", "code": "…"}}.

  • 400 invalid_json for a body that is not JSON, legacy_functions_not_supported for functions or function_call (the legacy format), n_not_supported when n is not 1, and invalid_max_tokens.
  • 400 tools_not_supported_for_model: the chosen model does not support tools; the quota is given back and the tools are never dropped silently.
  • 502 upstream_error and other provider codes: the AI provider failed. If it failed before the model started answering, the quota is given back.

GET /api/v1/models

With the ai:chat scope; a plan without AI is refused with 402 before the hourly cap. Returns an OpenAI list from the catalog of models configured on the server:

200
{ "object": "list", "data": [ { "id": "…", "object": "model", "created": 1757940000, "owned_by": "…" } ] }

Design system and logo

Both take the design scope, a plan without AI is refused with 402 before the hourly cap, and both count against the daily AI quota. They are the same functions the editor uses.

POST /api/v1/design/system takes {"projectId": "…", "brandName": "…", "industry"?, "mood"?, "primaryColor"?}, where mood is one of professional, playful, luxury, minimal, bold and warm (default professional). You must own the project (otherwise 404). The response is an SSE stream of start, then thinking and token, then files (the files after they are saved into the project, committed: true), then data: [DONE] — or an error event. If the project's files change during generation nothing is saved and an error event arrives.

POST /api/v1/design/logo takes {"brandName": "…", "style"?, "projectId"?}; without brandName: 400 «اسم العلامة مطلوب» (Brand name is required). It returns JSON with files and committed: with the projectId of a project you own, logo.svg is saved into its files (committed: true), or 409 MUBARMIJ_PROJECT_REVISION_CONFLICT if the files changed. An invalid logo from the model: 502.

Domains

GET /api/v1/domains/availability?domain=example.sa (domains:read) returns {"data": {"domain": "…", "status": "available" | "taken" | "unknown", "reason": "…"}}. unknown is an honest answer when the check could not be made, never "available". Without domain: 400 «النطاق مطلوب» (Domain is required). An extra cap of 20 lookups per minute applies.

POST /api/v1/domains/orders (domains:write, not in the default) submits a domain purchase or transfer order — a task priced after team review, not an immediate purchase. The body: domain, kind (PURCHASE by default, or TRANSFER_IN), years from 1 to 10 (default 1), contact with required name and email, authCode for transfers, and an optional projectId of a project you own. It returns 201 {"data": {…}}. A plan without custom domains: 402 PLAN_CAPABILITY_REQUIRED. A field error: 400 with the reason. An extra cap of 5 orders per hour applies.

GET /api/v1/templates

Public: no authentication and no key cap. Returns the industry templates the editor builds from; their count is in total.

200
{
  "data": [
    {
      "id": "…",
      "name": "…",
      "nameEn": "…",
      "icon": "…",
      "category": "…",
      "categoryLabel": { "ar": "…", "en": "…" },
      "colorScheme": …,
      "stages": 0,
      "description": "…",
      "descriptionEn": "…"
    }
  ],
  "total": 0,
  "categories": [ { "id": "…", "ar": "…", "en": "…" } ]
}

stages is the number of build stages in the template, not the stages themselves. For one template with all its stages: GET /api/v1/templates/{id} (public) returns {"data": {…}} with categoryLabel, or 404 «القالب غير موجود» (Template not found). See Templates.

POST /api/v1/templates/build

With the templates scope. Builds one stage of a template and returns its files without saving them into any project.

FieldNotes
industryIdRequired; a template id from GET /api/v1/templates
stageRequired, 1 to 5; otherwise 400 «stage يجب أن يكون 1-5» (stage must be 1-5)
companyName, companyDesc, logoUrl, colorOverrideOptional; colorOverride is {"primary"?, "secondary"?}
projectIdOptional, a project you own (otherwise 404): the products section renders that project's store catalog
200
{
  "files": [
    { "name": "index.html", "content": "…", "language": "html" },
    { "name": "style.css", "content": "…", "language": "css" },
    { "name": "script.js", "content": "…", "language": "javascript" }
  ],
  "progress": …,
  "sectionsAdded": …,
  "nextStageHint": …,
  "storefront": null
}

GET /api/v1/sections

Public. The section library the editor inserts: {"schema": …, "data": [{"id", "label", "hint", "markup"}], "packs": …, "total": …}, where markup is what the editor itself inserts for the section.

Downloads

GET /api/v1/downloads (public) returns {"data": […], "total": …} with only the tools actually available, each with id, platform, kind, fileName, version, sha256, size, source and href. A tool that is not available does not appear in the list at all.

GET /api/v1/downloads/{id} (public) streams the tool or redirects to it with 302, with the X-Checksum-SHA256 header so you can verify the file after download. An unknown id and an unavailable tool are the same: 404 with the code DOWNLOAD_UNAVAILABLE. See Desktop app: download and install.

Key-management endpoints (browser session)

GET /api/v1/api-keys

Your active (non-revoked) keys, newest first. The fingerprint never leaves the server. legacy is true for a legacy key, whose scopes are then an empty array.

200
{
  "keys": [
    {
      "id": "…",
      "name": "desktop app",
      "maskedKey": "mop_••••••••abcd",
      "createdAt": "…",
      "lastUsedAt": null,
      "expiresAt": null,
      "scopes": ["ai:chat", "projects:read", "projects:write"],
      "legacy": false
    }
  ]
}

POST /api/v1/api-keys

FieldTypeNotes
namestringRequired, up to 64 characters after trimming
expiresInDaysintegerOptional, 1 to 365. Omitted = a key that never expires
scopesarray of stringsOptional. Omitted = the default (every scope except publish and domains:write). An empty array or an unknown value is refused with 400
201
{
  "key": "mop_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "id": "…",
  "name": "desktop app",
  "maskedKey": "mop_••••••••XXXX",
  "createdAt": "…",
  "expiresAt": null,
  "scopes": ["ai:chat", "projects:read"],
  "prefix": "mop_",
  "warning": "انسخه الآن — لن يُعرض مرّةً أخرى."
}

key is the raw key and never comes back in any later read; warning means "Copy it now — it will not be shown again." Errors: 400 «جسم الطلب ليس JSON» (Request body is not JSON), 400 «الاسم مطلوب» (Name is required), 400 «الاسم يتجاوز 64 محرفاً» (Name exceeds 64 characters), 400 when expiresInDays is outside 1–365 or not an integer, 400 when scopes is empty or holds an unknown scope, 409 at 20 active keys, 401 without a session. This route does not check the plan: any account can create a key.

DELETE /api/v1/api-keys/{id}

Revokes the key (the record is kept). 200 {"ok":true}. A missing key, another account's key and an already-revoked key all return 404 {"error":"المفتاح غير موجود"} (Key not found) — the response does not distinguish them.

Webhooks (outside v1, browser session)

Webhooks are registered per project from its settings page, and their /api/webhooks routes accept only the project owner's browser session, not an API key. They need a plan that includes hosting; otherwise 402 PLAN_CAPABILITY_REQUIRED.

  • GET /api/webhooks?projectId=…: the registrations and the event list. POST /api/webhooks with {"projectId", "url", "events": […]} returns 201 with the signing secret, shown this one time only. At most 10 registrations per project (409).
  • Events: project.published (also fired by publishing with an API key), order.created and form.submitted.
  • Each delivery is a POST carrying the header x-mubarmij-signature: t=…,v1=…, where v1 is an HMAC-SHA256 with the secret over <t>.<body>. Only a public https address is accepted (400 WEBHOOK_URL_REFUSED).
  • If the platform's secret-encryption key is not configured, creation is refused with 503 WEBHOOK_SECRETS_UNCONFIGURED, and no secret is stored unencrypted.

Rate limits

Two main counters, both per account rather than per key, with numbers taken from your plan:

  1. Hourly API cap: every key-authenticated call counts, except GET /api/v1/me. The hourly cap equals the plan's daily AI quota.
  2. Daily AI quota: for POST /api/v1/ai, POST /api/v1/chat/completions, POST /api/v1/design/system and POST /api/v1/design/logo; a 24-hour window starting at the first request, shared with AI requests from the editor in the browser.

On top of them, some endpoints have their own caps: GET /api/v1/me 120 per hour, domain availability 20 per minute, domain orders 5 per hour, version saves 20 per minute, publishing 10 per minute. These endpoint caps return 429 with a text message and no rate-limit headers.

PlanAPI calls per hourAI requests per day
«المجانية» (Free)0 (key calls refused with 403 or 402, except /api/v1/me)0
«الأساسية» (Basic)5050
«المميزة» (Premium)200200
«الاحترافية» (Professional)10001000

Plan details in Billing and plans.

Rate-limit headers

The headers are sent only with the hourly cap's 429 response, not with successful responses:

HeaderMeaning
Retry-AfterSeconds until the window renews (at least 1)
X-RateLimit-LimitYour plan's hourly cap
X-RateLimit-RemainingCalls left in the window
X-RateLimit-ResetWhen the window renews, in Unix seconds
429
Retry-After: 1200
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1757940000

{ "error": "تجاوزتَ سقف باقتك (50 نداءً في الساعة) — أعد المحاولة بعد 1200 ثانية" }

The error text reads "You exceeded your plan's cap (N calls per hour) — retry after S seconds". Running out of the daily quota instead returns 429 without these headers, with a body carrying code and upgradeUrl:

429
{ "error": "وصلت الحد اليومي للطلبات. ترقّى لباقةٍ أعلى لطلبات أكثر.", "code": "RATE_LIMITED", "upgradeUrl": "/billing", "remaining": 0, "limit": 50 }

Errors

StatusBody or codeWhen
400prompt required / field validation messages / PUBLISH_REVISION_REQUIRED / OpenAI codes such as n_not_supported and tools_not_supported_for_modelA required field is missing or invalid
401Invalid API key with the code API_KEY_INVALIDKey endpoints: key missing, malformed, unknown, revoked or expired
401غير مصرّح (Unauthorized)Key-management and webhook endpoints: no valid browser session
402AI_PAID_ONLYA plan without AI: on code generation, chat, models and design, before the hourly cap
402PLAN_CAPABILITY_REQUIREDA domain order on a plan without custom domains, or webhooks on a plan without hosting
403API_REQUIRES_PAID_PLANA key call from an account on the «المجانية» (Free) plan
403API_KEY_SCOPE_REQUIRED with scopeThe key lacks the endpoint's scope, or a legacy key outside its three endpoints
403PUBLISH_NOT_STATIC or the content-scan messagePublishing refused
403STACK_NOT_IN_PLAN / PROJECT_QUOTA / the abuse-check messageProject creation: a framework your plan does not include, the project-count limit reached, or refused content
404المشروع غير موجود (Project not found) / المفتاح غير موجود (Key not found) / DOWNLOAD_UNAVAILABLEA project that is missing or not yours, a key that is missing, not yours or revoked, a tool that is not available
409Maximum-keys message / PUBLISH_REVISION_CONFLICT / PUBLISH_ADDRESS_TAKEN / MUBARMIJ_PROJECT_REVISION_CONFLICT20 active keys, the project's files changed, or the address was taken
429Hourly-cap message (no code), RATE_LIMITED, or an endpoint-cap messageHourly cap, daily quota or an endpoint's own cap
500Error messageGeneration failed on POST /api/v1/ai (the quota is given back)
502upstream_error and provider codesThe AI provider failed during chat, or the model returned an invalid logo

curl example

Keep your key in an environment variable rather than in saved commands:

export MOPARMEG_API_KEY=mop_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

curl https://moparmeg-ksa.com/api/v1/me -H "Authorization: Bearer $MOPARMEG_API_KEY"

curl https://moparmeg-ksa.com/api/v1/projects -H "Authorization: Bearer $MOPARMEG_API_KEY"

curl -X POST https://moparmeg-ksa.com/api/v1/chat/completions -H "Authorization: Bearer $MOPARMEG_API_KEY" -H "Content-Type: application/json" -d '{"messages":[{"role":"user","content":"Suggest a headline for a specialty coffee shop landing page"}]}'

curl -i https://moparmeg-ksa.com/api/v1/templates

The -i flag shows headers, including Retry-After on a 429. To start from scratch see Quickstart; to understand what the AI builds see Building with AI.

FAQ

Does my key work on routes like /api/ai/generate or /api/terminal?

No. Those are dashboard and editor routes and authenticate with the browser session. The key works only on the v1 endpoints above, according to its scopes: reading a project with its files (projects:read) and publishing it (publish) included. Deleting a project or restoring an earlier version with a key: Not available yet.

Is the project list paginated?

Not available yet. The list returns the 50 most recently updated of your projects; a project beyond them can be read by its id with GET /api/v1/projects/{id} if you know the id.

Should I use POST /api/v1/ai or POST /api/v1/chat/completions?

POST /api/v1/ai returns code files as JSON in one go and keeps working with a legacy key. POST /api/v1/chat/completions is compatible with OpenAI clients, supports streaming, tools and images, and needs a key with the ai:chat scope.

Why are there no X-RateLimit-* headers on successful responses?

The server sends them only with the hourly cap's 429. Track usage in your program, or read Retry-After when refused.

Can a key create another key?

No. Creating, listing and revoking keys takes the browser session only — see API keys.