API Documentation

Self-hosted instance — this server runs the open-source here.now platform at https://a7een.doy.tech. It implements the core Sites + Drives API described below. Features marked Hosted only are part of the managed here.now SaaS and are not available on self-hosted instances.

here lets agents publish websites and store private files in cloud drives. Use Sites to publish HTML, documents, images, PDFs, and static files to live URLs at https://a7een.doy.tech/s/<slug>. Use Drives for private cloud agent storage with presigned uploads, inline reads, and scoped tokens. All publishing and drive routes require an account — sign in by email and use the a7een_… API key shown after the magic link.

Overview

Sites — publish websites and files at https://a7een.doy.tech/s/<slug>.
Drives — private cloud storage for your agents.
Authenticate with Authorization: Bearer <API_KEY> (issued on sign-in) or a logged-in browser session. Drive read routes also accept a scoped Drive token via ?token=.

All API routes are under https://a7een.doy.tech/api/v1. An OpenAPI 3.1 spec (the authoritative contract) is at /openapi.json. Health check: GET /api/health.

Install here

Install the here skill so your agent can publish automatically:

Shell
# Global install (recommended)
npx skills add heredotnow/skill --skill here-now -g

# Project-local install (no -g flag)
npx skills add heredotnow/skill --skill here-now

# Fallback installer
curl -fsSL https://a7een.doy.tech/install.sh | bash

Once installed, agents can publish with a single script call. Store your API key:

Shell
mkdir -p ~/.herenow && echo "API_KEY" > ~/.herenow/credentials

Key resolution order (first match wins): --api-key flag → $HERENOW_API_KEY env → ~/.herenow/credentials file.

Quick start

Publish a site in three steps. You need an API key — sign in by email and copy the a7een_… key from the instructions page.

1. Create site (get presigned URLs)

cURL
curl -sS https://a7een.doy.tech/api/v1/publish \
  -H "Authorization: Bearer a7een_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "my-site",
    "title": "My Site",
    "files": [
      { "path": "index.html", "size": 1234, "hash": "<sha256-of-file>" }
    ]
  }'

The response contains siteUrl, presigned upload.uploads[], and a upload.finalizeUrl.

2. Upload changed files

cURL
curl -X PUT "<upload.uploads[0].url>" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @index.html

3. Finalize (go live)

cURL
curl -sS -X POST "<upload.finalizeUrl>" \
  -H "Authorization: Bearer a7een_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "files": [
    { "path": "index.html", "size": 1234, "hash": "<sha256-of-file>" }
  ] }'
Files whose hash already match the active version appear in upload.skipped[] — no upload needed for those; the server copies them at finalize.

Authentication

Two ways to authenticate:

  • Bearer API key — include Authorization: Bearer <API_KEY>. Keys are issued on sign-in (a7een_…) and verified by SHA-256 hash, so the plain key is shown only once.
  • Browser session — after signing in via the web app, API requests from the same browser session are authenticated automatically.
  • Drive read tokens — read-only file and list endpoints accept a scoped Drive token via ?token=drv_… instead of the header.
HTTP Headers
Authorization: Bearer a7een_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

Requests without a valid key or session return 401 with code unauthenticated or invalid_api_token.

Getting an API key

Sign-in is email-based: submit your email to receive a magic link. Opening the link creates your account (if new), issues an API key, and shows the agent instructions page.

1. Request a magic link

cURL
curl -sS -X POST https://a7een.doy.tech/start \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

2. Open the emailed link

The link signs you in, creates your personal team, and issues a fresh a7een_… API key. The key is shown on the /agent/instructions page at that moment — store it somewhere safe, the plain key is not shown again.

Signing in again from a browser also works — the browser session is authenticated for API calls even without the header.

Sites

Publish HTML, documents, images, PDFs, videos, and static files to live URLs. The publishing workflow is three steps: create (get presigned URLs) → upload files to S3 → finalize (activate version).

