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
- The API is available on paid plans. Your current plan is shown in your account.
- Open the API section of your account and click "Create key". The key is shown only once — store it somewhere safe right away.
- 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:
- A
POSTto the service URL creates a task and immediately returns202with the task object in statuspending. Credits are reserved at this moment: if there are not enough, the response is402 insufficient_creditsand no task is created. - You get the result by polling
GET /tasks/{id}, or it arrives at your webhook as atask.finishedevent.
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 whensucceeded; its fields are described for each service.error— the reason whenfailed:{code, message}. The codes are the same as in your account:timeout,content_rejected,insufficient_balanceand others; rely oncode.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 fromGET /providers;language_id— required (except the batch servicesarticlesandcards, where each item has its own language);style_id,tone_id,temperature(0–2),top_p(0–1),frequency_penaltyandpresence_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,1024x1536or1024x1024;art_style,photography_style,lighting,subject,camera_settings,composition,resolution,color,special_effects— optional, akeyfromGET /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—5or8seconds;quality—360p,540p,720por1080p;aspect_ratio—16:9,4:3,1:1,3:4or9:16;sound— with sound (true) whensound.availableinGET /video/optionsis true; the price is multiplied bysound.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 (seeresult.video_url_expires_at). An expired link returns403 link_expired; callGET /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 returns503 service_unavailablewith aRetry-Afterheader — 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.