Reference
Downora API
One endpoint: give it an Envato Elements or Freepik asset and it returns a direct download URL. You are charged only for a request that produced a downloadUrl — if it did not, the charge is $0.
Quickstart
- 1. Sign in, then create a key on API Keys. The full secret (prefixed dn_live_) is shown exactly once.
- 2. Top up your USD balance on Billing.
- 3. Call the endpoint below with your key and an asset URL.
curl -X POST https://your-api.downora.app/v1/download \
-H "X-API-Key: dn_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"platform": "freepik",
"url": "https://www.freepik.com/free-vector/example_16172815.htm"
}'Base URL: https://your-api.downora.app
Authentication
There are two credential types and they map to two different billing sources. Send one or the other, never both.
API source
Server-to-server. Uses your API key and is always billed against your USD balance at the API price — the free daily quota does not apply.
X-API-Key: dn_live_your_key_hereKeep API keys server-side. Anyone holding the key can spend your balance, and a leaked key can only be revoked, never rotated in place.
POST /v1/download
Supply platform plus either url (an asset page link) or assetId. Valid platforms are freepik and envato-elements.
| Field | Type | Required | Notes |
|---|---|---|---|
| platform | string | Yes | freepik | envato-elements |
| url | string | Either | Asset page link on an allowed host for that platform. |
POST https://your-api.downora.app/v1/download
X-API-Key: dn_live_your_key_here
Content-Type: application/json
Idempotency-Key: 3f9c1c8e-6b1a-4f2b-9a77-1c2d3e4f5a6b
{
"platform": "envato-elements",
"url": "https://elements.envato.com/some-asset-ABC123"
}{
"success": true,
"requestId": "req_01J8Z9K2M4N6P8Q0R2S4T6",
"provider": "primary",
"assetId": "ABC123",
"downloadUrl": "https://…signed-url…",
"licenceUrl": "https://…licence-pdf…",
"billing": {
"type": "paid",
"amount": "0.025000",
"currency": "USD"
}
}This response is flat — there is no data wrapper. Money values are decimal strings; parse them only for display. billing.type is free (covered by the daily quota), paid (drawn from your balance) or none.
downloadUrl and licenceUrl are short-lived and returned only here — they are never stored and cannot be re-read from your usage history. Download the file straight away.
{
"success": false,
"requestId": "req_01J8Z9K2M4N6P8Q0R2S4T6",
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Your balance does not cover this download."
}
}Idempotency
Send an Idempotency-Key header (a UUID you generate) on every download. If a network blip makes you retry, the worker replays the cached response instead of running a second download — so a retry cannot double-charge you.
Idempotency-Key: 3f9c1c8e-6b1a-4f2b-9a77-1c2d3e4f5a6b- • Use a fresh key per distinct download; reuse it only when retrying that same download.
- • Reusing a key with a different body returns IDEMPOTENCY_CONFLICT (409) rather than silently serving the wrong asset.
- • Keep the key alongside your own record of the request so a retry after a crash can still find it.
Error codes
Every failure has the same shape: success: false, a requestId to quote in support requests, and an error.code from this table. Branch on the code, not the message.
| Code | HTTP | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | No credential was sent, or the JWT is expired or malformed. |
| INVALID_API_KEY | 401 | The X-API-Key value does not match any key. |
| API_KEY_REVOKED | 401 | The key existed but has been revoked. Create a new one. |
| USER_SUSPENDED | 403 | The account behind the credential is suspended or disabled. |
| INVALID_PLATFORM | 400 | platform must be exactly 'freepik' or 'envato-elements'. |
| INVALID_ASSET | 400 | Neither a usable url nor assetId was supplied, or the asset does not exist. |
| INVALID_ASSET_URL | 400 | The url host is not an allowed host for that platform, or the shape is wrong. |
| INSUFFICIENT_BALANCE | 402 | Your USD balance cannot cover this download. Top up and retry. |
| PROVIDER_CONFIGURATION_MISSING | 502 | No provider is configured for that platform on our side. |
| PROVIDER_CONFIGURATION_INVALID | 502 | A provider is configured but its credentials or settings are unusable. |
| PROVIDER_AUTH_FAILED | 502 | The upstream provider rejected our credentials. |
| PROVIDER_TIMEOUT | 504 | The upstream provider did not answer in time. Safe to retry. |
| PROVIDER_FAILED | 502 | A provider failed for another reason. |
| ALL_PROVIDERS_FAILED | 502 | Every provider we could try failed for this asset. You are not charged. |
| IDEMPOTENCY_CONFLICT | 409 | The same Idempotency-Key was reused with a different request body. |
| RATE_LIMITED | 429 | You exceeded a rate limit. Back off and retry. |
| INTERNAL_ERROR | 500 | Something failed on our side. Retry, then contact support with the requestId. |
A failed request that never produced a downloadUrl is charged $0.
Rate limits
Three limits apply at once; the tightest one that matches wins. Exceeding any of them returns RATE_LIMITED (429).
Per API key
60
requests / minute
Per user
300
requests / minute
Per IP address
600
requests / minute
On a 429, back off before retrying — reuse the same Idempotency-Key so the retry stays safe.