API reference
Four anonymous calls that give an agent a temporary upload link, a shared page, a webhook URL or a website, with analytics on every link. No account, no API key, gone in 24 hours.
Last updated 21 October 2026
For agents
Start with the skill or the llms.txt index. This reference is also plain Markdown, the whole API is described at api.hello.new/openapi.json, and capabilities are listed at /.well-known/agent.json.
Agents in ChatGPT, Codex, Claude Code, Cursor or VS Code can use the same links as tools through the MCP server.
The shape of every call
The API base is https://api.hello.new. Requests and responses are JSON (a site may also arrive as an archive). There is no authentication to create a resource. Each resource comes back with:
url: the address you hand to a person or a service.key: a secret for reading the resource back. It is shown once. Send it as?key=orAuthorization: Bearer. Only this link key goes in?key=; account keys and session tokens go in the header.expires_at: when everything about the resource is deleted. Default 24 hours; passexpires_in(seconds, 60 to 86400) for less.nullon account links that never expire.claim_url: anonymous links only. Give it to your user if they want to keep the link; see Accounts.DELETEthe resource with the key when you are done, and it goes early.
Errors are { "error": "a sentence" } with the usual status: 401 wrong key, 403 needs an account or the account is full, 404 unknown or expired, 409 full or name taken, 413 too large, 422 bad input, 429 slow down.
Send X-Hello-Client: <your agent> (for example claude-code, codex, cursor, openclaw, chatgpt). It is optional, and it is the only telemetry there is.
Upload
A link a person opens to send files to your agent.
curl -X POST https://api.hello.new/upload \
-H 'Content-Type: application/json' \
-H 'X-Hello-Client: claude-code' \
-d '{"note": "Send me the logs from last night", "max_files": 3}'
{
"kind": "upload",
"id": "k7m2x9pq",
"url": "https://hello.new/u/k7m2x9pq",
"key": "…",
"files_url": "https://api.hello.new/u/k7m2x9pq/files?key=…",
"note": "Send me the logs from last night",
"max_files": 3,
"max_bytes": 52428800,
"created_at": "2026-09-23T09:14:02Z",
"expires_at": "2026-09-24T09:14:02Z"
}
Body fields, all optional: note (up to 280 characters, shown on the page), max_files (1 to 10, default 10), expires_in.
Give url to the person. When they have uploaded, list the files with the key:
curl 'https://api.hello.new/u/k7m2x9pq/files?key=…'
{
"kind": "upload",
"id": "k7m2x9pq",
"note": "Send me the logs from last night",
"expires_at": "2026-09-24T09:14:02Z",
"files": [
{
"seq": 1,
"name": "app.log",
"size": 48213,
"content_type": "text/plain",
"received_at": "2026-09-23T09:20:41Z",
"url": "https://hellouploads.com/u/k7m2x9pq/files/1?key=…"
}
]
}
Download a file from its url; the response carries the stored content type and a Content-Disposition with the original name. Uploaded files are served from hellouploads.com, a domain of their own that serves nothing else. Asking api.hello.new for one redirects there. If you listed the files with an account key instead of the link key, the file URLs carry no key: send the same Authorization header to download. Poll the list every few seconds while you wait, or ask the person to tell you when they are done.
You can also upload without the page. Anyone with the id can PUT a raw body:
curl -X PUT 'https://api.hello.new/u/k7m2x9pq/files/app.log' \
-H 'Content-Type: text/plain' --data-binary @app.log
GET /u/{id} without a key returns the note, limits and how many files have arrived. That is what the page shows. DELETE /u/{id}?key=… removes everything.
Limits: 50 MB a file, 10 files a link.
Share
A page that shows text or Markdown your agent produced.
curl -X POST https://api.hello.new/share \
-H 'Content-Type: application/json' \
-d '{"title": "Q3 summary", "text": "# Q3\n\nRevenue up 12%…"}'
{
"kind": "share",
"id": "b4nq8wzx",
"url": "https://hello.new/s/b4nq8wzx",
"raw_url": "https://hellouploads.com/s/b4nq8wzx/raw",
"key": "…",
"title": "Q3 summary",
"content_type": "text/markdown",
"created_at": "2026-09-23T09:14:02Z",
"expires_at": "2026-09-24T09:14:02Z"
}
Body fields: text (required), title (up to 120 characters), content_type (text/markdown, the default, or text/plain), expires_in.
Anyone with the link can read it. GET /s/{id} returns the JSON above with the text included; raw_url returns the text itself with its content type, which is what another agent should fetch. Like uploaded files, it is served from hellouploads.com, never hello.new. DELETE /s/{id}?key=… removes it.
Limit: 256 KB of text.
Webhook
A URL that accepts any HTTP request and keeps it for you to read.
curl -X POST https://api.hello.new/webhook \
-H 'Content-Type: application/json' \
-d '{"note": "Stripe test events"}'
{
"kind": "webhook",
"id": "f3gd7hxm",
"url": "https://api.hello.new/w/f3gd7hxm",
"key": "…",
"inbox_url": "https://api.hello.new/webhook/f3gd7hxm?key=…",
"note": "Stripe test events",
"max_requests": 100,
"max_bytes": 262144,
"created_at": "2026-09-23T09:14:02Z",
"expires_at": "2026-09-24T09:14:02Z"
}
Give url to the service. Every method is accepted, on /w/{id} and any path beneath it (/w/{id}/stripe, /w/{id}/github/push). The sender gets 200 { "ok": true, "seq": 1 }.
Read what arrived:
curl 'https://api.hello.new/webhook/f3gd7hxm?key=…&since=0&wait=25'
{
"kind": "webhook",
"id": "f3gd7hxm",
"note": "Stripe test events",
"expires_at": "2026-09-24T09:14:02Z",
"requests": [
{
"seq": 1,
"received_at": "2026-09-23T09:31:07Z",
"method": "POST",
"path": "stripe",
"query": "",
"headers": { "content-type": "application/json", "stripe-signature": "t=…" },
"content_type": "application/json",
"body": "{\"type\":\"payment_intent.succeeded\"}"
}
]
}
Headers come back as sent, except cookie, host and proxy headers (cf-*, x-forwarded-* and the like), which are not kept. since returns only requests with a larger seq, so keep the last one you saw. wait (up to 25 seconds) holds the request open until something new arrives, then returns it; an empty requests list after the wait means nothing came, not an error. Loop on that to wait for a callback. Bodies that are not UTF-8 arrive as body_base64 instead of body. DELETE /webhook/{id}?key=… removes it.
Limits: 100 requests kept, 256 KB each. The sender is not rate limited; the cap is.
Site
A website your agent built, at an address of its own. Static files only: HTML, CSS, JavaScript, images and fonts.
curl -X POST https://api.hello.new/site \
-H 'Content-Type: application/json' \
-d '{"title": "Bakery", "files": {"index.html": "<!doctype html><title>Bakery</title><link rel=stylesheet href=style.css><h1>Fresh bread</h1>", "style.css": "h1 { color: tomato }"}}'
{
"kind": "site",
"id": "k7m2x9pq",
"url": "https://k7m2x9pq.hellonew.app",
"key": "…",
"title": "Bakery",
"files": 2,
"bytes": 112,
"protected": false,
"created_at": "2026-10-03T09:14:02Z",
"expires_at": "2026-10-04T09:14:02Z"
}
files maps each path to its contents: text as a string, anything binary as {"base64": "…"}. index.html is the home page. The other body fields are title (up to 120 characters), expires_in, and on an account name and password (below).
A built app, such as a React or Vite project, goes up as its build folder in one archive:
npm run build
tar -cz -C dist . | curl -X POST 'https://api.hello.new/site?title=Bakery' --data-binary @-
Send a .tar or .tar.gz, with title, expires_in and name in the query string. Directories, links, .DS_Store and ._ files are left out. Next.js works with output: 'export'; anything that needs a server (server rendering, API routes, server actions) does not.
Paths are served the way Cloudflare Pages serves them:
/aboutservesabout.html, or redirects to/about/when there is anabout/index.html.- A path with no file serves
404.htmlwith a 404. Without a404.htmlit servesindex.html, so a single-page app’s routes survive a reload. - Every site has a subdomain to itself, so links to
/assets/…from the root work as built.
PUT /site/{id}?key=… with the same kind of body publishes again at the same address: the files you send replace every old one. GET /site/{id} describes the site. DELETE /site/{id}?key=… removes it. On an account, a name also gives the site hello.new/@you/{name}, which sends visitors on to it.
Everything in a site is public, including any key built into its JavaScript; with a password, it is open to everyone who has the password. To collect what visitors submit, post it to a webhook: browser code may call one directly, and your agent reads the entries. Sites made without an account carry a small “Hosted by hello.new” badge with a report link, ask search engines not to index them, and cannot contain a password field. Keep one with its claim_url, or publish with an account key, and it is served exactly as published.
A site on an account can ask for a password. Add password (4 to 128 characters; spaces at either end are dropped) to the JSON body, never to the URL:
curl -X POST https://api.hello.new/site \
-H 'Authorization: Bearer hk_…' \
-H 'Content-Type: application/json' \
-d '{"title": "Q3 plan", "expires_in": 86400, "password": "plum orbit 42", "files": {"index.html": "…"}}'
The response says "protected": true, and so does GET /site/{id}. Every address of the site then answers with hello.new’s own password page, and none of the site’s files, until the visitor enters the password. After that their browser stays in for 24 hours, or until the site expires if that is sooner. A visitor gets 10 tries every 15 minutes. Send the password to people separately from the link. A protected site is never cached or indexed.
PUT /site/{id} keeps the password unless its JSON body has one: a new password replaces it, and everyone who got in with the old one has to enter the new one; "password": null removes it. Anonymous sites can’t have a password.
Limits: 10 MB and 100 files a site, 25 MB a file. A JSON body can be up to 20 MB; send bigger sites as an archive.
Analytics
Every create response has an analytics_url: GET /analytics/{id}?key=… (or the owning account’s Authorization header) says what happened to the link, so you can tell your user. It also has a stats_url, https://hello.new/stats#id=…&key=…: the same numbers as a page with a chart, for your user to open themselves. The key rides in the URL fragment, which browsers never send to a server.
curl -s 'https://api.hello.new/analytics/b4nq8wzx?key=…'
{
"id": "b4nq8wzx",
"kind": "share",
"full": false,
"interval": "24h",
"unit": "hour",
"totals": { "views": 12, "visitors": 7, "reads": 2, "uploads": 0, "downloads": 0, "requests": 0,
"refused": 1, "bots": 3, "countries": 3, "bytes": 0 },
"timeseries": [ { "start": "2026-09-29T08:00:00.000Z", "views": 4, "visitors": 3, "reads": 0, "…": 0 } ],
"countries": [ { "value": "AU", "hits": 8, "visitors": 5 } ],
"locked": ["regions", "cities", "continents", "devices", "browsers", "os", "referrers", "…", "events"],
"claim_url": "https://hello.new/claim#id=b4nq8wzx&key=…"
}
- What counts.
viewsare people opening the page (the upload page or the shared page),visitorsthe distinct people among them,readsagents reading the link’s JSON or raw text,uploadsanddownloadsfiles in and out,requestscalls to a webhook.refusedis anything turned away, such as a view after the link expired or a download past the limit. Crawlers and link previews count asbots, not views. - The response says which link it is (
url,title,note,owner,name,created_at,expires_at) as well as what happened to it. - Anonymous links show their whole 24 hours as totals, an hourly
timeseriesand the top fivecountries. The other breakdowns are inlocked: every request is recorded in full from the start, and claiming the link (claim_url) opens all of it. - Account links add
regions,cities,continents,devices,browsers,os,referrers,utm_sources,utm_mediums,utm_campaigns,languages,networks(the visitor’s ISP or host) andagents(reads byX-Hello-Client), each as{ value, hits, visitors }, andevents, the latest 100 requests with their time, place, device, referrer and status (never an IP address, headers or anything else that singles a person out). Pick the window withinterval:24h,7d,30d(the default),90dorall. - Where visitors came from. A link’s own page passes on its referrer and any
utm_*tags, so add them to the URL you hand out (https://hello.new/s/b4nq8wzx?utm_source=newsletter) to tell your channels apart. - How long. An anonymous link’s analytics go with it after 24 hours; claim it to keep both.
Accounts
Anonymous links are the default and need nothing. An account adds a username and links that stay.
- Username. Claim one on the front page. It becomes the prefix of every account link:
hello.new/@sam/…. - Account key. Make one on the front page after claiming, and give it to your agent. Send it as
Authorization: Bearer hk_…on any create call. The response then hasownerandname,expires_atisnullunless you passexpires_in, and the link lives athello.new/@sam/<name or id>(webhooks atapi.hello.new/@sam/<name or id>). The account key also works in place of a linkkeyfor that user’s links, as a Bearer header only. - Header only. Account keys and session tokens go in
Authorization: Bearer …, never in the URL. In?key=they get401: “Only a link key goes in ?key=. Send account keys and session tokens in the Authorization header: Authorization: Bearer.” File lists read with an account credential return file URLs without a key. - Quota. An account keeps at most 1000 live links and 1 GB of stored files, share text and webhook bodies, or 100,000 and 100 GB on Pro. A new link past the limit gets
403; anything that would pass the storage limit gets413. Delete links to make room. name. Optional on account links: 2 to 30 lowercase letters, numbers and dashes. Unique per user across links and agent addresses;409if taken. Anonymous calls with anameget403.- Changing a username. Only while no links or agent addresses use it. Otherwise
409: remove them first. - Claiming.
POST /claimwith{ "id", "key", "name"? }and a signed-in session moves an anonymous link onto the account for good. It returnskind,id,url,nameandexpires_at(null). The link keeps its key, so the agent that made it can still read it, and the old address keeps working. Claiming counts against the account quota. People do this from theclaim_urlpage, which keeps the key out of server logs by carrying it in the URL fragment. - Lookup.
GET /at/{username}/{name}says what lives at an address without counting as a webhook call.
A link only ever resolves under the username that owns it.
Plans
Every account is on the free plan until it pays for Pro. Anonymous links get the free plan’s limits per link.
| Free | Pro | |
|---|---|---|
| Live links | 1,000 | 100,000 |
| Storage | 1 GB | 100 GB |
| File size | 50 MB | 100 MB |
| Files per upload link | 10 | 100 |
| Requests kept per webhook | 100 | 10,000 |
| Size of a webhook request | 256 KB | 1 MB |
| Share size | 256 KB | 5 MB |
| Website size | 10 MB | 100 MB |
| Files per website | 100 | 1,000 |
| New links an hour | 600 | 6,000 |
An agent with an account key can POST /billing/checkout, optionally with {"interval": "year"}, to get its owner an upgrade link: {"url": "https://checkout.stripe.com/…"}.
Rate limits
Creating links: 60 an hour per IP address, or 600 an hour per account (6,000 on Pro). Uploading files: 60 an hour per IP address. Publishing a site again: 60 times an hour per site, or 600 on an account (6,000 on Pro). Passwords on a protected site: 10 tries every 15 minutes per site and IP address. An IPv6 address counts as its whole /64. Over that, 429 with a sentence. After 30 failed sign-ins with a bad token in an hour, an IP address gets 429 too. Receiving webhook requests and reading resources are not limited.
JSON request bodies are limited to 64 KB (413), except POST /share, which takes a share’s full text, and POST /site and PUT /site/{id}, which take 20 MB.
What is kept, and for how long
Everything is deleted at expires_at, or earlier when you DELETE it. Account links have no expires_at and stay until deleted. The server stores the resource, its files, the IP address that created it, the X-Hello-Client value, and for account links the owning account. A protected site keeps a salted hash of its password, never the password. Nothing is indexed.
For analytics, each request to a link is recorded for 400 days: the time, what it did and its status, the approximate location and network, the browser, device and language, the referring site and utm_* tags, and the request headers without cookies or credentials. These records hold no content (no file names, text or bodies) and never the link key or anything named like a secret. The caller’s IP address is kept too, except for visitors in the EU, EEA, UK and Switzerland, whose postcode and coordinates are dropped as well; GET /analytics never returns IP addresses or headers. Read the privacy notice for the rest.
Agent addresses
hello.new also gives assistants an address, @user/handle, with a web chat and an MCP endpoint. That API is documented separately at /docs/addresses.