POST /api/v1/publish
Initialize a site deployment. Returns presigned S3 upload URLs for changed files and an upload.finalizeUrl. Files with a matching hash from the previous version appear in upload.skipped[] — no upload needed for those.

Request body

FieldTypeDescription
slugstringrequired Lowercase letters/numbers/hyphens, 3–32 chars, unique.
titlestringoptional Site title.
descriptionstringoptional Site description.
filesarrayrequired Array of { path, hash, size }.
files[].pathstringRelative to site root, e.g. index.html, assets/style.css.
files[].hashstringrequired SHA-256 hex (64 chars). Used for differential uploads — unchanged files land in skipped[].
files[].sizeintegerrequired File size in bytes, max 256 MB.
cURL
curl -sS https://a7een.doy.tech/api/v1/publish \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "my-site",
    "title": "My Site",
    "files": [
      { "path": "index.html", "size": 1234,
        "hash": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" },
      { "path": "assets/app.js", "size": 999,
        "hash": "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3" }
    ]
  }'
JavaScript
const res = await fetch('https://a7een.doy.tech/api/v1/publish', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    slug: 'my-site',
    files: await Promise.all(fileList.map(async f => ({
      path: f.path, size: f.size, hash: await sha256(f),
    }))),
  }),
});

const data = await res.json();
// Step 2: upload changed files in parallel
await Promise.all(data.upload.uploads.map(async ({ url, headers, path }) =>
  fetch(url, { method: 'PUT', headers, body: fileMap[path] })
));
// Step 3: finalize
await fetch(data.upload.finalizeUrl, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ files: fileList }),
});
201 Created
{
  "slug": "my-site",
  "siteUrl": "https://a7een.doy.tech/s/my-site",
  "versionHash": "x7k2Qr9cWt3nYv6f",
  "versionId": 12,
  "upload": {
    "versionId": "12",
    "uploads": [{
      "path": "index.html",
      "method": "PUT",
      "url": "https://<presigned-url>",
      "headers": { "Content-Type": "application/octet-stream" }
    }],
    "skipped": ["assets/app.js"],
    "finalizeUrl": "https://a7een.doy.tech/api/v1/publish/my-site/finalize",
    "expiresInSeconds": 1200
  },
  "uploadData": { "index.html": { "url": "https://<presigned-url>", "path": "index.html", "hash": "a1b2…" } }
}
POST /api/v1/publish/{slug}/finalize
Activate the new version and make the site live. The body repeats the files array from the create step (path + hash + size); the s3Key field is optional and ignored — the server derives object keys from the slug, version, and path. Skipped files are copied server-side; no re-upload needed. Requires Authorization: Bearer <API_KEY>.
JSON body
{
  "files": [
    { "path": "index.html", "size": 1234, "hash": "<sha256>" }
  ]
}
200 OK
{
  "message": "Site published successfully",
  "slug": "my-site",
  "siteUrl": "https://a7een.doy.tech/s/my-site",
  "versionHash": "x7k2Qr9cWt3nYv6f",
  "previousVersionId": null,
  "currentVersionId": "12"
}
GET/api/v1/publishes
List all sites owned by the authenticated user, with the current active version's file manifest. Requires Authorization: Bearer <API_KEY>. Query params: page (default 1), pageSize (default 20, max 100).
200 OK
{
  "sites": [{
    "slug": "my-site",
    "title": "My Site",
    "description": null,
    "siteUrl": "https://a7een.doy.tech/s/my-site",
    "activeVersion": {
      "versionHash": "x7k2Qr9cWt3nYv6f",
      "files": [{ "path": "index.html", "s3_key": "sites/my-site/versions/.../index.html", "hash": "<sha256>" }]
    }
  }],
  "pagination": { "page": 1, "pageSize": 20, "totalItems": 3, "totalPages": 1 }
}
PATCH/api/v1/publish/{slug}/metadata
Update site metadata. All fields optional. Requires Authorization: Bearer <API_KEY>. Only title, description, and is_spa affect runtime behavior on this instance; is_forkable, password_hash, and price are stored for compatibility but their enforcement is Hosted only.
FieldTypeDescription
titlestringDisplay title (max 255 chars).
descriptionstringDescription (max 5000 chars).
is_spabooleanUnknown paths fall through to index.html when serving.
is_forkablebooleanStored only — fork exposure is Hosted only.
password_hashstringStored only — server-side gating is Hosted only.
pricenumberStored only — payment gating is Hosted only.
DELETE/api/v1/publish/{slug}
Delete the site and all stored files. Requires Authorization: Bearer <API_KEY>.
200 OK
{ "message": "Site deleted successfully" }

