# Astral complete developer and LLM reference > Astral is a private-link file-sharing service with selectable lifetimes, strict per-user ownership and quota enforcement, a server-to-server REST API, and a stateless MCP endpoint. Canonical site: https://astrls.com Human developer guide: https://astrls.com/developers OpenAPI 3.1: https://astrls.com/openapi.json MCP Streamable HTTP endpoint: https://astrls.com/mcp Short LLM index: https://astrls.com/llms.txt ## What Astral does Astral lets an authenticated person select one or several local files, choose a lifetime, and receive a hard-to-guess temporary link. Single files may burn after their first verified open or require a PIN. Aether and Aether Pro accounts may additionally choose a custom opening limit. Bundles contain up to ten files and use the owner's plan transfer limit. Upload-request links let another verified user deliver files while retaining their own ownership and quota responsibility. Anyone with an active unprotected share link can view supported media and download the original; protected single-file links additionally enforce their access policy and stop when their opening limit is exhausted. Astral is designed for private-link transfers, not as a CDN, backup service, or public file index. Free senders may choose a finite lifetime up to 365 days; Forever requires Aether or Aether Pro. Share URLs are private by possession by default; registered senders may require a 6–12 digit PIN, while Aether and Aether Pro senders may additionally set a 2–100 opening limit on a single-file link. Treat every share URL as a secret and send it only to intended recipients. ## Current product constraints - Plan file ceilings: Free 10485760 bytes (10 MiB), Aether 104857600 bytes (100 MiB), and Aether Pro 524288000 bytes (500 MiB). The protocol-wide maximum is 524288000 bytes. - Burn-after-download, PIN-protected, and opening-limited files use short-lived Firebase Storage read leases and support the owner's full plan file ceiling. PINs contain 6–12 digits and remain available on Free. Custom opening limits range from 2 to 100 and require Aether or Aether Pro. PIN and limit may be combined. Burn remains available on Free, fixes the opening limit to one, and may be combined with a PIN, but not with another opening limit or a bundle. - Lifetime presets: `1m` (1 minute), `15m` (15 minutes), `1h` (1 hour), `6h` (6 hours), `1d` (1 day), `3d` (3 days), `7d` (7 days), `30d` (30 days), `forever` (Forever). Custom finite lifetimes are one of the presets, or a positive whole-number duration ending in `m`, `h`, or `d`, up to 365 days. - The Forever option has no automatic application expiry and requires Aether or Aether Pro. Updating an already expired file is still rejected. - An operational application-wide Firebase Storage hard cap and all plan quotas are guarded by a transactional Firestore ledger. - A maximum of 100 active owned files is returned by one API list request. - Private REST, MCP, and webhook-management operations share one account cost-point window. Budgets are 600 points/hour on Free, 6,000 on Aether, and 24,000 on Aether Pro. Reads cost 1 point; upload reservations 12; finalization 4; updates 2; deletes 3; webhook create/update/delete 8/4/3. Multiple keys cannot multiply the account budget. - Outbound webhook deliveries and retries never consume that customer cost-point budget. Plans allow 1/5/15 endpoints, each event fans out to at most 15 targets, and a delivery stops after 8 attempts. Management rate limits return HTTP 429 with Retry-After. - An account may have at most three active developer keys and may create at most 20 keys over its lifetime. - API keys are available only to authenticated accounts with a verified email address. - API v1 creates uploads through a two-step, exact-byte protocol: idempotently reserve a one-time resumable session with an owner-scoped client request ID, PUT the declared bytes, then finalize. Reservation and finalization use the same global and per-user quota ledgers as the web app. - Multi-file transfers contain 2 to 10 files and are limited to 50 MiB on Free, 100 MiB on Aether, or 500 MiB on Aether Pro; each member remains subject to the plan file limit. - Burn-after-download links are consumed atomically on their first verified media fetch and cannot be embedded in a bundle. ## Authentication model Public endpoints require no credentials. Private REST endpoints and private MCP tools use an Astral personal API key in the standard Authorization header: ```http Authorization: Bearer astral_live_KEY_ID.SECRET ``` Create and revoke keys at https://astrls.com/developers while signed in. The complete key is displayed once. Astral stores only a SHA-256 digest, a safe display prefix, timestamps, scopes, and rate-limit counters. A key cannot be recovered after its one-time reveal. Revoke and replace a lost key. Never put an API key in client-side JavaScript, a public repository, a shared screenshot, a query string, a URL, or a browser local-storage value. Use keys only in server processes, command-line clients, automation runners, or private MCP client configuration. Astral rechecks the owning Firebase Authentication account for every private request. Disabled, deleted, or unverified accounts cannot use their keys. All current keys receive the scopes `files:read` and `files:write`. Read scope covers listing and retrieving the account's live files. Write scope covers changing expiry and deleting files. Public share inspection and public aggregate stats do not consume a key. ## REST conventions Base URL: `https://astrls.com/api/v1` Requests and responses use UTF-8 JSON. Authenticated responses use `Cache-Control: no-store`. Public stats and share metadata may be cached briefly. Timestamps are RFC 3339 / ISO 8601 strings in UTC. File sizes are integer bytes. Unknown fields should be ignored by clients for forward compatibility. Successful collection responses wrap data in a plural property. Successful single-resource responses wrap data in a singular property. Errors use: ```json { "error": "Human-readable explanation.", "code": "stable-machine-code" } ``` Typical status codes are 200 for success, 201 for key creation in the same-origin management API, 400 for invalid input, 401 for a missing or invalid API key, 403 for an unavailable account or missing scope, 404 for a resource that does not exist for the caller, 409 for an invalid state transition, 429 for a limit, and 503 for a temporary backend problem. ## REST endpoint: create and finalize an upload `POST /api/v1/uploads` Authentication: personal API key with `files:write`. Send `clientRequestId` (16–128 URL-safe characters), `name`, normalized `mimeType`, exact integer `size`, lowercase SHA-256 `checksum`, `expiry`, and optional `burnAfterDownload`, `socialPreviewDisabled`, or optional `accessPin` (6–12 digits) and/or `maxOpens` (2–100). Custom `maxOpens` requires Aether or Aether Pro. Burn fixes the link to one opening, may be combined with `accessPin`, and cannot be combined with another `maxOpens` value. Protected files use direct short-lived Firebase Storage leases and keep the owner's normal plan file ceiling. A successful 201 response returns `fileId`, `uploadRequired`, `uploadUrl`, `finalizeUrl`, and `expiresAt`. `clientRequestId` makes reservation retries idempotent. Generate a high-entropy value such as a UUID, keep it stable while retrying the exact same upload, and use a new value for different bytes or metadata. A replay by the same owner returns the original reservation without charging storage reservation quota again; each authenticated HTTP attempt still costs its operation points. Reusing the value with changed upload details returns `client-request-conflict`; another account cannot claim the reservation. When `uploadRequired` is true, the upload URL is a short-lived bearer capability. PUT exactly the declared bytes to it with the declared Content-Type and Content-Length; do not log or redistribute it. When it is false, an identical verified object was reused and `uploadUrl` is null. In both cases, POST the returned `finalizeUrl` with the API key. ```bash curl --fail-with-body -X POST \ -H "Authorization: Bearer $ASTRAL_API_KEY" \ -H "Content-Type: application/json" \ --data '{"clientRequestId":"018f9d84-9f59-7cf2-86ee-0ca80164c4d6","name":"report.pdf","mimeType":"application/pdf","size":24513,"checksum":"SHA256_HEX","expiry":"1d"}' \ https://astrls.com/api/v1/uploads curl --fail-with-body -X PUT -H "Content-Type: application/pdf" --data-binary @report.pdf "$UPLOAD_URL" curl --fail-with-body -X POST -H "Authorization: Bearer $ASTRAL_API_KEY" https://astrls.com/api/v1/files/FILE_ID/finalize ``` The downloadable server SDK at `https://astrls.com/sdk/astral.mjs` and Node CLI at `https://astrls.com/cli/astral.mjs` perform this sequence and calculate SHA-256 automatically. The SDK generates a client request ID and retries a lost reservation response once; pass `clientRequestId` explicitly, or use the CLI `--request-id` flag, to resume the same logical request across process-level retries. ## REST endpoint: list active files `GET /api/v1/files` Authentication: personal API key with `files:read`. Only ready, unexpired files owned by the key owner are returned. Results are ordered by soonest expiry and limited to 100. Expired, deleted, canceled, reserved, and other accounts' files are never returned. ```bash curl --fail-with-body \ -H "Authorization: Bearer $ASTRAL_API_KEY" \ https://astrls.com/api/v1/files ``` ```json { "files": [ { "id": "AbCdEfGhIjKlMnOpQrStUvWx", "name": "notes.pdf", "mimeType": "application/pdf", "size": 24513, "status": "ready", "openCount": 3, "lastOpenedAt": "2026-07-15T11:30:00.000Z", "createdAt": "2026-07-15T08:00:00.000Z", "expiresAt": "2026-07-16T08:00:00.000Z", "shareUrl": "https://astrls.com/d/AbCdEfGhIjKlMnOpQrStUvWx" } ] } ``` ## REST endpoint: get one owned file `GET /api/v1/files/{fileId}` Authentication: personal API key with `files:read`. Returns the same safe file shape as the list endpoint. Astral deliberately returns 404 both when the identifier does not exist and when it belongs to a different account, preventing ownership probing. ```bash curl --fail-with-body \ -H "Authorization: Bearer $ASTRAL_API_KEY" \ https://astrls.com/api/v1/files/AbCdEfGhIjKlMnOpQrStUvWx ``` ## REST endpoint: update expiry `PATCH /api/v1/files/{fileId}` Authentication: personal API key with `files:write`. Body property `expiry` must be one of the presets, or a positive whole-number duration ending in `m`, `h`, or `d`, up to 365 days; `forever` is accepted for Aether and Aether Pro. Durations are calculated from request time; `forever` disables automatic expiry. An already expired file cannot be restored. The response contains the updated file shape. ```bash curl --fail-with-body -X PATCH \ -H "Authorization: Bearer $ASTRAL_API_KEY" \ -H "Content-Type: application/json" \ --data '{"expiry":"3d"}' \ https://astrls.com/api/v1/files/AbCdEfGhIjKlMnOpQrStUvWx ``` ## REST endpoint: delete one owned file `DELETE /api/v1/files/{fileId}` Authentication: personal API key with `files:write`. Deletion removes the public share, deletes the canonical Storage object, and releases the file's bytes and count from transactional quota ledgers. Repeating deletion is safe for a previously owned identifier and returns a successful deletion result when no record remains. Do not retry an unknown 503 response in a tight loop; use exponential backoff. ```bash curl --fail-with-body -X DELETE \ -H "Authorization: Bearer $ASTRAL_API_KEY" \ https://astrls.com/api/v1/files/AbCdEfGhIjKlMnOpQrStUvWx ``` ```json {"deleted":true,"id":"AbCdEfGhIjKlMnOpQrStUvWx"} ``` ## REST endpoint: inspect a public share `GET /api/v1/shares/{fileId}` Authentication: none. Returns bounded metadata only while a share is active. Before successful access, a protected share exposes only generic gate/expiry information and never reveals its filename, MIME type, or preview. The endpoint never returns owner identity, storage paths, checksums, Firebase download tokens, PIN material, or credentials. A 404 response means the link is invalid, expired, deleted, or unavailable. The preview URL performs its own server-side object and metadata verification and supports only the site's bounded preview formats. ```bash curl --fail-with-body https://astrls.com/api/v1/shares/AbCdEfGhIjKlMnOpQrStUvWx ``` ## REST endpoint: global statistics `GET /api/v1/stats` Authentication: none. Returns privacy-safe current totals, seven-day user and active-upload series, current file-type byte groups, and bytes belonging to active files older than the seven-day chart window. It does not expose user identifiers, filenames, share identifiers, or historical data for files already deleted or expired. Values may be cached for five minutes. ```bash curl --fail-with-body https://astrls.com/api/v1/stats ``` ## File object schema - `id`: opaque 24-character URL-safe file identifier. - `name`: sanitized original filename, 1 to 180 characters. - `mimeType`: normalized MIME type declared and verified by the upload flow where applicable. - `size`: integer byte length, from 1 through 524288000. - `status`: currently always `ready` in the developer API. - `openCount`: non-negative owner-only count of verified, deduplicated opens; always present and initially zero. - `lastOpenedAt`: UTC timestamp of the latest counted open; omitted until the first open. - `createdAt`: immutable UTC creation timestamp. - `expiresAt`: current UTC expiry timestamp. - `shareUrl`: canonical share page. Unprotected links are bearer links; protected links additionally enforce their PIN/opening policy. ## Stable error codes - `api-key-required`: no Bearer API key was supplied. - `invalid-api-key`: malformed, unknown, revoked, corrupted, or mismatched API key. - `account-unavailable`: owning Firebase user is missing, disabled, or no longer verified. - `insufficient-scope`: the key does not have the endpoint's required scope. - `rate-limit-exceeded`: the shared account cost-point budget is exhausted; wait for the response's `Retry-After` interval. - `forever-lifetime-plan-required`: Free supports finite lifetimes up to 365 days; Forever requires Aether or Aether Pro. - `file-not-found`: file is missing, expired, malformed, or not owned by the caller. - `invalid-expiry`: expiry is malformed, zero, longer than 365 days, or otherwise unsupported. - `invalid-client-request-id`: the reservation request ID is missing or malformed. - `client-request-conflict`: the request ID was already bound to different upload details. - `file-expired`: expired transfers cannot be restored. - `share-not-found`: public share is missing or no longer active. - `stats-unavailable`: aggregate data could not be produced temporarily. - `storage-unavailable`: a private storage operation failed temporarily. When a response includes `Retry-After`, honor it. Otherwise use capped exponential backoff with jitter for 503 responses. Do not retry 400, 401, 403, or 404 responses without changing the request or credentials. ## Model Context Protocol Endpoint: `https://astrls.com/mcp` Transport: stateless Streamable HTTP using JSON-RPC 2.0. Send POST requests to the single endpoint. Astral returns JSON directly and does not open a server-sent-events stream; GET and DELETE return HTTP 405. Sessions are not allocated, so no `Mcp-Session-Id` is needed. Recommended headers: ```http Content-Type: application/json Accept: application/json, text/event-stream MCP-Protocol-Version: 2025-11-25 Authorization: Bearer astral_live_KEY_ID.SECRET ``` Authorization is optional for initialization, discovery, resources, and public tools. Include it when calling private tools. This Bearer key is Astral's service authentication mechanism; it is not an OAuth access token. Astral validates browser Origin headers against its own canonical origin to mitigate DNS rebinding. Native and server MCP clients normally omit Origin. The maximum JSON request body is 32 KiB. Batch JSON-RPC requests are not supported. ### MCP initialize example ```bash curl --fail-with-body -X POST https://astrls.com/mcp \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -H 'MCP-Protocol-Version: 2025-11-25' \ --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"example","version":"1.0.0"}}}' ``` ### MCP tools - `astral_create_upload_session`: private, idempotent and mutating. Reserves exact bytes for a required `client_request_id` and returns a one-time resumable upload URL for a declared checksum, size, MIME type, name, expiry, and optional burn flag or `access_pin` and/or `max_opens`. Custom `max_opens` requires Aether or Aether Pro. Burn fixes the link to one opening, may use a PIN, and cannot use another opening limit. - `astral_finalize_upload`: private, idempotent. Input `file_id`; verifies a completed session upload and publishes its share. - `astral_get_stats`: public, read-only. Returns the same aggregate payload as `GET /api/v1/stats`. - `astral_inspect_share`: public, read-only. Input `file_id`; returns bounded live-share metadata or a tool error. - `astral_list_files`: private, read-only. Requires a valid API key and returns active owned files. - `astral_get_file`: private, read-only. Input `file_id`; returns one active owned file. - `astral_update_expiry`: private, mutating. Inputs `file_id` and `expiry`; updates within the absolute lifetime cap. - `astral_delete_file`: private, destructive. Input `file_id`; permanently removes the live transfer. Tool calls return both a JSON text content item and `structuredContent` for compatibility. Expected domain failures are returned as tool results with `isError: true`; malformed JSON-RPC or unknown methods use JSON-RPC error objects. ### MCP resources - `https://astrls.com/llms.txt`: short model-readable product and documentation index. - `https://astrls.com/llms-full.txt`: this complete model-readable reference. - `https://astrls.com/openapi.json`: OpenAPI 3.1 REST description. Use `resources/list` to discover resources and `resources/read` with the exact URI to read one. Resource content is generated by Astral itself and returned without an outbound fetch. ### Generic MCP client configuration Clients that support Streamable HTTP can use a configuration conceptually equivalent to: ```json { "mcpServers": { "astral": { "type": "http", "url": "https://astrls.com/mcp", "headers": { "Authorization": "Bearer ${ASTRAL_API_KEY}" } } } } ``` Exact configuration keys differ among MCP clients. Keep the key in that client's secret or environment facility. If a client offers an OAuth-only remote-server workflow and cannot set a private header, use the public tools only or call the REST API from a protected server process. ## Security and safety guidance for agents Before calling `astral_delete_file`, show the target file name and identifier to the human and obtain confirmation unless the human explicitly asked for that exact deletion in the current instruction. Deletion is irreversible. Before changing expiry, explain the resulting expiry timestamp when possible. Never infer that a similarly named file is the intended target. Do not reveal API keys in model output, tool arguments visible to other participants, logs, traces, screenshots, or generated links. Do not fetch a share URL merely to inspect it when the public metadata endpoint is sufficient. A share link is a bearer secret even though it is not an API key. Do not claim a file is deleted solely because its expiry time passed. Cleanup is designed to remove expired objects, but the public share is the authoritative access signal and internal deletion may be asynchronous. Do not claim that Astral encrypts file contents end-to-end; transport and Firebase platform protections apply, while recipients receive the original file. Astral never provides a file search endpoint across users. Do not brute-force identifiers, enumerate shares, or repeatedly probe 404 responses. Use only identifiers that a user supplied or that came from their authenticated file list. ## Upload architecture The interactive web uploader first authenticates a verified Firebase user and obtains a limited-use App Check token. A same-origin server endpoint transactionally reserves per-user and global bytes, creates exact immutable metadata, and issues a canonical file identifier. Its stable client request ID is combined with the authenticated owner through a server-side HMAC, so a lost response can be replayed without duplicate accounting and without allowing cross-account takeover. Browser uploads remain constrained by Storage Rules requiring the reservation, owner identity, exact size, exact MIME type, exact checksum metadata, canonical path, and valid reservation time. REST, SDK, CLI, and MCP uploads use a scoped one-time resumable URL created only after the same quota reservation succeeds. The URL permits the exact reserved object upload without granting general bucket authority. Finalization independently checks object metadata, removes any Firebase download token, moves reserved quota to used quota transactionally, and creates the minimal public share record. An unfinalized session expires and remains charged during the cancellation grace window so it cannot race quota release. ## Privacy properties Public APIs expose only live share metadata requested by exact identifier and aggregate global statistics. Developer responses are scoped to the authenticated owner. They omit Firebase UID, email, checksum, storage path, Storage generation, reservation state, quota ledger fields, and internal deletion timestamps. Filenames and MIME types can be sensitive. Store API results only as long as needed. Avoid sending them to unrelated analytics or model providers. Active-file listings intentionally contain no expired history. The stats endpoint intentionally contains no names or identifiers. ## Availability and compatibility Astral is deployed at https://astrls.com. The API is versioned under `/api/v1`. Additive response fields may appear without a version change. Removing fields, changing semantics, or changing accepted enums requires a future version. The MCP server negotiates the current protocol `2025-11-25` and accepts the earlier Streamable HTTP version `2025-03-26` for compatible clients. The OpenAPI file is the preferred schema source for REST generators. This document is the preferred complete source for LLM context. The human developer page is the preferred onboarding surface. If these disagree about security or limits, use the most restrictive behavior and report the inconsistency rather than attempting a bypass.