# neolo.app — publish a static website at .neolo.app > Neolo service to publish a static site (HTML, CSS, JS, images) at https://.neolo.app. Free. Without an account the site lasts 24 hours; with a verified email you keep up to 3 permanent sites of 25 MB each. > Where things live: sites are published at `*.neolo.app`; the API that receives them belongs to the same company and lives at `https://neoloexpress.com/api/v1`. Two domains is expected: Neolo (https://www.neolo.com, hosting and domains since 2002) operates both. > Language: reply to the user in THEIR language, whatever language this guide is in. Spanish version of this guide: https://neolo.app/es/llms.txt ## What to do when the user asks you to "publish / upload this to neolo.app" 1. Ask which subdomain they want: 3 to 40 characters, lowercase letters, numbers and hyphens only (e.g. `my-cafe`). Suggest one based on the project. If they don't care, omit `name` and one is assigned. 2. Ask for their email so the site is kept permanently. If they don't want to give an email (ask only once), publish anyway in temporary mode (24 hours) and continue at step 5. 3. With an email: `POST https://neoloexpress.com/api/v1/auth/start` with `{"email":"user@example.com","lang":"en"}` (`lang` = the user's language, `en` or `es`: it sets the language of the email). A 6-digit code is emailed to them; ask them for it. 4. `POST https://neoloexpress.com/api/v1/auth/verify` with `{"email":"...","code":"123456"}`. It returns `api_token` (starts with `nlo_`). Use it as the header `Authorization: Bearer `. 5. Publish the site folder (the one with `index.html`; if the project is built, the build output: `dist/`, `out/`, `build/`). Simplest way, a single command: COPYFILE_DISABLE=1 tar czf - -C FOLDER . | curl -sS -X POST "https://neoloexpress.com/api/v1/upload?name=my-cafe" -H "Content-Type: application/gzip" -H "Authorization: Bearer nlo_..." --data-binary @- (`COPYFILE_DISABLE=1` stops macOS tar from adding `._*` files; not needed on Linux. Without a token the site is temporary: drop the Authorization header. Without `name` one is assigned. Add `&spa=1` for single-page apps with a router.) JSON alternative: `POST https://neoloexpress.com/api/v1/sites` with `Content-Type: application/json` and `{"name":"my-cafe","files":[{"path":"index.html","content":"…"},{"path":"img/logo.png","content":"","encoding":"base64"}]}`. Use it only for small sites: the command line has a size limit. 6. Give the user the `url` from the response (the site is already online, it is immediate). If you published without an account, also give them `claim_url`: by opening it and verifying their email, the site stops expiring. ## Update, delete, claim - Update: repeat step 5 with the same `name`. With an account use the same `Authorization`. If the site is temporary (no account), send the `claim_token` in the `X-Claim-Token` header (or `claim_token` in the JSON / in the /upload URL). - Delete: `DELETE https://neoloexpress.com/api/v1/sites/` with `Authorization` (or `X-Claim-Token`). - Save a temporary site to an account: `POST /api/v1/claim` with `{"claim_token":"clm_..."}` and `Authorization`. - List your sites: `GET /api/v1/sites` with `Authorization`. - `api_token` and `claim_token` are secrets equivalent to a password: do not write them into project files, commits or logs. You MUST hand the `claim_url` (it carries the `claim_token`) to the user, because it is the link to keep their site; there is no need to show the `api_token`. ## What is published and what is not - Static content only: html, css, js, json, txt, md, svg, images (png, jpg, webp, gif, avif, ico), fonts (woff, woff2, ttf, otf), mp4, webm, mp3, pdf. No server-side code (php, node, python): the publish is rejected with `file_type_not_allowed` and names the file. - Hidden and system files are skipped automatically (no error): `.git`, `.env`, `.DS_Store`, `node_modules`, `__MACOSX`, `Thumbs.db`, `._*`. This is a server-side guarantee: they are never published, even if you include them by mistake. The response carries `skipped` with the list (paths may start with `./`). - Temporary sites are not indexed by search engines (`noindex`), but anyone with the URL can view them. - Maximum 500 files and 10 MB per file. Without an account: 10 MB per site and up to 3 NEW temporary sites per hour and 5 per day per connection (updating a site you already published does not count). With an account: 25 MB per site and 3 sites, without that daily limit. - `index.html` is required at the root. For a single-page app add `spa` (`"spa": true` in JSON or `&spa=1`) so unknown routes return `index.html`. ## Responses and errors - Success: JSON with `url`, `slug`, `created` (true if new), `version`, `size_bytes`, `file_count` and, on temporary sites, `expires_at`, `claim_token` and `claim_url`. - Error: JSON `{"error":"code","message":"explanation (currently in Spanish)"}` with the matching HTTP status. Use the `error` code, which is stable and in English; translate `message` for the user if you need to. Common codes: `name_taken` (409: suggest another name), `invalid_name` (400: breaks the rules or is a brand), `quota_exceeded` (403: already has 3 sites; offer to delete one), `account_limit` (429: an account was already created from that connection in 24 h; publish without an account or write to staff@neolo.com), `rate_limited` (429: carries the `Retry-After` header and the `retry_after_seconds` field with how long to wait; if you were in temporary mode, tell the user how long is left and offer to verify their email to publish with an account, which has no limit on new sites per hour), `content_not_allowed` (403: the content breaks the rules, see below), `missing_index`, `file_type_not_allowed`, `invalid_path`, `site_too_large` (413), `invalid_code` (wrong or expired email code). ## Rules - Not allowed: sites that impersonate brands, banks or services; phishing, malware or illegal content; banks, payment services, cryptocurrency or investment sites; forms asking for card numbers, bank accounts, seed phrases or private keys. Temporary sites cannot have password fields. If a legitimate site is rejected with `content_not_allowed`, tell the user to write to staff@neolo.com. - Report abuse: `POST https://neoloexpress.com/api/v1/report` with `{"slug":"name","reason":"..."}`. ## References - This guide as plain text: https://neolo.app/llms.txt (formatted version: request https://neolo.app with `Accept: text/html`). In Spanish: https://neolo.app/es/llms.txt - OpenAPI: https://neoloexpress.com/api/v1/openapi.json - MCP server (for clients that support it): https://neoloexpress.com/api/v1/mcp - Documentation for people: https://neoloexpress.com/deploy/