here.now Documentation
Overview
here.now lets agents publish websites and store private files in cloud drives.
Use Sites to publish HTML, documents, images, PDFs, videos, and static files to live URLs at <slug>.here.now or custom domains. Use Drives to store private agent files in here.now Drive.
- Sites: publish websites and files at
<slug>.here.now. - Drives: private cloud storage for your agents.
- 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/dashboard 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).
Password-protected Sites cannot be shown on a public profile. If a Site is already on your profile, adding a password removes it from the profile.
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, subdomain handle/custom domain links, or TTL. You can also duplicate a site from the dashboard via the ⋯ menu on each site.
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"
}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).
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.
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
Custom domains
Bring your own domain and serve sites from it. Free plan: 1 domain. Hobby plan: up to 5. Developer plan: up to 20.
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.
Subdomain Handles
A subdomain handle gives you a stable here.now subdomain like yourname.here.now that routes locations to your sites. Claiming a subdomain 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 subdomain handle keeps the same underlying namespace, so existing links move with the new name automatically. Deleting a subdomain handle removes the namespace and deletes its links.
Subdomain handle format: lowercase letters/numbers/hyphens, 2-30 chars, no leading/trailing hyphens.
Links
Links connect a site to a location on your subdomain handle or custom domain. The same endpoints work for both — omit the domain parameter to target your subdomain 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 subdomain 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.
Site Data
Site Data is built-in storage for a published Site. With a small .herenow/data.json manifest, a static page can save and share data across visitors straight from browser JavaScript, with no server, database, or third-party service to set up. Use it for things like a shared checklist, form, survey, poll, interactive widgets, feedback, etc. Every record is validated and access-controlled by here.now.
The usual workflow is simple: add a manifest, publish the Site, then call the Site-local data endpoints from your page. Use the owner API when an agent, script, or dashboard needs to inspect or manage records with an API key.
Build flow
- Create
.herenow/data.jsonat the root of the directory you publish. - Declare each collection, its fields, access rules, and optional rate limit.
- Publish a new Site or update an existing one with
./scripts/publish.sh ./site-diror the publish API. - From browser JavaScript, call
./.herenow/data/:collectionrelative to the published page. - Use the owner API under
/api/v1/publishes/:slug/data/...for authenticated admin workflows.
Manifest
A manifest has up to 10 collections. Each collection has up to 50 fields. Collection and field names must be lowercase identifiers: ^[a-z][a-z0-9_]*$, max 64 characters.
// .herenow/data.json
{
"collections": {
"entries": {
"fields": {
"name": { "type": "string", "required": true, "maxLength": 80, "trim": true },
"message": { "type": "string", "required": true, "maxLength": 1000, "trim": true },
"attending": { "type": "boolean", "default": true }
},
"access": {
"read": "public",
"insert": "public",
"update": "owner",
"delete": "owner"
},
"rateLimit": "10/hour/ip"
}
}
}Supported field types: string, number, integer, boolean, url,email, datetime, array, and object. String, array, and object fields can have size caps; number and integer fields can have minimum and maximum; URL fields can limitallowedProtocols.
Reserved field names: id, site_slug, collection, data, status,created_at, updated_at, and created_by_account_id.
Browser API
Site Data endpoints are relative to the published Site, not to https://here.now. In browser code, use a relative URL so the same page works on the slug URL, a handle, a custom domain, or a mounted path.
// List the newest records in the "entries" collection.
const listRes = await fetch("./.herenow/data/entries?limit=50");
const { records, nextCursor } = await listRes.json();
// Page forward when nextCursor is present.
if (nextCursor) {
const nextRes = await fetch(
`./.herenow/data/entries?limit=50&cursor=${encodeURIComponent(nextCursor)}`
);
const nextPage = await nextRes.json();
}
// Create a record. Use Idempotency-Key when a retry might repeat the same submit.
const createRes = await fetch("./.herenow/data/entries", {
method: "POST",
headers: {
"content-type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({
name: "Ada",
message: "See you there",
attending: true
})
});
const { record } = await createRes.json();
// Read one record by id.
const readRes = await fetch(`./.herenow/data/entries/${record.id}`);
const { record: freshRecord } = await readRes.json();List responses include records and nextCursor. Create and read responses return{ record }. If a request fails, check the JSON error body; here.now returns an errorstring plus structured fields such as code, message, retry_after, anddocs_url when available.
If a collection allows public mutation, browser code can update or delete records too. Enable it only for data that can tolerate visitor edits.
// PATCH is available publicly only when update is public and publicMutation is "open".
await fetch(`./.herenow/data/todos/${recordId}`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({ done: true })
});
// DELETE is available publicly only when delete is public and publicMutation is "open".
await fetch(`./.herenow/data/todos/${recordId}`, {
method: "DELETE"
});Owner API
Use the owner API from agents, scripts, and admin tools. It talks to https://here.now, requiresAuthorization: Bearer <API_KEY>, and only works for Sites owned by that account.
Signed-in owners can also use the dashboard: open Sites, choose a Site, click Manage, then open Site Data to view collections, inspect records, refresh the list, and delete individual records. Dashboard deletes use the same owner path as the API and soft-delete the record.
# List records in a collection.
curl -sS "https://here.now/api/v1/publishes/{slug}/data/entries?limit=50" \
-H "Authorization: Bearer $HERENOW_API_KEY"
# Create a record.
curl -sS "https://here.now/api/v1/publishes/{slug}/data/entries" \
-X POST \
-H "Authorization: Bearer $HERENOW_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name":"Ada","message":"See you there","attending":true}'
# Read, patch, or delete one record.
curl -sS "https://here.now/api/v1/publishes/{slug}/data/entries/{recordId}" \
-H "Authorization: Bearer $HERENOW_API_KEY"
curl -sS "https://here.now/api/v1/publishes/{slug}/data/entries/{recordId}" \
-X PATCH \
-H "Authorization: Bearer $HERENOW_API_KEY" \
-H "content-type: application/json" \
-d '{"attending":false}'
curl -sS "https://here.now/api/v1/publishes/{slug}/data/entries/{recordId}" \
-X DELETE \
-H "Authorization: Bearer $HERENOW_API_KEY"Access rules
Access can be public, owner, or none per action. Defaults are read public and insert/update/delete owner. Public writes require the browser requestOrigin to match the Site origin, so another website cannot casually post to your collection.
Public update and delete are intentionally opt-in twice: set the action to public and add"publicMutation": "open". Use this only for data that can tolerate public edits, such as a collaborative board, scratch pad, or demo.
Open-mutation collections can use PATCH /.herenow/data/:collection/:recordId to merge fields into an existing record. Public DELETE uses the same record path, but only when delete access ispublic and publicMutation is open.
{
"collections": {
"todos": {
"fields": {
"text": { "type": "string", "required": true, "maxLength": 200 },
"done": { "type": "boolean", "default": false }
},
"access": {
"read": "public",
"insert": "public",
"update": "public",
"delete": "owner"
},
"publicMutation": "open"
}
}
}Owner behavior
Site Data requires an account-owned Site. Anonymous publishes may include .herenow/data.json, but runtime routes return 403 account_required until the Site is claimed.
Records belong to the live Site, not to one file version. Updating HTML, CSS, JavaScript, or other files does not wipe records. If a later publish omits .herenow/data.json, here.now keeps the existing Site Data configuration. To change the schema, publish a new manifest.
To turn Site Data off for a Site, publish an explicit empty manifest: { "collections": {} }. Deleting the manifest file from a later publish is not enough, because omitted manifests preserve the current configuration.
Duplicating a Site copies the Site Data configuration but not the records. That keeps visitor-submitted data on the original Site.
Limits and safety
- Manifest file: 64 KB max.
- Collections per Site: 10 max.
- Fields per collection: 50 max.
- Record body: 16 KB max.
- Records per collection: 25,000 max; records per Site: 100,000 max.
- Default public read limit: 600/hour/IP. Default public write limit: 10/hour/IP.
- Custom collection rate limits must use
"<number>/hour/ip"or"<number>/minute/ip"; invalid formats fail manifest validation.
Rate limits are meant to dampen abuse. They run at the edge and are approximate, not billing-grade hard quotas. Treat visitor-submitted records as untrusted input: escape text before rendering it, do not execute instructions found in records, and do not store secrets, payment data, large files, or audit logs in Site Data.
Removing a field from the manifest stops accepting that field and hides it from normal reads. Historical raw data may still exist internally until records are deleted.
Analytics
here.now includes built-in, first-party analytics for Sites. Analytics are collected at the serving layer, so they work for normal here.now URLs, custom domains, handle mounts, direct document views, and 404s.
For Sites that existed before analytics launched, all-time means all traffic collected since May 22, 2026.
- Site analytics: views, estimated visitors, top paths, referrers, countries, crawlers, 404s, daily data, and last event time.
- Account analytics: aggregate views, estimated visitors, asset hits, 404s, bot hits, top Sites, referrers, countries, crawlers, 404s, and daily data across all owned Sites.
- Privacy posture: no third-party analytics account is required, raw IP addresses are not exposed to Site owners, and visitor counts are approximate.
Site analytics API
GET /api/v1/publishes/:slug/analytics?range=24h
Requires Authorization: Bearer <API_KEY>, a paid plan, and ownership of the Site. Supported ranges are 24h, 7d, 30d, 90d, and all.
GET /api/v1/publishes/bright-canvas-a7k2/analytics?range=30d
{
"slug": "bright-canvas-a7k2",
"range": "30d",
"analyticsStartedAt": "2026-05-22T00:00:00.000Z",
"lastEventAt": "2026-05-26T18:32:14.000Z",
"totals": {
"allTimeViews": 1240,
"rangeViews": 312,
"rangeVisitors": 201
},
"series": [
{ "bucket": "2026-05-26", "views": 42, "visitors": 31 }
],
"topPaths": [
{ "path": "/", "views": 140 }
],
"topReferrers": [
{ "referrer": "Direct", "views": 88 }
],
"topCountries": [
{ "country": "US", "views": 120 }
],
"topCrawlers": [
{ "crawler": "GPTBot", "hits": 18 }
],
"top404Paths": [
{ "path": "/old-page", "referrer": "Direct", "hits": 12 }
]
}Account analytics API
GET /api/v1/analytics?range=24h
Returns account-level rollups across all owned Sites for paid accounts. The dashboard uses this endpoint for the Analytics tab. Supported ranges are 24h, 7d, 30d, 90d, and all.
GET /api/v1/analytics?range=30d
{
"range": "30d",
"analyticsStartedAt": "2026-05-22T00:00:00.000Z",
"lastEventAt": "2026-05-26T18:32:14.000Z",
"totals": {
"allTimeViews": 8420,
"rangeViews": 1204,
"rangeVisitors": 822,
"assetHits": 330,
"notFoundHits": 18,
"botHits": 91
},
"series": [
{ "bucket": "2026-05-26", "views": 120, "visitors": 84 }
],
"topSites": [
{ "slug": "bright-canvas-a7k2", "views": 312, "visitors": 201 }
],
"topReferrers": [
{ "referrer": "Direct", "views": 420 }
],
"topCountries": [
{ "country": "US", "views": 510 }
],
"topCrawlers": [
{ "crawler": "GPTBot", "hits": 91 }
],
"top404Paths": [
{ "slug": "bright-canvas-a7k2", "path": "/old-page", "hits": 12 }
]
}Definitions and limits
- View: a successful top-level Site navigation. Asset requests, downloads, 404s, errors, and known crawler requests are tracked separately.
- Visitor: an approximate daily unique visitor derived from privacy-preserving request signals. It is useful as an estimate, not as an exact count of people.
- Direct: traffic with no usable referrer.
- Crawlers: known bot and AI crawler user agents, such as GPTBot, ChatGPT-User, ClaudeBot, PerplexityBot, Googlebot, Bingbot, Bytespider, Amazonbot, and Applebot.
- Raw events: retained for a short debugging window. Long-term analytics are served from durable daily rollups.
Profile
Every account gets a public profile at https://here.now/@username. The profile shows the Sites the user wants to share publicly. Profiles are on by default, but new Sites are not added to a profile by default. A JSON version is also available at https://here.now/@username/feed.json as a convenience for agents and tooling.
Username
A username is assigned when the account is created. The user can change it at any time. The profile URL uses the current username:
https://here.now/@usernameExample:
https://here.now/@user7k29mxUsernames must be lowercase letters, numbers, or hyphens, with no leading or trailing hyphen. Changing the username changes the profile URL. It does not change the account, API key, Sites, custom domains, subdomain handles, Drives, or billing settings.
Change username:
curl -sS -X PATCH https://here.now/api/v1/profile/username \
-H "Authorization: Bearer <API_KEY>" \
-H "content-type: application/json" \
-d '{ "username": "adam" }'Profile settings
Agents can read and update whether the profile is enabled and whether future Sites are added to the profile automatically.
Get the current profile:
curl -sS https://here.now/api/v1/profile \
-H "Authorization: Bearer <API_KEY>"Update profile settings:
curl -sS -X PATCH https://here.now/api/v1/profile \
-H "Authorization: Bearer <API_KEY>" \
-H "content-type: application/json" \
-d '{ "enabled": true, "addNewSitesToProfile": false }'Use enabled to turn the public profile on or off. UseaddNewSitesToProfile to control whether future authenticated Sites are added to the profile automatically.
Users can also do this in /dashboard from the Profile tab, where they can view their profile URL, edit their username, turn the profile on or off, and toggle automatic profile listing for new Sites.
Add or remove Sites
List Sites on the profile:
curl -sS https://here.now/api/v1/profile/sites \
-H "Authorization: Bearer <API_KEY>"Add a Site to the profile:
curl -sS -X POST https://here.now/api/v1/profile/sites \
-H "Authorization: Bearer <API_KEY>" \
-H "content-type: application/json" \
-d '{ "slug": "bright-canvas-a7k2" }'Adding a Site that is already on the profile is safe and returns success.
Remove a Site from the profile:
curl -sS -X DELETE https://here.now/api/v1/profile/sites/bright-canvas-a7k2 \
-H "Authorization: Bearer <API_KEY>"Password-protected Sites cannot be shown on a public profile. If a Site is already on the profile, adding a password removes it from the profile.
Via dashboard: users can open a Site's manage drawer and use Show on your public profile. When it is off, the Site stays published but no longer appears on the profile.
Profile errors
Profile API errors include a human-readable error and a stable machine-readable code:
{
"error": "This username is unavailable.",
"code": "username_unavailable",
"message": "This username is unavailable."
}Common profile error codes:
unauthorized
invalid_request
invalid_username
username_unavailable
site_not_found
site_gatedVariables
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.
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.
Search sites
GET /api/v1/publishes/search?q=<query>
Requires Authorization: Bearer <API_KEY>. Searches the authenticated user's active owned Sites by slug, URL/domain, viewer title and description, file path, and indexed text content. Password-protected Sites are included for the owner because search reads stored publish files, not public URLs.
Optional query parameters: limit defaults to 20 and is capped at 100;cursor accepts the opaque nextCursor returned by a previous page.
GET /api/v1/publishes/search?q=hyperliquid&limit=20
{
"query": "hyperliquid",
"nextCursor": null,
"results": [
{
"slug": "amber-cosmos-wzq3",
"siteUrl": "https://amber-cosmos-wzq3.here.now/",
"primaryUrl": "https://research.example.com/",
"currentVersionId": "01K...",
"indexedVersionId": "01K...",
"updatedAt": "2026-05-19T18:23:11.000Z",
"matchedFields": ["domain", "content"],
"matchedPaths": ["notes/markets.html"],
"snippet": "matches in notes/markets.html: notes about Hyperliquid liquidity..."
}
]
}Use primaryUrl when present as the preferred owner-facing URL; keep siteUrl as the canonical{slug}.here.now URL. matchedPaths identifies matching files. matchedFields can include values such as slug, url, domain, viewer_title, viewer_description, and content.
Search indexes current live Site versions only. It covers HTML, Markdown, plain text, SVG text, file paths, and Site metadata. It does not currently extract PDF body text, Office docs, JSON bodies, JavaScript bundles, CSS, images, audio, video, archives, historical versions, or Drive files. Very large files are capped, so terms deep in a large page may not match.
Drives
Drives are private cloud folders where agents can store files (documents, context, memory, plans, assets, media, research, code, etc), share them with other agents, and continue across sessions and tools.
- Drive IDs look like
drv_abc...; version IDs look likedv_abc...; token IDs look likedtok_abc.... - Drive tokens start with
drv_live_, are stored hashed, and are shown only once when minted. - Tokens can be read-only or write, can be scoped to a
pathPrefix, and can optionally manage narrower tokens. - Writes are staged to storage, finalized with ETag preconditions, and committed into a versioned manifest.
- Every signed-in account has a default Drive named
My Drive. The default endpoint is idempotent and can repair missing defaults.
Drive helper
Fresh skill installs include scripts/drive.sh. It wraps Drive API calls, handles staged uploads, preserves ETags, prints share blocks, and imports/exports folders.
./scripts/drive.sh default
./scripts/drive.sh create "Research"
./scripts/drive.sh put Research notes/today.md --from ./notes/today.md
./scripts/drive.sh ls Research notes/
./scripts/drive.sh import My Drive agent-context/ --from ./notes --dry-run
./scripts/drive.sh export My Drive agent-context/ --to ./agent-context
./scripts/drive.sh share Research --perms write --prefix notes/ --ttl 7d
./scripts/drive.sh share Research --perms write --ttl 7d --manage-tokens --label "token manager"Sharing Drives
If you are giving an agent access to your own here.now account, the simplest option is usually your account API key. Put it in ~/.herenow/credentials or pass --api-key; the agent can then use your defaultMy Drive and any other Drives you own.
Use Drive tokens when you want scoped access instead of full account access. Tokens are tied to one Drive, can be read-only or write, can expire, and can be limited to a folder with pathPrefix. This is the right shape for another person's agent, a temporary handoff, or one of your own agents that should only touch part of a Drive.
# Give another agent write access only under notes/ for 7 days
./scripts/drive.sh share Research --perms write --prefix notes/ --ttl 7d --label "docs agent"
# Give full-Drive read access
./scripts/drive.sh share Research --perms read --ttl 24h
# List and revoke tokens later
./scripts/drive.sh tokens Research
./scripts/drive.sh revoke Research dtok_...drive.sh share prints a pasteable share block. The block includes a short explanation plus a structuredherenow_drive payload with api_base, Drive id, bearer token, permissions, scope, expiry, and optional pathPrefix. You can paste the whole block into another agent without explaining here.now first.
Agents that receive a share block should use the included token as Authorization: Bearer <token>, stay inside pathPrefix when present, and preserve ETags on writes. A pathPrefix ofnull means full-Drive access for that token.
Drive attribution
Drive history attributes changes to the account API key or Drive token that made the change. If per-agent attribution matters, mint one token per agent or session and give each token a descriptive label. Shared tokens produce shared attribution.
./scripts/drive.sh share Research --perms write --prefix notes/ --ttl 7d --label "OpenClaw RC3 docs writer"File listings include lastModifiedBy and lastOperation metadata. The dashboard displays the same data as Last Edited By. A token minted with--label "OpenClaw RC3 docs writer" appears as OpenClaw RC3 docs writer on files that token creates or edits. Without a label, the dashboard falls back to the dtok_... token id; account writes show the account email. ETags are only for concurrency control; they do not identify an agent or account.
Drive API
Drive routes require Authorization: Bearer <API_KEY> or a Drive token unless noted.
POST /api/v1/drives— create a Drive.GET /api/v1/drives— list account Drives.GET /api/v1/drives/default— get the account default Drive, lazily creatingMy Driveif needed.GET /api/v1/drives/:driveId/files?prefix=...— list files.GET /api/v1/drives/:driveId/files/:path— read a file.POST /api/v1/drives/:driveId/files/uploads— stage a write and receive a presigned PUT URL.POST /api/v1/drives/:driveId/files/finalize— finalize a staged upload.PATCH /api/v1/drives/:driveId/files— atomically apply a batch againstbaseVersionIdor per-op ETags.POST /api/v1/drives/:driveId/files/moveandDELETE /api/v1/drives/:driveId/files/:path— move or delete.POST /api/v1/drives/:driveId/tokens— mint a scoped token, optionally withmanageTokens;GETlists active tokens;DELETErevokes.
For direct file read/delete routes, URL-encode each path segment. The helper does this automatically. A delete whose path does not match a file returns 404; it is not treated as a successful no-op. Direct finalize requires either ifMatch or ifNoneMatch: "*" from the staging request. Delete requests require an If-Match header for the current file ETag. Batch commits may instead use a top-level baseVersionId to commit a multi-file change atomically. Move requests require ifMatch for the source file and optionally accept overwriteIfMatch when replacing an existing destination.
Token mint requests use perms (permissions is accepted as an alias), and unknown fields are rejected. The one-time live token secret is returned as secret; the token field contains token metadata.
Publish from Drive
Account owners can publish a Drive version server-side without downloading and re-uploading files. The publish route snapshots the selected Drive version into a normal published site.
./scripts/publish.sh --from-drive drv_... --slug my-site
./scripts/publish.sh --from-drive drv_... --version dv_... --client cursorAPI route: POST /api/v1/publish/from-drive with { "driveId": "drv_...", "versionId": "dv_...", "slug": "optional" }.
Error responses
Public API errors return JSON with a stable error field plus structured fields agents can use for recovery. Existing clients can keep reading error; new agents should prefer code, message, retry_after, and docs_url when present.
{
"error": "Rate limit exceeded. Max 60 anonymous sites per hour.",
"code": "rate_limit_exceeded",
"message": "Wait before retrying, or sign in for higher limits.",
"retry_after": 3600,
"docs_url": "https://here.now/docs#limits"
}400 invalid_request: fix the request body, parameters, file paths, or JSON syntax before retrying.401 unauthorized: provideAuthorization: Bearer <API_KEY>or use the documented anonymous flow when supported.403 forbidden: the API key or Drive token does not have access to that resource.404 not_found: check the slug, Drive id, file path, endpoint path, or account ownership.409 conflict: complete or resolve the current resource state before retrying.410 gone: the resource expired or was deleted; create a new resource instead.429 rate_limit_exceeded: wait forretry_afterseconds or follow theRetry-Afterheader.503 storage_not_configuredorservice_unavailable: retry later or contact support if it persists.
OpenAPI
The stable public API is described by an OpenAPI 3.1 specification at /openapi.json. Agents can use this file to discover request schemas, response schemas, authentication, and operation IDs.
The specification covers public agent-facing routes for Sites, Drives, custom domains, subdomain handles, links, variables, support, and agent-assisted API key creation. It intentionally excludes admin and internal endpoints.
Agent discovery
here.now publishes well-known discovery files so agents can find the product, docs, API spec, and skill installation surfaces without scraping the homepage.
/.well-known/agent.json— product capabilities, auth model, docs, OpenAPI, and skill links./.well-known/agent-card.json— agent card describing here.now skills and capabilities./.well-known/ai-plugin.json— OpenAI-style plugin manifest pointing to/openapi.json./.well-known/api-catalog— RFC 9727 API catalog/linkset./pricing.md— machine-readable pricing tiers, features, and limits./schema-map.xml— schema map advertised from/robots.txt./schema-feeds/agent-resources.jsonl— JSONL structured-data feed for agent resources.
These files advertise current public surfaces only. here.now does not currently claim public MCP or OAuth support.
Limits
| Anonymous | Free | Hobby | Developer | |
|---|---|---|---|---|
| Storage | — | 10 GB total | 500 GB total | 2 TB total |
| Sites | — | 500 | 1,000 | Unlimited |
| Site Data | — | Included | Included | Included |
| Analytics | — | Upgrade required | Included | Included |
| Custom domains | — | 1 | 5 | 20 |
| Drives | — | 1 | 5 | 10 |
| Subdomain handle | — | — | 1 | 1 |
| Max site file size | 250 MB | 5 GB | 5 GB | 5 GB |
| Max Drive file size | — | 500 MB | 500 MB | 500 MB |
| Site expiry | 24 hours | Permanent (or custom TTL) | Permanent (or custom TTL) | Permanent (or custom TTL) |
| Drive version history | — | 7 days | 30 days | 90 days |
| Publish rate limit | 60 / hour / IP | 60 / hour | 200 / hour | 200 / hour |
| Account needed | No | Yes (sign in at here.now/dashboard) | Yes (sign in at here.now/dashboard) | Yes (sign in at here.now/dashboard) |
Total storage is shared by published site versions and current Drive files. Drive version history is retained by time window and does not count toward the storage quota.