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

  1. Sign in and open Balance and API keys.
  2. Create a key. It is shown once: copy it and keep it secret, like a password. You can revoke a key at any time.
  3. 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
servicerequiredService from /services
tierrequiredbasic, standard or premium
textrequired without answers, script or awaitBriefThe task: what to do, up to 10,000 characters. With script: how to read it (tone, pace, how to say names)
linkoptionalA 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
answersoptionalAnswers to the service's briefQuestions: [{"q": "question", "a": "answer"}], up to 8, each answer up to 5,000 characters
scriptrequired where script in /services is not null, refused elsewhereThe 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
localeoptionalLanguage of the result, en or ru. Your account language by default
addonsoptionalIds of add-ons from /services offered for this tier, e.g. [7]
promoCodeoptionalA promo code; if it does not apply, the order is not placed (see Errors)
awaitBriefoptionaltrue 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:

  1. Place the order with "awaitBrief": true and without text, link, answers and script. It is paid at once and its status is awaiting_brief; the deadline starts with the brief.
  2. For each file, POST /orders/{id}/files with {"name": "page-1.png", "size": 482113, "mime": "image/png"}: size is the exact size in bytes, mime is 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" }
}
  1. PUT the file's bytes to upload.url with exactly the headers given and no API key, before upload.expiresAt (an hour):
curl -X PUT "$url" -H "content-type: application/octet-stream" --data-binary @page-1.png
  1. POST /orders/{id}/files/{fileId}/complete. A file of another size than size, or whose bytes are not of a kind in accept, is refused with 422 and deleted: the name and mime do not count. Otherwise the answer is the file, now checking. After a few seconds, or minutes for a long recording, it is ready, or rejected with the reason in reason, e.g. recording_no_sound or file_unreadable. The order's files shows how they are.
  2. POST /orders/{id}/brief with text, link, answers and script as in POST /orders. The ready files not sent yet go with it, or list the ones to send in fileIds. While one of them is still checking, the brief is refused with 409 files_not_ready: send it again a little later. The answer (200) is the order, now in_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

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:

StatusMeaning
awaiting_briefPlaced with awaitBrief: waiting for POST /orders/{id}/brief
in_progressThe work is being done
quality_checkThe work is done and being checked before delivery
deliveredThe result is ready, see results. Accept it or ask for changes by autoAcceptAt
revisionChanges you asked for are being made
completedYou accepted the result, or it was accepted automatically
refundedThe 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

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" }
StatusCodeMeaning
400validationA field is missing or wrong; fields lists them
400link_requiredThis service needs link
400files_requiredThis service needs a file: place the order with awaitBrief and upload it
400script_requiredThis service reads a text aloud: send it in script
400script_too_longscript is longer than the package reads; maxChars in the answer is the most it takes. Nothing was charged
400script_not_acceptedThe service reads no text aloud (its script in /services is null): leave script out. Nothing was charged
400files_not_accepted, file_too_largeThe service takes no files, or the file is larger than its maxMb
400invalid_idempotency_keyIdempotency-Key is empty, longer than 255 characters or not printable ASCII
401invalid_api_keyNo 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
402insufficient_balanceNot enough money on the balance; nothing was charged
404package_not_found, order_not_found, file_not_foundNo such service, package, order or file
409order_not_delivered, no_revisions_leftThe order is not in a state for this
409brief_not_expectedThe order is not waiting for its brief: it was placed without awaitBrief, or its brief was already sent
409uploads_closedFiles are taken only while the order waits for its brief or is delivered
409files_limitMore files than the service takes with one brief or request, or over 5 files or 4 GB for the order
409upload_quotaWith 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
409files_not_ready, file_rejectedA file to send is still being checked, or was rejected
409upload_incompleteNothing was uploaded to the link yet: finish the PUT, then complete the upload again
409file_in_use, file_busyThe 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
409addon_unavailableAn add-on is not offered for this service and tier
409promo_not_found, promo_inactive, promo_used_up, promo_already_used, promo_first_order_only, promo_min_orderThe 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
409idempotency_key_in_progressA request with this Idempotency-Key is still being processed; repeat it a moment later
410upload_expired, file_removedThe upload link ran out before anything was uploaded, or you took the file back: start a new upload
415file_type_not_acceptedThe mime given is not of a kind the service takes
422idempotency_key_reusedThis Idempotency-Key was already used with a different request
422upload_size_mismatch, file_type_not_acceptedThe uploaded file is not as long as size, or its bytes are not of a kind the service takes; it was deleted
422file_unreadable, recording_no_sound, check_failedCompleting 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
429rate_limitedOver 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.