⚡ SPA routing

For single-page apps (React, Vue, Svelte): enable SPA mode so unknown paths serve index.html instead of 404. Static assets still resolve normally — only paths not matching any file fall through to the index.html fallback.

cURL
curl -sS -X PATCH https://a7een.doy.tech/api/v1/publish/my-site/metadata \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "is_spa": true }'

Use root-relative asset paths (/assets/app.js). Vite and Create React App do this by default.

🏢 Features not available on self-hosted instances

The following features exist in the managed here.now SaaS and are referenced by the external documentation. They are not implemented on self-hosted instances like this one — the endpoints below do not exist here and return 404.

  • Anonymous sites & claiming — publish without an account, 24-hour expiry, claimToken/claimUrl, POST /publish/{slug}/claim. Self-hosted requires an API key for all publishing.
  • Password protection{"password":…} server-side visitor gating.
  • Payment gating/api/v1/wallet, price metadata, Tempo stablecoin paywall, 402 + paymentSession.
  • Forksis_forkable, POST /publish/{slug}/duplicate, /.herenow/manifest.json, /.herenow/raw/{path}.
  • Proxy routes.herenow/proxy.json server-side upstream proxying with ${VAR_NAME}.
  • Account variables/api/v1/me/variables secret store.
  • Custom domains/api/v1/domains with auto SSL. Sites are served at https://a7een.doy.tech/s/<slug> only.
  • Handle & links/api/v1/handle, /api/v1/links, yourname.a7een.doy.tech subdomains.

Drives

Private cloud folders for agents to store files and share them across sessions. Uploads are single-step: POST /files/uploads returns a presigned S3 PUT URL, and you upload directly to it. Reads return file contents inline. Every account gets a default Drive named Default (5 GB).

Drive routes require Authorization: Bearer <API_KEY> (or a browser session). Read routes (GET /files, GET /files/{path}) also accept a scoped Drive token via ?token=.
POST/api/v1/drives
Create a named Drive. Body: {"name":"Research", "description":"…", "max_size":<bytes>} (all optional; max_size defaults to 5 GB). Response includes the Drive with id, total_size, and max_size.
GET/api/v1/drives/default
Get or auto-create the account's Default Drive. Idempotent — safe to call on every session.
POST/api/v1/drives/{driveId}/files/uploads
Initiate an upload and receive a presigned S3 PUT URL valid for 20 minutes. Returns 409 if the Drive is out of space.
JSON body
{
  "path": "notes/report.pdf",
  "size": 1024000
}
200 OK
{
  "message": "File upload initiated successfully",
  "upload": {
    "url": "https://<presigned-url>",
    "path": "notes/report.pdf",
    "expires_in": 1200
  }
}
cURL
# Upload directly to S3 — no server relay
curl -X PUT "<upload.url>" \
  --data-binary @report.pdf
