Skip to content
hello.new
Say hello →

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= or Authorization: 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; pass expires_in (seconds, 60 to 86400) for less. null on account links that never expire.
  • claim_url: anonymous links only. Give it to your user if they want to keep the link; see Accounts.
  • DELETE the 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:

  • /about serves about.html, or redirects to /about/ when there is an about/index.html.
  • A path with no file serves 404.html with a 404. Without a 404.html it serves index.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. views are people opening the page (the upload page or the shared page), visitors the distinct people among them, reads agents reading the link’s JSON or raw text, uploads and downloads files in and out, requests calls to a webhook. refused is anything turned away, such as a view after the link expired or a download past the limit. Crawlers and link previews count as bots, 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 timeseries and the top five countries. The other breakdowns are in locked: 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) and agents (reads by X-Hello-Client), each as { value, hits, visitors }, and events, 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 with interval: 24h, 7d, 30d (the default), 90d or all.
  • 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 has owner and name, expires_at is null unless you pass expires_in, and the link lives at hello.new/@sam/<name or id> (webhooks at api.hello.new/@sam/<name or id>). The account key also works in place of a link key for 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 get 401: “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 gets 413. 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; 409 if taken. Anonymous calls with a name get 403.
  • Changing a username. Only while no links or agent addresses use it. Otherwise 409: remove them first.
  • Claiming. POST /claim with { "id", "key", "name"? } and a signed-in session moves an anonymous link onto the account for good. It returns kind, id, url, name and expires_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 the claim_url page, 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.

FreePro
Live links1,000100,000
Storage1 GB100 GB
File size50 MB100 MB
Files per upload link10100
Requests kept per webhook10010,000
Size of a webhook request256 KB1 MB
Share size256 KB5 MB
Website size10 MB100 MB
Files per website1001,000
New links an hour6006,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.