Prefer a browser? The file uploader does the same thing without a terminal: drop a file, paste text, or paste a screenshot, and it hands you the shareable URL.
Store a JSON body
curl -X POST https://aisenseapi.com/services/v1/storage \
-H "Content-Type: application/json" \
-d '{"result":42,"status":"complete"}'{
"storage_id": "7bf4be7d-4386-49fd-99bb-6f2fe65f8a14",
"storage_url": "https://aisenseapi.com/services/v1/storage/7bf4be7d-4386-49fd-99bb-6f2fe65f8a14",
"sha256_hash": "111c1259e99d49608b5f394cab010b8214525bacdd3af5dede0870b1bac4b931",
"bytes": 33,
"expire_timestamp": 1787266276,
"expire_datetime": "2026-08-20T22:51:16+00:00"
}The identifier above came from one real call and has since expired - yours differs on every request. Six fields come back. storage_url is the link to hand on, and sha256_hash is the digest of the 33 bytes as stored, so whoever fetches them can tell they got what you sent. Keep the storage_id, because it is the only route back to the object. The service cannot look an ID up for you afterwards, and there is no listing call.
Store for a set number of downloads
Add max_downloads/{n} to the path and the object is removed after that many downloads, 1 to 100.
curl -X POST https://aisenseapi.com/services/v1/storage/max_downloads/2 \
-H "Content-Type: application/json" \
-d '{"report":"q3","pages":12}'{
"storage_id": "5e1c0a7e-9b3f-4c62-8a1d-2f7b6d4c9e10",
"storage_url": "https://aisenseapi.com/services/v1/storage/5e1c0a7e-9b3f-4c62-8a1d-2f7b6d4c9e10",
"sha256_hash": "700d8cea137c32c31e4865aaf5ae327931067ae454bc78777f8572dadf9c9809",
"bytes": 26,
"expire_timestamp": 1791409876,
"expire_datetime": "2026-10-07T21:51:16+00:00",
"downloads_max": 2,
"downloads_left": 2
}Two fields join the six. Every GET that serves the bytes takes one download and says how many remain in a Downloads-Left header. A 304 or a 412 takes none, since no bytes went out. Once the last download has gone out the bytes are removed, and the id answers HTTP 410 for the rest of its 24 hours.
{"error":"Storage object gone"}max_downloads/0, or no segment at all, is the plain 24-hour object. Any other value outside 1 to 100 answers HTTP 400. max_downloads/1 makes a link that works exactly once: hand it on, and the first fetch is the only fetch.
Retrieve the stored body
STORAGE_ID="the storage_id from the response above"
curl "https://aisenseapi.com/services/v1/storage/$STORAGE_ID"
{"result":42,"status":"complete"}That is the whole read path. No header, no token, no query string. The answer carries an ETag, which is sha256_hash in quotes, so a second fetch that sends If-None-Match with that value is answered 304 Not Modified with no body. An unknown or expired UUID answers with HTTP 404 and a short error object.
{"error":"Storage id unknown"}An object stored with a download limit whose downloads are all taken answers HTTP 410 with {"error":"Storage object gone"} instead, and stays that way until its 24 hours are over.
A link that checks itself
Put the digest in the link and the bytes come back only if they hash to it.
curl "https://aisenseapi.com/services/v1/storage/$STORAGE_ID/sha256/111c1259e99d49608b5f394cab010b8214525bacdd3af5dede0870b1bac4b931"If they do not, nothing is served and the answer is 412 Precondition Failed with both digests, so the receiver gets a hard failure instead of content it did not ask for.
{"error":"Stored bytes do not match the sha256 in the link","storage_id":"...","expected":"...","sha256_hash":"..."}A digest that is not 64 hexadecimal characters, or any algorithm other than sha256, answers 400. A client that can set a header may send If-Match with the same value in quotes instead, which is the standard spelling of the same condition.
This is a guard against a wrong id or the wrong object, not a proof. The service computes the comparison, so a caller that needs proof must hash the bytes itself. It is not access control either: the link without the digest still works.
What the free storage API endpoint stores
The request body is stored verbatim. Nothing is wrapped around it and nothing is stripped from it. POST {"result":42} and the GET response is exactly {"result":42}.
Plain text behaves the same way. Send build 91 passed and those fifteen bytes come back untouched, served as application/octet-stream. Content that parses as JSON is served as application/json instead. One rule, two content types, no surprises.
That predictability is the point. Two programs that already agree on a format can pass data through this service without either side learning a new envelope.
Response fields
| Field | Type | Description |
|---|---|---|
| storage_id | string | The UUID used in the GET path. Treat it as the retrieval key. |
| storage_url | string | The full link the object is fetched from. The one to hand on. |
| sha256_hash | string | The SHA-256 of the bytes as stored, and the value of the ETag the GET carries. |
| bytes | integer | How many bytes were stored. |
| expire_timestamp | integer | Unix time when the object disappears, 24 hours after the POST. |
| expire_datetime | string | The same moment as an ISO 8601 timestamp in UTC. |
| downloads_max | integer | Only with max_downloads: how many downloads the object was given. |
| downloads_left | integer | Only with max_downloads: how many remain, which at creation is all of them. Each download answers the current value in a Downloads-Left header. |
How a hand-off works
- The producer posts
A job, script or agent sends its output to
/storageand receives a UUID in return. - The UUID travels
A UUID is small. It fits inside a webhook payload, a queue message, a database column or a chat reply, where a multi-megabyte body never would.
- The consumer reads
The next step calls
GET /storage/{storage_id}and receives the original bytes. Nothing needs to be shared between the two machines except that one string.
Where the PDF renderer puts its output
The HTML to PDF API endpoint is the clearest example of this pattern in use. It renders your markup, writes the finished PDF into storage and returns the storage ID rather than the file. You then download the document with a plain GET, at whatever moment suits you.
So the free storage API endpoint is worth understanding even if you never call POST yourself. It is already the delivery mechanism behind another service on this site.
Common uses
Most callers reach the free storage API endpoint from a pipeline rather than a browser. Four patterns come up again and again.
Agent hand-off
One agent stores a long result and passes a short UUID to the next.
Pipeline state
Move structured output between systems with no shared disk.
Oversized callbacks
Send a UUID in the callback and leave the payload here.
Reproducible bugs
Park a failing input so a colleague fetches the exact bytes.
Read once
Store with max_downloads/1 and the link works exactly once, then answers 410.
Expiry and appropriate use
This is not a backup service. Objects become unavailable 24 hours after the POST, or sooner when a download limit was set, and there is no durability or service-level guarantee.
Anyone holding the UUID can read the object. Do not park secrets or regulated data here. Work that needs accounts, access policies or auditable deletion belongs in real storage.
The base URL is https://aisenseapi.com/services/v1. No key and no account are required. Every service on the free public REST APIs hub shares one limit of 5000 requests per IP per day, so a storage POST and a storage GET both count against the same budget.
Two limits apply to the store itself. Each address may store 80 MB per day; past that a POST answers HTTP 429 until the counter resets at midnight Norwegian time (Europe/Oslo). Executable files, meaning Windows, Linux and Mac programs judged on their first bytes, are refused with HTTP 415 and never stored. A stored file is shown in the browser only when it is an image, audio, video or PDF; everything else, SVG included, is served as a download. Content reported to abuse@aisense.no as unlawful or abusive is removed when the notice arrives.