GET/api/v1/drives/{driveId}/files?prefix=...
List files in a drive. Use ?prefix=notes/ to filter by folder. Also accepts ?token= for scoped read access.
200 OK
{
  "message": "Files listed successfully",
  "files": [{
    "key": "notes/report.pdf",
    "size": 1024000,
    "lastModified": "2026-05-17 10:30:00"
  }],
  "count": 1
}
GET/api/v1/drives/{driveId}/files/{path}
Read a file — the body contains the raw contents inline under file.contents (not a download URL). URL-encode each path segment. Also accepts ?token= for scoped read access.
200 OK
{
  "message": "File read successfully",
  "file": { "path": "notes/report.txt", "contents": "<raw file contents>" }
}
DELETE/api/v1/drives/{driveId}/files/{path}
Delete a file. Owner only (no token access). URL-encode each path segment.
POST/api/v1/drives/{driveId}/tokens
Mint a scoped Drive token. The plain token secret is returned once (plain_text_token) and stored hashed — it is never returned again. Use it as a bearer ?token= on read routes.
JSON body
{
  "name": "docs-writer",
  "permissions": "read",          // "read" | "write" | "read-write"
  "path_restriction": "notes/",   // optional folder scope
  "expires_at": "2026-08-10T12:00:00Z"  // optional expiry
}
201 Created — save plain_text_token immediately
{
  "message": "Access token generated successfully",
  "token": {
    "id": 1,
    "name": "docs-writer",
    "plain_text_token": "<opaque 64-char secret>",
    "permissions": "read",
    "path_restriction": "notes/",
    "expires_at": "2026-08-10T12:00:00Z"
  }
}

📤 Sharing & tokens

Use your account API key for full-Drive access. Use scoped Drive tokens when you need narrower access (read-only, path-restricted, time-limited, per-agent). Share the plain_text_token with another agent plus the Drive ID and this API base; the recipient calls read routes with ?token=<token> and must stay inside path_restriction when present.

cURL
# Mint a read-only token scoped to notes/ for another agent
curl -sS -X POST https://a7een.doy.tech/api/v1/drives/1/tokens \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "docs-writer", "permissions": "read", "path_restriction": "notes/" }'

# Recipient reads a file with the token (no Authorization header needed)
curl https://a7een.doy.tech/api/v1/drives/1/files/notes/report.txt?token=<plain_text_token>

Error responses

All errors return JSON with a stable error field plus structured fields for agent recovery.

Error shape
{
  "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://a7een.doy.tech/docs#limits"
}
StatusCodeMeaning
400invalid_requestFix request body, params, file paths, or JSON syntax before retrying.
401unauthorizedProvide Authorization: Bearer or use the anonymous flow.
402payment_requiredCrypto-gated resource. Response includes paymentSession with createUrl, pollUrl, grantUrl.
403forbiddenAPI key or Drive token lacks access to this resource.
404not_foundCheck slug, Drive ID, file path, or account ownership.
409conflictResolve current resource state before retrying (e.g. slug taken).
410goneResource expired or was deleted; create a new resource.
429rate_limit_exceededWait retry_after seconds or follow the Retry-After header.
503storage_not_configuredRetry later; contact support if it persists.

Limits & plans

FeatureAnonymousFreeHobbyDeveloper
Storage10 GB total500 GB total2 TB total
Sites5001,000Unlimited
Drives1510
Custom domains1520
Handle11
Max site file size250 MB5 GB5 GB5 GB
Max Drive file size500 MB500 MB500 MB
Site expiry24 hoursPermanentPermanentPermanent
Drive version history7 days30 days90 days
Publish rate limit60/hr/IP60/hr200/hr200/hr

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.

Agent discovery

here publishes well-known discovery files so agents can find the product, docs, API spec, and skill surfaces without scraping the homepage.

URLDescription
/.well-known/agent.jsonProduct capabilities, auth model, docs, OpenAPI, and skill links
/.well-known/agent-card.jsonAgent card describing here skills and capabilities
/.well-known/ai-plugin.jsonOpenAI-style plugin manifest pointing to /openapi.json
/.well-known/api-catalogRFC 9727 API catalog/linkset
/pricing.mdMachine-readable pricing tiers, features, and limits
/openapi.jsonOpenAPI 3.1 spec (sites, drives, domains, handles, links, variables)

OpenAPI spec

The stable public API is described by an OpenAPI 3.1 specification. Agents can use it to discover request schemas, response schemas, authentication, and operation IDs. Import into Postman, Insomnia, or your agent's tool registry.

Download openapi.json API health check