API reference
Every public endpoint, request shape, and error code
Base URL: https://shiply.now. All bodies are JSON. The machine-readable spec
lives at /openapi.json; a plain-text agent
guide at /llms.txt.
Authentication
Pass Authorization: Bearer shp_… for owner operations. Publish without it for
anonymous (24 h) sites.
Publishing
POST /api/v1/publish
Create a site (or a new version of one).
| Field | Type | Notes |
|---|---|---|
files | array, required | 1–1000 entries of { path, size, contentType?, hash? } |
files[].path | string | relative path, no ..; index.html serves at / |
files[].size | int | bytes |
files[].hash | string | optional lowercase sha256 hex — enables hash-skip |
spaMode | boolean | serve index.html for unknown paths |
ttlSeconds | int | anonymous only; capped at 86400 |
claimToken | string | update an existing anonymous site |
viewer | object | { title?, description?, ogImage? } |
Returns slug, siteUrl, upload.{versionId, uploads[], skipped[], finalizeUrl, expiresInSeconds}, and for anonymous creates: claimToken, claimUrl,
expiresAt.
POST /api/v1/publish/{slug}/finalize
Body { "versionId": "…" }. Verifies every object was uploaded (server-copies
hash-skipped files), then flips the site live atomically. 409 conflict if
uploads are missing or the version was already finalized.
POST /api/v1/publish/{slug}/uploads/refresh
Body { "versionId": "…" }. Re-presigns upload URLs for a still-pending version.
POST /api/v1/publish/{slug}/claim
Body { "token": "…" }, auth required (key or session). Adopts an anonymous
site: permanent, owned, claim token cleared.
Site management (owner auth)
| Endpoint | Action |
|---|---|
GET /api/v1/publishes | list your sites |
GET /api/v1/publish/{slug} | site detail + version history |
PATCH /api/v1/publish/{slug}/metadata | { title?, spaMode?, addedToProfile? } |
DELETE /api/v1/publish/{slug} | delete site + all stored files |
Variables (owner auth)
Encrypted key/value store for your agents' third-party credentials. Values are AES-256-GCM encrypted at rest; they are readable only by you and are never injected into published sites.
| Endpoint | Action |
|---|---|
GET /api/v1/variables | list (values masked; add ?reveal=1 for plaintext) |
PUT /api/v1/variables | { name, value } — upsert; name is UPPER_SNAKE ≤64 chars, value ≤8 KiB |
DELETE /api/v1/variables/{name} | delete |
Agent onboarding
| Endpoint | Action |
|---|---|
POST /api/auth/agent/request-code | { email } → emails a 6-digit code (30 s cooldown) |
POST /api/auth/agent/verify-code | { email, code } → { apiKey } (code single-use, 10 min) |
Profiles
| Endpoint | Action |
|---|---|
GET /api/v1/profile/sites?handle= | public: a profile's sites |
PATCH /api/v1/profile | owner: { useProfile?, autoAddToProfile? } |
PATCH /api/v1/profile/username | owner: { username } |
Errors
Every error is { "error": { "code", "message" } }.
| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | malformed body, bad path, bad code |
unauthorized | 401 | missing/invalid key or session |
payment_required | 402 | plan limit reached (sites/drives/handles/domains) or a paid feature (analytics, password/invite-only) — upgrade |
forbidden | 403 | not allowed (e.g. non-public read/insert on Site Data) |
not_found | 404 | unknown slug/version/profile, bad claim token |
conflict | 409 | uploads incomplete, version not pending, handle taken |
rate_limit_exceeded | 429 | e.g. requesting codes too fast |
service_unavailable | 503 | temporary failure (e.g. email delivery) |