here.now

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 -g

For 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 | bash

Once 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.html

3. 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/credentials

The publish script reads the key from these sources (first match wins):

  • --api-key flag (CI/scripting only — avoid in interactive use)
  • $HERENOW_API_KEY environment variable
  • ~/.herenow/credentials file (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 like my-project/index.html.
  • hash (optional): SHA-256 hex digest (64 lowercase chars). When updating, files whose hash matches the previous version are skipped from upload.uploads[] and listed in upload.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 no index.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 claimToken in 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 in upload.skipped[] instead of upload.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

  1. If index.html exists at root, serve it.
  2. If exactly one file in the entire site, serve an auto-viewer (rich viewer for images, PDF, video, audio; download page for everything else).
  3. If an index.html exists in any subdirectory, serve the first one found.
  4. Otherwise, serve an auto-generated directory listing. Folders are clickable, images render as a gallery, and other files are listed with sizes. No index.html required.

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 domain
  • GET /api/v1/domains — list your domains
  • GET /api/v1/domains/:domain — check status
  • DELETE /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/handle
  • GET /api/v1/handle
  • PATCH /api/v1/handle
  • DELETE /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 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/links
  • GET /api/v1/links
  • GET /api/v1/links/:location
  • PATCH /api/v1/links/:location
  • DELETE /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

  1. Create .herenow/data.json at the root of the directory you publish.
  2. Declare each collection, its fields, access rules, and optional rate limit.
  3. Publish a new Site or update an existing one with ./scripts/publish.sh ./site-dir or the publish API.
  4. From browser JavaScript, call ./.herenow/data/:collection relative to the published page.
  5. 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/@username

Example:

https://here.now/@user7k29mx

Usernames 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_gated

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.

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 like dv_abc...; token IDs look like dtok_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 creating My Drive if 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 against baseVersionId or per-op ETags.
  • POST /api/v1/drives/:driveId/files/move and DELETE /api/v1/drives/:driveId/files/:path — move or delete.
  • POST /api/v1/drives/:driveId/tokens — mint a scoped token, optionally with manageTokens; GET lists active tokens; DELETE revokes.

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 cursor

API 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: provide Authorization: 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 for retry_after seconds or follow the Retry-After header.
  • 503 storage_not_configured or service_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.

These files advertise current public surfaces only. here.now does not currently claim public MCP or OAuth support.

Limits

AnonymousFreeHobbyDeveloper
Storage10 GB total500 GB total2 TB total
Sites5001,000Unlimited
Site DataIncludedIncludedIncluded
AnalyticsUpgrade requiredIncludedIncluded
Custom domains1520
Drives1510
Subdomain handle11
Max site file size250 MB5 GB5 GB5 GB
Max Drive file size500 MB500 MB500 MB
Site expiry24 hoursPermanent (or custom TTL)Permanent (or custom TTL)Permanent (or custom TTL)
Drive version history7 days30 days90 days
Publish rate limit60 / hour / IP60 / hour200 / hour200 / hour
Account neededNoYes (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.