Overview
here.now is free, instant web hosting for agents.
Publish any file or folder and get a live URL at <slug>.here.now.
- Static hosting only: HTML, CSS, JS, images, PDFs, videos, and other files.
- No account required for anonymous sites (24 hour expiry).
- Use an API key for permanent sites and higher limits.
Install here.now
Install the here.now skill so your agent publish automatically:
npx skills add heredotnow/skill --skill here-now -gFor repo-pinned/project-local installs, run the Skills command without -g.
If that fails, try this fallback installer:
curl -fsSL https://here.now/install.sh | bashOnce installed, agents can publish with a single script call. See the skill repo for details.
Quick start
Publish a site in three steps. No account needed:
1. Create site
curl -sS https://here.now/api/v1/publish \
-H "X-HereNow-Client: cursor/direct-api" \
-H "content-type: application/json" \
-d '{
"files": [
{ "path": "index.html", "size": 1234, "contentType": "text/html; charset=utf-8" }
]
}'2. Upload file
curl -X PUT "<upload.uploads[0].url>" \
-H "Content-Type: text/html; charset=utf-8" \
--data-binary @index.html3. Finalize
curl -sS -X POST "<finalizeUrl>" \
-H "content-type: application/json" \
-d '{ "versionId": "<versionId>" }'The response to step 1 includes the live siteUrl, presigned upload URLs, and a finalizeUrl. Anonymous sites expire in 24 hours. Only anonymous sites include claimUrl; share it with the user so they can keep the site permanently.
Authentication
Two modes:
- Authenticated: include
Authorization: Bearer <API_KEY>. Get your key via agent code verification or from the dashboard after web sign-in. - Anonymous: omit the header. Sites expire in 24 hours with lower limits.
- Optional attribution: include
X-HereNow-Client: <agent>/<tool>(example:cursor/publish-sh) so here.now can debug reliability by client. Missing or invalid values are ignored.
Getting an API key
There are two ways to get an API key:
Option A: Agent-assisted sign-up. The sign-up flow can be completed entirely within the agent. Request a one-time code by email:
curl -sS https://here.now/api/auth/agent/request-code \
-H "content-type: application/json" \
-d '{"email": "user@example.com"}'The user copies the code from email and pastes it into the agent. Then verify the code to receive the API key:
curl -sS https://here.now/api/auth/agent/verify-code \
-H "content-type: application/json" \
-d '{"email":"user@example.com","code":"ABCD-2345"}'If the email is new, the account is created automatically.
Option B: Dashboard sign-up. Sign in at here.now and copy the API key from the dashboard.
Storing the API key
After obtaining a key (via either method), save it to the credentials file:
mkdir -p ~/.herenow && echo "<API_KEY>" > ~/.herenow/credentials && chmod 600 ~/.herenow/credentialsThe publish script reads the key from these sources (first match wins):
--api-keyflag (CI/scripting only — avoid in interactive use)$HERENOW_API_KEYenvironment variable~/.herenow/credentialsfile (recommended)
Create a site
POST /api/v1/publish (alias: POST /api/v1/artifact)
Request body:
{
"files": [
{ "path": "index.html", "size": 1234, "contentType": "text/html; charset=utf-8", "hash": "a1b2c3d4..." },
{ "path": "assets/app.js", "size": 999, "contentType": "text/javascript; charset=utf-8", "hash": "e5f6a7b8..." }
],
"ttlSeconds": null,
"viewer": {
"title": "My site",
"description": "Published by an agent",
"ogImagePath": "assets/cover.png"
}
}files(required): array of{ path, size, contentType, hash }. Paths should be relative to the site root (e.g.index.html,assets/style.css) — don't include a parent directory name likemy-project/index.html.hash(optional): SHA-256 hex digest (64 lowercase chars). When updating, files whose hash matches the previous version are skipped fromupload.uploads[]and listed inupload.skipped[]. The server copies them at finalize.ttlSeconds(optional): expiry in seconds. Ignored for anonymous sites.viewer(optional): metadata for auto-viewer pages (only applies when noindex.html).
Response (authenticated):
{
"slug": "bright-canvas-a7k2",
"siteUrl": "https://bright-canvas-a7k2.here.now/",
"upload": {
"versionId": "01J...",
"uploads": [
{
"path": "index.html",
"method": "PUT",
"url": "https://<presigned-url>",
"headers": { "Content-Type": "text/html; charset=utf-8" }
}
],
"skipped": ["assets/app.js"],
"finalizeUrl": "https://here.now/api/v1/publish/bright-canvas-a7k2/finalize",
"expiresInSeconds": 3600
}
}Anonymous responses also include:
{
"claimToken": "abc123...",
"claimUrl": "https://here.now/claim?slug=bright-canvas-a7k2&token=abc123...",
"expiresAt": "2026-02-19T01:00:00.000Z",
"anonymous": true,
"warning": "IMPORTANT: Save the claimToken and claimUrl. They are returned only once and cannot be recovered. Share the claimUrl with the user so they can keep the site permanently."
}IMPORTANT: The claimToken and claimUrl are returned only once and cannot be recovered. Always save the claimToken and share the claimUrl with the user so they can claim the site and keep it permanently. If you lose the claim token, the site will expire in 24 hours with no way to save it.
claimToken, claimUrl, and expiresAt are only present for anonymous sites. Authenticated sites do not include these fields.
Upload files
For each entry in upload.uploads[], PUT the file to the presigned URL:
curl -X PUT "<presigned-url>" \
-H "Content-Type: <content-type>" \
--data-binary @<local-file>Uploads can run in parallel. Presigned URLs are valid for 1 hour.
Finalize
POST /api/v1/publish/:slug/finalize (alias: POST /api/v1/artifact/:slug/finalize)
{ "versionId": "01J..." }Owned sites require Authorization: Bearer. Anonymous sites can finalize without auth.
Response:
{
"success": true,
"slug": "bright-canvas-a7k2",
"siteUrl": "https://bright-canvas-a7k2.here.now/",
"previousVersionId": null,
"currentVersionId": "01J..."
}Update an existing site
PUT /api/v1/publish/:slug (alias: PUT /api/v1/artifact/:slug)
Same request body as create. Returns new presigned upload URLs and a new finalizeUrl.
- Owned sites: requires
Authorization: Bearer <API_KEY>. - Anonymous sites: include
claimTokenin the request body. Updates do not extend the expiration. - Incremental deploys: include
hash(SHA-256 hex) on each file. Files whose hash matches the previous version appear inupload.skipped[]instead ofupload.uploads[]— no upload needed. The server copies them at finalize.
Claim an anonymous site
POST /api/v1/publish/:slug/claim (alias: POST /api/v1/artifact/:slug/claim)
Requires Authorization: Bearer <API_KEY>.
{ "claimToken": "abc123..." }Transfers ownership, removes the expiration. Users can also claim by visiting the claimUrl and signing in.
Password protection
Add a password to any site so visitors must authenticate before viewing. This is server-side enforcement — content is never sent to the browser until the password is verified. All files under the site are protected, not just the index page.
Set or change a password via PATCH /api/v1/publish/:slug/metadata with {"password": "secret"}. Remove it with {"password": null}. You can also manage passwords from the dashboard via the ⋯ menu on each site.
Password protection survives redeploys — it's metadata, not content. Changing or removing a password immediately invalidates all existing sessions. Requires an authenticated site (anonymous sites cannot be password-protected).
Payment gating and password protection are mutually exclusive. Setting a price removes the password, and setting a password removes the price. Both are also mutually exclusive with forkable — disable forkable before adding a gate.
Payment gating
Require visitors to pay with stablecoins on the Tempo network before accessing your site. Payments go directly from the visitor's wallet to yours.
Setup: set your Tempo wallet address via PATCH /api/v1/wallet, then set a price on any site via PATCH /api/v1/publish/:slug/metadata with a price field.
// Set wallet
PATCH /api/v1/wallet
{ "address": "0xYOUR_TEMPO_ADDRESS" }
// Set price on a site
PATCH /api/v1/publish/:slug/metadata
{ "price": { "amount": "0.50", "currency": "USD" } }
// Remove price
PATCH /api/v1/publish/:slug/metadata
{ "price": null }Visitors see a payment page with a QR code and deposit address. After paying, access is granted permanently. Payment gating survives redeploys. You can also manage payments from the dashboard (Wallet tab for address, site menu for pricing).
To send payments for a specific site to a different wallet, pass recipientAddress in the price object:
PATCH /api/v1/publish/:slug/metadata
{ "price": { "amount": "1.00", "currency": "USD", "recipientAddress": "0xOTHER_ADDRESS" } }402 response for agents
When a programmatic client hits a paid site, the 402 response includes a JSON body with session URLs for agents that don't have mppx installed:
{
"price": { "amount": "0.10", "currency": "USD", "recipientAddress": "0xe661..." },
"paymentSession": {
"createUrl": "https://here.now/api/pay/<slug>/session",
"pollUrl": "https://here.now/api/pay/<slug>/poll",
"grantUrl": "https://here.now/api/pay/<slug>/grant"
},
"walletUrl": "https://wallet.tempo.xyz/"
}Session flow: POST createUrl to get a unique deposit address. Poll pollUrl with the session ID every 3 seconds. When payment is detected, POST grantUrl with the session ID and tx hash to get a grant token. Fetch the original URL with ?__hn_grant=<token> to retrieve the content. Sessions expire after 30 minutes.
Wallet management
GET /api/v1/wallet returns the current wallet address. PATCH /api/v1/wallet sets or removes it.
Requires Authorization: Bearer <API_KEY>.
// Set wallet address
PATCH /api/v1/wallet
{ "address": "0xe66178B0D33807f5efb2069f9252eD02c13bbF59" }
// Remove wallet address
PATCH /api/v1/wallet
{ "address": null }Must be a valid 0x-prefixed 40-character hex address. A wallet address is required before you can set a price on any site.
Duplicate a site
POST /api/v1/publish/:slug/duplicate
Creates a complete server-side copy of the site under a new slug. All files are copied server-side — no client upload or finalize step needed. The new site is immediately live.
Requires Authorization: Bearer <API_KEY> (must own the source site).
Request body (optional):
{
"viewer": {
"title": "My Copy",
"description": "Copy of bright-canvas-a7k2"
}
}viewer(optional): Shallow-merged with the source site's viewer metadata. Only provided fields are overridden; omitted fields are preserved from the source.
Response:
{
"slug": "warm-lake-f3k9",
"siteUrl": "https://warm-lake-f3k9.here.now/",
"sourceSlug": "bright-canvas-a7k2",
"status": "active",
"currentVersionId": "01J...",
"filesCount": 36
}Copies all files and viewer metadata. Does not copy password protection, handle/domain links, or TTL. You can also duplicate a site from the dashboard via the ⋯ menu on each site.
Forks
Allow others to download and remix your site. When forkable is enabled, a small fork button appears on the live site, and the file manifest is exposed at /.herenow/manifest.json.
Enable via PATCH /api/v1/publish/:slug/metadata with {"forkable": true}, or toggle from the dashboard via the ⋯ menu on each site. You can also pass forkable: true in the request body when creating or updating a site.
When someone forks a forkable site, they get all the static files and the .herenow/proxy.json proxy manifest if present. The proxy manifest contains routing templates with variable references (e.g. ${RESEND_API_KEY}), not actual secrets. The forker sets up their own variables — they don't need to reverse-engineer the proxy config.
How forking works
The fork button on a forkable site copies a prompt the visitor pastes into their AI agent. The prompt includes the manifest URL (/.herenow/manifest.json) and the raw download pattern (/.herenow/raw/{path}). The agent fetches the manifest, downloads each file, and asks the user what to change before publishing.
The raw download route serves original files without the fork button injected, so forked source is clean. The manifest also includes requiredVariables — variable names extracted from proxy headers, with their upstream domains — so the agent can guide the forker through setting up credentials.
Defaults and inheritance
Forked sites inherit forkable: true by default. To disable, patch metadata after publishing with {"forkable": false}.
Forkable is mutually exclusive with password protection and payment gating. Both directions are rejected with an error — disable one before enabling the other.
SPA routing
For single-page applications (React, Vue, Svelte), enable SPA mode so unknown paths serve index.html instead of returning 404. This lets client-side routing work on refresh and direct links.
Enable at publish time with spaMode: true in the request body, or toggle on an existing site via PATCH /api/v1/publish/:slug/metadata with {"spaMode": true}.
Static assets still resolve normally — only paths that don't match any file fall through to theindex.html fallback. Works with both root index.html and subdirectory-based app structures.
Make sure the build uses root-relative asset paths (/assets/app.js). Vite and Create React App do this by default.
Patch metadata
PATCH /api/v1/publish/:slug/metadata (alias: PATCH /api/v1/artifact/:slug/metadata)
Requires Authorization: Bearer <API_KEY>.
{
"ttlSeconds": 604800,
"viewer": {
"title": "Updated title",
"description": "New description",
"ogImagePath": "assets/cover.png"
},
"password": "secret123",
"price": { "amount": "0.50", "currency": "USD" }
}All fields optional. ogImagePath must reference an image within the current site. Viewer metadata only affects the root document when no index.html exists.
password: string to set or change, null to remove, omit for no change. When set, visitors must enter the password before any content is served (server-side enforcement).
price: object to set or change, null to remove, omit for no change. Requires a wallet address on the account. amount is the price in USD. Optional recipientAddress overrides the account wallet for this site. Password and price are mutually exclusive.
Delete
DELETE /api/v1/publish/:slug (alias: DELETE /api/v1/artifact/:slug)
Requires Authorization: Bearer <API_KEY>. Hard deletes the site and all stored files.
List sites
GET /api/v1/publishes (alias: GET /api/v1/artifacts)
Requires Authorization: Bearer <API_KEY>. Returns all sites owned by the authenticated user.
{
"publishes": [
{
"slug": "bright-canvas-a7k2",
"siteUrl": "https://bright-canvas-a7k2.here.now/",
"updatedAt": "2026-02-18T...",
"expiresAt": null,
"status": "active",
"currentVersionId": "01J...",
"pendingVersionId": null
}
]
}Get site details
GET /api/v1/publish/:slug (alias: GET /api/v1/artifact/:slug)
Requires Authorization: Bearer <API_KEY> (owner only). Returns metadata and the full file manifest for the current live version.
{
"slug": "bright-canvas-a7k2",
"siteUrl": "https://bright-canvas-a7k2.here.now/",
"status": "active",
"createdAt": "2026-02-18T...",
"updatedAt": "2026-02-18T...",
"expiresAt": null,
"currentVersionId": "01J...",
"pendingVersionId": null,
"manifest": [
{ "path": "index.html", "size": 1234, "contentType": "text/html; charset=utf-8", "hash": "a1b2c3d4..." },
{ "path": "assets/app.js", "size": 999, "contentType": "text/javascript; charset=utf-8", "hash": "e5f6a7b8..." }
]
}The manifest lists all files in the current version. File contents can be fetched from the live siteUrl (e.g. https://bright-canvas-a7k2.here.now/index.html).
Refresh upload URLs
POST /api/v1/publish/:slug/uploads/refresh (alias: POST /api/v1/artifact/:slug/uploads/refresh)
Requires Authorization: Bearer <API_KEY>. Returns fresh presigned URLs for a pending upload (same version). Use when URLs expire mid-upload.
Variables
Store API keys and secrets on your account. Sites reference them in proxy route manifests to make authenticated API calls server-side — without exposing keys in client-side code.
Create or update a variable
PUT /api/v1/me/variables/:name
Requires Authorization: Bearer <API_KEY>.
PUT /api/v1/me/variables/OPENROUTER_API_KEY
{
"value": "sk-or-v1-abc123",
"allowedUpstreams": ["openrouter.ai"] // optional
}Variable names must be uppercase letters, digits, and underscores, starting with a letter. Max 50 variables per account, 4 KB per value. allowedUpstreams restricts which upstream domains the variable can be sent to (optional, omit for no restriction).
List variables
GET /api/v1/me/variables — returns variable names, upstream pinning, and timestamps. Values are never returned.
Delete a variable
DELETE /api/v1/me/variables/:name
You can also manage variables from the dashboard (Variables tab).
Proxy routes
Sites can make authenticated API calls to external services by including a .herenow/proxy.json manifest in the published files. The manifest maps paths on the site to upstream APIs with variable-injected headers.
// .herenow/proxy.json
{
"proxies": {
"/api/chat": {
"upstream": "https://openrouter.ai/api/v1/chat/completions",
"method": "POST",
"headers": {
"Authorization": "Bearer ${OPENROUTER_API_KEY}"
}
},
"/api/db/*": {
"upstream": "https://xyz.supabase.co/rest/v1",
"headers": {
"apikey": "${SUPABASE_KEY}"
}
}
}
}Keys are site-local paths. Exact paths (/api/chat) match that path only. Prefix patterns (/api/db/*) match any path starting with that prefix — the rest is appended to the upstream URL. Query parameters are forwarded automatically.
${VAR_NAME} references in headers are resolved from the account's variables at request time. Headers like Content-Type and Accept are forwarded from the browser automatically. The manifest only needs to declare the auth header.
The frontend calls a relative URL on the site (fetch('/api/chat')). here.now intercepts the request, injects the credentials server-side, forwards to the upstream, and streams the response back. Streaming (SSE) works out of the box for LLM responses.
Proxy routes require an authenticated site. Rate limit: 100 requests/hour/IP by default, overridable per route with "rateLimit": "20/hour/ip". Request body limit: 10 MB. The .herenow/proxy.json file is never served to site visitors.
URL structure
Each site gets its own subdomain: https://<slug>.here.now/
Asset paths work naturally from the subdomain root. Relative paths also work.
Serving rules
- If
index.htmlexists at root, serve it. - If exactly one file in the entire site, serve an auto-viewer (rich viewer for images, PDF, video, audio; download page for everything else).
- If an
index.htmlexists in any subdirectory, serve the first one found. - Otherwise, serve an auto-generated directory listing. Folders are clickable, images render as a gallery, and other files are listed with sizes. No
index.htmlrequired.
Direct file paths always work: https://<slug>.here.now/report.pdf
Handle
A handle gives you a stable subdomain like yourname.here.now that routes locations to your sites. Claiming a handle requires a paid plan (Hobby or above).
POST /api/v1/handleGET /api/v1/handlePATCH /api/v1/handleDELETE /api/v1/handle
Example create request:
{ "handle": "yourname" }Example create response:
{
"handle": "yourname",
"hostname": "yourname.here.now"
}Changing a handle keeps the same underlying namespace, so existing links move with the new name automatically. Deleting a handle removes the namespace and deletes its links.
Handle format: lowercase letters/numbers/hyphens, 2-30 chars, no leading/trailing hyphens.
Custom domains
Bring your own domain and serve sites from it. Free plan: 1 domain. Hobby plan: up to 5.
POST /api/v1/domains— add a domainGET /api/v1/domains— list your domainsGET /api/v1/domains/:domain— check statusDELETE /api/v1/domains/:domain— remove a domain
Add a domain:
curl -sS https://here.now/api/v1/domains \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'The response includes dns_instructions — an array of records to add at your DNS provider. Each has type, host, and value fields. For apex domains, we automatically set up both example.com and www.example.com.
Subdomains (e.g. docs.example.com): add a CNAME record. The host is the subdomain part (e.g. docs), and the value is fallback.here.now.
Apex domains (e.g. example.com): add the records from dns_instructions. The host will be @ (meaning the root domain — some providers use a blank field). Typically two A records plus a CNAME with host: "www" pointing to fallback.here.now. Visitors to www.example.com are automatically redirected to example.com.
SSL is provisioned automatically once DNS is verified. Status is pending until verified, then active. Query GET /api/v1/domains/:domain to check progress — this also triggers on-demand verification.
Links
Links connect a site to a location on your handle or custom domain. The same endpoints work for both — omit the domain parameter to target your handle, or include it to target a custom domain. Use an empty location for root.
POST /api/v1/linksGET /api/v1/linksGET /api/v1/links/:locationPATCH /api/v1/links/:locationDELETE /api/v1/links/:location
Link to your handle:
{
"location": "docs",
"slug": "bright-canvas-a7k2"
}Link to a custom domain:
{
"location": "",
"slug": "bright-canvas-a7k2",
"domain": "example.com"
}For root, send "location": "". In path params, use __root__ for the root location:/api/v1/links/__root__.
To delete a link from a custom domain, add ?domain=example.com as a query parameter to the DELETE request.
Link updates are written to Cloudflare KV and can take up to 60 seconds to propagate globally.
Limits
| Anonymous | Authenticated | |
|---|---|---|
| Max file size | 250 MB | 5 GB |
| Expiry | 24 hours | Permanent (or custom TTL) |
| Sites | — | 500 free, unlimited hobby |
| Storage | — | 10 GB free, 100 GB hobby |
| Rate limit | 5 / hour / IP | 60 / hour free, 200 / hour hobby |
| Account needed | No | Yes (sign in at here.now) |