> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sinarcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send requests

> Call the SinarCloud AI API with your API key. Covers the base URL, endpoints, request rules, streaming, error codes, and how each request is charged.

The SinarCloud AI API uses the OpenAI API format. You can call it over plain HTTP, or point an OpenAI SDK at it by changing the base URL, the API key, and the model name. It also accepts the Anthropic Messages format, which tools such as Claude Code use. See [Anthropic Messages format](#anthropic-messages-format). This page covers the endpoints, the request rules SinarCloud enforces, the errors you can receive, and how each request is charged to your balance.

## Base URL and API key

The base URL is `https://ai.sinarcloud.com/v1`.

**1. Create an API key**

In the console sidebar, under **AI**, click **API keys**. Create a key there, for example with **Create API key** in the quick start. See [API keys](https://docs.sinarcloud.com/ai/api-keys.md).

**2. Store your API key as an environment variable**

```bash macOS/Linux theme={null}
export SINARCLOUD_API_KEY="paste-your-api-key"
```

```powershell Windows theme={null}
$env:SINARCLOUD_API_KEY = "paste-your-api-key"
```

Send the key as a bearer token in the `Authorization` header of every request, including `GET` requests:

```text theme={null}
Authorization: Bearer YOUR_API_KEY
```

SinarCloud reads the key from the `Authorization` header. The Anthropic Messages endpoints also accept it in the `x-api-key` header, where Anthropic clients send it. If a request sends both headers, they must hold the same key. Other headers, such as `api-key`, are not accepted.

> [!WARNING]
>
> Keep your API key on your server. Anyone who has the key can make requests that are charged to your account.

## Endpoints

| Method | Path | The model must list |
| - | - | - |
| `POST` | `/v1/chat/completions` | **Chat completions** |
| `POST` | `/v1/completions` | **Completions** |
| `POST` | `/v1/embeddings` | **Embeddings** |
| `POST` | `/v1/responses` | **Responses** |
| `POST` | `/v1/responses/compact` | **Responses** |
| `POST` | `/v1/messages` | **Chat completions** |
| `POST` | `/v1/messages/count_tokens` | **Chat completions** |
| `GET` | `/v1/models` | — |
| `GET` | `/v1/models/MODEL_ALIAS` | — |

The base URL already ends in `/v1`, so add only the rest of the path. For example, use `https://ai.sinarcloud.com/v1/chat/completions`. Any other path under `/v1` returns `404` with the code `unsupported_endpoint`. A path that doesn't start with `/v1` returns `404` with the code `not_found`. `POST` requests to the OpenAI endpoints must not include query parameters. The Anthropic Messages endpoints ignore them.

The other `POST` endpoints take the same headers and the standard OpenAI body for that endpoint. For example, an embeddings request looks like this:

```bash theme={null}
curl "https://ai.sinarcloud.com/v1/embeddings" \
  -H "Authorization: Bearer $SINARCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ALIAS", "input": "Text to embed"}'
```

Replace `MODEL_ALIAS` with the alias of a model that lists **Embeddings** on the **Models** page.

## Send a chat completion

The Python and TypeScript examples use the official OpenAI SDK (`pip install openai` or `npm install openai`). The cURL examples use macOS and Linux shell syntax. For a guided first call, see [Make your first AI request](https://docs.sinarcloud.com/ai-quickstart.md).

```bash cURL theme={null}
curl "https://ai.sinarcloud.com/v1/chat/completions" \
  -H "Authorization: Bearer $SINARCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek/deepseek-v4-1-flash",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Hello"}
    ],
    "max_completion_tokens": 200
  }'
```

```python Python theme={null}
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://ai.sinarcloud.com/v1",
    api_key=os.environ["SINARCLOUD_API_KEY"],
)

completion = client.chat.completions.create(
    model="deepseek/deepseek-v4-1-flash",
    messages=[{"role": "user", "content": "Hello"}],
)

print(completion.choices[0].message.content)
print(completion.usage)
```

```typescript TypeScript theme={null}
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://ai.sinarcloud.com/v1",
  apiKey: process.env.SINARCLOUD_API_KEY,
});

const completion = await client.chat.completions.create({
  model: "deepseek/deepseek-v4-1-flash",
  messages: [{ role: "user", content: "Hello" }],
});

console.log(completion.choices[0].message.content);
console.log(completion.usage);
```

To call a different model, replace `deepseek/deepseek-v4-1-flash` with the alias of a model from the **Models** page. The response uses the OpenAI chat completion format. Read the reply from `choices[0].message.content` and the token counts from `usage`. The `model` field in the response always contains the alias you sent.

Some models also return the reasoning they do before they answer, in a `reasoning_content` or `reasoning` field of `message`, or of `delta` when you stream. Reasoning tokens count toward `completion_tokens` in `usage` and are charged at the output price. See [How tokens are counted](https://docs.sinarcloud.com/ai/tokens.md).

> [!TIP]
>
> The console also has ready-made requests. On the **API keys** page, the **Quick start (cURL)** box holds a sample cURL request for a chat model. To call a different model, swap in another alias. On the **Models** page, each model's details have a **Use via API** tab with samples for that model in cURL, Python, TypeScript, and Go. Replace `YOUR_API_KEY` in either sample with your key.

## Anthropic Messages format

Tools and SDKs built for Anthropic's API, such as [Claude Code](https://docs.sinarcloud.com/integrations/claude-code.md), can call `POST /v1/messages` with the standard Anthropic Messages body. They add `/v1/messages` to the base URL themselves, so give them `https://ai.sinarcloud.com` as the base URL, without `/v1`.

```bash cURL theme={null}
curl "https://ai.sinarcloud.com/v1/messages" \
  -H "x-api-key: $SINARCLOUD_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek/deepseek-v4-1-flash",
    "max_tokens": 200,
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

The response uses the Anthropic message format, with the reply in `content`. SinarCloud runs these requests on the model's chat completions, so the model must list **Chat completions**. Requests are charged like any other request.

These rules differ from the OpenAI endpoints:

* `max_tokens` is required and can't be higher than the model's **Maximum output (tokens)**.
* Tools must be your own: each tool needs a `name` and an `input_schema`, and no `type` other than `custom`. Anthropic's built-in tools, such as web search or code execution, return `400` with the message "Only caller-executed tools are supported." So does `mcp_servers`.
* Documents such as PDFs aren't accepted. They return `400` with the message "This model does not support PDF input or other documents." Images need a model that lists **Image input** or **Vision**.
* `metadata` may hold only a `user_id` string.
* If the model has no cache write price for the duration you request, SinarCloud removes the `cache_control` marks and runs the request without them, instead of returning an error.

`POST /v1/messages/count_tokens` counts the input tokens of a Messages request without running it. It doesn't need `max_tokens`, and it's never charged. The count is an estimate. See [Count tokens before you send](https://docs.sinarcloud.com/ai/tokens.md#count-tokens-before-you-send).

Errors from these endpoints use Anthropic's error format, with the error type in `error.type`, for example `authentication_error` for `401`, `billing_error` for `402`, and `not_found_error` for `404`. The request ID is also in a `request-id` header.

## Choose a model alias

Every request names its model by alias in the `model` field. To find an alias, open **Models**. Each model's alias appears under its name. Click the copy icon (**Copy model alias**) next to it to copy it. Aliases use lowercase letters, digits, `.`, `_`, `:` and `-`, and can contain `/`.

The model must be available and must list the capability for the endpoint you call. Otherwise the request fails with `404` and the code `model_not_found`. To see what a model supports, click **Details** in its row. The details list its capabilities under **Model capabilities**, and its output limit under **Maximum output (tokens)**. See [Models and pricing](https://docs.sinarcloud.com/ai/models.md).

### Zero data retention (ZDR) versions

Some models also have a zero data retention version, marked with a **ZDR** tag on the **Models** page. When you hover over the tag, it reads "Zero data retention: your prompts and responses aren't kept after your request is complete." Not every model has a ZDR version.

A ZDR version is a separate model. It has its own alias, which ends in `-zdr`, and its own prices and limits. Its prices can be higher than the standard version's, so check them in its details.

The **Models** page lists ZDR versions together with all the other models. Copy a ZDR version's alias and send it in `model`, like any other model. Everything else on this page applies to ZDR versions in the same way.

## Request body rules

SinarCloud checks your JSON body, then forwards it to the model. If the model does not support a parameter, the parameter may be ignored instead of returning an error.

* `model` is required and must be a string.
* `stream`, when sent, must be `true` or `false`.
* The output limit must be a positive whole number no higher than the model's **Maximum output (tokens)**. Chat completions use `max_completion_tokens` or `max_tokens`, completions use `max_tokens`, and responses use `max_output_tokens`.
* `n` and `best_of` must be `1` when sent. Each request returns one choice.
* `metadata`, when sent, must be a JSON object. Some keys inside `metadata` are reserved and return `400`.
* The body must stay within the request size limit. A larger body returns `413` with the code `request_too_large`.

SinarCloud rejects these with `400` and the code `invalid_request`:

* Fields that pass credentials, choose where the request is sent, or change routing, retries, or timeouts. Examples are `api_key`, `base_url`, `fallbacks`, `num_retries`, `timeout`, `extra_body` and `service_tier`.
* Anything that stores data for later requests or points to stored data: `"store": true`, `"background": true`, `previous_response_id`, `conversation`, `prompt` (stored prompt references) on the Responses API, file inputs, and `file_id` references. With the Responses API, send the full context in each request and leave `store` unset or `false`.
* Built-in tools that would run outside your application, such as web search, file search, code interpreter, image generation, and MCP tools, and the `web_search_options` field. Use tools that your application runs, such as `function` tools.

These rules apply to fields in the JSON body. OpenAI SDK client options such as `timeout` and `max_retries` are not sent in the body, so you can use them. The SDK's `extra_body` option is also fine, as long as it does not add any of the fields above.

### Features that need a model capability

| When your request includes | The model must list |
| - | - |
| `"stream": true` | **Streaming** |
| `tools`, `functions`, or a `tool_choice` or `function_call` other than `"none"` | **Tool use** |
| `response_format` (or `text.format` in the Responses API) with type `json_object` | **JSON mode** |
| `response_format` or `text.format` with type `json_schema`, or a tool with `"strict": true` | **Structured outputs** |
| `image_url` or `input_image` content parts | **Image input** or **Vision** |
| `input_audio` content parts | **Audio input** |
| `"audio"` in `modalities`, or an `audio` field | **Audio output** |

If the model does not list the capability, the request fails with `400` and the code `unsupported_model_capability`. The message names the feature, for example "The requested model does not support streaming."

### Prompt caching

Each model's details on the **Models** page list three cache prices: **Cache read per 1M tokens**, **Cache write (standard) per 1M tokens**, and **Cache write (1 hour) per 1M tokens**. A price the model doesn't have shows **Not available**. The model list also has **Cache read** and **Cache write** columns, where a dash means the model has no such price. The **Cache write** column shows the standard price. Cached input tokens that the model reports are charged at the cache read price.

To mark content for caching, add `"cache_control": {"type": "ephemeral"}` to a message, content block, or tool. The default duration is `5m`. Add `"ttl": "1h"` inside `cache_control` to cache for one hour. Any other `ttl` value is rejected. The `prompt_cache_retention` field isn't accepted and returns `400` with the code `invalid_request`, so set the duration with `ttl` instead.

Use one cache duration per request. The default `5m` duration needs a **Cache write (standard)** price, and `1h` needs a **Cache write (1 hour)** price. If that price shows **Not available**, the request fails with `400`, the code `invalid_request`, and the message "The requested cache write duration is unavailable for this model." So send `cache_control` only to models that show a cache write price.

## Stream responses

Set `"stream": true` on a model that lists **Streaming**. The response is a stream of server-sent events (`text/event-stream`). Each event has a `data:` line that holds one JSON chunk. Responses API streams also include an `event:` line that names each event. A chat completion stream ends with `data: [DONE]`. Lines that start with `:` are keep-alive comments, so ignore them.

```bash cURL theme={null}
curl -N "https://ai.sinarcloud.com/v1/chat/completions" \
  -H "Authorization: Bearer $SINARCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "deepseek/deepseek-v4-1-flash", "stream": true, "messages": [{"role": "user", "content": "Write a haiku"}]}'
```

```python Python theme={null}
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://ai.sinarcloud.com/v1",
    api_key=os.environ["SINARCLOUD_API_KEY"],
)

stream = client.chat.completions.create(
    model="deepseek/deepseek-v4-1-flash",
    messages=[{"role": "user", "content": "Write a haiku"}],
    stream=True,
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
```

```typescript TypeScript theme={null}
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://ai.sinarcloud.com/v1",
  apiKey: process.env.SINARCLOUD_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "deepseek/deepseek-v4-1-flash",
  messages: [{ role: "user", content: "Write a haiku" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

To get token counts for a chat completion stream, add `"stream_options": {"include_usage": true}` to the body. A chunk with `usage` then arrives at the end of the stream.

If the model fails after the stream starts, the stream can end before `[DONE]`. It can also contain a chunk with an `error` object that uses one of the codes under [Errors](#errors), such as `upstream_error` or `context_length_exceeded`.

## List models

`GET /v1/models` returns the models that are currently available. The list includes models of every type, such as chat and embedding models. Check a model's capabilities on the **Models** page before you call it. `GET /v1/models/MODEL_ALIAS` returns one model. You can put an alias that contains `/` directly in the path. If the model is not available, the response is `404` with the code `model_not_found`.

```bash theme={null}
curl "https://ai.sinarcloud.com/v1/models" \
  -H "Authorization: Bearer $SINARCLOUD_API_KEY"
```

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "MODEL_ALIAS",
      "object": "model",
      "created": 1767225600,
      "owned_by": "..."
    }
  ]
}
```

Use `id` as the `model` value in your requests. The list does not include prices or capabilities. Check those on the **Models** page. Listing models is never charged.

## Track a request

Every response includes an `X-Request-Id` header. To use your own ID, send an `X-Request-Id` header of up to 100 characters. It must start with a letter or digit and can contain letters, digits, and `.`, `_`, `:`, `/` and `-`. Otherwise SinarCloud generates an ID for you.

The same value appears as **Request ID** when you click **Show details** on a request under **Recent requests**, on the **Log** tab of the **Usage** page. Only your most recent requests are listed, so record the `X-Request-Id` from the response if you need it later.

## Errors

Errors use the OpenAI error format, except on the [Anthropic Messages endpoints](#anthropic-messages-format). Check the `code` field to tell errors apart, because several errors can share one HTTP status.

```json theme={null}
{
  "error": {
    "message": "Invalid API key.",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
```

| Status | `code` | Meaning |
| - | - | - |
| 400, 422 | `invalid_request` | The body is not valid JSON, `model` is missing or not a valid alias, a field is not allowed, or a value is out of range. The message explains which. If the model itself rejects the request, the message is "The model rejected the request as invalid." or names the field whose value it rejected. |
| 400 | `context_length_exceeded` | The messages are longer than the model's context window. When the limit is known, the message states it. Shorten the conversation, or choose a model with a larger context. |
| 400 | `content_policy_violation` | The model's content policy blocked the request. Sending it again unchanged gets the same result. |
| 400 | `unsupported_model_capability` | The request uses a feature the model does not list. |
| 401 | `invalid_api_key` | The `Authorization` header is missing or malformed, the key is unknown, revoked, or expired, or your account is not active or its email address isn't verified. |
| 402 | `insufficient_balance` | Your balance is zero or negative and no token credits remain for this model. [Add balance](https://docs.sinarcloud.com/billing/top-up.md) to continue. |
| 403 | `insufficient_scope` | The key cannot call this endpoint. Create a new key on the **API keys** page. |
| 404 | `model_not_found` | The alias does not exist, the model is temporarily unavailable, or the model does not support this endpoint. |
| 404 | `unsupported_endpoint` | The path or method is not one of the endpoints above. Use `https://ai.sinarcloud.com/v1` as the base URL; don't add `/chat/completions` yourself when the SDK adds it. |
| 404 | `not_found` | The path doesn't start with `/v1`. Check that the base URL is `https://ai.sinarcloud.com/v1`. |
| 413 | `request_too_large` | The request body is over the size limit, or the request is too large for this model. |
| 429 | `rate_limit_exceeded` | Your account already has the maximum number of requests in progress across all your API keys. Retry when one finishes. With the message "The model is receiving too many requests right now. Retry shortly.", the model itself is busy: retry later, or try another model. |
| 502 | `upstream_unavailable` | The model could not be reached. |
| 503 | `key_provisioning_pending` | Your API key can't make requests and won't become usable by itself, so retrying with it doesn't help. Create a new key on the **API keys** page and use it instead. |
| 503 | `authentication_unavailable`, `billing_unavailable`, `catalog_unavailable` | A SinarCloud service is temporarily unavailable. |
| 504 | `upstream_timeout` | The model didn't respond in time. |
| 502 and other `5xx` | `upstream_error` | The model couldn't complete the request. The message is "The inference request could not be completed." |

> [!TIP]
>
> Retry `429`, `502`, `503` and `504` errors after a short wait, and increase the wait between attempts. The exception is a `503` with `key_provisioning_pending`: create a new key instead. Change the request or your account before you retry a `400`, `401`, `402`, `403`, `404`, `413` or `422` error. A `404` with `model_not_found` can also mean the model is temporarily unavailable. Check the model on the **Models** page, then retry later.

## How requests are charged

### Before a request starts

SinarCloud starts a request only when one of these is true:

* Your balance is above zero.
* You have [token credits](https://docs.sinarcloud.com/ai/token-credits.md) left for that model.
* The request costs nothing, for example on a free model or with a promotion that makes the model free.

Otherwise the request fails with `402` and the code `insufficient_balance`.

### After a request finishes

Model prices are in rupiah per 1M tokens and appear on the **Models** page. The price and any promotion in effect when your request starts apply to that request. The full price of a request is:

```text theme={null}
( input tokens                × input price
+ cache read tokens           × cache read price
+ standard cache write tokens × cache write (standard) price
+ 1-hour cache write tokens   × cache write (1 hour) price
+ output tokens               × output price ) ÷ 1,000,000
```

The cache write price is **Cache write (standard) per 1M tokens** for the default 5-minute cache, or **Cache write (1 hour) per 1M tokens** when you set `"ttl": "1h"`. Input tokens in this formula exclude the tokens charged as cache reads or cache writes. If the model has no price for a kind of cache token, those tokens are charged at the input price. Output tokens include any reasoning tokens the model uses, even when the reasoning text is not returned to you. For worked examples, see [How tokens are counted](https://docs.sinarcloud.com/ai/tokens.md#from-tokens-to-a-charge).

Every request that costs something has a full price of at least Rp 0.6. If a request's tokens cost less than that, its full price is Rp 0.6. Requests on a free model, and requests that use no tokens, stay free.

SinarCloud then pays the full price in this order:

1. Your token credits for the model cover as many tokens as they can.
2. A promotion shown for the model on the **Models** page discounts what remains. See [Models and pricing](https://docs.sinarcloud.com/ai/models.md).
3. The rest is deducted from your balance.

A promotion that makes the model free applies first, so your token credits are not used while the promotion lasts.

You are charged after the request finishes, so a large request can take your balance below zero. Your next top-up covers the negative amount first. A request that the model rejects or fails isn't charged. If the model produced output that didn't fully reach you, for example after a timeout, a dropped connection, or a stream you stopped early, the request can still be charged for the tokens it used.

Your latest charged requests appear under **Recent requests** on the **Usage** page, with their tokens and cost. Older requests are included in the totals and reports. When token credits or a promotion reduced the charge, **Show details** includes a **Charge breakdown**. See [Usage and spending](https://docs.sinarcloud.com/ai/usage.md).

On the **Billing** page, the **Transactions** tab adds up each day's AI charges into one **AI usage** transaction. See [Balance and billing](https://docs.sinarcloud.com/billing/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.