AiGolova API — documentation

AiGolova API

The API lets your own software — a CRM, a website, scripts — use AiGolova services. It speaks JSON over HTTPS. Base URL:

https://aigolova.com/api/v1

Every endpoint with its parameters and sample responses is listed below on this page, in the "API reference" section.

Quick start

  1. The API is available on paid plans. Your current plan is shown in your account.
  2. Open the API section of your account and click "Create key". The key is shown only once — store it somewhere safe right away.
  3. Check the key by requesting your account details:
curl https://aigolova.com/api/v1/account \
  -H "Authorization: Bearer ag_your_key"

The response contains your credit balance, plan and current limits.

Authentication

Every request passes the key in a header:

Authorization: Bearer ag_…
  • Keys start with ag_. A key is a secret: keep it on your server, in environment variables or a secret store. Never put it into website or mobile app code — browser requests to the API (CORS) are blocked for exactly this reason.
  • An account can have up to 5 keys, for example one per integration. Revoke a key you no longer need in your account — it stops working immediately.
  • Each key can be restricted to specific IP addresses (single addresses or CIDR subnets). An empty list allows requests from any IP.
  • When a key is created, an email is sent to the account address. If you did not create the key, revoke it and change your password.
  • Keys are revoked automatically when the password is changed or reset. Create new keys in the API section afterwards.

Tasks

Generation takes from seconds to minutes, so every service works the same asynchronous way:

  1. A POST to the service URL creates a task and immediately returns 202 with the task object in status pending. Credits are reserved at this moment: if there are not enough, the response is 402 insufficient_credits and no task is created.
  2. You get the result by polling GET /tasks/{id}, or it arrives at your webhook as a task.finished event.

GET /services returns the list of services, their task creation URLs and prices.

Task object

{
  "id": "tsk_01J9Z8Q4M6X2V7T3K5N8R1W0YB",
  "service": "titles",
  "status": "succeeded",
  "created_at": "2026-10-01T10:00:00Z",
  "finished_at": "2026-10-01T10:00:07Z",
  "input": { "text": "How to choose a laptop", "count": 5 },
  "result": { },
  "error": null,
  "cost": { "estimated_credits": 10.5, "reserved_credits": 12.6, "charged_credits": 9.8 },
  "retry_available": false
}
  • status: pending — in progress, succeeded — done, failed — error.
  • result — the result when succeeded; its fields are described for each service.
  • error — the reason when failed: {code, message}. The codes are the same as in your account: timeout, content_rejected, insufficient_balance and others; rely on code.
  • cost — the cost in credits, see below.
  • Times are in UTC, ISO 8601.

Tasks created through the API are also shown in your account, in the history of their service. Tasks created in your account are not returned by the API. Results are kept the same way as in your account, with no expiry.

Cost

Images, video and transcription have a price known in advance — exactly that amount is reserved. The price of a text depends on the length of the result, so an estimate with a margin is reserved (reserved_credits) and the actual cost is charged (charged_credits). The difference is returned to your balance automatically when the task finishes. If the result is longer than estimated, the actual cost is charged — it can exceed the reserve, but never the reserve plus your remaining balance. If your balance covers the estimate but not the margin, the task is still created. When a task failed, the whole reserve is returned.

Polling

curl https://aigolova.com/api/v1/tasks/tsk_01J9Z8Q4M6X2V7T3K5N8R1W0YB \
  -H "Authorization: Bearer $AIGOLOVA_API_KEY"

While the task is in progress, the response has a Retry-After header — in how many seconds to ask again (texts — about 4 s, images — 5 s, video — 20 s). Frequent polling runs into the request limit and does not make the result arrive sooner. Another account's task or a missing one returns 404 not_found.

GET /tasks lists your tasks, newest first. Filters: service and status; page size: limit (up to 100). For the next page pass cursor equal to next_cursor from the previous response; null means there are no more pages.

Retry after a failure

If a task failed through no fault of yours, you can retry it once for free: POST /tasks/{id}/retry. The retry_available field shows whether it is possible. The task goes back to pending, and a new webhook is sent when it finishes. If a retry is not available — 409 retry_not_available. A retry takes a slot among tasks in progress again: when the limit is reached, the response is 429 too_many_active_tasks and the task stays failed — try again later.

Repeating a request and Idempotency-Key

If the connection dropped and you do not know whether the task was created, repeating the POST creates a second task and charges credits a second time. To prevent this, send the optional Idempotency-Key header with creation requests — any unique string up to 191 characters, such as a UUID:

Idempotency-Key: 5f0c7a1e-3b9d-4e2a-9c61-0d4b8e7f2a13
  • same key and same body — the already created task is returned (response 200), nothing is charged again;
  • same key with a different body — 409 idempotency_conflict;
  • a key is valid for 24 hours; after that the same key creates a new task.

Without the header every POST creates a new task.

Cost estimate

Every service has POST …/estimate that takes the same body as task creation. It validates the parameters and returns {"estimated_credits": …, "reserved_credits": …} without creating a task or touching your credits — handy for showing the price up front.

References

