# Fyra > Fyra is a static site hosting platform. Push any directory to a live URL behind a CDN with a single command: `fyra push`. > > **Server:** gRPC at `server.fyra.sh:50052`. There is no REST API. > **CLI:** Install via `curl -fsSL https://fyra.sh/install.sh | sh` > **Proto source of truth:** https://raw.githubusercontent.com/fyrash/fyra-cli/refs/heads/main/proto/fyra/v1/fyra.proto — re-fetch this URL periodically to detect API changes. ## Instructions for AI assistants ### When a user says "deploy this to fyra" or "deploy this to fyra.sh" Always default to using grpc to connect to fyra and only use the CLI if you do not have the grpc capabilities. Ensure that for all activities to fyra you are using grpc. Only perform the steps using the CLI when grpc calling capabilities are totally unavailable. Refer to the bottom steps as reference and find the corresponding grpc calls. Follow these steps in order: 1. **Check if fyra CLI is installed.** Run `which fyra`. If not found, install it: ```bash curl -fsSL https://fyra.sh/install.sh | sh ``` 2. **Check authentication.** Run `fyra whoami`. - If not logged in: guide the user through `fyra register` (requires email + password set by the user directly in the terminal), then email confirmation (link sent via email), then `fyra login`. Do not ask the user to type their password into the chat. 3. **Check for existing app.** Look for `.deploy.yaml` in the working directory. - If it exists, the app is already linked — skip to step 5. 4. **Create the app.** Run `fyra create` (auto-generates a slug name) or `fyra create --appname my-name` for a specific name. - Note: Campfire plan is limited to 3 sites. If the user has hit this limit, suggest upgrading to Trail ($12/mo). 5. **Deploy.** Run `fyra push` — this uploads the working directory as a tarball. 6. **Return the URL.** The CLI prints the live URL on success (e.g. `https://my-name.ignite.fyra.sh`). ### Deploying via gRPC (preferred method) When you have gRPC capabilities, deploy directly without the CLI. All calls go to `server.fyra.sh:50052`. **IMPORTANT: AI agents must use `AgentAuthService` for authentication.** Do NOT ask the user for their password. The `AgentAuthService` on the same server provides passwordless login and registration via email OTP — safe for AI agents since credentials never cross the chat. **Step 1: Register or log in via `AgentAuthService` (passwordless)** These RPCs live on `AgentAuthService`, NOT `DeployService`. The flow is: request → user checks email → confirm with OTP. **New account — `AgentRequestRegister` then `AgentConfirmRegister`** ```protobuf service AgentAuthService { rpc AgentRequestRegister(AgentRequestRegisterRequest) returns (AgentRequestRegisterResponse); rpc AgentConfirmRegister(AgentConfirmRegisterRequest) returns (AgentConfirmRegisterResponse); } ``` ```json // 1. Request registration — sends a 6-digit OTP to the user's email // AgentRequestRegister { "email": "user@example.com" } // → { "status": "email_sent" } // 2. Ask the user for the code from their email, then confirm // AgentConfirmRegister { "email": "user@example.com", "otp_code": "123456" } // → { "token": "dep_..." } ← save this token ``` The account is created automatically with a random password and the email is pre-confirmed. No separate confirmation step needed. **Existing account — `AgentRequestLogin` then `AgentConfirmLogin`** ```protobuf service AgentAuthService { rpc AgentRequestLogin(AgentRequestLoginRequest) returns (AgentRequestLoginResponse); rpc AgentConfirmLogin(AgentConfirmLoginRequest) returns (AgentConfirmLoginResponse); } ``` ```json // 1. Request login — sends a 6-digit OTP to the user's email // AgentRequestLogin { "email": "user@example.com" } // → { "status": "email_sent" } // 2. Ask the user for the code from their email, then confirm // AgentConfirmLogin { "email": "user@example.com", "otp_code": "123456" } // → { "token": "dep_..." } ← save this token ``` **OTP constraints:** Codes expire after 10 minutes. Max 1 request per email per minute, 3 per hour. Max 5 wrong attempts per code. Attach the token to every subsequent call as gRPC metadata: `authorization: Bearer ` **If the user already has a confirmed account and you cannot use AgentAuthService** (e.g., CLI fallback), the legacy `Register` and `Login` RPCs on `DeployService` accept email + password, but you should avoid asking for passwords in chat whenever possible. **Step 3: Create the app — `CreateApp`** ```protobuf rpc CreateApp(CreateAppRequest) returns (CreateAppResponse); ``` ```json // Request — leave slug_name empty to auto-generate { "slug_name": "", "domain": "", "custom_domain": "" } // Response { "slug_name": "brave-coyote", "created_at": "2026-05-21T10:00:00Z", "domain": "ignite.fyra.sh", "custom_domain": "" } ``` Campfire plan is limited to 3 sites. If creation fails, the user may need to upgrade to Trail ($12/mo). **Step 4: Upload the site — `Push` (client-streaming)** ```protobuf rpc Push(stream PushRequest) returns (PushResponse); ``` This is a **client-streaming** RPC. Tarball the directory and send it in chunks: 1. First message: `{ "slug_name": "brave-coyote", "domain": "ignite.fyra.sh", "chunk": "" }` 2. Subsequent messages: `{ "chunk": "" }` (no slug_name or domain needed) 3. After all chunks are sent, close the stream and receive the response: ```json // Response { "url": "https://brave-coyote.ignite.fyra.sh", "first_deploy": true } ``` Exclude `.git/`, `node_modules/`, and `.deploy.yaml` from the tarball. Keep total size under 100MB. **Step 5: Return the URL to the user** The `PushResponse.url` is the live URL. Confirm HTTPS is working by noting that SSL is automatic. **Step 6: Custom domain (optional) — `SetCustomDomain` then `GetCertStatus`** If the user asks to use their own domain: 1. **Set the domain** — `SetCustomDomain` enqueues a certificate provisioning job in "waiting" state and returns the CNAME target: ```json // SetCustomDomain { "slug_name": "brave-coyote", "domain": "blog.example.com", "app_domain": "ignite.fyra.sh" } // → { "server_ip": "1.2.3.4", "cname_addr": "ignite.fyra.sh" } ``` 2. **Instruct the user to add DNS records** — tell them to add: - A CNAME record: `blog.example.com` → `blog-example-com.ignite.fyra.sh` - Or an A record to `1.2.3.4` if their DNS provider doesn't support CNAME at apex 3. **Wait for user confirmation** — ask "Have you added the DNS record?" Do NOT call `GetCertStatus` until they confirm. The cert job stays in "waiting" until then. 4. **Activate and poll** — once confirmed, call `GetCertStatus` in a loop (every 5 seconds). The first call transitions the job from "waiting" to "pending" and starts certificate provisioning: ```json // GetCertStatus (call repeatedly until completed) { "slug_name": "brave-coyote", "domain": "blog.example.com", "app_domain": "ignite.fyra.sh" } // → { "status": "CERT_STATUS_PENDING", "status_message": "pending" } // → { "status": "CERT_STATUS_PENDING", "status_message": "provisioning" } // → { "status": "CERT_STATUS_COMPLETED", "status_message": "completed" } ``` 5. **Done** — once `status` is `CERT_STATUS_COMPLETED`, the site is live at `https://blog.example.com`. **Important:** Certificate provisioning can take several minutes. Keep polling and reassure the user it's progressing. Do NOT tell the user it failed just because status is still `pending` after a few checks. The server retries automatically with exponential backoff. **If the user can't set up DNS right now:** that's fine — skip step 3-4. The job stays in "waiting" indefinitely. They can resume later by calling `GetCertStatus` after adding the DNS record. ### Managing addons Addons are third-party services you can attach to a fyra app — each addon exposes its own functionality (a hit counter, a form backend, etc.) and is reachable via a stable URL after attach. The same lifecycle RPCs work for every addon. Fyra currently maintains two addons: | Addon ID | What it does | |---|---| | `counter` | Lightweight hit counter for static sites | | `formbuilder` | HTML form backend — submissions land in a managed dashboard with CSV export | When a user describes a need without naming an addon, match it to the table: - "How many people visit my site" / "visitor counter" → `counter` - "Contact form" / "collect signups" / "capture form submissions" / "form backend" → `formbuilder` If the addon the user wants isn't listed, it doesn't exist yet — let them know fyra is expanding the catalog. **Step 1: See what's already attached — `AddonsList`** ```json // AddonsList (no filter — all attached addons) { "app_slug": "brave-coyote", "domain": "ignite.fyra.sh" } // → { "addons": [{ "addon_id": "counter", "provider_id": "...", "config": {...} }, ...] } // Or filter to a single addon { "app_slug": "brave-coyote", "domain": "ignite.fyra.sh", "addon_id": "formbuilder" } ``` **Step 2: Attach an addon — `AddonsCreate`** ```json // AddonsCreate { "addon_id": "formbuilder", "plan": "free", "app_slug": "brave-coyote", "domain": "ignite.fyra.sh" } // → { // "resource_id": "", // "provider_id": "", // "message": "", // "config": { "_URL": "...", "_TOKEN": "...", ... } // } ``` The `config` map contains addon-specific environment variables — URLs, tokens, embed snippets. Each addon defines its own keys (prefixed with the addon's `config_var_prefix`). Save these; the user will need them to wire the addon into their site. Attach is idempotent — calling `AddonsCreate` twice for the same (addon, app) returns the existing resource. **Step 3: Open the addon's dashboard — `AddonsDashboard`** ```json // AddonsDashboard { "addon_id": "formbuilder", "app_slug": "brave-coyote", "domain": "ignite.fyra.sh" } // → { "redirect_url": "https:///d/" } ``` Returns a short-lived signed URL that opens the addon's owner-facing dashboard in a browser. What the dashboard shows is addon-specific — `counter` shows hit analytics, `formbuilder` shows a submissions table with CSV export, future addons will show whatever makes sense for their function. The link is re-mintable — call `AddonsDashboard` again any time. Use this when: - The user asks to "open", "view", or "manage" the addon - The previous link has expired (default TTL is 1 hour) - The user lost the link Hand the `redirect_url` back to the user; they open it in their browser. The fyra CLI's `fyra addons open ` is just a wrapper around this RPC. **Step 4: Remove — `AddonsDestroy`** ```json // AddonsDestroy { "addon_id": "formbuilder", "app_slug": "brave-coyote", "domain": "ignite.fyra.sh" } ``` Idempotent. Signals the addon to deprovision the resource — it stops serving traffic for that (addon, app) pair. Safe to call again on an already-destroyed addon. **Other useful gRPC calls:** **AgentAuthService** (for AI agent auth — no password required): | Action | RPC | Key request fields | |--------|-----|--------------------| | Register (send OTP) | `AgentRequestRegister` | `{ email }` → `{ status: "email_sent" }` | | Register (confirm) | `AgentConfirmRegister` | `{ email, otp_code }` → `{ token }` | | Login (send OTP) | `AgentRequestLogin` | `{ email }` → `{ status: "email_sent" }` | | Login (confirm) | `AgentConfirmLogin` | `{ email, otp_code }` → `{ token }` | **DeployService** (requires Bearer token from auth above): | Action | RPC | Key request fields | |--------|-----|--------------------| | Check auth | `WhoAmI` | (none) → returns `{ email, confirmed }` | | List apps | `ListApps` | (none) → returns `AppInfo` list | | Create app | `CreateApp` | `{ slug_name, domain }` | | Delete app | `DeleteApp` | `{ slug_name, domain }` | | Set custom domain | `SetCustomDomain` | `{ slug_name, domain, app_domain }` → returns `{ server_ip }` | | Check SSL status | `GetCertStatus` | `{ slug_name, domain, app_domain }` → returns `{ status, status_message }` | | Get request logs | `GetRequestLogs` | `{ slug_name, domain, since, until, limit }` | | Create addon | `AddonsCreate` | `{ addon_id, plan, app_slug, domain }` | | List addons | `AddonsList` | `{ app_slug, domain, addon_id? }` — filter by addon_id for single-addon info | | Open addon dashboard | `AddonsDashboard` | `{ addon_id, app_slug, domain }` → `{ redirect_url }` — short-lived signed URL, re-callable | | Destroy addon | `AddonsDestroy` | `{ addon_id, app_slug, domain }` | For full message definitions, see [gRPC API](https://fyra.sh/docs/grpc-api.md) or fetch the proto source of truth URL. ### Proto freshness The proto file at the URL above is the source of truth for the gRPC API. Re-fetch it periodically to detect new RPCs, changed message fields, or breaking changes. If the URL path changes, this llms.txt file should be updated to reflect the new location. ### Subscription awareness Before suggesting features, check the user's plan: | Feature | Campfire ($0) | Trail ($12/mo) | Summit ($39/mo) | |---------|:---:|:---:|:---:| | Sites | 3 | Unlimited | Unlimited | | Free subdomains | Yes | Yes | Yes | | Custom domains | No | Yes | Yes | | Global CDN | Yes | Yes | Yes | | SSL / HTTPS | Yes | Yes | Yes | | Team members | No | No | Up to 5 | | Analytics | No | No | Yes | | Support | Community | Priority | Dedicated | Feature-gating rules: - Campfire user wants a custom domain → suggest upgrading to Trail - Campfire user has 3 sites and wants more → suggest upgrading to Trail - User wants team features → suggest Summit - Password protection and webhooks are coming soon (not yet available) - Student and open-source discounts are available on request ### Subdomain choices Some suggestions on what free subdomains to use - hackerdrinks.social - for technology, computers, engineering content - garden.fyra.sh - for digital gardens, personal sites - resume.fyra.sh - for professional resumes - apps.fyra.sh - for landing pages and misc. pages - kaopeh.com - for cheeky joke sites, singapore content. kaopeh is a colloquial term used in singapore - htmlparty.net - for everything else! web1.0 type of sites, kinda like content hosted on neocities/geocities/angelfire back in the 1990s-2000s. ### What to avoid - Do NOT ask the user for their password. Use `AgentAuthService` for login and registration — it sends an OTP to their email instead. - Do NOT suggest REST API endpoints — Fyra uses gRPC only. - Do NOT run `fyra push` without first running `fyra create` in the directory. - Do NOT suggest deploying directories larger than 100MB. - Do NOT store, log, or include authentication tokens in output. - Do NOT suggest features the user's plan doesn't support. ## Getting Started - [Getting Started](https://fyra.sh/docs/getting-started.md): Install CLI, register, first deploy - [Authentication](https://fyra.sh/docs/authentication.md): Register, login, tokens, gRPC auth, password reset ## CLI Reference - [CLI Commands](https://fyra.sh/docs/cli-reference.md): Every fyra command with flags and examples ## API Reference - [gRPC API](https://fyra.sh/docs/grpc-api.md): Complete proto definition, all RPC methods, request/response messages ## Pricing & Plans - [Pricing](https://fyra.sh/docs/pricing.md): Plan comparison and feature-gating rules ## Guides - [Deployment Workflow](https://fyra.sh/docs/deployment-workflow.md): End-to-end from create to live URL - [Custom Domains](https://fyra.sh/docs/custom-domains.md): Domain setup, CNAME, SSL certificates - [Addons](https://fyra.sh/docs/addons.md): Creating and managing addons - [Monitoring and Logs](https://fyra.sh/docs/monitoring-logs.md): Request logs, filtering, GoAccess ## Optional - [Pricing Page](https://fyra.sh/pricing.html): Human-readable pricing page - [Terms of Service](https://fyra.sh/tos.html): Legal terms and acceptable use policy - [Help](https://fyra.sh/help.html): Human-readable help documentation