Skip to content

Developer documentation

Guides for integrating with the API. The complete list of endpoints is in the API reference.

Quickstart

Create a project and an API key in the developer console. Keys are shown once; store them as secrets on your server and never in a browser or mobile app.

List the models available to your key, then send your first request.

bash
export API_KEY=sk_live_...
curl https://gapze.ir/v1/models -H "Authorization: Bearer $API_KEY"

curl https://gapze.ir/v1/responses \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"MODEL_SLUG","input":"Write a haiku about Tehran"}'

Authentication

Send the key in the Authorization header as a bearer token (or in X-API-Key). Requests without a valid key receive 401; keys without the needed scope receive 403.

http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
# or
X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxx

Streaming

Set "stream": true to receive server-sent events. Each event carries a delta; the stream ends with [DONE]. Generations started through /v1/generations can be resumed with Last-Event-ID.

bash
curl -N https://gapze.ir/v1/chat/completions \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"MODEL_SLUG","stream":true,"messages":[{"role":"user","content":"Hi"}]}'

# data: {"choices":[{"delta":{"content":"Hel"}}]}
# ...
# data: [DONE]

Errors

Errors use a single JSON shape with a stable machine-readable code, a human message, a type, whether retrying can help and the request id. Include the request id when contacting support.

402 means the balance or budget is not enough; 409 a conflicting state; 422 invalid input; 429 a rate limit; 5xx a temporary failure.

json
{
  "error": {
    "code": "insufficient_budget",
    "message": "your balance is not enough for this request",
    "type": "billing_error",
    "retryable": false,
    "request_id": "req_01..."
  }
}

Rate limits

Limits apply per key and per project. A 429 response includes Retry-After in seconds; wait at least that long before retrying, preferably with exponential backoff and jitter.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 6000

Idempotency

Send a unique Idempotency-Key header with requests that create or charge something. Retrying with the same key returns the original result and is never charged twice.

bash
curl https://gapze.ir/v1/chat/completions \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: 3f1c2a6e-8b9d-4c1f-9e2a-7d5b6c4a3f21" ...

Balance, quotes and usage

Usage is charged to the wallet of the key’s owner. Ask for a binding quote before expensive jobs, and read your balance and usage at any time.

bash
curl https://gapze.ir/v1/balance -H "Authorization: Bearer $API_KEY"
curl "https://gapze.ir/v1/usage?from=2026-10-01" -H "Authorization: Bearer $API_KEY"
curl https://gapze.ir/v1/generations/quotes -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" -d '{"model":"MODEL_SLUG","messages":[...]}'

Files

Upload files for use as inputs. Small files use a single multipart request; large files use resumable uploads in parts.

bash
curl https://gapze.ir/v1/files -H "Authorization: Bearer $API_KEY" \
  -F purpose=assistants -F file=@report.pdf
# large files: POST /v1/files/uploads → PUT /v1/files/uploads/{id}/parts/{n} → POST .../complete

Long-running jobs

Video and other long jobs return a job id immediately. Poll the job, or subscribe to the job.completed webhook, then download the result.

bash
curl https://gapze.ir/v1/videos/generations -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" -d '{"model":"VIDEO_MODEL","prompt":"..."}'
# → {"id":"job_...","status":"queued"}
curl https://gapze.ir/v1/jobs/job_... -H "Authorization: Bearer $API_KEY"
curl -L https://gapze.ir/v1/jobs/job_.../content -H "Authorization: Bearer $API_KEY" -o out.mp4

Webhooks

Each delivery carries webhook-id, webhook-timestamp and webhook-signature headers. Verify the HMAC-SHA256 signature over “id.timestamp.body” with your signing secret, reject old timestamps and deduplicate by id. During a secret rotation both the old and new signatures are sent.

python
import base64, hashlib, hmac, time

def verify(secret: str, headers: dict, body: bytes, tolerance=300) -> bool:
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > tolerance:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(mac).decode()
    return any(hmac.compare_digest(expected, s) for s in headers["webhook-signature"].split())

Test mode

Test keys only reach the built-in simulator and spend test credits. Switch to a live key when you are ready.

bash
# test keys (sk_test_...) use the built-in simulator and test credits only
curl https://gapze.ir/v1/chat/completions -H "Authorization: Bearer sk_test_..." ...