Take parameter values from the reference endpoints — they match the forms in your account:

Request For parameter
GET /providers provider of text services: {code, name}
GET /languages language_id: {id, name}
GET /styles style_id
GET /tones tone_id
GET /images/options size and image style settings
GET /video/options duration, quality, aspect_ratio, sound availability

Texts

Ten text services are created with POST /texts/<service>:

URL What it does Own fields result
/texts/tasks An article on a topic text (topic, up to 191 characters), headings_count, headings_type text, html
/texts/titles Article titles text, count items[] — titles, text, html
/texts/meta Title, description, H1 text (comma-separated keywords), title / description / h1 — at least one true text, html
/texts/semantics Keyword expansion text, count text, html
/texts/rewrite Rewrite name, text text, html
/texts/translations Translation name, text; language_id is the target language text, html
/texts/reviews Reviews text, type (neutral / positive / negative), count, words_limit (1–1800) text, html
/texts/content Website content text, title / description / h1 / body — at least one true, output_format (json / xml / csv) text — a ready file in the chosen format
/texts/articles Article outlines, batch items[]: {text, language_id, headings_count}, up to 20 items[]: {text, language_id, headings[]}
/texts/cards Product cards, batch items[]: {text, language_id, length} (length up to 1500), up to 20 items[]: {text, language_id, result: {text, html}}

Fields shared by all text services:

  • provider — required, an AI model from GET /providers;
  • language_id — required (except the batch services articles and cards, where each item has its own language);
  • style_id, tone_id, temperature (0–2), top_p (0–1), frequency_penalty and presence_penalty (−2…2) — optional; omitted ones are taken from your account settings;
  • callback_url — an optional webhook for this task.

text is limited to 20,000 characters (191 for tasks). In result, text is the result as stored and html is how your account shows it.

curl https://aigolova.com/api/v1/texts/titles \
  -H "Authorization: Bearer $AIGOLOVA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1842-titles" \
  -d '{"provider": "gemini", "language_id": 1, "text": "Apartment renovation", "count": 5}'

The response is 202 with the task in pending. Once it finishes:

"result": {
  "items": ["Turnkey apartment renovation: where to start", "…"],
  "text": "1. Turnkey apartment renovation: where to start\n2. …",
  "html": "1. Turnkey apartment renovation: where to start<br />\n2. …"
}

The price of a text depends on the result length — see “Cost” above.

Images

POST /images:

  • prompt — what to draw, up to 500 characters;
  • count — number of images, 1–10;
  • size — 1536x1024, 1024x1536 or 1024x1024;
  • art_style, photography_style, lighting, subject, camera_settings, composition, resolution, color, special_effects — optional, a key from GET /images/options.

The price of the requested number of images is reserved; only the images actually generated are charged. The result is result.images[] with public {url} links. Images created through the API are not added to the public gallery.

Video

POST /video — text-to-video:

  • prompt — description, up to 2048 characters;
  • duration — 5 or 8 seconds;
  • quality — 360p, 540p, 720p or 1080p;
  • aspect_ratio — 16:9, 4:3, 1:1, 3:4 or 9:16;
  • sound — with sound (true) when sound.available in GET /video/options is true; the price is multiplied by sound.credits_multiplier.

Things to know:

  • Generation starts at the provider within the create request. If the provider rejects the prompt for its content, the response is 422 content_rejected; if the provider is unavailable, 502 provider_unavailable. In both cases no task is created and nothing is charged — change the prompt or try again later.
  • At most 2 videos can be in progress at once (429 too_many_active_tasks).
  • The finished video is at result.video_url. This is our temporary download link: it needs no API key and is valid for 24 hours (see result.video_url_expires_at). An expired link returns 403 link_expired; call GET /tasks/{id} again for a fresh one. Download the file if you need it for longer. Occasionally the file reaches the provider a few seconds after the task finishes; the link then returns 503 service_unavailable with a Retry-After header — retry the request.

Video from photos and templates is not yet available through the API.

Limits

What is limited Limit Response when exceeded
Requests per minute per account (all keys together) 60 429 rate_limited
New tasks per minute 20 429 rate_limited
Tasks in progress at the same time 10, of them video — 2 429 too_many_active_tasks
Invalid keys per minute from one IP 20 429 rate_limited

Limits on your plan may differ — GET /account returns the exact values in the limits field. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining headers; a 429 response also carries Retry-After (seconds to wait before retrying).

Errors

All errors share one format:

{
  "error": {
    "code": "validation_failed",
    "message": "Request parameters are invalid.",
    "details": { "text": ["The text field is required."] }
  }
}

Rely on code — it is stable and never translated. message is a human-readable explanation and its wording may change. Not every error has details.

