Skip to content

Moenjo API

The API follows the OpenAI chat completions format, so the OpenAI SDKs and most existing tools work once you change the base URL and the key.

Create an API keyhttps://moenjo.com/api/v1

Quickstart

Create a key in your dashboard, then send a message. The model id is moenjo-1.0.

curl https://moenjo.com/api/v1/chat/completions \
  -H "Authorization: Bearer $MOENJO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moenjo-1.0",
    "messages": [
      {"role": "user", "content": "Pakistan ke sab se bare shehar kaun se hain?"}
    ]
  }'

Authentication

Send your key in the Authorization header as a bearer token. Keys start with mj_live_ and are shown in full only once, when you create them.

Authorization: Bearer mj_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Each key is billed either to your subscription or to your pay-as-you-go credit. You choose when you create the key and can switch it later. Keep keys on your server: anyone who has one can spend your limits or credit.

Chat completions

POST https://moenjo.com/api/v1/chat/completions

ParameterTypeWhat it does
modelstring, requiredThe model id, for example moenjo-1.0.
messagesarray, requiredThe conversation so far. Each item has a role (system, user, assistant or tool) and content.
streambooleanSend the reply as server-sent events while it is being written. Default false.
temperaturenumber, 0 to 2Higher values give more varied replies. Defaults to the model's own setting.
top_pnumber, 0 to 1Nucleus sampling. Change this or temperature, not both.
max_tokensintegerThe most tokens the reply may use. Capped at the model's maximum output.
stopstring or arrayUp to 8 sequences that end the reply when generated.
toolsarrayFunctions the model may call. See Tool calls.
tool_choicestring or objectauto, none, required, or a specific function.
response_formatobjectSet {"type": "json_object"} to get valid JSON back.
web_searchbooleanMoenjo extension. Lets the model search the web and open pages on our servers when a message needs current information.
reasoning_effortstringHow hard the model thinks before answering: off, minimal, low, medium, high, xhigh or max. See Reasoning.
thinking_budget_tokensintegerHow many tokens the model may spend thinking, up to your plan's number for the level. See Reasoning.
seedintegerMakes sampling repeatable where the model supports it.

The response has the same shape as an OpenAI chat completion:

{
  "id": "chatcmpl-9f2KQw1x7LmZ3vBn5TyR8cPd",
  "object": "chat.completion",
  "created": 1790889257,
  "model": "moenjo-1.0",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Karachi, Lahore aur Faisalabad..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 24, "completion_tokens": 58, "total_tokens": 82 }
}

Streaming

Set stream to true to receive the reply in pieces. Each event is a chat.completion.chunk; the stream ends with data: [DONE]. Add "stream_options": {"include_usage": true} to get token counts in a final chunk.

stream = client.chat.completions.create(
    model="moenjo-1.0",
    messages=[{"role": "user", "content": "Allama Iqbal ke baare mein batao"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)

Images

Models with vision accept images inside a user message. Give a public URL or a base64 data URL. PNG, JPEG, WebP and GIF are supported.

reply = client.chat.completions.create(
    model="moenjo-1.0",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Is tasveer mein kya hai?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}},
        ],
    }],
)

Tool calls

Describe your functions in tools. When the model wants one, the reply has finish_reason: "tool_calls" and a tool_calls list. Run the function yourself, then send the result back as a tool message to get the final answer.

tools = [{
    "type": "function",
    "function": {
        "name": "track_parcel",
        "description": "Look up where a parcel is right now",
        "parameters": {
            "type": "object",
            "properties": {"tracking_id": {"type": "string"}},
            "required": ["tracking_id"],
        },
    },
}]

messages = [{"role": "user", "content": "Mera parcel LHE-48213 kahan hai?"}]
first = client.chat.completions.create(model="moenjo-1.0", messages=messages, tools=tools)

call = first.choices[0].message.tool_calls[0]
messages.append(first.choices[0].message)
messages.append({"role": "tool", "tool_call_id": call.id, "content": '{"status": "Out for delivery"}'})

final = client.chat.completions.create(model="moenjo-1.0", messages=messages, tools=tools)
print(final.choices[0].message.content)

Reasoning

When the model thinks before it answers, its reasoning is returned separately from the answer: as message.reasoning_content in a normal response, and as delta.reasoning_content chunks when streaming. Reasoning tokens count as output tokens.

reasoning_effort sets how hard it thinks: off answers straight away, then minimal, low, medium, high, xhigh and max think for longer. Each plan allows some of these levels and has a default; GET /models lists what your key may send for each model under reasoning_efforts, with each level's limits: the max_tokens and thinking_budget_tokens used when you send none, which are also the most you may ask for. A level your plan does not include is refused with effort_not_included. Higher levels take longer and use more output tokens.

thinking_budget_tokens caps the thinking itself; reasoning tokens count inside max_tokens. A value above the level's number is refused with thinking_budget_too_large, and a max_tokens above the level's is brought down to it. Neither has any meaning at off.

reply = client.chat.completions.create(
    model="moenjo-1.0",
    messages=[{"role": "user", "content": "Is 221 a prime number?"}],
    reasoning_effort="high",
    extra_body={"thinking_budget_tokens": 2000},
)
print(reply.choices[0].message.reasoning_content)
print(reply.choices[0].message.content)

List models

GET https://moenjo.com/api/v1/models returns the models your key can call, with each one's context window, capabilities and pay-as-you-go price.

curl https://moenjo.com/api/v1/models \
  -H "Authorization: Bearer $MOENJO_API_KEY"

Limits and billing

Subscription keys share your plan's daily message limit: each request counts as one message, and the count resets at midnight, Pakistan time. Pay-as-you-go keys are charged per token from your credit and stop working when the balance reaches zero.

Each plan also sets a context size. A request whose messages are longer than that is refused with context_length_exceeded, and the reply is capped so the request stays inside it.

Every response tells you where you stand:

HeaderMeaning
X-Moenjo-PlanThe plan this request was billed to.
X-Moenjo-Billingsubscription or payg.
X-RateLimit-Limit-Messages-DayHow many messages your plan allows in a day.
X-RateLimit-Remaining-Messages-DayMessages left today, after this one.
X-RateLimit-ResetSeconds until the daily count resets.
X-Moenjo-Context-LimitThe context size, in tokens, your plan allows on this model.
Retry-AfterOn a 429, how many seconds to wait before trying again.

See your usage by day, model and key on the Usage page.

Errors

Errors use standard HTTP status codes and a JSON body with a readable message and a stable code you can branch on.

{
  "error": {
    "message": "You've used your daily token limit on the Free plan. It frees up as older usage rolls off.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "param": null
  }
}
StatusCodeWhat to do
400invalid_requestFix the request body. The message names the field.
400context_length_exceededSend fewer or shorter messages.
401invalid_api_keyCheck the key, or create a new one.
402insufficient_creditsAdd pay-as-you-go credit, or switch the key to your subscription.
403model_not_includedYour plan doesn't include this model.
404model_not_foundCheck the model id against the models list.
429rate_limit_exceededWait for the time in Retry-After, or move to a bigger plan.
503model_unavailableThe model is busy or restarting. Retry with a short delay.