API
Order services from your own website, shop or script: list services and prices, place orders with the task, track them and fetch the results. It is the same service as on the site, so orders placed through the API also appear in your account.
Getting started
- Sign in and open Balance and API keys.
- Create a key. It is shown once: copy it and keep it secret, like a password. You can revoke a key at any time.
- Top up the balance. Orders placed through the API are paid from it, in US dollars.
Send the key with every request:
Authorization: Bearer dlx_your_key
Address of the API: https://dleex.com/api/v1. Requests and responses are JSON. Prices and amounts are whole US cents: 1500 means $15.00.
Services
GET /services lists what can be ordered.
curl https://dleex.com/api/v1/services -H "Authorization: Bearer dlx_your_key"
[
{
"service": "seo-article",
"category": "seo",
"title": { "en": "SEO article", "ru": "SEO-статья" },
"linkRequired": false,
"files": null,
"script": null,
"briefQuestions": [
{ "q": { "en": "What is the article about?", "ru": "О чём статья?" }, "hint": { "en": "The topic, or a link to the page.", "ru": "..." } },
{ "q": { "en": "Who will read it?", "ru": "..." } }
],
"packages": [
{ "tier": "basic", "title": { "en": "...", "ru": "..." }, "priceUsdCents": 1500, "days": 2, "revisions": 1 },
{ "tier": "standard", "...": "..." },
{ "tier": "premium", "...": "..." }
],
"addons": [
{
"id": 7,
"title": { "en": "Express delivery", "ru": "Срочное выполнение" },
"description": null,
"priceUsdCents": 1000,
"extraDays": -1,
"tiers": ["standard", "premium"]
}
]
}
]
days is how long the work takes; revisions is how many times you can ask for changes. addons are extras you can order with the packages in tiers: the price is added to the package's, and extraDays to its deadline (negative: faster, never below 1 day). briefQuestions are what the service asks about the task, some with a hint; answer them in answers when you place an order. The list is empty when a service asks nothing. linkRequired is true for a service done from what a link opens, such as a profile or post, or a recording to subtitle: an order of it needs link.
files is null for a service that takes no files with the brief. Otherwise it says which it takes, e.g. {"accept": ["image"], "maxFiles": 3, "maxMb": 5, "required": true, "insteadOfLink": false}: the kinds (image, audio, video), how many files with one brief or revision request, how large each file may be in MB, whether the brief needs at least one, and whether a file can take the place of link (linkRequired is then still true for orders sent without files). See Files.
script is null for a service that reads no text aloud. A voice-over service has the longest text each package reads, in characters, e.g. {"maxChars": {"basic": 1000, "standard": 3000, "premium": 9000}}: its orders need script, the text to read word for word. The recording is read in a synthesized voice: wherever you publish it, tell your listeners that the voice is synthesized (see the Terms).
Balance
GET /balance
{ "balanceUsdCents": 10000, "currency": "USD" }
Placing an order
POST /orders. The price (the package and add-ons, less any discount) is taken from the balance and the work starts at once.
| Field | ||
|---|---|---|
service | required | Service from /services |
tier | required | basic, standard or premium |
text | required without answers, script or awaitBrief | The task: what to do, up to 10,000 characters. With script: how to read it (tone, pace, how to say names) |
link | optional | A link to the page, profile or post, or to a recording: a direct link to the file, or a Dropbox or Yandex Disk link. Required where linkRequired is true |
answers | optional | Answers to the service's briefQuestions: [{"q": "question", "a": "answer"}], up to 8, each answer up to 5,000 characters |
script | required where script in /services is not null, refused elsewhere | The text to read aloud, word for word, up to the package's maxChars (at most 20,000 characters, a line break counted as one). Leave a blank line between paragraphs |
locale | optional | Language of the result, en or ru. Your account language by default |
addons | optional | Ids of add-ons from /services offered for this tier, e.g. [7] |
promoCode | optional | A promo code; if it does not apply, the order is not placed (see Errors) |
awaitBrief | optional | true to send the brief later, after uploading files: the order is paid and waits as awaiting_brief. Leave out text, link, answers and script then. See Files |
curl https://dleex.com/api/v1/orders -H "Authorization: Bearer dlx_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <your-order-id>" \
-d '{"service":"seo-article","tier":"basic","text":"An article about trail running shoes, 5 keywords: ..."}'
Idempotency-Key is optional and lets you repeat the request safely; every new order needs a new key. See Idempotency below.
Give either text or at least one answer, or both; for a voice-over, script is enough. Write each question in q as it is in briefQuestions, in the language of the order, and leave out the ones you have no answer to:
{
"service": "seo-article",
"tier": "basic",
"answers": [
{ "q": "What is the article about?", "a": "Choosing trail running shoes" },
{ "q": "Who will read it?", "a": "Runners moving from road to trail" }
]
}
The answer is the order (status 201):
{
"id": 1042,
"status": "in_progress",
"service": "seo-article",
"tier": "basic",
"priceUsdCents": 1500,
"addons": [],
"discountUsdCents": 0,
"promoCode": null,
"locale": "en",
"text": "An article about trail running shoes...",
"link": null,
"answers": [],
"script": null,
"fileIds": [],
"files": [],
"revisionsLeft": 1,
"createdAt": "2026-10-16T11:00:00.000Z",
"dueAt": "2026-10-18T11:00:00.000Z",
"deliveredAt": null,
"autoAcceptAt": null,
"completedAt": null,
"results": []
}
Files
Some services work from the client's files, such as pictures of pages to type out or a recording to subtitle: their files in /services is not null. Files are uploaded to an order that waits for its brief:
- Place the order with
"awaitBrief": trueand withouttext,link,answersandscript. It is paid at once and its status isawaiting_brief; the deadline starts with the brief. - For each file,
POST /orders/{id}/fileswith{"name": "page-1.png", "size": 482113, "mime": "image/png"}:sizeis the exact size in bytes,mimeis optional. The answer (201) is the file and where to upload it:
{
"file": { "id": 77, "name": "page-1.png", "mime": "application/octet-stream", "size": 482113, "status": "pending", "used": false, "uploadExpiresAt": "2026-10-16T12:00:00.000Z", "createdAt": "2026-10-16T11:00:00.000Z" },
"upload": { "url": "https://...", "headers": { "content-type": "application/octet-stream" }, "expiresAt": "2026-10-16T12:00:00.000Z" }
}
PUTthe file's bytes toupload.urlwith exactly theheadersgiven and no API key, beforeupload.expiresAt(an hour):
curl -X PUT "$url" -H "content-type: application/octet-stream" --data-binary @page-1.png
POST /orders/{id}/files/{fileId}/complete. A file of another size thansize, or whose bytes are not of a kind inaccept, is refused with422and deleted: the name andmimedo not count. Otherwise the answer is the file, nowchecking. After a few seconds, or minutes for a long recording, it isready, orrejectedwith the reason inreason, e.g.recording_no_soundorfile_unreadable. The order'sfilesshows how they are.POST /orders/{id}/briefwithtext,link,answersandscriptas inPOST /orders. The ready files not sent yet go with it, or list the ones to send infileIds. While one of them is stillchecking, the brief is refused with409 files_not_ready: send it again a little later. The answer (200) is the order, nowin_progress.
DELETE /orders/{id}/files/{fileId} takes back a file not sent yet. An order takes at most 5 files and 4 GB in all; a file whose upload was started counts until it is completed or taken back, and for a day from the start of their upload, files taken back or refused count toward 12 GB together with the order's files: past that, an upload is refused with 409 upload_quota and retryAt, the time it is taken again. A delivered order takes files for a revision request too: upload them the same way, then send fileIds with the request.
Without awaitBrief, an order of a service whose files are required is refused with 400 files_required, and one where a file can take the place of the link, sent without a link, with 400 link_required.
In the order, files lists your files with their status, and fileIds those that went with the brief. A ready file has a url that works until expiresAt, like result files. Files are kept for 30 days after the order is closed.
Idempotency
A request can time out after the order was already placed. To repeat POST /orders without paying twice, send an Idempotency-Key header with it: a string of 1 to 255 printable ASCII characters that you choose once per order, such as a UUID or your own order number. Send the same key and the same body when you repeat the request.
curl -i https://dleex.com/api/v1/orders -H "Authorization: Bearer dlx_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: shop-order-981" \
-d '{"service":"seo-article","tier":"basic","text":"An article about trail running shoes, 5 keywords: ..."}'
If an order was already placed with this key, the answer is that order as it is now, with status 201 and one more header. Nothing is charged again:
HTTP/1.1 201 Created
Idempotent-Replayed: true
- The key belongs to the request it was first sent with. The same key with a different body, such as other add-ons or another promo code, is refused with
422 idempotency_key_reused, and nothing is charged. - If several requests with one key arrive at once, one order is placed and the others answer with it. A request that has to wait too long answers
409 idempotency_key_in_progressand charges nothing: repeat it a moment later. - A request that failed, for example with
400,402,404or an add-on or promo code error, does not use up its key: fix it and send it again with the same key. - A key is kept for 24 hours. After that the same key places a new order.
- Keys are separate for each account. Without the header every request places a new order.
Accepting, briefs and revision requests take no key. Accepting is safe to repeat: once the order is completed, a repeat answers 409 order_not_delivered. So is a brief: once it went through, a repeat answers 409 brief_not_expected and the order is in_progress. A revision request is different: while the work is being redone a repeat answers 409 order_not_delivered, but once the redo is delivered a repeat asks for another revision. Before repeating a revision request, check the order with GET /orders/{id} (status and revisionsLeft).
Tracking orders
GET /orders/{id} returns one order, GET /orders your orders, newest first. The list takes status, limit (1 to 100, 20 by default) and before (an order id, to get older orders).
Order status:
| Status | Meaning |
|---|---|
awaiting_brief | Placed with awaitBrief: waiting for POST /orders/{id}/brief |
in_progress | The work is being done |
quality_check | The work is done and being checked before delivery |
delivered | The result is ready, see results. Accept it or ask for changes by autoAcceptAt |
revision | Changes you asked for are being made |
completed | You accepted the result, or it was accepted automatically |
refunded | The money went back to your balance |
Check every few minutes; there is no need to ask more often. When the order is delivered, results holds the work: kind is text (Markdown), report (a delivery report for social media orders) or files (files to download, with an optional note in body). Each result has a files list, empty for text and reports: every file has name, mime, size in bytes and a url that works until expiresAt, about an hour. Read the order again for fresh links. The link may redirect to the file storage, so follow redirects, e.g. curl -L -o result.png "$url", and send no API key with it: the link alone opens the file.
Accepting and revisions
POST /orders/{id}/acceptaccepts the result and completes the order.POST /orders/{id}/revisionwith{"text": "what to change"}asks for changes, whilerevisionsLeftis above zero. Where the service takes files,fileIdslists the files uploaded for the request; without it, the ready files not sent yet go with it.
If you neither accept nor revise a delivered order, it is accepted automatically 3 days after delivery, at the time in autoAcceptAt (null in other states). A new delivery after a revision starts the 3 days again. You can also accept the work on the site. If you write in the order chat on the site after the delivery, the order is not accepted automatically and autoAcceptAt is null until you accept it or we redo the work or refund it. After answering your message, we may also start the 3 days again: autoAcceptAt then shows the new time.
Errors
Errors come with an HTTP status and a code:
{ "code": "insufficient_balance" }
| Status | Code | Meaning |
|---|---|---|
| 400 | validation | A field is missing or wrong; fields lists them |
| 400 | link_required | This service needs link |
| 400 | files_required | This service needs a file: place the order with awaitBrief and upload it |
| 400 | script_required | This service reads a text aloud: send it in script |
| 400 | script_too_long | script is longer than the package reads; maxChars in the answer is the most it takes. Nothing was charged |
| 400 | script_not_accepted | The service reads no text aloud (its script in /services is null): leave script out. Nothing was charged |
| 400 | files_not_accepted, file_too_large | The service takes no files, or the file is larger than its maxMb |
| 400 | invalid_idempotency_key | Idempotency-Key is empty, longer than 255 characters or not printable ASCII |
| 401 | invalid_api_key | No key, a wrong key or a revoked key. Keys are also revoked when the account's unconfirmed email is confirmed by signing in with Google or by setting a new password |
| 402 | insufficient_balance | Not enough money on the balance; nothing was charged |
| 404 | package_not_found, order_not_found, file_not_found | No such service, package, order or file |
| 409 | order_not_delivered, no_revisions_left | The order is not in a state for this |
| 409 | brief_not_expected | The order is not waiting for its brief: it was placed without awaitBrief, or its brief was already sent |
| 409 | uploads_closed | Files are taken only while the order waits for its brief or is delivered |
| 409 | files_limit | More files than the service takes with one brief or request, or over 5 files or 4 GB for the order |
| 409 | upload_quota | With this one, the order's files (sent ones and unfinished uploads included) and those started in the last day and since taken back or refused are over 12 GB: try again after retryAt |
| 409 | files_not_ready, file_rejected | A file to send is still being checked, or was rejected |
| 409 | upload_incomplete | Nothing was uploaded to the link yet: finish the PUT, then complete the upload again |
| 409 | file_in_use, file_busy | The file went with the brief or a request and stays with the order, or is being moved by its check: try again in a moment |
| 409 | addon_unavailable | An add-on is not offered for this service and tier |
| 409 | promo_not_found, promo_inactive, promo_used_up, promo_already_used, promo_first_order_only, promo_min_order | The promo code does not exist, is not active now, has run out, was already used by you, is for a first order only, or needs a bigger order. Nothing was charged |
| 409 | idempotency_key_in_progress | A request with this Idempotency-Key is still being processed; repeat it a moment later |
| 410 | upload_expired, file_removed | The upload link ran out before anything was uploaded, or you took the file back: start a new upload |
| 415 | file_type_not_accepted | The mime given is not of a kind the service takes |
| 422 | idempotency_key_reused | This Idempotency-Key was already used with a different request |
| 422 | upload_size_mismatch, file_type_not_accepted | The uploaded file is not as long as size, or its bytes are not of a kind the service takes; it was deleted |
| 422 | file_unreadable, recording_no_sound, check_failed | Completing a file the check rejected: it does not open, the recording has no sound, or it could not be checked. Upload the file again or another one |
| 429 | rate_limited | Over 120 requests a minute with one key; wait the seconds in Retry-After |
Refunds
If we refund an order, the money goes back to the balance and the order becomes refunded.