HTTP code What happened
400 bad_request Bad request
401 unauthenticated Key missing or invalid
401 key_revoked Key revoked — create a new one in your account
402 insufficient_credits Not enough credits on the balance
403 api_disabled The API is temporarily unavailable
403 api_not_in_tariff Your current plan does not include the API
403 ip_not_allowed Request from an IP that is not in the key's list
403 account_blocked The account is blocked or deleted
403 link_expired The video download link has expired or is invalid
404 not_found Resource not found
405 method_not_allowed Method not allowed for this URL
409 idempotency_conflict Idempotency-Key already used with a different body
409 retry_not_available Retry is not available: the task has not failed or the free retry is used up
413 payload_too_large Request is too large
422 validation_failed Invalid parameters, see details
422 content_rejected The provider rejected the video prompt for its content; nothing is charged
429 rate_limited Rate limit exceeded, see Retry-After
429 too_many_active_tasks Too many tasks in progress
500 internal_error An error on our side
502 provider_unavailable The generation provider is unavailable; nothing is charged
503 service_unavailable Service temporarily unavailable

Webhooks

A webhook is a request AiGolova sends to your server when an event happens, so you do not have to keep polling the API. Set the URL in the API section of your account; there is one URL per account. Only https:// URLs on public servers are accepted.

The request is a POST with a JSON body {event, created_at, data}. When a task finishes (succeeded or failed), a task.finished event arrives with the task object in data, the same as in GET /tasks/{id}:

{
  "event": "task.finished",
  "created_at": "2026-10-01T10:00:08Z",
  "data": {
    "id": "tsk_01J9Z8Q4M6X2V7T3K5N8R1W0YB",
    "service": "titles",
    "status": "succeeded",
    "…": "…"
  }
}

For a single task the URL can be overridden with the callback_url field in the creation request. If neither the account URL nor callback_url is set, no webhook is sent — get the result by polling.

Headers: X-Aigolova-Event — the event type, X-Aigolova-Signature — the body signature. The "Send a test event" button in your account sends a webhook.test event (its data holds only message). Be ready for new event types: simply ignore unknown event values.

Delivery. A 2xx response means the event is delivered. If your server responds with another code, does not respond within 10 seconds or is unreachable, we retry once after 5 seconds and then give up. Redirects (3xx) are not followed and count as a failure. The delivery history with response codes is shown in your account. A webhook is only a notification: if an event is lost, you can always get the current state with GET /tasks/{id}. Respond with 2xx right away and do slow processing after responding.

An event describes the task at the moment it finished. If you retry a failed task before the failure event has been sent, that event is dropped — you get a single event when the retry finishes.

Verifying the signature

The signature is the hex HMAC-SHA256 of the request body with the secret from your account. Verifying it is optional but recommended: anyone who learns your webhook URL could send a fake event. Compute the signature over the raw request body, before parsing JSON.

PHP:

$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, getenv('AIGOLOVA_WEBHOOK_SECRET'));
$valid = hash_equals($expected, $_SERVER['HTTP_X_AIGOLOVA_SIGNATURE'] ?? '');

Python:

import hmac, hashlib
expected = hmac.new(secret.encode(), request_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers.get("X-Aigolova-Signature", ""))

Node.js:

const crypto = require('crypto');
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const received = req.get('X-Aigolova-Signature') || '';
const valid = received.length === expected.length
  && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));

If you prefer not to verify signatures, fetch the task with GET /tasks/{id} and your own key after receiving a webhook — that response is guaranteed to come from AiGolova.

Examples

curl:

curl https://aigolova.com/api/v1/account \
  -H "Authorization: Bearer $AIGOLOVA_API_KEY"

PHP:

$ch = curl_init('https://aigolova.com/api/v1/account');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer '.getenv('AIGOLOVA_API_KEY')],
    CURLOPT_RETURNTRANSFER => true,
]);
$account = json_decode(curl_exec($ch), true);
echo $account['balance']['credits'];

Python:

import os, requests

response = requests.get(
    "https://aigolova.com/api/v1/account",
    headers={"Authorization": f"Bearer {os.environ['AIGOLOVA_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
print(response.json()["balance"]["credits"])

Polling a task until it finishes, PHP:

function aigolovaTask(string $id): array
{
    while (true) {
        $ch = curl_init('https://aigolova.com/api/v1/tasks/'.$id);
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => ['Authorization: Bearer '.getenv('AIGOLOVA_API_KEY')],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
        ]);
        $response = curl_exec($ch);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        $task = json_decode(substr($response, $headerSize), true);

        if ($task['status'] !== 'pending') {
            return $task;
        }

        preg_match('/^Retry-After:\s*(\d+)/mi', substr($response, 0, $headerSize), $m);
        sleep((int) ($m[1] ?? 5));
    }
}

Python:

import os, time, requests

def aigolova_task(task_id):
    headers = {"Authorization": f"Bearer {os.environ['AIGOLOVA_API_KEY']}"}
    while True:
        response = requests.get(f"https://aigolova.com/api/v1/tasks/{task_id}", headers=headers, timeout=30)
        response.raise_for_status()
        task = response.json()
        if task["status"] != "pending":
            return task
        time.sleep(int(response.headers.get("Retry-After", 5)))

Versions

The API version is part of the URL: /api/v1. Within v1 changes are only additive: new response fields, new services and endpoints. Do not rely on a fixed set of fields — ignore fields you do not know. Breaking changes will ship as /api/v2, and we will email you in advance before an old version is retired.

API reference