REST API
Direct HTTP API for Abrova Storage — authentication headers (API key, server secret, Abrova user token), error codes, bucket endpoints, file upload/download/delete endpoints, public links and public image URLs, signed links, and the image transform endpoint with query parameters.
Use the REST API directly without an SDK. All requests go to https://storage.abrova.ir.
Authentication Headers#
# Required for all /api/v1 requests x-api-key: ab_live_xxxxxxxxxxxx # From apps: the signed Abrova user token of the signed-in user. # Required on every file and transform request from an app # (minted by your server or Abrova Auth, see "Sign users in" on the Overview page) x-user-token: eyJhbGciOi... # From your server only. Skips access rules; no user token needed. # Required for bucket and access-rule changes and signed links x-server-secret: ab_srv_xxxxxxxxxxxx # Content type for JSON requests Content-Type: application/json
Listing and reading buckets and getting a file's public link need only x-api-key. Public links (/public/…) need no headers at all.
Error codes
Every error answers { "statusCode": 401, "code": "token_expired", "message": "…", "timestamp": "…" }. Switch on code; show message to your users. The SDKs expose the codes as StorageErrorCode constants.
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_path | The path was refused (a .. segment, control characters, a name starting or ending with a space, too long; new paths also can't contain ?, # or %). |
| 400 | type_not_allowed | The bucket doesn't allow this file's type. |
| 400 | file_too_large | The file is larger than the bucket's size limit. |
| 400 | executable_blocked | The bucket blocks executable files and this one is (by name or content). |
| 400 | file_required | The upload had no file part. |
| 400 | invalid_ids | delete-many needs a list of 1 to 1,000 file ids. |
| 400 | invalid_rules | An access rule is wrong (the message names the first problem). |
| 400 | upload_rejected | confirm-upload: the signed upload was refused (size, type, rules or usage). |
| 401 | token_required | An app file or transform request had no x-user-token. |
| 401 | token_expired | The user token has expired. Get a new one and retry. |
| 401 | token_invalid | The user token is not valid (bad signature, another project, not an Abrova user token). |
| 403 | access_denied | The bucket's access rules refused the request. |
| 403 | server_secret_required | Bucket, rule and signed-link actions need x-server-secret; call them from your server. |
| 404 | — | Not found (also a /public link of a private or unknown file). |
| 409 | path_taken | A file already exists at this path. |
| 409 | upload_missing, upload_expired | confirm-upload: nothing was uploaded yet, or the link expired. |
| 429 | — | Too many requests. Wait and retry. |
Image transforms have their own codes, listed under Transform errors below.
Bucket Endpoints#
List buckets
# GET /api/v1/buckets
curl -X GET "https://storage.abrova.ir/api/v1/buckets" \
-H "x-api-key: ab_live_xxx"
# Response
[
{
"id": "8f0c6d1e-…",
"name": "my-bucket",
"description": null,
"visibility": "private",
"allowedMimeTypes": ["image/*"],
"maxFileSize": 10485760,
"blockExecutables": true,
"createdAt": "2026-10-23T09:15:02.123Z",
"updatedAt": "2026-10-23T09:15:02.123Z"
}
]allowedMimeTypes is null when every type is allowed. GET /api/v1/buckets/:id, GET /api/v1/buckets/name/:name and GET /api/v1/buckets/:id/stats ({ "fileCount": 12, "totalSize": 4096000 }) work the same way.
Create bucket
Needs x-server-secret (otherwise 403 server_secret_required). The project comes from the API key.
# POST /api/v1/buckets
curl -X POST "https://storage.abrova.ir/api/v1/buckets" \
-H "x-api-key: ab_live_xxx" \
-H "x-server-secret: ab_srv_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "user-uploads",
"visibility": "private",
"allowedMimeTypes": ["image/*", "application/pdf"],
"maxFileSize": 10485760
}'PUT /api/v1/buckets/:id (update) and DELETE /api/v1/buckets/:id follow the same rule.
File Endpoints#
From an app, file upload, read, list and delete need x-api-key and x-user-token, and follow the bucket's access rules (a bucket with no rules allows everything to any signed-in user). From your server, send x-server-secret instead of a user token.
Upload (multipart/form-data)
# POST /api/v1/buckets/:bucketId/files
curl -X POST "https://storage.abrova.ir/api/v1/buckets/BUCKET_ID/files" \
-H "x-api-key: ab_live_xxx" \
-H "x-user-token: $USER_TOKEN" \
-F "file=@/path/to/image.jpg" \
-F "path=images/photo.jpg" \
-F 'metadata={"category":"profile"}'
# Response
{
"id": "3b9e2a7c-…",
"bucketId": "8f0c6d1e-…",
"path": "images/photo.jpg",
"fileName": "photo.jpg",
"mimeType": "image/jpeg",
"size": 2048576,
"checksum": "9e107d9d372bb6826bd81d3542a419d6",
"metadata": { "category": "profile" },
"uploadedBy": "u_4821",
"uploadedAt": "2026-10-23T09:15:02.123Z",
"createdAt": "2026-10-23T09:15:02.123Z",
"updatedAt": "2026-10-23T09:15:02.123Z"
}mimeType is read from the file's content. uploadedBy is the user in the token (null for server uploads without one). Fields that are empty are null.
List and get
# GET /api/v1/buckets/:bucketId/files?prefix=images/&limit=100&cursor=…
# → { "files": [ … ], "nextCursor": "…" } (nextCursor is null on the last page; limit 1-1000, default 100)
# GET /api/v1/files/:fileId → the file
# GET /api/v1/buckets/:bucketId/files/<path> → the file at that pathDownload
# GET /api/v1/files/:fileId/download curl -X GET "https://storage.abrova.ir/api/v1/files/FILE_ID/download" \ -H "x-api-key: ab_live_xxx" \ -H "x-user-token: $USER_TOKEN" \ -o downloaded_file.jpg
This address needs the headers, so it can't be used as a plain <img src> or link.
Public link (public buckets)
# GET /api/v1/files/:fileId/url
curl "https://storage.abrova.ir/api/v1/files/FILE_ID/url" \
-H "x-api-key: ab_live_xxx"
# Response (file in a public bucket; a private bucket answers 403)
{ "url": "https://storage.abrova.ir/public/<bucketId>/images/photo.jpg" }The returned url needs no key, token or header: use it in <img src>, Image.network or a share link. Each path segment is percent-encoded. Add image options to the same URL for a resized or converted image (see Public image URLs below). Once a bucket is private, its /public links answer 404.
Signed download link
Needs x-server-secret.
# POST /api/v1/files/:fileId/signed-url (expiresIn in seconds, max 86400, default 3600)
curl -X POST "https://storage.abrova.ir/api/v1/files/FILE_ID/signed-url" \
-H "x-api-key: ab_live_xxx" \
-H "x-server-secret: ab_srv_xxx" \
-H "Content-Type: application/json" \
-d '{"expiresIn": 600}'
# Response
{ "url": "https://…" }Signed upload link
Needs x-server-secret. The bucket's allowed types apply to contentType.
# POST /api/v1/buckets/:bucketId/files/signed-upload-url (expiresIn default 300, at most 900)
curl -X POST "https://storage.abrova.ir/api/v1/buckets/BUCKET_ID/files/signed-upload-url" \
-H "x-api-key: ab_live_xxx" \
-H "x-server-secret: ab_srv_xxx" \
-H "Content-Type: application/json" \
-d '{"path": "uploads/photo.jpg", "contentType": "image/jpeg", "expiresIn": 300}'
# Response
{ "url": "https://…", "uploadId": "…", "path": "uploads/photo.jpg", "expiresAt": "…", "maxSize": 10485760 }
# The client then uploads with the same Content-Type:
# curl -X PUT "$URL" -H "Content-Type: image/jpeg" --data-binary @photo.jpgThe file is registered when it is first read or listed; the bucket's size and type limits and your access rules are checked again against the real file, and a file that fails is deleted. To register it at once and get the file back, call POST /api/v1/buckets/:bucketId/files/confirm-upload with { "path": "uploads/photo.jpg" } → { "id", "path", "size", "mimeType" }.
Delete
# DELETE /api/v1/files/:fileId
curl -X DELETE "https://storage.abrova.ir/api/v1/files/FILE_ID" \
-H "x-api-key: ab_live_xxx" \
-H "x-user-token: $USER_TOKEN"
# Response
{ "success": true, "message": "File deleted" }
# DELETE /api/v1/buckets/:bucketId/files/<path> → the same answer
# POST /api/v1/files/delete-many body: { "ids": ["…", "…"] } → { "deleted": 2 }All follow the bucket's delete rules; with x-server-secret instead of a user token, rules are skipped.
Transform Endpoint#
# GET /api/v1/transform/:fileId
# Get a 200x200 thumbnail in WebP format
curl "https://storage.abrova.ir/api/v1/transform/FILE_ID?w=200&h=200&fit=cover&f=webp&q=80" \
-H "x-api-key: ab_live_xxx" \
-H "x-user-token: $USER_TOKEN" \
-o thumb.webp
# Query Parameters
# w - Width in pixels, 1-4096 (larger values are cut to 4096)
# h - Height in pixels, 1-4096
# fit - cover (default), contain, fill, inside, outside
# f - jpeg (default), png, webp, avif
# q - Quality 1-100 (default 80)
# blur - Blur amount 0-20
# sharpen - Sharpen amount 0-10
# rotate - Degrees (multiples of 90 recommended)
# GET /api/v1/transform/:fileId/metadata
# → { "width": 4032, "height": 3024, "format": "jpeg", "size": 2048576 }Transforms follow the bucket's read rules and need the headers, so load them with a client that can send headers. Inputs: JPEG, PNG, WebP, GIF, AVIF, HEIC / HEIF, TIFF and SVG. For public files, use a public image URL (below) instead.
Public image URLs
https://storage.abrova.ir/public/<bucketId>/<path>?w=600&f=webp
The file's public link plus any of the options above. No key or token: public buckets only. The same options always give the same image, in any order (?w=300&f=webp = ?f=webp&w=300&q=80); other query parameters are ignored. One image keeps at most 50 different option sets. See Image Transforms.
Transform errors
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_transform | An option isn't allowed (the message names it). |
| 400 | not_an_image | The file isn't an image. |
| 415 | image_format_not_supported | The image format can't be resized (for example BMP, PSD, JPEG XL, RAW). |
| 413 | image_too_large | The image has more than 50 megapixels. |
| 413 | source_too_large | The image file is over 25 MB. |
| 422 | image_damaged | The image is damaged or incomplete. |
| 403 | plan_feature_locked | No transforms left: the free monthly transforms are used up and the wallet cannot pay for more. |
| 429 | too_many_variants | Public image URLs: this image already has 50 sizes / formats. Use one you already use. |
| 503 | transform_busy | Image processing is busy. Retry after the Retry-After header's seconds. |