Conventions.
Paths are relative to this website’s origin. Both endpoints accept POST requests with application/json or application/x-www-form-urlencoded. Request bodies are capped at 2,048 bytes.
JSON requests receive JSON. Successful URL-encoded form requests receive an HTML confirmation page; errors remain JSON. The Accept header does not change this behavior.
Responses use Cache-Control: no-store. There is no API-key mechanism. Browser requests must be same-origin; the service does not enable cross-origin CORS access. An absent Origin is allowed for non-browser clients.
Download the OpenAPI 3.1 contract. The API paths are unversioned. The contract describes the current website implementation.
/api/early-access
Registers an email with explicit consent to early-access updates. Input is validated before a signup attempt is counted.
| Field | Contract |
|---|---|
email | Required string. Trimmed and lowercased; maximum 254 characters. Local part: maximum 64 characters, no leading, trailing or consecutive dots. ASCII mailbox syntax; use punycode for internationalized domains. The domain must contain a dot. |
consent | Required. JSON accepts true or "on"; an HTML form sends on. Other values are rejected. |
website | Optional honeypot. Omit or leave empty. Non-empty trimmed string values are rejected. |
Additional fields are ignored. Validation does not verify that the sender owns the email address or that the mailbox exists.
// Website form: emailInput is the visitor's email field.
const response = await fetch('/api/early-access', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: emailInput.value,
consent: consentInput.checked,
website: ''
})
});
const result = await response.json();
if (!response.ok || result.success !== true) {
throw new Error(result.error || 'Signup was not confirmed.');
}
// A new signup returns result.removalToken. Save it privately.
// A duplicate succeeds without returning a token.# Local Pages runtime. Use an address you control.
: "${SIGNUP_EMAIL:?Set SIGNUP_EMAIL before submitting}"
curl -i http://localhost:8789/api/early-access \
-H 'Origin: http://localhost:8789' \
--data-urlencode "email=$SIGNUP_EMAIL" \
--data-urlencode 'consent=on'Successful responses
- 201 JSON: a new row was persisted. The response has
success: trueandremovalToken, a random 64-character lowercase hexadecimal credential. - 200 JSON: the email was already present. The response is
{"success":true}. The original removal credential is retained and is not disclosed. - 200 HTML: a form submission was saved or already existed. A new signup’s confirmation page includes its private removal link and token.
A successful signup requires a successful D1 operation. The frontend displays “You’re in” only after receiving that confirmation. No email is sent by this endpoint.
Rate limit
After input validation, an atomic counter permits ten signup attempts per network address per hourly bucket. Duplicate attempts count too. The next attempt receives 429 with Retry-After in seconds. Buckets follow the server’s Unix-time hour boundaries.
/api/remove
Accepts a required token string matching ^[a-f0-9]{64}$. The service hashes it and deletes the matching signup. No email address is required.
# Use the token returned by your local signup.
: "${REMOVAL_TOKEN:?Set REMOVAL_TOKEN before submitting}"
curl -i http://localhost:8789/api/remove \
-H 'Origin: http://localhost:8789' \
--data-urlencode "token=$REMOVAL_TOKEN"A successful JSON response is {"success":true} with status 200. A form request receives an HTML confirmation. A valid token that matches no row also succeeds, making removal idempotent. Success does not reveal whether a matching signup existed.
The removal endpoint has the same origin, body-size and content-type checks. It does not use the signup endpoint’s rate limiter.
Errors & retries.
Errors return a JSON object with success: false and a human-readable error string. There is no separate machine-readable error-code field.
| HTTP | Meaning |
|---|---|
400 | Invalid input, malformed JSON, missing consent, a filled honeypot or an invalid removal token. |
403 | A supplied Origin differs from the request origin, or Sec-Fetch-Site is cross-site. |
405 | The request method is not POST. The response includes Allow: POST. |
413 | The request body exceeds 2,048 bytes. |
415 | Content-Type is not application/json or application/x-www-form-urlencoded. |
429 | Signup attempt limit reached. Retry-After contains the seconds until the next hourly bucket. Signup only. |
503 | The required database binding is unavailable or the database operation failed. |
Correct input errors before retrying. For 429, respect Retry-After. For a network failure or 503, show an unconfirmed state and offer a later retry. Repeated email submissions do not create additional rows.
If a new signup is saved but its response never reaches the browser, a retry returns duplicate success without the original token. There is currently no token-recovery or email-delivery flow. Do not promise one.