# Overview Source: https://archie.com/docs/account Overview of the My Account surface in Archie — profile, billing, notifications, activity, and security in one place. Your account surface centralizes everything tied to you as an individual user, separate from your projects and organizations. Open it from the avatar menu in the top-right of any page. ## What is in your account The account section has five surfaces, each covered in detail on its own page: > **[Profile and preferences](/docs/introduction/account/profile-and-preferences)** > > Name, email, time zone, and interface preferences. > **[Notifications](/docs/introduction/account/notifications)** > > Configure email and in-app notifications across projects and orgs. > **[Activity](/docs/introduction/account/activity)** > > Timeline of actions across your projects and organizations. > **[Security](/docs/introduction/account/security)** > > Password, two-factor authentication, and active sessions. ## Billing Billing, plans, credits, and invoices all live in [Plans and credits](/docs/introduction/getting-started/plans-and-credits). That page covers Free, Pro, Team, and Enterprise pricing, Archie Core backend tiers, AI credit usage, top-ups, rollovers, and invoice access. ## Account vs. organization vs. project Archie has three scopes. Settings on each scope only apply at that scope: * **Account** — your personal user. Profile, personal billing, personal notifications. * **Organization** — a workspace shared across a team. Org plan, members, integrations. See [Organizations overview](/docs/introduction/organizations/overview). * **Project** — an individual application. Data model, deployments, environment variables. Settings live in the project surface. If you are on a Team or Enterprise plan, billing typically lives at the organization level rather than your account. ## Switching between organizations If you belong to multiple organizations, the org switcher appears in the top-left. Switching changes which projects are visible — your account surface stays the same regardless of the active org. # Activity Source: https://archie.com/docs/account/activity Review a timeline of your actions across projects, organizations, and teams in Archie. The activity page shows a timeline of every action you have taken across Archie — projects you opened, plans you edited, builds you ran, comments you left, members you invited. ## What is in the activity feed? Each entry has a timestamp, the action, the project or organization scope, and a deep link to the affected surface. Entries cover: * Project creation, deletion, archive, and restore * Plan and prototype edits * Builds and deployments (start, finish, success, failure) * Comments, mentions, replies * Team invitations and role changes * Billing actions (plan changes, payment method updates) * API key creation and rotation * Environment changes ## How do I find a specific action? Filters at the top of the page let you narrow by: * **Time range** — last 24 hours, 7 days, 30 days, custom * **Project** — only show entries from a specific project * **Action type** — builds, comments, settings changes, and so on * **Scope** — your account, a specific organization, or all A search field also matches the free-form description text on each entry. ## Personal activity vs. team activity The activity page on your account shows actions *you* took. To see what your teammates have been doing in a shared organization, open **Organization → Activity** instead. Team-level visibility requires Team or Enterprise plans, where activity feed is part of [team management](/docs/introduction/organizations/managing-team-members). ## Audit retention How long activity stays available depends on your plan: * Free: 1 hour * Pro: 7 days * Team: 90 days * Enterprise: custom (typically 1 year, configurable) For audit needs longer than the retention window, export the activity feed as CSV from the page header. Or contact [support](/docs/introduction/community-support/contacting-support) about Enterprise audit log retention. # Notifications Source: https://archie.com/docs/account/notifications Configure email and in-app notifications for Archie project events, team activity, and billing. Archie sends notifications about project activity, team changes, and billing events. The notifications page lets you control which events trigger an email or appear in the in-app feed. ## What can I be notified about? Notifications fall into four categories: * **Project events** — build started, build finished, deployment success or failure, errors. * **Team activity** — new members, role changes, invitations accepted, comments. * **Billing** — upcoming renewals, failed payments, credit warnings, plan changes. * **Product updates** — release notes, breaking changes (toggleable). For each category, you can choose **email**, **in-app**, **both**, or **none**. ## How do I change my notification preferences? Open **My Account → Notifications**. Each category has a row with email and in-app toggles. Save happens inline as you change toggles. ## Per-project overrides If you want different defaults for a specific project — for example, all events for a critical production project but only failures for a sandbox — open the project settings and override per category. Per-project settings take precedence over your account defaults. ## In-app notifications The bell icon in the top nav shows unread count. Click it to see recent notifications grouped by project. Click a notification to jump to the relevant surface (a build log, a deployment, a billing page). ## Email digests Instead of one email per event, you can enable a daily digest for low-priority categories like product updates and team activity. Critical events (failed payments, build failures) always send immediately. ## What about Slack or webhook delivery? Slack notifications are an [organization integration](/docs/introduction/organizations/organization-integrations) — they apply to all members of an org or project. Webhook-driven notifications run through [Archie Core webhooks](/docs/features/backend/integrations/webhooks) inside your built app. # Profile and preferences Source: https://archie.com/docs/account/profile-and-preferences Update your name, email, time zone, and Archie interface preferences from your account settings. The profile and preferences page controls how Archie identifies you across projects and how the interface presents itself. ## How do I update my profile? Open **My** **Account → Profile and preferences**. The fields you can edit: * **Display name** — shown in activity feeds, comments, and team views. * **Email** — the primary email for sign-in, billing, and notifications. Changing it requires re-verification. * **Avatar** — uploaded image, defaults to initials on a colored background. * **Time zone** — used to format timestamps in the activity feed and notifications. * **Locale** — language preference for the interface. Changes save inline as you make them. Email changes require you to click the verification link sent to the new address before they take effect. ## Interface preferences Theme, density, and editor preferences also live on this page: * **Theme** — system, light, or dark. * **Editor theme** — for the IDE view inside projects. * **Default project view** — visual editor or code IDE. * **Sidebar collapse** — keep the project sidebar expanded or collapsed by default. Preferences sync across browsers when you sign in. ## Connected accounts If you signed up with Google or GitHub, that connection shows here. You can connect or disconnect identity providers — disconnecting requires that another sign-in method is active so you do not lock yourself out. ## What does not live here Notification preferences are on the [Notifications page](/docs/introduction/account/notifications). Active sessions and password changes are on the [Security page](/docs/introduction/account/security). Plan, payment, and invoice history live on [Plans and credits](/docs/introduction/getting-started/plans-and-credits). # Security Source: https://archie.com/docs/account/security Manage your password, two-factor authentication, active sessions, and connected devices for your Archie account. The security page is where you manage how you sign in and which sessions are active. Take a moment after signing up to enable two-factor authentication and review the session list. ## Changing your password Open **My Account → Security** and click **Change password**. Enter your current password, then the new one. Archie signs out all other active sessions when the password changes — you stay signed in on the device where you made the change. If you signed up with Google or GitHub and never set an Archie-native password, the password section shows a **Set password** option instead. ## Two-factor authentication Two-factor authentication (2FA) adds a code from an authenticator app on top of your password. Enable it from **Account → Security → Two-factor authentication**. You scan a QR code with an authenticator app (Authy, 1Password, Google Authenticator), then enter a code to confirm. Archie generates 10 backup codes — store them in a password manager. Each code works once, in case you lose access to your authenticator. Multi-factor authentication is required on all Enterprise plans. On other plans it is optional but recommended. ## Active sessions The sessions list shows every device currently signed in to your account: device, browser, location (approximate, IP-based), and last activity. Sign out of any session you do not recognize by clicking **Sign out** next to it. **Sign out all** signs out every session including the current one. ## Connected accounts OAuth providers connected to your account (Google, GitHub) appear here with disconnect buttons. You cannot disconnect the only sign-in method on your account — set a password first, or connect another provider. ## API keys API keys for Archie itself are scoped per project, not per account, and live in [project settings](/docs/features/backend/settings/api-keys). The security page does not manage them. ## Suspicious activity If you receive a notification about an unfamiliar sign-in, change your password immediately and sign out all other sessions. Then contact [support](/docs/introduction/community-support/contacting-support) if you suspect your account has been accessed without your permission. # Archie alternatives: how Archie compares to other AI app builders Source: https://archie.com/docs/alternatives The best Archie alternatives compared: Lovable, Bolt, Base44, v0, Replit, and Cursor — how each works and when to choose Archie instead. **Looking at Archie next to Lovable, Bolt, Base44, v0, Replit, or Cursor? Here's the short version.** Most AI app builders now generate working apps, and many include a backend and a planning step before changes. Archie's difference is the approach: it generates a plan of the whole app that you shape first, then builds the frontend and a relational backend from that plan, with Git-like environments for shipping changes safely. > [!NOTE] > > Competitor products change often. These comparisons reflect each product's public documentation and pricing as of September 2026. If something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## Compare Archie with > **[Archie vs. Lovable](/docs/faq/archie-vs-lovable)** > > Conversational builder with Lovable Cloud vs. plan-first with Archie Core. > **[Archie vs. Bolt](/docs/faq/archie-vs-bolt)** > > In-browser speed and tokens vs. whole-app structure and credits. > **[Archie vs. Base44](/docs/faq/archie-vs-base44)** > > Managed platform backend vs. a standard relational database. > **[Archie vs. v0](/docs/faq/archie-vs-v0)** > > Next.js on Vercel with a connected backend vs. a backend included. > **[Archie vs. Replit](/docs/faq/archie-vs-replit)** > > General-purpose cloud workspace vs. a focused path to production. > **[Archie vs. Cursor](/docs/faq/archie-vs-cursor)** > > AI code editor vs. AI app builder — and how to use both. ## At a glance | | Starts from | Backend | Best for | | - | - | - | - | | **Archie** | A plan of the whole app | Included: Archie Core on PostgreSQL or MySQL | Production web apps with real users, roles, and data | | **Lovable** | A prompt in chat | Included: Lovable Cloud, plus Supabase | Fast conversational iteration | | **Bolt** | A prompt in chat | Included: Bolt Cloud | Quick experiments in the browser | | **Base44** | A prompt in chat | Included: Base44's managed backend | Zero-setup internal tools and MVPs | | **v0** | A prompt in chat | Connects to external backends | Next.js UI on Vercel | | **Replit** | A prompt or code | Built-in database you configure | General-purpose cloud development | | **Cursor** | Your codebase | You bring your own | Developers editing existing code | ## What sets Archie apart > **[Definition first](/docs/features/plan/overview)** > > A plan of the whole app — modules, user types, services, integrations — that every build follows. > **[A real backend](/docs/features/backend/overview)** > > PostgreSQL or MySQL, auto-generated GraphQL and REST APIs, and row-level permissions. > **[Safe changes](/docs/features/backend/environments/overview)** > > Git-like environments with branching, schema diff, merge, and backups. Have a specific question? See the [Archie vs. other tools FAQ](/docs/faq/archie-vs-other-tools). # Archie vs. Base44: which AI app builder should you use? Source: https://archie.com/docs/alternatives/archie-vs-base44 Archie vs. Base44 compared: planning, backend and database, portability, environments, pricing, and when to choose each AI app builder. **Archie and Base44 both generate full-stack web apps — frontend, backend, database, and auth — from a plain-language description.** Base44, owned by Wix since 2025, is built for the fastest path to a working app on its managed platform. Archie is definition-first: it generates a plan of the whole app that you shape before any code is written, and it runs your backend on a standard relational database you can query, branch, and export. > [!NOTE] > > Last reviewed September 2026, based on Base44's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | Base44 | | - | - | - | | **Starting point** | A structured [plan](/docs/features/plan/overview) of the whole app | A prompt in chat | | **Planning** | A lasting definition of the app that every build follows | A plan mode for mapping out a build | | **Backend** | [Archie Core](/docs/features/backend/overview) on PostgreSQL or MySQL | Base44's managed backend: data, functions, auth, hosting | | **Querying your data** | Standard SQL in the [SQL Playground](/docs/features/backend/data-model/sql-playground), plus GraphQL and REST | Through Base44's platform and SDK | | **Environments** | Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups | Managed by the platform | | **Code sync** | Two-way GitHub sync on paid plans | GitHub integration on Builder and above | | **Credits** | One pool of AI credits | Two pools: message credits for building, integration credits for runtime actions | | **Free plan** | 50 AI credits per month | 25 message credits and 100 integration credits | | **Entry paid plan** | Pro from \$25/month for 200 AI credits | Starter from \$20/month (\$16/month billed annually) | ## How each one works **Base44** generates a working web app from a prompt, with the database, backend logic, authentication, and hosting handled on its managed platform. Built-in integrations cover services like Stripe, Slack, SendGrid, and Twilio. Base44 also offers its backend as a service for developers building their own apps. **Archie** starts with a [plan](/docs/features/plan/overview) — overview, modules, user types, system services, and integrations — that you edit until it's right. **Generate Frontend** lays out every screen in a [UI architecture](/docs/features/frontend/generating) and builds a click-through prototype, **Generate Backend** builds the schema and APIs on [Archie Core](/docs/features/backend/overview), and **Generate Full-Stack** turns the prototype into a deployed app. Like Base44, Archie Core is also available on its own as a backend for developers. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### Planning the whole app first Base44's plan mode helps you map out a build before it starts. Archie's plan goes further: it's the lasting definition of the whole app, and every build reads from it — the sitemap of every screen, each screen's spec and access level, and the backend's data model and permissions. Change the plan, and the next build follows. See [The plan and how Archie works](/docs/faq/the-plan). ### A standard relational database Archie Core runs on PostgreSQL or MySQL. You design the schema visually in the [data model](/docs/features/backend/data-model/overview), see it as a live [ERD](/docs/features/backend/data-model/erd), query it with plain SQL in the [SQL Playground](/docs/features/backend/data-model/sql-playground), shape it with [views](/docs/features/backend/data-model/views) and [indexes](/docs/features/backend/data-model/indexes), and [export](/docs/features/backend/data-model/exporting-data) any table to CSV. Every table gets auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs. ### Portability With Archie, your code syncs two ways with GitHub on paid plans, and your data lives in a standard relational database. If you ever want to leave, you can export the code and the data and run them elsewhere. See [Does Archie lock me into its platform?](/docs/faq/security#does-archie-lock-me-into-its-platform) ### Environments Archie Core backends support Git-like [environments](/docs/features/backend/environments/overview): branch a development or staging copy, compare schemas with [diff](/docs/features/backend/environments/diff), [merge](/docs/features/backend/environments/merge) changes forward, and restore from automatic [backups](/docs/features/backend/environments/backups). Multiple environments are available on Professional and Enterprise backends. ### Pricing model Base44 uses two credit pools: message credits for building with the AI, and integration credits that your live app uses when it sends email, calls an API, or runs an AI feature. Paid plans run Starter, Builder, Pro, and Elite. Archie uses one pool of [AI credits](/docs/introduction/getting-started/plans-and-credits#ai-credits) for AI-assisted building; manual edits, deploying, and exporting code are free, and heavier tasks show their cost before they run. ## When to choose Base44 * You want the quickest path to a working internal tool or MVP with zero infrastructure setup. * You're comfortable with your app's backend living on a managed platform. * You already work in the Wix ecosystem. ## When to choose Archie * You want the whole app defined — users, roles, modules, data — before code is written. * You want your data in a standard PostgreSQL or MySQL database you can query with SQL and export. * You need row-level permissions, webhooks, queues, and environments you can branch and merge. * You want your code synced to your own GitHub repository. ## FAQ ### Is Archie a Base44 alternative? Yes. Both generate full-stack web apps with a built-in backend. Archie adds a plan-first workflow, a relational database you can query and export, and Git-like environments. ### Can I export my data from Archie? Yes. You can export any table to CSV, query your data directly with SQL, or read it through the auto-generated GraphQL and REST APIs. See [Can I export my data?](/docs/faq/security#can-i-export-my-data) ### Does Archie charge for my app's runtime actions? Archie's AI credits are spent on AI-assisted building. Your app's backend runs on an Archie Core backend tier with included usage. See [Archie Core backends](/docs/introduction/getting-started/plans-and-credits#archie-core-backends). ### Which is better for internal tools? Both can build internal tools. Archie is the stronger fit when the tool needs strict role-based permissions, row-level access rules, reporting views, or a database your team can query directly. See [Building with Archie](/docs/faq/building). ## Related comparisons > **[Archie vs. Lovable](/docs/faq/archie-vs-lovable)** > **[Archie vs. Replit](/docs/faq/archie-vs-replit)** > **[All comparisons](/docs/alternatives)** # Archie vs. Bolt: which AI app builder should you use? Source: https://archie.com/docs/alternatives/archie-vs-bolt Archie vs. Bolt.new compared: planning, backend, environments, editing, pricing model, and when to choose each AI app builder. **Archie and Bolt both build web apps from natural language, but they're built around different ideas.** Bolt is a fast, browser-based builder: it runs a full development environment right in your browser and generates code as you prompt. Archie is definition-first: it generates a structured plan of the whole app, lets you shape it, and then builds the frontend and a relational backend from that plan. > [!NOTE] > > Last reviewed September 2026, based on Bolt's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | Bolt | | - | - | - | | **Starting point** | A structured [plan](/docs/features/plan/overview) of the whole app | A prompt in chat | | **Planning** | A lasting definition of the app that every build follows | Plan and Discussion modes for thinking through changes | | **Where it runs** | Hosted platform with a built-in IDE | A full dev environment in the browser (WebContainers) | | **Backend** | [Archie Core](/docs/features/backend/overview): PostgreSQL or MySQL, GraphQL and REST APIs, role-based access | Bolt Cloud: databases, auth, file storage, edge functions, hosting | | **Environments** | Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups | Deploy-focused; staging patterns are up to you | | **Usage metering** | AI credits weighted by task complexity, cost shown before heavier tasks | Tokens; usage grows with project size | | **Free plan** | 50 AI credits per month | 1M tokens per month, 300K daily limit | | **Entry paid plan** | Pro from \$25/month for 200 AI credits | Pro from \$25/month for 10M tokens | ## How each one works **Bolt**, from StackBlitz, runs a complete development environment — file system, terminal, package manager, and preview — inside your browser using WebContainers. You prompt, Bolt generates and runs the code, and you see the result almost immediately. Bolt Cloud adds built-in databases, authentication, file storage, edge functions, and hosting. Plan and Discussion modes let you work through a change before code is generated. **Archie** starts with a [plan](/docs/features/plan/overview): overview, modules, user types, system services, and integrations, all editable. From the plan, **Generate Frontend** lays out every screen in a [UI architecture](/docs/features/frontend/generating) and builds a click-through prototype, and **Generate Backend** builds the schema and APIs on [Archie Core](/docs/features/backend/overview). **Generate Full-Stack** turns the prototype into a deployed app. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### Speed of the first draft vs. structure of the whole app Bolt is built for speed: prompt to running preview in seconds, all in the browser. That makes it great for experiments, demos, and hackathons. Archie trades a few minutes up front for structure. The plan defines who uses the app, what each user type can do, and how the modules fit together before code exists. Every screen gets a spec and an access level in the UI architecture. The build follows that definition, which is what keeps an app coherent as it grows. See [Why the plan comes first](/docs/introduction/getting-started/welcome#why-the-plan-comes-first). ### Backend depth Both tools include a backend. Bolt Cloud covers databases, auth, storage, edge functions, and hosting. Archie Core is built for production workloads: a relational database with a visual [data model](/docs/features/backend/data-model/overview), auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs with an [OpenAPI spec](/docs/features/backend/rest-api-explorer/openapi-specification), [role-based access](/docs/features/backend/app-services/role-based-access) with [row-level filters](/docs/features/backend/app-services/row-level-filters), [webhooks](/docs/features/backend/rest-api-explorer/webhooks), [queues and an event bus](/docs/features/backend/app-services/queues-and-event-bus/overview), [scheduled tasks](/docs/features/backend/scheduled-tasks/overview), and built-in integrations for [Stripe](/docs/features/backend/integrations/stripe), [SendGrid](/docs/features/backend/integrations/sendgrid), [Twilio](/docs/features/backend/integrations/twilio), [Slack](/docs/features/backend/integrations/slack), and [Shopify](/docs/features/backend/integrations/shopify). ### Environments and safe changes Archie Core gives backends Git-like [environments](/docs/features/backend/environments/overview): branch development and staging copies, compare schemas with [diff](/docs/features/backend/environments/diff), [merge](/docs/features/backend/environments/merge) changes forward transactionally, and restore from automatic [backups](/docs/features/backend/environments/backups). Multiple environments are available on Professional and Enterprise backends. ### How usage is metered Bolt meters usage in tokens. Because the AI reads your project on each message, token use tends to grow as the project gets larger. Archie uses [AI credits](/docs/introduction/getting-started/plans-and-credits#ai-credits) weighted by task complexity. Heavier tasks show their estimated cost before they run, and manual edits, deploying, and exporting code never use credits. ## When to choose Bolt * You want the fastest possible path from prompt to running preview, entirely in the browser. * You're building a demo, a hackathon project, or a quick JavaScript experiment. * You like working directly in a full in-browser IDE with a terminal. ## When to choose Archie * You want the whole app defined — users, roles, modules, data — before code is written. * Your app has multiple user types with different permissions. * You need a relational backend with GraphQL and REST APIs, row-level permissions, and environments you can branch and merge. * You want usage costs you can see before heavier tasks run. ## FAQ ### Is Archie a Bolt alternative? Yes. Both are AI app builders that generate full-stack web apps from natural language. Archie adds a plan-first workflow and a relational backend with Git-like environments; Bolt focuses on speed with an in-browser development environment. ### Does Bolt have a backend? Yes. Bolt Cloud includes databases, authentication, file storage, edge functions, and hosting. Archie's backend, Archie Core, adds PostgreSQL or MySQL with auto-generated GraphQL and REST APIs, row-level permissions, webhooks, queues, scheduled tasks, and environments. See [Infrastructure and Archie Core](/docs/faq/infrastructure). ### Tokens vs. credits: which is more predictable? It depends on how you work. Bolt's token use grows with project size because the AI reads more of the project on each message. Archie's credits are weighted by task complexity, and heavier tasks show their cost before they run. See [AI credits](/docs/introduction/getting-started/plans-and-credits#ai-credits). ### Can I export my code from Archie? Yes. Paid plans include two-way GitHub sync, and your data lives in a standard PostgreSQL or MySQL database you can export. See [Security, compliance, and data](/docs/faq/security). ## Related comparisons > **[Archie vs. Lovable](/docs/faq/archie-vs-lovable)** > **[Archie vs. v0](/docs/faq/archie-vs-v0)** > **[All comparisons](/docs/alternatives)** # Archie vs. Cursor: AI app builder or AI code editor? Source: https://archie.com/docs/alternatives/archie-vs-cursor Archie vs. Cursor compared: who each is for, how you build, backend and deployment, pricing, and how to use them together. **Cursor and Archie solve different problems, and many teams use both.** Cursor is an AI-powered code editor: it makes developers faster at writing, refactoring, and reviewing code they already own. Archie is an AI app builder: it takes an idea, generates a plan of the whole app, and builds and deploys the full-stack application — frontend, backend, database, and hosting. Because Archie can sync your code to a standard GitHub repository, you can open the same project in Cursor. > [!NOTE] > > Last reviewed September 2026, based on Cursor's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | Cursor | | - | - | - | | **What it is** | An AI app builder | An AI-powered code editor | | **Built for** | Founders, product teams, designers, and developers | Developers | | **You start with** | An idea in plain language | A codebase | | **Architecture** | Defined by the [plan](/docs/features/plan/overview) | Decided by you | | **Backend and hosting** | Included: [Archie Core](/docs/features/backend/overview) and a global CDN | You bring your own | | **Coding required** | No — but full code access when you want it | Yes | | **Pricing** | Free with 50 AI credits per month; Pro from \$25/month | Free Hobby plan; Pro \$20/month; Pro+ \$60/month; Ultra \$200/month | ## How each one works **Cursor** is an editor you install and point at your code. Its agents handle multi-file changes, refactors, and test-and-fix loops, cloud agents run tasks in the background, and it supports frontier models from several providers. It assumes you know how to architect, deploy, and operate the application you're working on. **Archie** starts one step earlier. Your idea becomes a [plan](/docs/features/plan/overview) — overview, modules, user types, system services, and integrations — that you shape before any code exists. **Generate Frontend** lays out every screen in a [UI architecture](/docs/features/frontend/generating) and builds a click-through prototype, **Generate Backend** builds the database and APIs on [Archie Core](/docs/features/backend/overview), and **Generate Full-Stack** deploys the app. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### Writing code faster vs. getting the whole app built Cursor makes an experienced developer faster inside an existing codebase. Archie produces the codebase — and the architecture, backend, and deployment around it — from a description. If you're not a developer, or you don't want to assemble infrastructure, Archie does that part for you. ### The backend and infrastructure With Cursor, you choose and run the database, auth, APIs, and hosting. With Archie, they're included: a relational database with a visual [data model](/docs/features/backend/data-model/overview), auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs, [authentication](/docs/features/backend/app-services/authentication-providers/overview), [role-based access](/docs/features/backend/app-services/role-based-access), Git-like [environments](/docs/features/backend/environments/overview), and one-click [deployment](/docs/features/frontend/deployment) with custom domains. ### Editing Cursor is built around editing code. Archie gives you four ways to change an app: [talk to Archie](/docs/features/frontend/talking-to-archie), the [visual editor](/docs/features/frontend/visual-editor), [draw mode](/docs/features/frontend/draw-mode), and the built-in [code editor](/docs/features/frontend/code-editor) — or sync to GitHub and edit anywhere, including Cursor. ## Using Archie and Cursor together A common workflow: define and generate the app in Archie, connect GitHub, and let developers extend it in Cursor. Archie's two-way GitHub sync pushes generated code to your repository and pulls back commits made outside Archie, so both tools work on the same project. The backend stays on Archie Core, with its APIs, auth, and environments. See [Organization integrations](/docs/introduction/organizations/organization-integrations). ## When to choose Cursor * You're a developer working in an existing codebase. * You want AI help writing, refactoring, and reviewing code, and you'll handle architecture and infrastructure yourself. ## When to choose Archie * You want to go from an idea to a deployed full-stack app without assembling the architecture, backend, and hosting yourself. * You want the app defined — users, roles, modules, data — before code is written. * You want a team with different skill levels working on the same app. ## FAQ ### Is Archie a Cursor alternative? Not exactly. Cursor is a code editor for developers; Archie is an app builder that generates and deploys the whole application. They're often used together. ### Can I open an Archie project in Cursor? Yes. Connect GitHub and Archie keeps your code in a standard repository with two-way sync, so you can work on it in Cursor, Claude Code, or any editor. See [Does Archie work with Cursor, Claude Code, or other AI coding agents?](/docs/faq/archie-vs-other-tools#does-archie-work-with-cursor-claude-code-or-other-ai-coding-agents) ### Does Archie work with Windsurf or Claude Code too? Yes. Anything that works with a GitHub repository works with an Archie project once GitHub sync is connected. ### Do I need to be a developer to use Archie? No. You describe the app and shape the plan; Archie builds it. Developers get full access to the code, data model, and infrastructure. See [Who is Archie for?](/docs/faq/getting-started#who-is-archie-for) ## Related comparisons > **[Archie vs. Replit](/docs/faq/archie-vs-replit)** > **[Archie vs. v0](/docs/faq/archie-vs-v0)** > **[All comparisons](/docs/alternatives)** # Archie vs. Lovable: which AI app builder should you use? Source: https://archie.com/docs/alternatives/archie-vs-lovable Archie vs. Lovable compared: planning, backend, environments, editing, code ownership, and pricing — and when to choose each AI app builder. **Archie and Lovable both turn a natural-language description into a working full-stack web app. The difference is where they start.** Lovable starts from a prompt and builds through conversation. Archie starts by generating a structured plan of the whole app — modules, user types, services, and integrations — that you shape before any code is written, and then builds the frontend and backend from that plan. > [!NOTE] > > Last reviewed September 2026, based on Lovable's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | Lovable | | - | - | - | | **Starting point** | A structured [plan](/docs/features/plan/overview) of the whole app | A prompt in chat | | **Planning** | A lasting definition of the app that every build follows | Plan mode for reviewing larger changes before they're made | | **Screen design** | A [UI architecture](/docs/features/frontend/generating) of every screen, with a spec and access level per screen, before code | Screens generated through chat | | **Backend** | [Archie Core](/docs/features/backend/overview): PostgreSQL or MySQL, GraphQL and REST APIs, role-based access with row-level filters | Lovable Cloud built-in backend, plus a Supabase integration | | **Environments** | Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups | Managed through Lovable Cloud | | **Editing after build** | Chat, visual editor, draw mode, code editor | Chat, visual edits, code mode | | **Code ownership** | Two-way GitHub sync on paid plans | Git sync with GitHub, GitLab, or Bitbucket | | **Free plan** | 50 AI credits per month | 5 daily credits, up to 30 per month | | **Entry paid plan** | Pro from \$25/month for 200 AI credits | Pro from \$25/month for 100 credits | ## How each one works **Lovable** is a conversational builder. You describe what you want, Lovable generates a working application with frontend, backend, database, and authentication, and you keep refining it through chat. Plan mode lets you review what Lovable intends to do before a larger change. Lovable Cloud provides a built-in backend, and apps live inside shared workspaces with a pooled credit balance. **Archie** is definition-first. Your prompt produces a [plan](/docs/features/plan/overview) with five editable sections: overview, modules, user types, system services, and integrations. You shape that plan until it matches what you mean. Then generation splits into two independent tracks: **Generate Frontend** lays out every screen in a sitemap and builds a click-through [prototype](/docs/features/frontend/prototype/overview), and **Generate Backend** builds the schema and APIs on [Archie Core](/docs/features/backend/overview). **Generate Full-Stack** turns the prototype into a deployed application. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### Planning: a plan mode vs. a plan of the whole app Lovable's Plan mode is a step before a change: the agent proposes what it will do, and you approve it. It's useful, and the plan covers that one change. Archie's plan is the definition of the entire app, and it stays with the project. Every build reads from it — the UI architecture, each screen's spec, and the backend's data model and permissions. When you want a structural change, like a new user type or module, you edit the plan and the next build follows it. See [The plan and how Archie works](/docs/faq/the-plan). ### Backend: built in either way, but a different foundation Both tools include a backend, so you don't have to wire one up yourself. Lovable's backend is Lovable Cloud, and it also integrates with Supabase. Archie's backend is [Archie Core](/docs/features/backend/overview): a relational database (PostgreSQL or MySQL) with a visual [data model](/docs/features/backend/data-model/overview) and [ERD](/docs/features/backend/data-model/erd), auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs with an OpenAPI spec, [role-based access](/docs/features/backend/app-services/role-based-access) with [row-level filters](/docs/features/backend/app-services/row-level-filters), [field-level encryption](/docs/features/backend/app-services/encryption/overview), [queues and an event bus](/docs/features/backend/app-services/queues-and-event-bus/overview), [scheduled tasks](/docs/features/backend/scheduled-tasks/overview), and a [SQL Playground](/docs/features/backend/data-model/sql-playground) for querying your data directly. ### Environments and change management Archie Core gives every backend Git-like [environments](/docs/features/backend/environments/overview): branch a development or staging copy, compare schemas with [diff](/docs/features/backend/environments/diff), [merge](/docs/features/backend/environments/merge) changes forward, and restore from automatic [backups](/docs/features/backend/environments/backups) taken before every merge. Multiple environments are available on Professional and Enterprise backends. This is the workflow a team needs once real customers depend on the app. ### Editing after the build Both tools let you keep building through chat and edit code directly. Archie adds a [visual editor](/docs/features/frontend/visual-editor) for point-and-click changes and [draw mode](/docs/features/frontend/draw-mode) for marking up the live preview, alongside [talking to Archie](/docs/features/frontend/talking-to-archie) and the [code editor](/docs/features/frontend/code-editor). ### Pricing model Both tools use credits. Lovable Pro starts at \$25/month for 100 credits, and Business starts at \$50/month for 100 credits with SSO and governance features. Lovable's credits cover building as well as Cloud hosting and in-app AI usage. Archie Pro starts at \$25/month for 200 AI credits and includes a Starter backend; manual edits, deploying, and exporting code never use credits, and heavier tasks show their cost before they run. See [Plans and credits](/docs/introduction/getting-started/plans-and-credits). ## When to choose Lovable * You want to iterate quickly through conversation and see changes appear as you chat. * Your project is a landing page, a quick prototype, or an app where structure matters less than speed. * Your team already builds on Supabase and wants to keep that stack. ## When to choose Archie * You want the whole app defined — users, roles, modules, and data — before code is written, so the build lands close to what you mean. * Your app has several user types with different permissions, like a marketplace, a SaaS product, or an internal tool. * You need a relational backend with GraphQL and REST APIs, row-level permissions, and environments you can branch and merge. * You're building software you plan to ship and maintain, not a demo. ## FAQ ### Is Archie a Lovable alternative? Yes. Archie is an AI app builder that generates full-stack web applications from natural language, like Lovable. The main difference is that Archie generates and lets you edit a plan of the whole app before it builds, and its backend runs on a relational database with Git-like environments. ### Can I move a project from Lovable to Archie? There's no direct import today. Most people describe the app again in Archie and let the plan capture its structure. If you're moving a production app with existing data, [Archie Labs](https://archielabs.com) offers migration services as part of Enterprise onboarding. ### Do Lovable and Archie both have a backend? Yes. Lovable includes Lovable Cloud and integrates with Supabase. Archie includes Archie Core, with PostgreSQL or MySQL, auto-generated GraphQL and REST APIs, authentication, role-based access, and environments. See [Infrastructure and Archie Core](/docs/faq/infrastructure). ### Which is cheaper, Archie or Lovable? Both entry Pro plans start at \$25/month. Archie Pro includes 200 AI credits and a Starter backend; Lovable Pro includes 100 credits. The real cost depends on how much AI work you do — compare [Archie's plans](/docs/introduction/getting-started/plans-and-credits) with Lovable's current pricing page. ## Related comparisons > **[Archie vs. Bolt](/docs/faq/archie-vs-bolt)** > **[Archie vs. Base44](/docs/faq/archie-vs-base44)** > **[All comparisons](/docs/alternatives)** # Archie vs. Replit: which should you use to build an app? Source: https://archie.com/docs/alternatives/archie-vs-replit Archie vs. Replit Agent compared: planning, scope, backend, deployment, pricing model, and when to choose each for building an app. **Replit and Archie can both take you from a natural-language description to a deployed app, but they're different kinds of tools.** Replit is a general-purpose cloud development platform with an AI agent: it can build web apps, mobile apps, scripts, and more in a full cloud workspace. Archie is focused on production web applications and works definition-first — it generates a plan of the whole app that you shape before any code is written, then builds the frontend and backend from it. > [!NOTE] > > Last reviewed September 2026, based on Replit's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | Replit | | - | - | - | | **What it is** | An AI app builder for production web apps | A cloud development platform with an AI agent | | **Scope** | Full-stack web applications | Web apps, mobile apps, scripts, and more | | **Planning** | A lasting [plan](/docs/features/plan/overview) of the whole app that every build follows | Plan mode for mapping out work before code changes | | **Backend** | [Archie Core](/docs/features/backend/overview): PostgreSQL or MySQL, GraphQL and REST APIs, auth, role-based access | Built-in database; you design the backend with the agent | | **Environments** | Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups | Workspace and deployment types you configure | | **Hosting** | Frontend on a global CDN, backend on Archie Core | Replit deployments (autoscale, reserved VM, static, scheduled) | | **Pricing model** | AI credits weighted by task complexity, cost shown before heavier tasks | Effort-based agent billing plus usage-based deployments | | **Free plan** | 50 AI credits per month | Starter with a limited agent allowance | ## How each one works **Replit** gives you a full cloud workspace — editor, terminal, database, and deployments — with Replit Agent to build from natural language. Plan mode lets you work out an approach before the agent changes code. It's flexible: you can build almost anything, and you make the architecture, hosting, and infrastructure choices along the way. **Archie** is opinionated. Your prompt becomes a [plan](/docs/features/plan/overview) with overview, modules, user types, system services, and integrations. **Generate Frontend** lays out every screen in a [UI architecture](/docs/features/frontend/generating) and builds a click-through prototype, **Generate Backend** builds the schema and APIs on [Archie Core](/docs/features/backend/overview), and **Generate Full-Stack** deploys the app. The architecture and infrastructure decisions are made for you, following the plan. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### General-purpose vs. focused Replit's strength is range: one workspace for many kinds of projects, with full control over the stack. Archie's strength is focus: one structured path from idea to a production web app, with the architecture defined by the plan and the infrastructure provided by Archie Core. ### Planning the whole app first Replit's Plan mode helps you scope work before a change. Archie's plan is the definition of the whole app — users, roles, modules, services — and it stays with the project. Every screen in the UI architecture has its own spec and access level, and the backend's data model and permissions come from the same plan. See [The plan and how Archie works](/docs/faq/the-plan). ### A production backend out of the box Archie Core includes a relational database with a visual [data model](/docs/features/backend/data-model/overview), auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs, [authentication](/docs/features/backend/app-services/authentication-providers/overview), [role-based access](/docs/features/backend/app-services/role-based-access) with [row-level filters](/docs/features/backend/app-services/row-level-filters), [webhooks](/docs/features/backend/rest-api-explorer/webhooks), [queues and an event bus](/docs/features/backend/app-services/queues-and-event-bus/overview), [scheduled tasks](/docs/features/backend/scheduled-tasks/overview), and Git-like [environments](/docs/features/backend/environments/overview). You don't assemble these pieces — they're there when the backend is generated. ### Pricing model Replit bills agent work by effort, per checkpoint, and bills deployments, compute, and storage by usage on top of the plan's included credits. Archie uses [AI credits](/docs/introduction/getting-started/plans-and-credits#ai-credits) for AI-assisted building, weighted by task complexity, with the cost of heavier tasks shown before they run. Hosting runs on an included [Archie Core backend tier](/docs/introduction/getting-started/plans-and-credits#archie-core-backends). ## When to choose Replit * You want a general-purpose cloud workspace for many kinds of projects, not just web apps. * You want full control over the stack, hosting type, and infrastructure. * You're learning to code or exploring ideas in a full development environment. ## When to choose Archie * You want a structured path from idea to a production web app, with the architecture decided for you. * You want the whole app defined — users, roles, modules, data — before code is written. * You need a relational backend with APIs, row-level permissions, and environments without setting them up yourself. ## FAQ ### Is Archie a Replit alternative? For building production web apps, yes. Replit is broader — a general-purpose cloud development platform — while Archie focuses on taking a web app from idea to production with a plan-first workflow and an included backend. ### Do I need to know how to code to use Archie? No. You describe the app, shape the plan, and Archie builds it. Technical users can still open the code editor or sync with GitHub. See [Do I need to know how to code?](/docs/faq/getting-started#do-i-need-to-know-how-to-code-to-use-archie) ### Can I build mobile apps with Archie? Archie generates responsive web applications that work on phones, tablets, and desktops. Native iOS and Android apps are not available yet. See [Can I build a mobile app with Archie?](/docs/faq/building#can-i-build-a-mobile-app-with-archie) ### How does hosting work in Archie? Your frontend is hosted on a global CDN and your backend runs on Archie Core, with one-click publishing and custom domains. See [Deployment](/docs/features/frontend/deployment). ## Related comparisons > **[Archie vs. Cursor](/docs/faq/archie-vs-cursor)** > **[Archie vs. Base44](/docs/faq/archie-vs-base44)** > **[All comparisons](/docs/alternatives)** # Archie vs. v0: which AI app builder should you use? Source: https://archie.com/docs/alternatives/archie-vs-v0 Archie vs. v0 by Vercel compared: planning, UI generation, backend, hosting, editing, and when to choose each AI app builder. **v0 and Archie both generate web applications from natural language, and they're strongest in different places.** v0, by Vercel, is known for high-quality UI and builds Next.js apps that deploy to Vercel and connect to external databases and backends. Archie is definition-first: it generates a plan of the whole app that you shape before any code is written, and every app ships with its own backend — database, APIs, auth, and environments — on Archie Core. > [!NOTE] > > Last reviewed September 2026, based on v0's public documentation and pricing. Competitor products change often — if something here is out of date, tell us at [support@archie.com](mailto:support@archie.com). ## At a glance | | Archie | v0 | | - | - | - | | **Starting point** | A structured [plan](/docs/features/plan/overview) of the whole app | A prompt in chat | | **Frontend stack** | Generated React app on a global CDN | Next.js with React, Tailwind, and shadcn/ui | | **Backend** | Included: [Archie Core](/docs/features/backend/overview) with PostgreSQL or MySQL, GraphQL and REST APIs, auth, roles | Connects to external databases and backends | | **Environments** | Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups | Vercel preview and production deployments | | **Hosting** | Frontend on a global CDN, backend on Archie Core | Vercel | | **Editing after build** | Chat, visual editor, draw mode, code editor | Chat and a VS Code-style editor | | **Code sync** | Two-way GitHub sync on paid plans | Git integration | | **Free plan** | 50 AI credits per month | \$5 in monthly credits | ## How each one works **v0** started as a UI generator and has grown into a full-stack builder. You describe what you want, and v0 generates a Next.js app using React, Tailwind, and shadcn/ui, with a strong default visual finish. It includes a code editor, Git integration, and a sandbox runtime, connects to external databases and services, and deploys to Vercel in one click. **Archie** starts with a [plan](/docs/features/plan/overview): overview, modules, user types, system services, and integrations, all editable. **Generate Frontend** lays out every screen in a [UI architecture](/docs/features/frontend/generating) and builds a click-through prototype with your design system applied. **Generate Backend** builds the schema and APIs on [Archie Core](/docs/features/backend/overview). **Generate Full-Stack** turns the prototype into a deployed app. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Key differences ### Included backend vs. connected backend v0 builds the application and connects it to the database and services you choose, which fits teams that already have a backend or a preferred provider. With Archie, the backend is part of the product. Every app runs on [Archie Core](/docs/features/backend/overview): a relational database with a visual [data model](/docs/features/backend/data-model/overview), auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs, [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview) or your own [auth provider](/docs/features/backend/app-services/authentication-providers/overview), [role-based access](/docs/features/backend/app-services/role-based-access), [file storage](/docs/features/backend/app-services/file-manager), and [notifications](/docs/features/backend/app-services/notifications/overview) — managed in one place, with nothing extra to wire up. ### Planning the whole app first Archie generates a plan before code: who the users are, what each user type can do, which modules the app needs. From the plan, the [UI architecture](/docs/features/frontend/generating) maps every screen with its own spec and access level, so the build covers the whole app, not one screen at a time. See [Why the plan comes first](/docs/introduction/getting-started/welcome#why-the-plan-comes-first). ### Design system and visual editing v0 is known for polished UI out of the box. Archie applies a full design system in the [prototype](/docs/features/frontend/prototype/overview) — [theme](/docs/features/frontend/prototype/design/theme), [color](/docs/features/frontend/prototype/design/color), [type](/docs/features/frontend/prototype/design/read), [feel](/docs/features/frontend/prototype/design/feel), and [polish](/docs/features/frontend/prototype/design/polish) — before the app is built, and after the build you can change any element with the [visual editor](/docs/features/frontend/visual-editor) or mark up the preview with [draw mode](/docs/features/frontend/draw-mode). ### Environments Archie Core backends support Git-like [environments](/docs/features/backend/environments/overview) for your data and schema: branch, [diff](/docs/features/backend/environments/diff), [merge](/docs/features/backend/environments/merge), and restore from [backups](/docs/features/backend/environments/backups). Multiple environments are available on Professional and Enterprise backends. ## When to choose v0 * You work in Next.js and deploy on Vercel. * You mostly need high-quality UI, landing pages, or screens for an app that already has a backend. * You want to bring your own database and services. ## When to choose Archie * You want the whole app — frontend and backend — generated and managed in one platform. * You want the app defined before code: users, roles, modules, and every screen. * You need role-based permissions, row-level filters, webhooks, and environments without assembling them yourself. ## FAQ ### Is Archie a v0 alternative? Yes, especially if you want the backend included. Both generate web apps from natural language; Archie adds a plan-first workflow and runs every app on its own backend, Archie Core. ### Does Archie use Next.js? Archie generates a React frontend and chooses and manages the stack for you, so you don't configure it. If you have a hard requirement, mention it in your prompt before you generate. See [What tech stack does Archie use?](/docs/faq/the-plan#what-tech-stack-does-archie-use) ### Do I need Vercel or Supabase with Archie? No. Archie hosts your frontend on a global CDN and runs your backend on Archie Core. See [Does Archie handle hosting and deployment?](/docs/faq/infrastructure#does-archie-handle-hosting-and-deployment) ### Can I use Archie and v0 together? Yes. Archie Core can be used on its own as a backend, with auto-generated GraphQL and REST APIs any frontend can call. See the [Backend overview](/docs/features/backend/overview). ## Related comparisons > **[Archie vs. Bolt](/docs/faq/archie-vs-bolt)** > **[Archie vs. Lovable](/docs/faq/archie-vs-lovable)** > **[All comparisons](/docs/alternatives)** # Overview Source: https://archie.com/docs/assets Upload, organize, and serve images, videos, documents, and other files used in your Archie app from a centralized media surface. The Assets surface is where you store and manage files used in your app — images, videos, documents, fonts, and other media. Files are served from the [file storage provider](/docs/features/backend/app-services/file-manager) configured in your project. ## What you can do * **Upload** files individually or in bulk * **Browse** assets with search and filters * **Preview** images, videos, and documents in place * **Copy URLs** for use in code or pages * **Delete** assets, with confirmation * **Organize** assets with folders and tags ## Uploading Click **Upload** in the Assets surface or drag and drop files into the browser. Archie processes the upload, stores the file in your configured provider (S3, Google Cloud Storage, Azure Blob, Filestack), and adds it to the asset index. For large files or batches, the upload runs in the background and the asset appears once processing completes. You can keep working while uploads run. ## Using assets in your app Once uploaded, an asset has a URL you can reference from anywhere: * **The [visual editor](/docs/features/frontend/visual-editor)** — replace an image element by selecting and uploading * **Frontend code** — paste the URL into an `` or `Image` component * **[Logic](/docs/backend/logic)** — fetch the asset server-side * **Generated email templates** — reference assets by URL Asset URLs are stable. Re-uploading a file with the same name produces the same URL by default, so existing references keep working. ## Storage providers Files are stored in the file storage provider configured for your project. Archie supports: * **AWS S3** — default for most projects * **Google Cloud Storage** * **Azure Blob Storage** * **Filestack** Each provider integrates with a CDN, so assets are served fast worldwide. Switch providers in [App services → File manager](/docs/features/backend/app-services/file-manager). ## Limits Limits depend on your plan and the provider's quotas: * **Starter backend** — 1 GB included, \$0.021/GB/mo overage, 25 GB max * **Professional backend** — 10 GB included, same overage rate, 250 GB max * **Enterprise** — custom See [plans and credits](/docs/introduction/getting-started/plans-and-credits) for the full breakdown. ## FAQ > **What file types are supported?** > > Images (PNG, JPG, GIF, SVG, WebP), video (MP4, WebM), audio (MP3, WAV, AAC), PDF, and common document formats. Type restrictions are configurable per project. > **Are assets served via CDN?** > > Yes. Each storage provider integrates with a global CDN (CloudFront for S3, Cloud CDN for GCS, Azure CDN for Blob). Asset URLs are CDN URLs — no extra setup needed. > **Can I bulk-upload via API?** > > Yes. The asset upload API is available through the [REST API](/docs/features/backend/rest-api-explorer/overview) and the typed client. Authenticate with an [API key](/docs/features/backend/settings/api-keys). > **What happens to URLs when I delete an asset?** > > URLs return 404 immediately. References in your code or pages should be updated before deletion. The asset history shows deleted assets for 30 days, after which the file is permanently removed. # About your backend Source: https://archie.com/docs/backend Build, extend, and integrate with apps you ship on Archie. The Backend section covers the technical surface of Archie — APIs, custom functions, data access, integrations, environments, and GitHub workflow. This is for engineers building or extending Archie-generated apps directly in code. If you are looking for product-level documentation (plans, building, deployment), see the [Get Started tab](/docs/introduction/getting-started/welcome). ## The Backend Console The Backend Console is the central command center for your application's server-side logic, data architecture, and infrastructure. It provides a unified interface to manage your database, explore your auto-generated APIs, and configure essential application services. ### Data Model The **Data Model** is the foundation of your application. Define the structure of your database without writing complex migration scripts. * **Tables:** Create and manage database tables (e.g., `users`, `audit_events`) to store your application records. * **Schema & Fields:** Define specific data types (Text, Number, Date, UUID) and configure relationships between tables. * **Views & Data Types:** Create SQL Views for complex data aggregation and define reusable Data Types (Enums) to standardize values across your schema. See [Data → Data Model](/docs/features/backend/data-model/overview). ### GraphQL API Explorer The **GraphQL API Explorer** is an integrated GraphiQL environment that allows you to interact directly with your application's API. * **Auto-generated API:** The system automatically generates GraphQL Queries, Mutations, and Subscriptions based on your Data Model. * **Composer:** A visual tool to build queries by simply clicking fields, eliminating the need to type syntax manually. * **Documentation:** Access real-time, inline documentation for your schema to understand available fields and arguments. See [APIs → API Explorer](/docs/features/backend/graphql-api-explorer/overview). ### REST API Explorer The **REST API Explorer** provides a comprehensive Swagger UI environment to test and interact with your auto-generated RESTful endpoints. * **Standardized endpoints:** Instantly test GET, POST, PUT, and DELETE operations for all your data models without any manual setup. * **Interactive testing:** Execute API requests directly from the browser, passing parameters, headers, and request bodies easily. * **OpenAPI documentation:** Access fully detailed OpenAPI (Swagger) documentation that automatically stays in sync with your data schema. See [APIs → REST API Explorer](/docs/features/backend/rest-api-explorer/overview). ### SQL Playground For advanced data operations, the **SQL Playground** provides a raw SQL editor. * **Direct access:** Execute standard SQL queries (`SELECT`, `JOIN`, etc.) directly against your database tables. * **Debugging:** Ideal for testing complex logic or debugging data issues that are difficult to trace via the GraphQL API. See [Data → SQL Playground](/docs/features/backend/data-model/sql-playground). ### Integrations Extend the capabilities of your application by connecting third-party services via the **Integrations** module. * **Communication:** Connect **Slack** or **Twilio** for messaging. * **Email:** Configure **SendGrid** for transactional emails. * **Payments:** Integrate **Stripe** for processing payments. * **E-commerce:** Integrate **Shopify** for managing your online store and products. See [Integrations → Overview](/docs/features/backend/integrations/overview). ### App Services App Services manage the core infrastructure components required for a modern application: * **File Manager:** Configure storage providers like AWS S3, Google Cloud Storage, Filestack, or Azure Blob Storage to handle user uploads and file assets. * **Authentication Providers:** Set up secure login methods using the built-in Archie Auth, or external providers like Auth0, Google, AWS Cognito, or standard API Tokens. * **Roles (RBAC):** Define Role-Based Access Control to manage permissions and restrict what different users (e.g., Admin, Customer) can see or do. > **Note:** Authentication providers, gateway routes, file storage providers, and roles are all configured **per environment**. This guarantees isolation and allows you to maintain independent configurations for development, testing, and production. ### Environments The **Environments** feature provides Git-like branching for your entire backend, enabling isolated development, testing, and staging workflows. * **Branch:** Create new environments by cloning an existing one's database (full data or schema-only). * **Isolate:** Each environment has its own database, roles, providers, and API configuration. * **Diff:** Compare schemas between environments to see exactly what changed (tables, columns, indexes, enums, views, relationships). * **Copy configuration:** Optionally copy file providers, gateway routes, security settings, and environment variables when branching. See [Environments → Overview](/docs/features/backend/environments/overview). ### Settings The **Settings** menu controls the global configuration and security of your project: * **API Keys:** Generate and manage API keys to allow external applications to securely connect to your backend. * **Environment Variables:** Store sensitive configuration values (like secret keys or external URLs) securely as key-value pairs. * **Network:** Configure CORS (Cross-Origin Resource Sharing) policies and set Rate Limits to protect your API from abuse. See [APIs → API Keys](/docs/features/backend/settings/api-keys) and [Environments → Configuration](/docs/features/backend/environments/configuration). ## What is here * **[Overview](/docs/backend/overview)** — your backend at a glance: structure and usage * **[Data Model](/docs/features/backend/data-model/overview)** — tables, fields, relationships, views * **[GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview)** — auto-generated GraphQL surface * **[REST API Explorer](/docs/features/backend/rest-api-explorer/overview)** — auto-generated REST surface * **[Logic](/docs/backend/logic)** — server-side handlers * **[App Services](/docs/features/backend/app-services/overview)** — auth providers, RBAC, file manager, custom APIs * **[Integrations](/docs/features/backend/integrations/overview)** — Stripe, SendGrid, webhooks, custom integrations * **[Environments](/docs/features/backend/environments/overview)** — branching, diff, merge, backups * **[Settings](/docs/features/backend/settings/api-keys)** — API keys, environment variables, network policy ## Voice and assumptions These pages assume you read code, run a terminal, and understand HTTP, REST, GraphQL, and your project's stack. Where Archie's generator produces code, examples show actual file paths and call signatures. # Overview Source: https://archie.com/docs/backend/app-services App Services control authentication, permissions, file storage, and the custom API gateway across your backend. App Services control authentication, permissions, file storage, and the custom API gateway across your backend. Open any project, switch to the Backend Console, and the **App Services** section is where every cross-cutting concern that sits in front of your Data Model lives. ## What's in App Services > **[Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview)** > > Configure how users sign in. Built-in Archie Auth, third-party providers, or both running side by side. > **[Role-Based Access](/docs/features/backend/app-services/role-based-access)** > > Define roles and the read, write, update, and delete permissions they grant on every table — including row-level filters. > **[File Manager](/docs/features/backend/app-services/file-manager)** > > Connect a storage backend (S3, GCS, Azure Blob, Filestack) and serve user uploads through the API. > **[Custom APIs](/docs/features/backend/app-services/custom-apis)** > > Register HTTP routes that reverse-proxy to internal services with CORS, rate limiting, and circuit breakers. > **[Notifications](/docs/features/backend/app-services/notifications/overview)** > > Define notification types, let users set preferences, and send across in-app, email, and other channels with one API call. > **[Encryption](/docs/features/backend/app-services/encryption/overview)** > > Mark fields as private so they're stored encrypted, and returned as plaintext only to callers that sign the request with your key. > **[Queues & Event Bus](/docs/features/backend/app-services/queues-and-event-bus/overview)** > > Run work in the background with queues, broadcast events to many subscribers with the Event Bus, or combine them to fan one event out to several queues. > **[Archie AI](/docs/backend/app-services/archie-ai)** > > Let your apps call chat and embedding models through the project API. Enable providers, set default models, cap spending, and test prompts. > **[Global Search](/docs/backend/app-services/global-search)** > > Make your app's data searchable. Choose which tables and fields are indexed and add row filters. ## How App Services fit together The four sub-areas form one coherent surface around your data: * **Authentication Providers** decide who can sign in. * **Role-Based Access** decides what an authenticated user (or an API key) is allowed to do. * **File Manager** holds files that records reference. * **Custom APIs** sit in front of internal services and bring them under the same auth and rate-limiting model. A request flowing through Archie touches each of them in turn: the auth provider issues the token, RBAC checks the operation against the caller's roles, the [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs serve the data, the File Manager backs any file fields, and a Custom API route can wrap any of that with extra policy. ## Per-environment configuration Every App Services configuration is **per-environment**. Each [environment](/docs/features/backend/environments/overview) maintains its own: * Active authentication providers and their secrets * Roles and their permissions * File storage providers * Custom API routes When you branch a new environment, gateway routes and file-storage configurations can optionally be copied. **Authentication provider secrets and OAuth credentials are never copied** — they have to be re-entered in each environment for security reasons. ## Permissions are the API surface Whatever you configure in [Role-Based Access](/docs/features/backend/app-services/role-based-access) is what the auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs enforce. There's no separate "API permission" layer — the role grid is the contract. External clients calling the API use tokens generated under [Backend → Settings → API Keys](/docs/features/backend/settings/api-keys). Roles attach to those keys the same way they attach to authenticated users. ## FAQ > **Where do I configure API keys?** > > Under **Backend → Settings → API Keys** — see [API Keys](/docs/features/backend/settings/api-keys). Roles defined in [Role-Based Access](/docs/features/backend/app-services/role-based-access) attach to those keys to control what they can do. > **Are App Services configurations copied between environments?** > > File storage providers and Custom API routes can be copied when you branch a new environment. Roles live per-environment. Authentication provider secrets are deliberately not copied — re-enter them in each environment. > **Can I run multiple authentication providers at once?** > > Yes. Multiple providers can be active simultaneously — for example, Archie Auth for end users and an API Token provider for service-to-service traffic. See [Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). > **Where do I configure CORS for the auto-generated APIs?** > > Under **Backend → Settings → Network**. The same allowed-origins list applies to GraphQL and REST. For per-route CORS on a [Custom API](/docs/features/backend/app-services/custom-apis), configure it on the route itself. > **What's the difference between RBAC and the auth provider?** > > The auth provider verifies who you are and issues a token. RBAC takes that identity (or an API key) and decides what it's allowed to do on each table. Auth is the doorman; RBAC is the access policy inside. # Archie AI Source: https://archie.com/docs/backend/app-services/archie-ai Archie AI lets the apps you build call AI models through your project's API. You turn it on per environment, choose providers and default models, set a spend cap, and test prompts in a playground. **Archie AI** is a [Core Service](/docs/backend/app-services) that lets your apps call chat and embedding models through your project's API. Archie manages the provider keys and bills usage as AI credits. ## Turn Archie AI on Archie AI is off until you activate it, and it's configured per [environment](/docs/backend/environments). 1. Go to **Backend → Core Services → Archie AI**. 2. Turn on the **Activate Archie AI** switch. While it's off, the page shows an **Inactive** badge and the message "Archie AI is not active in this environment. Turn it on to configure it." ![The Archie AI page showing the Inactive badge and the Activate Archie AI switch turned off.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/23-archie-ai-inactive.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=07a9cf32de475e1a28dae3f64385b5ea) Once it's active, the badge changes to **Active** and five tabs appear: | Tab | What it does | | - | - | | **Providers** | Enable the AI providers your apps can use. See [Providers and default models](/docs/backend/app-services/archie-ai/providers-and-models). | | **Default models** | Choose the chat and embedding models used when an app doesn't name one. | | **Limits & usage** | Cap spending and review what the environment consumed. See [Limits and usage](/docs/backend/app-services/archie-ai/limits-and-usage). | | **Expose to apps** | See the operations your apps can call. See [Expose to apps and playground](/docs/backend/app-services/archie-ai/expose-and-playground). | | **Playground** | Send a test prompt to a model. | To turn Archie AI off, switch **Activate Archie AI** back off. ## FAQ > **Is Archie AI shared across environments?** > > No. Activation and configuration apply to the environment you have selected in the Backend Console. > **Who pays for model usage?** > > By default, Archie manages the provider keys and bills usage as AI credits. Bringing your own key is marked **Enterprise** in the console. See [Providers and default models](/docs/backend/app-services/archie-ai/providers-and-models). # Expose to apps and playground Source: https://archie.com/docs/backend/app-services/archie-ai/expose-and-playground See which Archie AI operations your apps can call through the project API, copy an example request, and try prompts in the playground before you wire them into your app. ## Expose to apps The **Expose to apps** tab lists the runtime operations your apps can call once Archie AI is active. ![The Expose to apps tab listing the aiChat, aiEmbed, and aiModels operations with their type, transport, and access.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/27-archie-ai-expose.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=e62816852d078b87bb802263c669ec1e) | Operation | Description | Type | Transport | Access | | - | - | - | - | - | | `aiChat` | Send a chat completion to a model. | Mutation | GraphQL, REST | Signed-in users | | `aiEmbed` | Generate embeddings for text. | Mutation | GraphQL, REST | Signed-in users | | `aiModels` | List the models available to the project. | Query | GraphQL | All roles | ### Call it from your app Below the list, **Call it from your app** shows an example `POST /ai/v1/chat` request through the gateway. Click **Copy**, then replace `YOUR_TOKEN` with a user token or an API key token. See [API keys](/docs/backend/settings/api-keys). ### Gateway rules The tab also lists the gateway rules: * Streaming responses use Server-Sent Events (SSE). * Timeout headers are set to 600 seconds. * Rate limit: 60 requests per minute per user. * Everything under `/_internal` is denied. ## Playground The **Playground** tab sends a prompt to a model so you can try it before building against it. ![The Playground tab with a system prompt section, an empty conversation area, a Message box, and a Send button.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/28-archie-ai-playground.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=3ff4fbddab1621fc7b30c4019a762268) > [!WARNING] > > The playground sends a real request to the model. It consumes AI credits from this environment. 1. Pick a **Model**, or keep **Console default** to use your [default model](/docs/backend/app-services/archie-ai/providers-and-models). The model list appears once you enable a provider. 2. Optionally expand **System prompt (optional)** and add instructions. 3. Type your prompt in **Message** and click **Send**, or press ⌘ + Enter. The conversation area shows your message and the model's response. Under a response, the playground shows the model that answered and its usage, such as credits and input and output tokens. ## FAQ > **Who can call these operations?** > > `aiChat` and `aiEmbed` require a signed-in user. `aiModels` is available to all roles. # Limits and usage Source: https://archie.com/docs/backend/app-services/archie-ai/limits-and-usage Cap the AI credits an environment can spend and review what it consumed. Requests stop when the cap is reached. The **Limits & usage** tab helps you control and review what Archie AI costs in the selected [environment](/docs/backend/environments). ## Spend cap The **Spend cap** sets the maximum AI credits this environment can spend. Requests stop when the cap is reached. ![The Spend cap card with a Cap (credits) field and the Set cap and Remove cap buttons.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/26-archie-ai-limits.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=4fbb87aac6d8c105d078e6f1105fe9f3) 1. Enter a number in **Cap (credits)**. 2. Click **Set cap**. To lift the limit, click **Remove cap**. When no cap is set, the field shows **No cap**. ## Usage The **Usage** card shows AI credits and tokens consumed. Use the period selector to switch between **Last 24 hours**, **Last 7 days**, **Last 30 days**, and **This month**. For the selected period, the card shows totals for credits, input tokens, and output tokens. When a cap is set, it also shows credits spent against the cap. Breakdowns appear **By day**, **By model**, and **By feature**. When nothing was used, the card reads "No usage in this period." ## FAQ > **What happens when the cap is reached?** > > Requests stop until you raise or remove the cap. > **Does the playground count toward usage?** > > Yes. The [playground](/docs/backend/app-services/archie-ai/expose-and-playground) sends real requests that consume AI credits from the environment. # Providers and default models Source: https://archie.com/docs/backend/app-services/archie-ai/providers-and-models Enable the AI providers your apps can use, then choose the default chat and embedding models. Archie manages the provider keys and bills AI credits. Open **Backend → Core Services → Archie AI** and make sure Archie AI is [active](/docs/backend/app-services/archie-ai). ## Providers The **Providers** tab lists the providers your apps can use: **Anthropic**, **OpenAI**, and **Google**. Each provider has an on/off switch and shows **Disabled** until you turn it on. Archie manages the keys and bills AI credits. ![The Providers tab listing Anthropic, OpenAI, and Google, each with a switch set to Disabled.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/24-archie-ai-providers.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=6a158b2525e6e83098f2ef6d3c4ef820) When you enable a provider that uses Archie's key, the card reads "Ready to use · no setup needed" and shows a **Use your own key** button marked **Enterprise**. ### Use your own key On an Enterprise plan, you can bring your own provider key: 1. Enable the provider and click **Use your own key**. 2. Enter the key in the **API key** field and click **Save key**. Archie sends the key once and never shows it again. Afterwards, the card shows only the last characters as **Stored key**. With your own key, requests are billed by the provider. Click **Replace key** to change it, or **Use Archie's key** to switch back. > [!WARNING] > > Treat provider keys as secrets. Never paste a key into a chat, a screenshot, or a shared document. ## Default models The **Default models** tab sets the models your apps get when a request doesn't name one. Only active models from enabled providers can be chosen. ![The Default models tab with Chat and Embeddings selectors, both set to No default.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/25-archie-ai-default-models.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=2930e884224365b8080266e6d3707b94) * **Chat** sets the default chat model. Until you enable a provider, the tab shows "Enable a provider to choose a chat model." * **Embeddings** sets the default model for generating embeddings. Both selectors start at **No default**. Each option can show metadata such as its context size or its number of dimensions. If a default model later becomes unavailable, the tab warns you. It reports that the model is deprecated, that its provider is disabled, or that it's no longer in the catalog, and it suggests a replacement when one exists. ## FAQ > **Why is the Chat selector empty?** > > You need at least one enabled provider before you can choose a chat model. > **Does my choice here affect requests that name a model?** > > No. Defaults apply only when an app doesn't name a model. # Overview Source: https://archie.com/docs/backend/app-services/authentication-providers Authentication Providers configure who can sign into your app and how — built-in Archie Auth, third-party providers, or both. Authentication Providers configure who can sign into your app and how — built-in Archie Auth, third-party providers, or both running side by side. Each provider tells Archie how to verify a credential and turn it into a token; once you have the token, [Role-Based Access](/docs/features/backend/app-services/role-based-access) decides what it's allowed to do. To open it, navigate to **App Services → Authentication Providers** in the Backend Console. ![Authentication providers overview](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/authentication-providers/authentication-providers-overview.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=e4f880c6de36c57a6b79cb7ec05d0f43) ## Per-environment configuration Auth providers are scoped per environment. Each [environment](/docs/features/backend/environments/overview) keeps its own set of configured providers. > [!WARNING] > > Authentication provider secrets and OAuth credentials are **never copied** when you branch a new environment. Every environment must be configured independently — it's a deliberate isolation boundary so a leaked `dev` secret can't be replayed against `production`. ## Available providers > **[Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview)** > > Built-in email/password auth with user management, password reset, and email verification. The recommended starting point — no external dependency. > **Auth0** > > Third-party identity platform. Reach for it when you already use Auth0 or need MFA, enterprise SSO, or custom rules. > **Clerk** > > Modern third-party auth with pre-built UI components. Reach for it when you want sign-in / sign-up UI out of the box. > **OAuth 2.0** > > Generic OAuth 2.0 — any compliant identity provider. Bring your own authorization, token, and userinfo URLs. > **API Token** > > Server-to-server auth via static tokens validated against an external endpoint. Use for service accounts and machine-to-machine traffic. > **AWS Cognito** > > AWS's managed identity service. Reach for it when your stack is AWS-centric and you already run a Cognito User Pool. > **Google** > > Sign in with Google. Useful for SaaS where your audience already has Google accounts. > **Apache Shiro** > > Java-based enterprise security framework. Use to integrate with existing Shiro realms. > [!NOTE] > > Setup guides for the third-party providers are coming soon. The Archie Auth section is fully documented today. ## Combining providers Multiple providers can be active simultaneously. Common patterns: * **End-user auth + service-to-service** — Archie Auth for app users, an API Token provider for backend-to-backend traffic. * **Social plus password** — Google for social sign-in, Archie Auth for users who prefer email/password. * **Migration** — keep Auth0 active while migrating users into Archie Auth, then disable Auth0 once the migration completes. When multiple providers are active, every incoming token is validated against each enabled provider until one accepts it. Routes and queries can be scoped to specific providers if you need finer control. ## Choosing between providers | Choose | When | | - | - | | **Archie Auth** | You don't already have an identity provider and want email/password auth without a dependency. | | **Auth0** | You already use Auth0, or you need MFA, enterprise SSO, or rule-based authorization beyond what Archie Auth covers. | | **Clerk** | You want pre-built sign-in UI components and rich user-management UI out of the box. | | **OAuth 2.0** | Your identity provider doesn't have a dedicated integration but speaks standard OAuth 2.0. | | **API Token** | You need machine-to-machine auth where the validating service is external. | | **AWS Cognito** | You run on AWS and already have a Cognito User Pool. | | **Google** | You only want social login with Google accounts. | | **Apache Shiro** | You're integrating with an existing Java backend that uses Shiro realms. | For most projects starting fresh, [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview) is the right default — it ships with the password policy, lockouts, email verification, and admin controls you'd otherwise have to wire up by hand. ## Roles, tokens, and the API surface Whichever provider you pick, the result is the same: a signed token attached to subsequent requests. The token carries the user's identity and assigned [roles](/docs/features/backend/app-services/role-based-access). The auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs check the token, look up the roles, and enforce the permission matrix on every operation. For external clients without an interactive user, generate an [API key](/docs/features/backend/settings/api-keys) and attach roles to it directly — no auth provider involved. ## FAQ > **Can I run more than one provider at the same time?** > > Yes. Enable any combination of providers — every active provider validates incoming tokens until one matches. Useful for migrations and for splitting end-user auth from service-to-service traffic. > **Why aren't auth secrets copied when I branch an environment?** > > Deliberately. A copied secret means a `dev` leak compromises `production`. Re-entering credentials per environment makes the security boundary explicit. > **Which provider should I start with?** > > [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview) for most cases — email/password with admin controls and no external dependency. Switch to a third-party provider later if you outgrow it; the rest of the platform doesn't care which provider issued the token. > **How does the auth provider relate to RBAC?** > > The auth provider verifies the credential and issues a token. [RBAC](/docs/features/backend/app-services/role-based-access) decides what that token can do. A user can be authenticated but unauthorized for a specific operation — that's a `403`, not a `401`. > **What if I just need machine-to-machine auth?** > > Use either an [API Key](/docs/features/backend/settings/api-keys) (managed inside Archie) or the API Token provider (validated against an external endpoint). Both attach to roles and bypass interactive auth flows. # Overview Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth Archie Auth is the built-in authentication system — email/password sign-in, user management, password reset, and email verification, all per-environment. Archie Auth is the built-in authentication system — email/password sign-in, user management, password reset, and email verification, all configured per-environment. Enable it with one click, and your users can sign up, verify their email, log in, and recover passwords without you wiring any of it up. It's the recommended starting point for new projects: you get bcrypt password hashing, JWT tokens with optional encryption, brute-force protection, customizable email templates, and a full admin user-management panel out of the box. ## What Archie Auth handles | Capability | What you get | | - | - | | **Email / password** | Sign up, sign in, sign out, with bcrypt-hashed passwords. | | **JWT tokens** | RS256-signed access tokens (15 min default), with optional JWE encryption for sensitive claims. | | **Refresh tokens** | 30-day default, rotated on every use. | | **Email verification** | 6-digit code sent on signup with a 1-hour expiry. | | **Password recovery** | "Forgot password" flow with 6-digit recovery code. | | **Brute-force protection** | Configurable account lockout after N failed logins. | | **Rate limiting** | Per-IP limits on signup and login, per-email on recovery. | | **Admin user management** | Block / unblock, force logout, resend verification from the dashboard. | | **Customizable emails** | Subject, body, and branding per environment. | | **JWKS endpoint** | Public keys exposed at `/.well-known/jwks.json` for external token validation. | ## When to use Archie Auth vs. a third-party provider | Choose Archie Auth | Choose a third-party provider | | - | - | | You don't already have an identity provider. | You already use Auth0, Clerk, or Cognito. | | Email / password is enough for your users. | You need MFA, enterprise SSO, or rule-based authorization. | | You want zero external dependencies. | You want pre-built sign-in UI components (Clerk). | | You want admin controls without configuring a third party. | You want social login as the only sign-in method. | You can also run Archie Auth alongside other [providers](/docs/features/backend/app-services/authentication-providers/overview) — for example, Archie Auth for email/password and Google for social sign-in, with both writing to the same user record. ## Per-environment isolation Archie Auth configuration, signing keys, and user records are all per-environment. Each [environment](/docs/features/backend/environments/overview) maintains: * Its own auth configuration (password policy, token TTLs, email templates). * Its own RSA signing and encryption keys. * Its own user list. Tokens issued in one environment are not valid in another. Auth configuration is **not copied** when branching environments — you enable it independently in each. ## Where to go next > **[Getting started](/docs/features/backend/app-services/authentication-providers/archie-auth/getting-started)** > > Enable Archie Auth, configure it, and run the full sign-up / sign-in flow. > **[User management](/docs/features/backend/app-services/authentication-providers/archie-auth/user-management)** > > Block, force-logout, and otherwise manage registered users from the dashboard. > **[Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security)** > > Password hashing, rate limiting, key rotation, and JWE encryption. > **[Email templates](/docs/features/backend/app-services/authentication-providers/archie-auth/email-templates)** > > Customize verification and recovery emails plus sender branding. > **[GraphQL API](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api)** > > GraphQL mutations and queries for every auth operation. > **[REST API](/docs/features/backend/app-services/authentication-providers/archie-auth/rest-api)** > > HTTP endpoints — `/auth/signup`, `/auth/login`, `/auth/refresh-token`, and the JWKS endpoint. ## FAQ > **Do I have to enable Archie Auth in every environment separately?** > > Yes. Each environment is isolated — enable it once per environment that needs auth. Auth configuration is intentionally not copied when branching, so a leaked dev secret can't compromise production. > **Can I migrate users into Archie Auth from another provider?** > > Yes — sign users up via the standard signup endpoint with their existing emails. Passwords have to be reset (you can't import bcrypt hashes from a different cost factor) — the standard recovery flow handles that. For machine-readable user data, use the GraphQL API. > **What happens to existing users if I disable Archie Auth?** > > All active refresh tokens are revoked. When you re-enable, you can choose whether to drop the `_auth_credentials` table (wiping users) or keep it (so existing users can sign in again). > **Can I customize the password policy?** > > Yes — minimum length, required character classes, max failed attempts, and lock duration are all configurable per environment. See [Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security). > **Does Archie Auth support MFA?** > > Not in the built-in flow today. For MFA, run a third-party provider like Auth0 alongside or in place of Archie Auth. # Email templates Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/email-templates Customize the verification and password-recovery email templates and the sender branding — per environment, via the dashboard or the GraphQL API. Archie Auth sends two transactional emails — a verification email at signup and a password-recovery email when the user requests a reset. Both the content and the sender branding are customizable per environment, in the dashboard or via the GraphQL API. ## The two default emails | Email | Sent when | Subject (default) | Code expiry | | - | - | - | - | | **Verification** | User signs up (if email verification is on) | `Verify your email — {AppName}` | 1 hour | | **Password recovery** | User requests password recovery | `Reset your password — {AppName}` | 1 hour | Both emails carry a 6-digit code. The recovery email also includes a "if you didn't request this, ignore" line. ## Customizing templates Open **App Services → Authentication Providers → Archie Auth → Settings** and scroll to the **Email Templates** section. The editor has: * **Template selector** — switch between **Verification** and **Recovery**. * **Subject** — plain-text input. * **Body** — rich-text editor (TipTap) with inline variable chips. * **Live preview** — renders the email with branding and sample variable values in real time. ### Template variables Insert variables as colored chips in the body. Click a variable in the toolbar to drop it at the cursor. | Variable | What it expands to | Example | | - | - | - | | `{{otp}}` | The 6-digit code. | `123456` | | `{{otpExpirationMinutes}}` | Code validity in minutes. | `60` | ### Reset to default The **Reset to Default** button discards your custom template and falls back to the platform default. Reset is per-template — resetting Verification doesn't touch Recovery. > [!NOTE] > > Templates are **per-environment**. Editing the template in `staging` doesn't affect `master`. Branching an environment doesn't copy templates — start from default in the new environment, or copy the body manually. ## Customizing via the GraphQL API For codified configuration, set templates with `configureProjectAuth`: ```graphql theme={null} mutation { configureProjectAuth(input: { emailTemplates: { verification: { subject: "Welcome! Verify your email" body: "Hi! Your verification code is {{otp}}. It expires in {{otpExpirationMinutes}} minutes." } recovery: { subject: "Password Reset Request" body: "Your password reset code is {{otp}}. Valid for {{otpExpirationMinutes}} minutes. If you didn't request this, ignore this email." } } }) { success } } ``` See the full [GraphQL API reference](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api#configureprojectauth) for every field on the `configureProjectAuth` input. ## Branding Customize the visual identity of the emails — logo, company name, sender name, footer link. ### Branding fields | Field | What it controls | Default | | - | - | - | | **Logo URL** | Image shown in the email header. | Archie logo | | **Company Name** | Footer company name. | Archie | | **Company Website** | Footer link target. | archie.com | | **Sender Name** | The "From" name on outgoing emails. | Platform default | Set these in the **Email Branding** section of Settings, or programmatically: ```graphql theme={null} mutation { configureProjectAuth(input: { emailBranding: { logoUrl: "https://example.com/logo.png" companyName: "Acme Corp" companyWebsite: "https://acme.com" senderName: "Acme Auth" } }) { success } } ``` ### Branding behavior * Custom branding overrides Archie's defaults in the header, footer, and sender name. * The overall email layout — structure, responsiveness — stays consistent. Only the branding slots change. * Empty fields fall back to platform defaults. * The `senderName` field overrides the SendGrid "From Name" without changing the verified sender email address (which stays platform-managed). * Branding is **per-environment** — `staging` and `master` can use different logos and company names. ## Email delivery Two transports back the same MJML templates: | Transport | Description | | - | - | | **SES** | Amazon SES — production default. | | **SMTP** | Standard SMTP relay — used for self-hosted and fallback. | The transport is configured at the platform level and isn't changeable per project. Both transports deliver identical emails. For projects routing through SendGrid, see the [SendGrid integration](/docs/features/backend/integrations/sendgrid). ### Rate limiting Email sending is rate-limited to **5 emails per user per hour** to defend against abuse (someone repeatedly triggering recovery emails to flood an inbox). ## FAQ > **Why aren't templates copied when I branch an environment?** > > Same reason auth secrets aren't — environment isolation. Branching a `staging` environment from `master` shouldn't silently propagate template wording you might be testing. Copy manually if you want them to match. > **Can I add my own variables to a template?** > > Today the supported variables are `{{otp}}` and `{{otpExpirationMinutes}}`. Anything else in `{{...}}` is rendered as literal text. To inject more context, do it server-side via the auth events and send your own email through a custom function. > **The user reports they didn't receive the email — what do I check?** > > First, the spam folder. Then verify the address in the user record (typos at signup are common). If consistently undelivered, check transport-level delivery logs in your email provider (SES or SendGrid) and confirm the sender domain's DKIM / SPF records are valid. > **Can I send a custom email from auth events?** > > Yes — subscribe to events like `auth.user.registered` from a custom function or webhook and send your own email via the integrated provider. The built-in templates handle the OTP delivery; everything else (welcome flow, marketing onboarding) is yours. > **Does the senderName appear in the From: header?** > > Yes — it overrides the display name. The sender email address stays the platform-verified address, which is what the receiving mail server validates against DKIM / SPF. # Getting started Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/getting-started Enable Archie Auth, configure the password policy and token TTLs, and walk the full signup → verify → login flow. This page gets you from a blank project to a working signup / login flow against Archie Auth in a few minutes. After that, see [User management](/docs/features/backend/app-services/authentication-providers/archie-auth/user-management), [Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security), and the [REST](/docs/features/backend/app-services/authentication-providers/archie-auth/rest-api) / [GraphQL](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api) API references for the full surface. ## Enable Archie Auth **1. Open Authentication Providers** Navigate to **App Services → Authentication Providers** in the Backend Console. Archie Auth sits at the top of the list with a "Built-in" badge. **2. Click Configure → Enable** Toggle **Enable** to activate Archie Auth for the **current environment**. Each environment must be enabled independently. **3. Wait for setup to complete** On enable, Archie automatically: * Generates a 2048-bit RSA signing key pair (RS256) for JWT signing. * Generates a 2048-bit RSA encryption key pair (RSA-OAEP-256) for optional JWE encryption. * Encrypts both private keys with AES-GCM before storing them. * Creates the `_auth_credentials` table in the project database. * Registers the identity service with the GraphQL schema. The Archie Auth panel now shows three tabs: **Settings**, **Users**, and **API**. ## Configure the policy The **Settings** tab is where you tune behavior. Defaults are sensible — you can leave most of it alone — but the configurable fields are: ### General | Setting | Default | Description | | - | - | - | | **Self-Signup** | On | Whether anonymous visitors can register themselves. | | **Email Verification** | On | Whether users must confirm their email before logging in. | | **Allowed Email Domains** | empty | Optional allowlist of domains permitted to sign up. | ### Security policy | Setting | Default | Description | | - | - | - | | **Password Min Length** | 8 | Shortest password accepted. | | **Require Uppercase** | Yes | At least one A–Z. | | **Require Lowercase** | Yes | At least one a–z. | | **Require Digit** | Yes | At least one 0–9. | | **Require Special Char** | Yes | At least one of `!@#$%^&*` etc. | | **Max Failed Attempts** | 5 | Failed logins before lockout. `0` disables. | | **Lock Duration** | 30 min | How long a locked account stays locked. | ### Token configuration | Setting | Default | Description | | - | - | - | | **Access Token TTL** | 900 seconds (15 min) | Lifetime of an access token. | | **Refresh Token TTL** | 2,592,000 seconds (30 days) | Lifetime of a refresh token. | | **JWE Encryption** | Off | Encrypt access token claims (turns 3-part JWS into 5-part JWE). | For the cryptographic background and JWE trade-off, see [Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security). ## Quick test — the full auth flow After enabling, exercise the public endpoints directly with `curl`. The same operations are available on the [GraphQL API](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api). ### 1. Sign up ```bash theme={null} curl -X POST https://your-gateway.example.com/auth/signup \ -H "Content-Type: application/json" \ -H "X-Project-Id: your-project-id" \ -H "environment: master" \ -d '{ "email": "user@example.com", "password": "SecureP@ss1", "firstName": "John", "lastName": "Doe" }' ``` ```json theme={null} { "userId": "a1b2c3d4-...", "message": "Verification email sent" } ``` The response is `201 Created`. A 6-digit verification code lands in the user's inbox. ### 2. Confirm the email ```bash theme={null} curl -X POST https://your-gateway.example.com/auth/confirm-signup \ -H "Content-Type: application/json" \ -H "X-Project-Id: your-project-id" \ -H "environment: master" \ -d '{ "email": "user@example.com", "code": "123456" }' ``` ```json theme={null} { "accessToken": "eyJhbGciOiJSUzI1NiIs...", "refreshToken": "dGhpcyBpcyBh...", "user": { "id": "a1b2c3d4-...", "email": "user@example.com", "firstName": "John", "lastName": "Doe", "roles": [] } } ``` Confirming email returns the user logged in — you don't need a separate login call after signup. ### 3. Log in For users that confirmed earlier and need a fresh token: ```bash theme={null} curl -X POST https://your-gateway.example.com/auth/login \ -H "Content-Type: application/json" \ -H "X-Project-Id: your-project-id" \ -H "environment: master" \ -d '{ "email": "user@example.com", "password": "SecureP@ss1" }' ``` The response is the same shape as confirm-signup — access token, refresh token, user object. ### 4. Use the access token Send the access token as a Bearer token on subsequent API calls: ```bash theme={null} curl -X POST https://your-api.example.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ -H "X-Project-Id: your-project-id" \ -H "environment: master" \ -d '{ "query": "{ usersList { items { id email } } }" }' ``` The token is checked on every request. The carrier's [roles](/docs/features/backend/app-services/role-based-access) decide which fields and rows the response contains. ### 5. Refresh when the access token expires Access tokens are short-lived by design. Refresh them with the refresh token: ```bash theme={null} curl -X POST https://your-gateway.example.com/auth/refresh-token \ -H "Content-Type: application/json" \ -H "X-Project-Id: your-project-id" \ -H "environment: master" \ -d '{ "refreshToken": "dGhpcyBpcyBh..." }' ``` > [!WARNING] > > Refresh tokens are **rotated on use**. The previous refresh token is invalidated and a new one is returned in the response. Always store the new refresh token. ## Disabling Archie Auth If you outgrow Archie Auth or migrate to another provider: **1. Open the Archie Auth panel** Navigate to **App Services → Authentication Providers → Archie Auth**. **2. Click Disable** A confirmation dialog appears with an option to drop the `_auth_credentials` table. **3. Choose what happens to existing users** * **Keep the table** — re-enabling later preserves the user list and credentials. * **Drop the table** — wipes the user list. Re-enabling creates a fresh table. > [!WARNING] > > Disabling auth revokes all active refresh tokens immediately. Users will need to log in again after you re-enable. ## FAQ > **Why does the access token expire so fast?** > > Short-lived access tokens limit the blast radius of a stolen token. The refresh token (30 days by default) is the long-lived credential — it stays on the client and gets rotated on every use, so a leaked refresh token also gets invalidated quickly. > **Can I bypass email verification during development?** > > Yes — toggle **Email Verification** off in the Settings tab for that environment. Users sign up and immediately get a usable session. Re-enable it before exposing the environment to real users. > **What's the cleanest way to seed admin users?** > > Use the GraphQL API with an admin token, or the [REST signup endpoint](/docs/features/backend/app-services/authentication-providers/archie-auth/rest-api#post-auth-signup) with the `roleId` field set to your admin role's ID. Either way the user goes through the standard signup path so the `_auth_credentials` row is consistent. > **What if my emails aren't being delivered?** > > The transport (SES or SMTP) is configured at the platform level. Check the user's spam folder first. If consistently undelivered, see [Email templates](/docs/features/backend/app-services/authentication-providers/archie-auth/email-templates) for branding and sender configuration, and [SendGrid integration](/docs/features/backend/integrations/sendgrid) if you're routing through SendGrid. > **How do I rotate my signing key?** > > From the Settings tab or via the `rotateAuthKeys` mutation. Old keys are kept for a 1-hour grace period so in-flight tokens stay valid. See [Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security#key-rotation). # GraphQL API Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/graphql-api GraphQL mutations and queries for Archie Auth — signup, login, password reset, refresh, admin operations, and configuration. Archie Auth exposes the full auth surface through the platform's unified [GraphQL API](/docs/features/backend/graphql-api-explorer/overview). This page is the operation reference — every mutation and query, with example payloads. For the equivalent HTTP endpoints, see the [REST API reference](/docs/features/backend/app-services/authentication-providers/archie-auth/rest-api). ## Required headers | Header | Required | Description | | - | - | - | | `Authorization` | For authenticated operations | Bearer access token. | | `X-Project-Id` | Yes | Your project identifier. | | `environment` | Yes | Target environment name (e.g., `master`). | Auth operations route through the API Manager directly to the identity service — they don't pass through the regular GraphQL resolver layer, but they appear as standard GraphQL operations to the client. ## User mutations ### `authSignup` Register a new user account. ```graphql theme={null} mutation { authSignup(input: { email: "user@example.com" password: "SecureP@ss1" firstName: "John" lastName: "Doe" }) { userId message } } ``` Returns a `userId` and a confirmation message. A verification email is sent if email verification is enabled in the project settings. ### `authLogin` Authenticate with email and password. ```graphql theme={null} mutation { authLogin(input: { email: "user@example.com" password: "SecureP@ss1" }) { accessToken refreshToken user { id email firstName lastName roles } } } ``` Returns the access/refresh token pair and the user object including assigned roles. ### `authConfirmSignup` Confirm an email with the 6-digit verification code. ```graphql theme={null} mutation { authConfirmSignup(input: { email: "user@example.com" code: "123456" }) { accessToken refreshToken user { id email firstName lastName } } } ``` On success the user is logged in immediately — no follow-up `authLogin` call needed. ### `authRecoverPassword` Request a password recovery email. Always returns success to prevent email enumeration. ```graphql theme={null} mutation { authRecoverPassword(input: { email: "user@example.com" }) { message } } ``` ### `authResetPassword` Reset a password using the recovery code from the recovery email. ```graphql theme={null} mutation { authResetPassword(input: { email: "user@example.com" newPassword: "NewSecureP@ss2" code: "654321" }) { message } } ``` A successful reset clears any active account lockout. ### `authRefreshToken` Exchange a refresh token for a new access/refresh token pair. ```graphql theme={null} mutation { authRefreshToken(input: { refreshToken: "base64-encoded-token..." }) { accessToken refreshToken } } ``` > [!WARNING] > > Refresh tokens are rotated on every use — the previous one is invalidated. Always store the new refresh token from the response. ## Admin mutations These mutations require admin-level authentication. Attach the `admin` role (or another role with the necessary permissions) to a user or [API key](/docs/features/backend/settings/api-keys) and use that token to call them. ### `adminResendVerification` Re-send the verification code to a specific user. ```graphql theme={null} mutation { adminResendVerification(input: { email: "user@example.com" }) { success message } } ``` ### `adminForceLogout` Invalidate the refresh token of a specific user. Their access token continues to work until natural expiry; the next refresh fails. ```graphql theme={null} mutation { adminForceLogout(input: { userId: "a1b2c3d4-e5f6-..." }) { success message } } ``` ### `adminForceLogoutAll` Invalidate every refresh token in the current environment. Use after a security incident. ```graphql theme={null} mutation { adminForceLogoutAll { success message } } ``` ### `adminToggleUserStatus` Block or unblock a user account. Blocked users cannot log in. ```graphql theme={null} mutation { adminToggleUserStatus(input: { userId: "a1b2c3d4-e5f6-..." disabled: true }) { success message } } ``` ## Admin queries ### `adminListCredentials` List every registered credential in the current environment. ```graphql theme={null} query { adminListCredentials { id userId email emailVerified disabled lastLoginAt failedAttempts lockedUntil createdAt } } ``` Useful for building admin UIs, exporting user lists, or syncing user state into another system. ## Auth configuration mutations These manage the auth setup for the current project + environment. They require admin-level authentication. ### `enableProjectAuth` Enable Archie Auth for the current environment. Generates the signing and encryption key pairs, creates the `_auth_credentials` table, and registers the identity service. ```graphql theme={null} mutation { enableProjectAuth { success message } } ``` ### `disableProjectAuth` Disable Archie Auth. Optionally drop the `_auth_credentials` table — keep it to preserve user records if you intend to re-enable later. ```graphql theme={null} mutation { disableProjectAuth(input: { dropTable: false }) { success message } } ``` Disabling revokes all active refresh tokens. ### `configureProjectAuth` Update auth settings for the current environment. Every field is optional — pass only the fields you want to change. ```graphql theme={null} mutation { configureProjectAuth(input: { selfSignup: true emailVerification: true jweEnabled: false passwordPolicy: { minLength: 10 requireUppercase: true requireLowercase: true requireDigit: true requireSpecial: true } accountLockout: { maxAttempts: 5 lockDuration: 1800 } tokenTTL: { accessToken: 900 refreshToken: 2592000 } emailTemplates: { verification: { subject: "Verify your email" body: "Your code is {{otp}}" } recovery: { subject: "Reset your password" body: "Your recovery code is {{otp}}" } } emailBranding: { logoUrl: "https://example.com/logo.png" companyName: "My Company" companyWebsite: "https://example.com" senderName: "My Company Auth" } }) { success } } ``` For background on each policy option, see [Security](/docs/features/backend/app-services/authentication-providers/archie-auth/security). For email customization, see [Email templates](/docs/features/backend/app-services/authentication-providers/archie-auth/email-templates). ### `rotateAuthKeys` Rotate signing and/or encryption keys. Old keys are kept for a 1-hour grace period so existing tokens stay valid through the rollover. ```graphql theme={null} mutation { rotateAuthKeys(input: { keyType: "both" }) { success message } } ``` `keyType` accepts `"signing"`, `"encryption"`, or `"both"` (default `"both"`). ## Configuration query ### `getProjectAuth` Read the current auth configuration for the active environment. ```graphql theme={null} query { getProjectAuth { enabled selfSignup emailVerification jweEnabled passwordPolicy { minLength requireUppercase requireLowercase requireDigit requireSpecial } accountLockout { maxAttempts lockDuration } tokenTTL { accessToken refreshToken } emailBranding { logoUrl companyName companyWebsite senderName } } } ``` ## FAQ > **Why don't admin operations show up in the GraphQL Documentation panel?** > > Admin operations require admin-level authentication. The Documentation panel reflects the schema available to your current token — if you're logged in with a non-admin role, admin operations are filtered out. Switch to an admin token to see them. > **Can I run these operations from the GraphQL API Explorer?** > > Yes. The Explorer authenticates as your workspace user, so all admin operations are available without extra setup. Outside the Explorer, attach an admin token (or API key) on each request. > **Are there subscriptions for auth events?** > > Auth events are emitted as system events — `auth.user.registered`, `auth.user.login.success`, etc. — but not as GraphQL subscriptions. Consume them via webhooks or custom functions. See [Security → Authentication events](/docs/features/backend/app-services/authentication-providers/archie-auth/security#authentication-events). > **What's the right way to handle a refresh-token rotation race?** > > Single-flight the refresh on the client — only one in-flight refresh at a time. If two tabs trigger refresh simultaneously, the second one fails with `AUTH_TOKEN_INVALID` because the first already rotated the token. Coordinate via a shared lock or a single-tab refresh worker. > **How do I configure the password policy without the dashboard?** > > Call `configureProjectAuth` with the `passwordPolicy` field set. Useful for codifying configuration alongside infra-as-code. # REST API Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/rest-api HTTP endpoints for Archie Auth — signup, login, password reset, refresh, JWKS, and the error code reference. Archie Auth exposes its full surface as REST endpoints on the platform gateway. Use them when you'd rather call HTTP directly than go through GraphQL — anywhere a `curl` or a thin HTTP client is the right tool. For the equivalent GraphQL operations, see the [GraphQL API reference](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api). ## Required headers | Header | Required | Default | Description | | - | - | - | - | | `X-Project-Id` | Yes | — | Your project identifier. | | `environment` | No | `master` | Target environment name. | | `Content-Type` | Yes | — | `application/json`. | | `Authorization` | Protected endpoints only | — | `Bearer `. | **Base URL:** `https://your-gateway.example.com` ## Public endpoints These don't require authentication — they're the way unauthenticated visitors enter the system. ### POST `/auth/signup` Register a new user account. **Request:** ```json theme={null} { "email": "user@example.com", "password": "SecureP@ss1", "firstName": "John", "lastName": "Doe", "roleId": "optional-role-id" } ``` **Responses:** | Status | Body | When | | - | - | - | | 201 | `{ "userId": "uuid", "message": "Verification email sent" }` | Created. | | 400 | `{ "error": "VALIDATION_ERROR", "violations": [...] }` | Invalid input or weak password. | | 409 | `{ "error": "AUTH_EMAIL_EXISTS" }` | Email already registered. | | 404 | `{ "error": "AUTH_NOT_CONFIGURED" }` | Auth not enabled for this project + environment. | | 429 | `{ "error": "RATE_LIMIT_EXCEEDED" }` | Too many signups (10/min per IP). | If email verification is disabled, the user is auto-verified and can log in immediately. ### POST `/auth/login` Authenticate and receive an access/refresh token pair. **Request:** ```json theme={null} { "email": "user@example.com", "password": "SecureP@ss1" } ``` **Successful response (200):** ```json theme={null} { "accessToken": "eyJhbGciOiJSUzI1NiIs...", "refreshToken": "base64-encoded-token...", "user": { "id": "a1b2c3d4-e5f6-...", "email": "user@example.com", "firstName": "John", "lastName": "Doe", "roles": ["Member"] } } ``` **Error responses:** | Status | Body | When | | - | - | - | | 401 | `{ "error": "AUTH_INVALID_CREDENTIALS" }` | Wrong email or password. | | 403 | `{ "error": "AUTH_EMAIL_NOT_VERIFIED" }` | Email not yet confirmed. | | 423 | `{ "error": "AUTH_ACCOUNT_LOCKED", "lockedUntil": "..." }` | Locked from failed attempts. | | 429 | `{ "error": "RATE_LIMIT_EXCEEDED" }` | Too many logins (20/min per IP). | When JWE encryption is enabled, the `accessToken` is a 5-part JWE string instead of a 3-part JWS. Both formats are accepted on inbound requests. ### POST `/auth/confirm-signup` Confirm an email with the 6-digit code. **Request:** ```json theme={null} { "email": "user@example.com", "code": "123456" } ``` **Responses:** | Status | Body | When | | - | - | - | | 200 | `{ "accessToken": "...", "refreshToken": "...", "user": {...} }` | Confirmed and logged in. | | 400 | `{ "error": "AUTH_INVALID_CODE" }` | Code wrong, expired, or attempt limit hit. | Codes expire after **1 hour**. Up to **5 attempts** are allowed before the code is invalidated. ### POST `/auth/recover-password` Request a password recovery email. Always returns 200, regardless of whether the email exists, to prevent enumeration. **Request:** ```json theme={null} { "email": "user@example.com" } ``` **Response (always 200):** ```json theme={null} { "message": "If account exists, recovery email sent" } ``` Rate-limited to **5 / minute per email**. ### POST `/auth/reset-password` Reset a password using the recovery code. **Request:** ```json theme={null} { "email": "user@example.com", "newPassword": "NewSecureP@ss2", "code": "654321" } ``` **Responses:** | Status | Body | When | | - | - | - | | 200 | `{ "message": "Password reset successfully" }` | Updated. | | 400 | `{ "error": "AUTH_INVALID_CODE" }` | Code wrong or expired. | A successful reset clears any active account lockout. ### POST `/auth/refresh-token` Exchange a refresh token for a new access/refresh pair. **Request:** ```json theme={null} { "refreshToken": "base64-encoded-token..." } ``` **Responses:** | Status | Body | When | | - | - | - | | 200 | `{ "accessToken": "...", "refreshToken": "..." }` | New token pair. | | 401 | `{ "error": "AUTH_TOKEN_INVALID" }` | Invalid or already-used refresh token. | > [!WARNING] > > Refresh tokens rotate on every use. The previous refresh token is invalidated; store the new one from the response. Reusing an old refresh token returns `AUTH_TOKEN_INVALID` and triggers a session-wide invalidation as a token-theft signal. ### GET `/auth/.well-known/jwks.json` Public JWKS endpoint for external services validating Archie-issued tokens. No authentication required. **Response (200):** ```json theme={null} { "keys": [ { "kty": "RSA", "kid": "abc12345", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" } ] } ``` When JWE encryption is enabled, two keys are returned — one for signing and one for encryption: ```json theme={null} { "keys": [ { "kty": "RSA", "kid": "abc12345", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" }, { "kty": "RSA", "kid": "enc-def67890", "use": "enc", "alg": "RSA-OAEP-256", "n": "...", "e": "AQAB" } ] } ``` The response is cached in-memory for 5 minutes. External services should refresh on `kid` mismatch. ## Protected endpoints Require `Authorization: Bearer `. ### POST `/auth/logout` Revoke the current access token and invalidate the associated refresh token. ```bash theme={null} curl -X POST https://your-gateway.example.com/auth/logout \ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ -H "X-Project-Id: your-project-id" \ -H "environment: master" ``` **Responses:** | Status | Body | When | | - | - | - | | 200 | `{ "message": "Logged out successfully" }` | Token revoked. | | 401 | `{ "error": "AUTH_TOKEN_INVALID" }` | Missing or invalid token. | Revoked tokens are tracked in a distributed blacklist until their natural expiry, so re-presenting the access token after logout fails with `401`. ## Error codes reference | Code | HTTP | Meaning | | - | - | - | | `AUTH_EMAIL_EXISTS` | 409 | Email already registered. | | `AUTH_INVALID_CREDENTIALS` | 401 | Wrong email or password. | | `AUTH_EMAIL_NOT_VERIFIED` | 403 | Email not yet confirmed. | | `AUTH_ACCOUNT_LOCKED` | 423 | Too many failed attempts; account temporarily locked. | | `AUTH_INVALID_CODE` | 400 | Verification or recovery code wrong, expired, or out of attempts. | | `AUTH_TOKEN_EXPIRED` | 401 | Access token has expired. | | `AUTH_TOKEN_INVALID` | 401 | Token malformed, revoked, or tampered. | | `AUTH_NOT_CONFIGURED` | 404 | Auth not enabled for this project + environment. | | `RATE_LIMIT_EXCEEDED` | 429 | Too many requests; honor `Retry-After`. | | `VALIDATION_ERROR` | 400 | Request body validation failed (see `violations`). | ## Rate limits | Endpoint | Limit | Scope | | - | - | - | | `POST /auth/signup` | 10 / minute | Per IP | | `POST /auth/login` | 20 / minute | Per IP | | `POST /auth/recover-password` | 5 / minute | Per email | `429` responses include a `Retry-After` header indicating seconds to wait. ## FAQ > **Why does `/auth/recover-password` always return 200?** > > To prevent email enumeration. If the response distinguished "exists" from "doesn't exist", an attacker could probe a list of emails and learn which are registered. Returning 200 either way removes that signal. > **What's the safe way to validate tokens at the edge?** > > Fetch the JWKS from `/auth/.well-known/jwks.json` and validate signatures locally. Cache the JWKS on your edge service; refresh on `kid` mismatch (which signals key rotation). Don't call Archie on every request to validate. > **My token validates locally but Archie rejects it on the API — why?** > > Most likely a revocation. Archie checks a distributed token blacklist on every request, so logged-out and force-logged-out tokens fail even if the signature is valid. The local-only validation can't see the blacklist. > **How do I handle 423 Account Locked?** > > Surface a clear "account temporarily locked" message and either show the countdown from `lockedUntil` or offer a password reset link — a successful reset clears the lockout. > **Can I rate-limit more aggressively for my own use case?** > > Put a [Custom API](/docs/features/backend/app-services/custom-apis) in front of `/auth/login` with a tighter rate-limit policy. The auth endpoint defaults are platform-wide; per-route Custom API config lets you go stricter. # Security Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/security Password hashing, JWT signing, JWE encryption, refresh-token rotation, rate limiting, account lockout, and key rotation. This page is the security reference for Archie Auth — the cryptography under the hood, the rate-limit and lockout defaults, and how to rotate keys without taking users offline. ## Password hashing Passwords are hashed with **bcrypt** (cost factor 12) before storage. Plaintext passwords are never persisted. Bcrypt's per-hash salt means identical passwords produce different hashes — rainbow tables are useless against the stored values. ### Password policy Configurable per environment. Defaults: | Rule | Default | | - | - | | Minimum length | 8 characters | | Uppercase letter | Required | | Lowercase letter | Required | | Digit | Required | | Special character | Required | Policy violations come back as an `array` in the signup / reset response, so your UI can show targeted feedback ("password needs a special character") rather than a generic error. ## Token strategy Archie Auth issues a two-tier token pair on every successful authentication: ### Access token | Property | Value | | - | - | | Format | JWT | | Signing algorithm | RS256 (RSA + SHA-256) | | Default TTL | 15 minutes | | Claims | `iss`, `sub` (userId), `aud` (projectId), `exp`, `iat`, `email`, `roles[]`, `environment` | Sent on every API call as `Authorization: Bearer `. The short lifetime is deliberate — limits how long a stolen token is useful. ### Refresh token | Property | Value | | - | - | | Format | Cryptographically random opaque string | | Default TTL | 30 days | | Rotation | Each use issues a new refresh token and invalidates the previous one | Sent only to `/auth/refresh-token`. **Always store the new refresh token from every refresh response** — the previous one is invalidated immediately. Reusing a previously-rotated refresh token is detected and triggers a session-wide invalidation. ## JWS vs. JWE Two access-token formats. Pick based on whether the claims need to be confidential. ### JWS — signed, claims visible (default) A standard 3-part `header.payload.signature` JWT. Claims are readable to anyone who decodes the token; the signature prevents tampering. Validated with the public key from the JWKS endpoint. This is the right default. Most applications can tolerate readable claims (subject, email, roles). ### JWE — encrypted (opt-in) A 5-part JWE string. Claims are encrypted and unreadable without the platform's private decryption key. Built on top of JWS: tokens are signed first, then encrypted (nested JWT). | Algorithm | Used for | | - | - | | **RSA-OAEP-256** | Key wrapping. | | **A256GCM** | Content encryption. | Enable JWE when claims contain sensitive information that must not be inspectable at the client or in logs, or when compliance mandates strict claim confidentiality. Toggle **JWE Encryption** in the Archie Auth settings to enable, or call `configureProjectAuth` with `jweEnabled: true`. Both formats are accepted on inbound requests, so you can flip the toggle without breaking in-flight tokens. ## Key management ### Two key pairs Separate 2048-bit RSA key pairs for signing and encryption. Private keys are encrypted at rest with AES-GCM. Public signing keys are exposed at the standard JWKS endpoint: ```text theme={null} GET https://your-gateway.example.com/auth/.well-known/jwks.json ``` External services (microservices, edge workers, anything that needs to validate Archie-issued tokens locally) can fetch the JWKS and verify tokens without calling Archie. ### Per-environment keys Each environment manages its own keys independently. A token signed in `staging` cannot be validated in `master`. That isolation is the security boundary — there's no path by which a `dev` token bleeds into production traffic. ### Key rotation Rotate from the Settings tab or via the `rotateAuthKeys` mutation. The flow: 1. New key pairs are generated. 2. Old public keys are kept for a **1-hour grace period**, so existing tokens stay valid while clients pick up the new keys. 3. New tokens are signed with the new keys immediately. 4. After the grace period, old keys are dropped. ```graphql theme={null} mutation { rotateAuthKeys(input: { keyType: "both" }) { success message } } ``` `keyType` accepts `"signing"`, `"encryption"`, or `"both"`. Rotate signing keys regularly; rotate encryption keys when you suspect compromise or on a compliance schedule. ## Rate limiting Built-in protection on the public auth endpoints: | Endpoint | Limit | Scope | | - | - | - | | `POST /auth/signup` | 10 / minute | Per IP | | `POST /auth/login` | 20 / minute | Per IP | | `POST /auth/recover-password` | 5 / minute | Per email | Exceeding any limit returns `429 Too Many Requests` with a `Retry-After` header. Recovery is per-email (not per-IP) so a single attacker can't burn the rate-limit budget for every user from one address. ## Account lockout After too many failed logins, an account is temporarily locked to defend against credential stuffing. | Setting | Default | | - | - | | Max Failed Attempts | 5 | | Lock Duration | 30 minutes | | Reset on Success | Yes — counter resets on the next successful login. | | Reset on Password Change | Yes — counter and lock state both clear after password reset. | Locked accounts return `423 Locked` with a `lockedUntil` timestamp on the next login attempt. ## Token revocation When a user logs out (or an admin force-logs them out), the access token is added to a distributed blacklist. The blacklist is checked on every request, so a revoked token is rejected before its natural expiry. Refresh tokens are invalidated synchronously — the database row is removed, so the next refresh attempt fails. There is no grace period on revoked refresh tokens. ## Verification code security Email verification and password recovery codes: | Property | Value | | - | - | | Format | 6-digit numeric | | Expiry | 1 hour | | Max attempts | 5 before the code is invalidated | Codes are single-use; on success they're deleted. On lockout, the user (or an admin) can request a fresh code. ## Authentication events Archie Auth emits system events at key points in the lifecycle. Subscribe to them via custom functions or webhooks for audit logging, anomaly detection, or downstream sync. | Event | When it fires | | - | - | | `auth.user.registered` | Successful signup. | | `auth.user.login.success` | Successful login. | | `auth.user.login.failed` | Failed login (wrong password, account state, etc.). | | `auth.user.logout` | Logout (manual or admin force-logout). | | `auth.user.password.changed` | Password updated. | | `auth.user.password.recovery` | Password recovery flow initiated. | | `auth.user.email.verified` | Email confirmed. | | `auth.user.locked` | Account locked due to failed attempts. | Event payloads include environment, user ID, email, timestamp, and (for failures) the reason and originating IP. ## FAQ > **Should I enable JWE?** > > Most apps don't need it — JWS signing is enough. Enable JWE if your access tokens carry sensitive claims you don't want readable client-side or in logs, or if compliance mandates encrypted claims. > **How often should I rotate keys?** > > On a schedule that matches your threat model — every 90 days is a common cadence. Rotate immediately on any suspected compromise. Rotation is non-disruptive thanks to the 1-hour grace period. > **Where do I tune rate limits and lockout?** > > Lockout (max attempts, lock duration) is configurable per environment via the Archie Auth Settings tab or `configureProjectAuth`. Rate limits on the public auth endpoints are platform defaults — for tighter limits, put a [Custom API](/docs/features/backend/app-services/custom-apis) in front with a per-route rate-limit policy. > **Can external services validate tokens without calling Archie?** > > Yes — fetch the JWKS from `/auth/.well-known/jwks.json` and verify locally. The endpoint is public, the response is cached for 5 minutes, and includes both the signing key (`use=sig`) and, when JWE is enabled, the encryption key (`use=enc`). > **What happens if a refresh token is reused?** > > Reuse is treated as a token-theft signal. The session is invalidated and all associated tokens are revoked. The legitimate user sees a forced re-login on their next refresh attempt. # User management Source: https://archie.com/docs/backend/app-services/authentication-providers/archie-auth/user-management Block users, force logout, resend verification, and inspect login activity from the Archie Auth admin panel — or do all of it via the GraphQL admin API. The **Users** tab in the Archie Auth panel is where you manage registered users in the current environment. Block accounts under investigation, force a logout after a security incident, resend verification when an email got lost, or just see who's signed up. Every action available in the UI has a matching [GraphQL admin mutation](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api#admin-mutations) for programmatic use. ## The users table | Column | What it shows | | - | - | | **Email** | The user's registered address. | | **Name** | First + last name. | | **Status** | One of Active, Unverified, Locked, or Disabled (see below). | | **Last Login** | Timestamp of the most recent successful login. | | **Actions** | Per-row admin controls. | Filter by email with the search box. Results paginate at 20 per page. ## User statuses | Status | What it means | | - | - | | **Active** | Verified, not blocked. Can log in. | | **Unverified** | Signed up but hasn't confirmed email. Login blocked until verified. | | **Locked** | Hit the failed-login threshold. Auto-unlocks after the configured lock duration. | | **Disabled** | Manually blocked by an admin. Stays disabled until unblocked. | ## Admin actions ### Block / unblock | Action | Effect | | - | - | | **Block** | Sets `disabled = true` on the credential record. Active sessions aren't terminated; new logins are refused. | | **Unblock** | Clears `disabled`, restoring login access. | Use Block for investigations and account suspensions. To kick a blocked user out of an active session immediately, **Force Logout** them after blocking. ### Resend verification If a user didn't receive (or lost) the verification code, resend it. A new 6-digit code is generated with a fresh 1-hour expiry. The previous code is invalidated. ### Force logout Invalidates the user's refresh token. Their access token stays valid until natural expiry (15 minutes by default), but they can't refresh — so they'll be forced back to the login screen on the next refresh attempt. For an instant kick-out, combine Force Logout with Block. The token blacklist on every request will reject the access token even before it expires. ### Force logout all Revokes every user's refresh token in the current environment. Use after a security incident — a leaked signing key, a database breach, anything that requires forcing every user to re-authenticate. The blast radius is intentionally environment-scoped: forcing logout in `master` doesn't touch `staging`. ## Per-environment scope User management is scoped to the **current environment**. A user registered in `staging` is a different record from a user with the same email registered in `master`. Switching the environment selector changes the entire users table. This separation is the point — it means dev and staging traffic doesn't pollute production user records, and a developer logging in to `staging` doesn't burn rate-limit budget in `master`. ## GraphQL admin API Every dashboard action has a corresponding GraphQL mutation. Useful for scripted user-management — bulk-revoke after an incident, sync user state from another system, or build your own admin UI. | Mutation / Query | Purpose | | - | - | | `adminListCredentials` | List every registered user with status, email, and last-login. | | `adminToggleUserStatus` | Block or unblock a user. | | `adminResendVerification` | Send a fresh verification code to a user. | | `adminForceLogout` | Force-logout a specific user. | | `adminForceLogoutAll` | Force-logout every user in the current environment. | ```graphql theme={null} mutation BlockUser { adminToggleUserStatus(input: { userId: "a1b2c3d4-e5f6-..." disabled: true }) { success message } } ``` Full request/response shapes are in the [Archie Auth GraphQL API reference](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api#admin-mutations). ## Permissions Admin operations require admin-level authentication — typically a user with the `admin` role or an [API key](/docs/features/backend/settings/api-keys) attached to that role. Define the role in [Role-Based Access](/docs/features/backend/app-services/role-based-access) and grant it the system permissions needed for the admin mutations. ## FAQ > **What's the difference between Block and Force Logout?** > > Block prevents future logins. Force Logout terminates the current session by invalidating the refresh token. Combine them for an instant kick-out: Block stops new logins; Force Logout cuts the live session. > **A user is locked but the lock duration is too long — how do I unlock them?** > > Block then Unblock the user. That clears the lockout state. Alternatively, run a successful password reset — the recovery flow clears the lockout on success. > **Does Force Logout All affect API keys?** > > No — API keys are managed separately under [Backend → Settings → API Keys](/docs/features/backend/settings/api-keys) and aren't touched by user-session operations. Rotate compromised API keys there. > **Can I delete a user instead of disabling them?** > > Use the Data Model's `users` table or `_auth_credentials` to delete the row. Hard deletion removes the credential entirely; disabling preserves history. Most teams disable rather than delete to keep an audit trail. > **How do I bulk-export the user list?** > > Use `adminListCredentials` over the [GraphQL API](/docs/features/backend/app-services/authentication-providers/archie-auth/graphql-api), or query the `_auth_credentials` table directly via the [Data Viewer](/docs/features/backend/data-model/data-viewer) or [SQL Playground](/docs/features/backend/data-model/sql-playground). # Custom APIs Source: https://archie.com/docs/backend/app-services/custom-apis Register HTTP routes that reverse-proxy to internal services with CORS, rate limiting, request transformation, circuit breakers, and caching. Custom APIs are HTTP routes you register on Archie's gateway that reverse-proxy to internal services. Each route maps a public-facing URL path to a target URL and brings the target under the same auth, CORS, rate-limiting, and resilience policies as the rest of your backend. Use a Custom API when: * You have an existing internal service you want to expose under your project's domain. * You want CORS, rate limiting, or a circuit breaker in front of a third-party endpoint. * You need request/response transformation that doesn't belong in your service code. For CRUD against the [Data Model](/docs/features/backend/data-model/overview), you don't need a Custom API — the auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs already cover that. To open it, navigate to **App Services → Custom APIs** in the Backend Console. ![Custom API Gateway overview](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/custom-api-gateway-overview.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=a70dfae05f2a035ea2e1fd608cf5d82e) ## Per-environment scoping Routes are scoped per environment. Each [environment](/docs/features/backend/environments/overview) has its own routes — production routes can point at production services while `dev` routes point at staging instances of the same service. When branching an environment with the **Gateway routes** copy option, every route is duplicated into the new environment. Each copy receives a new ID and can be edited independently. ## Registering a route The configuration is split across four tabs: **General**, **Security**, **Request & Response**, and **Resilience**. ### General — routing logic The **General** tab defines what the route catches and where it goes. | Field | Purpose | | - | - | | **Path** | Public-facing URI path clients call (e.g., `/api/v1/users`). | | **Target URL** | Internal destination URL where the gateway forwards the request. | | **Methods** | HTTP verbs the route responds to: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. | | **Enabled** | Toggle to activate or deactivate the route without deleting it. | ![General Configuration Form](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/custom-api-gateway-general-config.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=0a911ffe73a24dc6dd7217003a07ee52) ### Security — CORS and rate limiting The **Security** tab controls cross-origin access and rate limits per route. #### CORS | Setting | What it does | | - | - | | **Allow All Origins** | Accept requests from any domain. Disable to use the allowlist. | | **Allowed Origins** | Whitelist of specific domains permitted to call the route. | | **Allowed Methods & Headers** | HTTP verbs and custom headers the gateway will accept. | | **Allow Credentials** | Whether browsers should include cookies or auth headers. | | **Preflight Cache Duration** | How long browsers cache the CORS preflight response. | | **Expose Headers** | Response headers safe to expose to the client. | #### Rate limiting | Setting | What it does | | - | - | | **Average** | Baseline requests allowed per time unit. | | **Burst Allowance** | Extra requests allowed in short spikes above the average. | | **Time Window** | Counting period — typically per second or per minute. | | **Rate Limit by** | How to identify unique callers — typically by IP address. | When the limit is exceeded, the gateway returns `429 Too Many Requests` with a `Retry-After` header. ![Route Security Configuration](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/custom-api-gateway-route-security.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=e586179a9e9951c44bef317994783479) For project-wide CORS and rate-limit defaults, see [Backend → Settings → Network](/docs/features/backend/settings/network). ### Request & Response — transformations The **Request & Response** tab controls how data is shaped on the way through the gateway. #### Custom headers Add or override headers per route: * **Custom request headers** — sent to your backend on top of what the client sent. * **Custom response headers** — added to the response on the way back. Each header is a name and a value. Useful for injecting service-to-service auth tokens or stamping responses with a `Cache-Control` policy. #### Path rewriting Modify the request path before it reaches your backend: | Operation | Example | | - | - | | **Strip prefix** | `/v1/users/123` → `/users/123` (strip `/v1`). | | **Add prefix** | `/users/123` → `/api/users/123` (add `/api`). | | **Regex pattern & replacement** | `^/old/(.*)` → `/new/$1` for arbitrary remapping. | #### Query parameter forwarding Choose which query string parameters reach the backend: | Mode | Behavior | | - | - | | **Forward all** | Pass the entire query string unchanged. | | **Allow list** | Forward only the named parameters. | | **Deny list** | Forward all parameters except those explicitly blocked. | #### Body size limits | Setting | Purpose | | - | - | | **Request body size limit** | Maximum size of incoming request bodies. | | **Response body size limit** | Maximum size of outgoing response bodies. | Exceeding either limit returns `413 Payload Too Large`. ![Request and Response Settings](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/custom-api-gateway-request-response.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=95847c675e730e3501b8893a00edc114) ### Resilience — timeouts, circuit breakers, caching The **Resilience** tab keeps your route healthy when the backend doesn't cooperate. #### Custom timeout How long the gateway waits for a response before giving up. Set between 1 and 300 seconds. Without a timeout, a slow backend can pin gateway capacity indefinitely. #### Circuit breaker Automatically stop forwarding requests to a failing backend so failures don't cascade. Apply a preset or write a custom expression: | Preset | Trips when | | - | - | | **50% errors** | Error ratio crosses 0.5 over the check window. | | **30% errors** | Error ratio crosses 0.3. | | **High latency** | p99 response time crosses the threshold. | | **500+ errors** | Sustained 5xx responses. | | Setting | Purpose | | - | - | | **Expression** | Custom trip condition (e.g., `NetworkErrorRatio() > 0.5`). | | **Check (s)** | How often the breaker checks while open. | | **Fallback (s)** | Wait before attempting a partial close. | | **Recovery (s)** | Recovery phase duration before fully closing. | #### Response caching | Setting | Purpose | | - | - | | **Cache TTL (seconds)** | Time-to-live for `GET` responses. Subsequent identical requests within the TTL are served from gateway cache. | Caching applies only to safe (`GET`) requests. Mutations are never cached. ![Resilience and Reliability Settings](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/custom-api-gateway-resilience.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=7dd88f6a232a47218da9a1d77e7e0320) ## Programmatic management Routes can also be created via GraphQL — useful for codifying gateway configuration alongside your data model. ```graphql theme={null} mutation RegisterRoute($input: RegisterRouteInput!) { registerRoute(input: $input) { id path targetUrl methods enabled environment } } ``` When you delete an environment, all routes attached to it are cleaned up automatically. To delete environment routes manually: ```graphql theme={null} mutation DeleteRoutesByEnvironment($input: DeleteRoutesByEnvironmentInput!) { deleteRoutesByEnvironment(input: $input) { deletedCount } } ``` ## Permissions Custom API routes inherit the same auth and authorization model as the rest of your backend. Authenticated requests carry their roles to the route, where you can apply per-route policy. For full coverage, see [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **When should I use a Custom API instead of a custom function?** > > Use a Custom API when you have an existing service to expose — the gateway adds CORS, rate limiting, and resilience without code. Use a custom function when the logic itself runs inside Archie. The two compose: a Custom API can route to a custom function the same way it routes to any backend. > **Are routes copied between environments?** > > Optionally — tick **Gateway routes** in the branching modal. Each copy gets a new ID. After branching, edits in either environment are independent. > **What's the difference between per-route CORS and the Network settings?** > > [Settings → Network](/docs/features/backend/settings/network) sets the default CORS for the auto-generated GraphQL and REST APIs. Per-route CORS overrides that for a specific Custom API path. > **Can I cache POST requests?** > > No — caching only applies to `GET`. Caching mutations would silently return stale state to clients that expected fresh writes. > **What happens when the circuit breaker opens?** > > The gateway short-circuits incoming requests with a `503 Service Unavailable` response without forwarding to the backend. After the recovery period, it lets a small number of probe requests through to test whether the backend has recovered before fully closing the circuit. # Overview Source: https://archie.com/docs/backend/app-services/encryption Field-level encryption stores selected columns as ciphertext in your database and returns them decrypted only to callers that prove authorization with a signed request. Field-level encryption lets you mark individual fields as **private**. Archie encrypts those values before they are written to your database and, by default, returns them as ciphertext. A caller only sees the plaintext when it proves authorization by signing the request with your project's private key. Use it for fields that must stay unreadable in the database itself — medical notes, government IDs, secrets — so a database dump, a backup, or direct database access never exposes them. > [!NOTE] > > Encryption is **server-side**: Archie holds the keys and can decrypt on your behalf when a request is authorized. This protects your data at rest against dumps, backups, and direct database access. It is **not** end-to-end encryption — a compromise of the platform can read the data. Communicate this guarantee to your stakeholders before relying on it. ## How it works at a glance **1. Mark a field as encrypted** In the [Data Model](/docs/features/backend/data-model/data-types), open a field's settings and turn on **Encrypted**. The flag is stored per field, per environment. **2. Generate the key pair** Under **Backend → App Services → Encryption**, generate the key pair for the active environment. The private key is shown **once** — save it. It is what authorizes decryption. **3. Write data normally** Send plaintext to your create and update mutations. Archie seals each encrypted field before it hits the database. Nothing changes in how you write. **4. Read data back** A normal read returns the encrypted value (ciphertext). To get the plaintext, the caller signs the request with the private key and sends three [decrypt headers](/docs/features/backend/app-services/encryption/reading-encrypted-data). Without a valid signature, the value stays ciphertext — the read never fails. ## What gets protected Each encrypted value is sealed individually with **AES-256-GCM**. The key that encrypts your data (the data encryption key) is itself wrapped by a master key that never leaves the platform process — a pattern called envelope encryption. The result is that: * The value stored in your database is an opaque, authenticated ciphertext. * A database dump, backup, or read replica contains only ciphertext for those fields. * Every value uses a fresh nonce, so identical plaintexts produce different ciphertexts. ## Enable encryption on a field **1. Open the field settings** In the **Data Model**, select the table and open the settings for the field you want to protect. **2. Turn on Encrypted** Toggle **Encrypted** on and save. Archie normalizes the column to a text type so it can hold the ciphertext envelope, which is larger than the plaintext. Not every field can be encrypted. Archie rejects the toggle when it would break data integrity: | Field kind | Why it can't be encrypted | | - | - | | Primary key | Identifies the row; must stay readable and stable. | | Unique field | Randomized ciphertext breaks uniqueness — the same input encrypts differently each time. | | Foreign key (either side) | Relationships are resolved on the stored value. | | Generated field | Computed by the database, not written by you. | | Non-text field | The ciphertext envelope is text; the column must be able to store it. | | Short fixed-length text | A `VARCHAR(n)` too small to hold the envelope would truncate it. Use `TEXT` or a longer length. | > [!WARNING] > > Turning encryption **on** for a field that already has data is not covered by the standard toggle — existing rows would stay in plaintext while new writes get sealed. Enable encryption on empty fields, or contact support about migrating a populated field. ## Generate the key pair The key pair authorizes decryption. It is scoped to the **active environment** — each environment has its own. **1. Open the Encryption panel** Go to **Backend → App Services → Encryption**. **2. Generate** Click **Generate key pair**. Archie creates an Ed25519 key pair and provisions the data key for the environment. **3. Save the private key** The **private key is displayed once and never again**. Copy it and store it in a secret manager. Archie keeps only the public key, which it uses to verify signed requests. > [!WARNING] > > If you lose the private key, generate a new pair — but a new pair invalidates any request signed with the old one. Regenerating the key pair does not re-encrypt your data; it only changes what can authorize decryption going forward. ## Limitations Because each value is encrypted with a fresh nonce, the ciphertext is not searchable or comparable. Archie enforces this rather than returning silently wrong results: * Encrypted fields are **excluded from full-text search** — they never enter the search index. * **Filtering, sorting, and aggregating** on an encrypted field is **rejected**, not ignored. * Exact-match lookup on an encrypted field is not supported in this version. Plan your data model so that fields you need to search, filter, or sort on stay unencrypted, and only the sensitive payload is private. ## FAQ > **Can Archie read my encrypted data?** > > Yes. This is server-side encryption: the platform holds the keys and decrypts when a request is authorized. It protects against database dumps, backups, and direct database access — not against a platform compromise. It is not end-to-end encryption. > **Do normal reads break when I encrypt a field?** > > No. A read without a decrypt signature returns the ciphertext instead of failing. Only a request that presents a valid signature gets the plaintext. See [Reading encrypted data](/docs/features/backend/app-services/encryption/reading-encrypted-data). > **Is the key pair shared across environments?** > > No. The key pair is scoped to the environment you generated it in. Generate one per environment. Key material is never copied when you branch an environment. > **Can I search or filter on an encrypted field?** > > No. Encrypted values use a fresh nonce each time, so they aren't comparable. Full-text search excludes them, and filter, sort, and aggregate on an encrypted field are rejected. Keep searchable fields unencrypted. > **What happens if I lose the private key?** > > Generate a new key pair from the Encryption panel. The old private key stops working immediately. Your stored data is unaffected — only decryption authorization changes. # Reading encrypted data Source: https://archie.com/docs/backend/app-services/encryption/reading-encrypted-data Encrypted fields return ciphertext by default. To read the plaintext, a caller signs the request with the project's private key and sends three decrypt headers. An encrypted field is returned as an **AENC envelope** — a Base64 string that always begins with `QUVO` — on a normal read. To receive the plaintext, a request must prove it is authorized by **signing a canonical string** with the project's [private key](/docs/features/backend/app-services/encryption/overview) and attaching three headers. Authorization is checked per request. A request with no signature, or an invalid one, gets the ciphertext back — the read **never fails** because decryption was not authorized. This keeps ordinary reads working while gating plaintext behind the signature. ## The decrypt headers Send all three headers together. They are all-or-nothing — a partial set is ignored. | Header | Value | | - | - | | `X-Decrypt-Signature` | Base64 of the Ed25519 signature over the canonical string. | | `X-Decrypt-Timestamp` | The timestamp used in the canonical string. Unix seconds or RFC 3339. | | `X-Decrypt-Nonce` | A unique, single-use random value for this request. | ## The canonical string The signature is computed over a canonical string the server reconstructs from **its own trusted context** — you sign the same shape the server will rebuild. Fields are joined with a newline (`\n`): ```text theme={null} v1 {projectId} {environment} {method} {timestamp} {nonce} ``` | Field | Value | | - | - | | `v1` | Literal version prefix. | | `{projectId}` | Your project ID. | | `{environment}` | The environment you are calling (for example `master`). | | `{method}` | `query` for reads. | | `{timestamp}` | The same value sent in `X-Decrypt-Timestamp`. | | `{nonce}` | The same value sent in `X-Decrypt-Nonce`. | > [!WARNING] > > Sign the raw bytes of the exact string above — real newline characters, no trailing newline, no extra whitespace. A single mismatched byte produces a different signature and the value comes back as ciphertext. ## Signing algorithm The key pair is **Ed25519**. To authorize a read: **1. Build the canonical string** Assemble `v1\n{projectId}\n{environment}\nquery\n{timestamp}\n{nonce}` using a fresh timestamp and a fresh random nonce. **2. Sign it** Sign the UTF-8 bytes of that string with your Ed25519 private key. **3. Base64-encode the signature** Base64-encode the 64-byte signature — that is the `X-Decrypt-Signature` value. **4. Send the request with the three headers** Attach `X-Decrypt-Signature`, `X-Decrypt-Timestamp`, and `X-Decrypt-Nonce`, then run your query. Encrypted fields in the response come back as plaintext. ## How the server verifies The server checks each step in order and stops at the first failure — returning ciphertext, never an error: 1. **Timestamp skew** — the timestamp must be within the allowed window of the server's clock. A stale or future-dated timestamp is rejected. 2. **Public key** — the server loads the environment's public key. If no key pair has been generated, no request can decrypt. 3. **Signature** — it reconstructs the canonical string from trusted context and verifies the signature against the public key. 4. **Nonce** — only after the signature is valid, the nonce is consumed. Nonces are **single-use**: reusing one (even from a request that failed) is rejected as a replay. Generate a fresh nonce for every request, including retries. > [!NOTE] > > The nonce is consumed only after the signature verifies, so invalid signatures can't exhaust the nonce space. If the replay store is unavailable, verification fails closed — the value stays ciphertext. ## Worked example Signing with `tweetnacl` in JavaScript (for example, in a Postman pre-request script): ```javascript theme={null} const nacl = require("tweetnacl"); const util = require("tweetnacl-util"); const projectId = "your-project-id"; const environment = "master"; const method = "query"; const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = util.encodeBase64(nacl.randomBytes(16)); // Build the canonical string — real newlines, no trailing newline. const canonical = ["v1", projectId, environment, method, timestamp, nonce].join("\n"); // privateKey is the Base64 private key from the Encryption panel (64-byte Ed25519 secret key). const secretKey = util.decodeBase64(privateKey); const signature = nacl.sign.detached(util.decodeUTF8(canonical), secretKey); // Attach these to the request. const headers = { "X-Decrypt-Signature": util.encodeBase64(signature), "X-Decrypt-Timestamp": timestamp, "X-Decrypt-Nonce": nonce, }; ``` Then run any read that returns the encrypted field. With a valid signature the field is plaintext; without it, the field is the `QUVO…` ciphertext. ## CORS If you call the API from a browser, the three decrypt headers must be allowed by CORS. The auto-generated GraphQL endpoint already allows them. For a browser app hitting the API directly, configure the allowed request headers under **Backend → Settings → Network**. ## FAQ > **Why did my field come back as ciphertext even though I sent a signature?** > > The signature didn't verify against the reconstructed canonical string. Check that the project ID, environment, `method` (`query`), timestamp, and nonce in your signed string exactly match what you sent in the headers — including newline characters and no trailing newline. Also confirm the timestamp is current and the nonce is fresh. > **Can I reuse a nonce or a signature?** > > No. Nonces are single-use and are rejected on reuse as a replay — even if the first request failed. Generate a fresh timestamp and nonce for every request, including retries. > **Does a failed decrypt return an error?** > > No. Authorization is fail-open to ciphertext: a missing or invalid signature returns the encrypted value, not an error. Only the data write path can error on encryption. > **Do related and nested records decrypt too?** > > Yes. Encrypted fields on related records honor the same signed authorization as top-level fields, so a record and its relations decrypt consistently in one request. # File Manager Source: https://archie.com/docs/backend/app-services/file-manager Connect your project to a cloud storage provider — S3, Google Cloud Storage, Azure Blob, or Filestack — to serve uploads and assets. The File Manager is where you connect your project to a cloud storage provider. Pick a provider, paste in credentials, and the [REST file endpoints](/docs/features/backend/rest-api-explorer/file-management) and the [Assets feature](/docs/features/assets/overview) start using it as their backing store. No new code on your end. To open it, navigate to **App Services → File Manager** in the Backend Console. ![File Manager overview and storage options](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-overview.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=03c238b70e04d1e2cc9a78ba4572dbda) ## Per-environment configuration File storage providers are scoped per environment. Each [environment](/docs/features/backend/environments/overview) has its own set of configured providers — useful for keeping `dev` uploads in a separate bucket from `production`. When you branch a new environment, you can optionally tick **File storage providers** in the branching modal to copy the configuration. Each copy gets a new unique identifier so it can be edited independently. ## Supported providers > **Amazon S3** > > Connect an AWS S3 bucket via Access Key ID and Secret Access Key. > **Google Cloud Storage** > > Connect a GCP bucket via a service account credentials JSON. > **Azure Blob Storage** > > Connect an Azure Storage container via account name and access key. > **Filestack** > > Connect a Filestack bucket via the API key from your Filestack dashboard. To configure a provider, click **Configure** next to it and fill in the credentials. The provider becomes available immediately to the file endpoints. ### Amazon S3 | Field | What goes in it | | - | - | | **Bucket Name** | Exact name of the S3 bucket where files will be stored. | | **Access Key ID** | Public identifier for the AWS IAM user authorized to write to the bucket. | | **Secret Access Key** | Private key paired with the Access Key ID. Stored encrypted. | | **Region** | AWS region code where the bucket lives (e.g., `us-east-1`). | ![Amazon S3 configuration](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-amazon-s3.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=92ef1826efb7d2d8b8cf861040c205a5) ### Google Cloud Storage | Field | What goes in it | | - | - | | **Bucket Name** | Name of the bucket created in your GCP console. | | **Project ID** | Unique identifier of the GCP project. | | **Credentials JSON** | Complete contents of the service account credentials JSON file. | ![Google Cloud Storage configuration](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-google-cloud-storage.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=cb610a6bc7e962afd6331abac3239ad3) The service account needs the **Storage Object Admin** role on the bucket — enough to create, read, and delete objects but not to manage the bucket itself. ### Azure Blob Storage | Field | What goes in it | | - | - | | **Container Name** | Name of the container within your storage account. | | **Account Name** | Name of the Azure Storage Account. | | **Access Key** | Storage account access key for authenticating requests. | ![Azure Blob Storage configuration](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-azure-blob-storage.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=8ef66548ce85ff929a13e1f8f0edb00a) ### Filestack | Field | What goes in it | | - | - | | **Bucket Name** | Name of the bucket associated with your Filestack storage backend. | | **API Key** | Filestack API Key from your Filestack dashboard. | ![Filestack configuration](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-filestack.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=08fcd6290fdf7918197531b6467e4516) ## Uploading and retrieving files The configured provider backs the standard file endpoints. You don't call the storage provider directly — Archie handles signing, validation, and access control. * **REST:** `POST /api/rest/files`, `GET /api/rest/files/{id}`, `DELETE /api/rest/files/{id}`. Full reference at [REST → File management](/docs/features/backend/rest-api-explorer/file-management). * **Records:** Store the returned `fileId` on a record (typically in a JSONB or text field) and re-issue the signed download URL when you need to serve the file. ### Uploading via GraphQL Archie also exposes an `uploadFile` mutation automatically — you don't write a resolver for it. ```graphql theme={null} mutation UploadFile( $providerType: ProviderType! $file: Upload! $filename: String! $contentType: String! ) { uploadFile( providerType: $providerType file: $file filename: $filename contentType: $contentType ) } ``` ```json theme={null} { "providerType": "ARCHIE_STORAGE", "filename": "image.png", "contentType": "image/png" } ``` The same mutation works for every provider — only `providerType` changes: | providerType | Provider | | - | - | | `S3` | Amazon S3 | | `AZURE` | Azure Blob Storage | | `FILESTACK` | Filestack | | `GCS` | Google Cloud Storage | | `ARCHIE_STORAGE` | Archie's built-in storage | Pass whichever value matches the provider configured for your project. Once the upload succeeds, Archie creates a record in the **files** table of the Data Model with everything you need to consume the file from any application — `filename`, `size`, `mimeType`, and `downloadUrl`. ![Files table record with upload metadata](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/app-services/file-manager-files-table.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=054a61222c7da60de2e4d4a545d7526c) ## Supported MIME types The upload endpoint accepts a broad allowlist by prefix: | Category | Examples | | - | - | | Images | All `image/*` (jpeg, png, gif, webp, svg+xml, bmp, tiff, avif) | | Video | All `video/*` (mp4, webm, quicktime) | | Audio | All `audio/*` (mpeg, wav, ogg) | | Text | All `text/*` (plain, csv, html, xml) | | Documents | `application/pdf`, MS Office, OpenXML formats | | Archives | `application/zip`, `application/gzip`, `application/x-tar` | | Data | `application/json`, `application/xml` | | Generic | `application/octet-stream` | Unsupported types return `422 Unprocessable Entity` from the upload endpoint. ## Permissions Who can upload, list, download, and delete files is governed in [Role-Based Access](/docs/features/backend/app-services/role-based-access) like every other endpoint. For per-record file access (a user can only download files attached to records they own), reference the file from a Data Model record and govern access through that table's permissions. ## FAQ > **Can I configure more than one provider at the same time?** > > Yes — every supported provider can be configured concurrently. When uploading, you select the target provider via the `providerType` form field. Useful for migrations or tiered storage (hot in S3, cold in Glacier-backed S3, archived in another bucket). > **Are credentials copied when I branch an environment?** > > Only if you explicitly tick **File storage providers** in the branching modal. The copy gets a new ID, so future edits don't propagate back. Branching never copies authentication-provider secrets — those stay env-specific by design. > **What happens to existing files if I switch providers?** > > They stay in the old provider until you migrate them. Archie tracks the provider per file, so old uploads keep resolving to their original bucket. Run a migration script if you want to consolidate. > **What's the maximum upload size?** > > Enforced at the gateway. Exceeding it returns `413 Payload Too Large`. For very large files, use a direct-to-storage upload pattern through your provider's SDK and store only the resulting object key in Archie. > **How do I attach a file to a record?** > > Upload the file to get a `fileId`, then store that `fileId` on a JSONB or text field on the record. Re-issue the signed URL through the download endpoint when you need to serve it. See [REST → File management](/docs/features/backend/rest-api-explorer/file-management). # Global Search Source: https://archie.com/docs/backend/app-services/global-search Global Search lets users search across your app's data, and your app can query the same results through the GraphQL API. You turn it on per environment, then choose which tables and fields are searchable and add row filters. **Global Search** is a [Core Service](/docs/backend/app-services) that makes your app's data searchable. You decide which tables users can search, which fields are indexed, and which rows are included. ## Turn Global Search on Global Search is off until you activate it, and it's configured per [environment](/docs/backend/environments). 1. Go to **Backend → Core Services → Global Search**. 2. Turn on the switch in the top-right corner of the page header. Until you turn it on, the **Data Model** card under **Search Scope** reads "Enable global search to configure tables" and **Configure** stays unavailable. After you turn it on, Archie provisions indexes for eligible tables, and the card shows how many tables are enabled. ![The Global Search page with the switch on and the Data Model card showing "15 of 15 tables enabled" with a Configure button.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/29-global-search-enabled.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=abeec3730777c6451ba9365748fe3a2c) ## Configure the search scope Click **Configure** on the **Data Model** card to open the **Configure Search Scope** drawer. Use it to choose what users can search. ![The Configure Search Scope drawer listing tables with checkboxes, a Search data table field, and Cancel and Save buttons.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/30-global-search-configure-scope.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=3c96affd7d9edfacb865706ad95d21db) * **Search data table** filters the list by table name. * **Show enabled only** and **Show all** switch between all tables and only the enabled ones. A counter, such as "15 of 15 tables enabled", shows how many are on. * Each table has a checkbox. Clear it to keep that table out of search. * Some tables show **not searchable** with a reason, such as "no eligible text columns" or "no single scalar id column for re-fetch". They can't be enabled. Click **Save** to apply your changes, or **Cancel** to close the drawer without saving. ### Choose fields and row filters Click a table to expand it. ![The bugs table expanded in the Configure Search Scope drawer, with description and title selected under Searchable fields and an empty Row filter section.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/31-global-search-table-fields.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=4ccba32491237514d6d613e72ef1a89d) * **Searchable fields** lists the table's indexed text fields. Clear a checkbox to exclude a field from search. * **Row filter** limits which rows are searchable. Click **Add condition**, then pick a field, an operator, and a value. Only rows that match all conditions are searchable. For example, use a filter to hide soft-deleted rows. With no conditions, the table shows "No filter — all rows searchable." Row filter operators are **is null**, **is not null**, **equals**, **not equals**, **in**, and **not in**. A table you changed from its defaults is marked **customized**. ## Search from your app When Global Search is on in an environment, the GraphQL API for that environment exposes a `search` query. Pass the search term in the `query` argument. The query returns results only from the tables and fields enabled in your search scope. ```graphql theme={null} query SearchRecords { search(query: "Hey!") { items { fields id rank table } totalCount } } ``` The `search` result contains these fields: * `items`: the matching records. Each item includes: * `table`: the table the result comes from. * `id`: the ID of the record. * `rank`: the relevance of the result. Higher values are more relevant. * `fields`: the field values of the matching record. * `totalCount`: the total number of results. You can try the query in the [GraphQL API Explorer](/docs/backend/graphql-api-explorer). Calls from your app authenticate the same way as the rest of the API, using your [API keys](/docs/backend/settings/api-keys). ## FAQ > **Why can't I enable some tables?** > > Tables with no eligible text columns are marked **not searchable**. Global Search needs at least one text field to index. > **Do my changes apply right away?** > > No. Changes in the drawer apply when you click **Save**. > **What happens when I turn Global Search off?** > > The switch returns to its off state and **Configure** becomes unavailable again. # Overview Source: https://archie.com/docs/backend/app-services/notifications Keep your users informed across in-app, email, and other channels from a single, consistent place. Define notification types, let users set preferences, and send with one API call. The Notifications module lets your application keep its users informed — a build finished, an invoice is due, someone mentioned them in a comment — across multiple channels (in-app inbox, email, and more) from a single, consistent place. To access this section, navigate to **App Services → Notifications** in the left-hand sidebar. > [!NOTE] > > Notifications are scoped **per environment**. Each [environment](/docs/features/backend/environments/overview) keeps its own notification types, user preferences, and delivered notifications, so what you configure in `development` never leaks into `production`. ## What you can do * **Define the kinds of notifications your app sends** (a "notification type" such as `build_failed` or `invoice_payment`), grouped into **categories**. * **Let each user choose what they receive** through per-user **preferences** (turn a type on or off, pick channels like in-app or email). * **Send a notification** with a single API call (`emitNotification`) — to specific users or to a whole audience. * **Show an in-app inbox** and **receive notifications in real time** through a live subscription. ## The building blocks | Concept | What it is | Where it lives | | - | - | - | | **Category** | A way to group related notification types (e.g., *Billing*, *Collaboration*). | `archie_notification_categories` | | **Notification type** | A reusable definition of one kind of notification: its key, default channels, templates, and whether users may turn it off. | `archie_notification_types` | | **User preference** | One user's choice for one type: enabled or not, and on which channels. | `archie_user_notification_preferences` | | **In-app notification** | A delivered item in a user's inbox (read/unread, with an optional action link). | `archie_notifications_in_app` | | **Delivery record** | An internal log of what was sent, per channel. | `archie_notifications` | These tables are created for you automatically the first time the Notifications module is activated in an environment. You configure them from the **Archie web app** or through the **GraphQL API** — both are covered in the guides below. ## Channels A notification can be delivered through one or more of these channels: | Channel | Value | Notes | | - | - | - | | In-app inbox | `in_app` | The built-in inbox; always available. | | Email | `email` | Requires an email integration (e.g., SendGrid) configured for the environment. | | SMS | `sms` | Requires an SMS integration (e.g., Twilio). | | WhatsApp | `whatsapp` | Requires a WhatsApp integration. | | Slack | `slack` | Requires a Slack integration. | ## Guides > **[Notification types & categories](/docs/features/backend/app-services/notifications/notification-types)** > > Define the catalog of notifications your app can send and organize it into categories. > **[User preferences](/docs/features/backend/app-services/notifications/user-preferences)** > > Let your users decide which notifications they receive and on which channels. > **[Sending notifications](/docs/features/backend/app-services/notifications/sending-notifications)** > > Emit a notification to specific users or to an audience with the `emitNotification` mutation. > **[In-app inbox & real-time updates](/docs/features/backend/app-services/notifications/in-app-inbox)** > > Show a user's inbox, mark items as read, and stream new notifications live over a subscription. ## A typical flow 1. **Set up your catalog once.** Create the notification types your app needs (or use the ready-made ones that ship with every project) and group them into categories. 2. **Let users opt in or out.** Your users adjust their preferences — for example, *"email me about billing, but only show collaboration mentions in the inbox."* 3. **Send notifications from your app.** Whenever something happens, call `emitNotification` with the type's key and a target. Archie resolves who should receive it and on which channels, then delivers it. 4. **Show them in your UI.** Read the user's inbox and subscribe to live updates so new notifications appear instantly. # In-app inbox & real-time updates Source: https://archie.com/docs/backend/app-services/notifications/in-app-inbox Display a user's notification inbox, mark items as read, and stream new notifications to your UI in real time over a WebSocket subscription. Every notification delivered through the `in_app` channel becomes an item in the recipient's **inbox**. This guide shows how to display that inbox, mark items as read, and stream new notifications to your UI in real time. The inbox lives in the `archie_notifications_in_app` table, exposed through the auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) operations on your project endpoint (`https://archie-core.services.archie.com/graphql`) with the usual `X-Project-Id`, `X-Environment`, and `Authorization` headers. ## What an inbox item contains | Field | Meaning | | - | - | | `receiverId` | The user the item belongs to. | | `notificationType` | The type it was sent from (its `eventKey`, `name`, …). | | `notificationKey` | The event key, stored on the item for convenience. | | `params` | The variables used to render it (your JSON payload). | | `actionUrl` / `actionText` | Optional call-to-action link and label. | | `isRead` / `readAt` | Read state and timestamp. | | `metadata` | Optional extra JSON you attached. | | `expiresAt` | When the item expires and should stop being shown. | | `createdAt` | When it arrived. | ## Show a user's inbox Fetch the most recent, non-expired items for the signed-in user: ```graphql theme={null} query Inbox { archieNotificationsInApp( filter: { receiverId: { equals: "2685ec12-a4c7-491d-a155-d0b09190993b" } } orderBy: { createdAt: DESC } first: 20 ) { items { id notificationKey params actionUrl actionText isRead createdAt notificationType { name } } } } ``` > [!NOTE] > > **Soft-deleted records.** List queries exclude soft-deleted rows by default, so a dismissed or removed inbox item won't reappear. To include records that have been soft-deleted, pass the `withDeleted: true` argument on the list query (e.g., `archieNotificationsInApp(withDeleted: true) { items { id } }`). Omit it — or set it to `false` — to return only active rows. ### Count unread Use a filter to show an unread badge: ```graphql theme={null} query UnreadCount { archieNotificationsInApp( filter: { receiverId: { equals: "2685ec12-…" } isRead: { equals: false } } ) { count } } ``` ## Mark as read ```graphql theme={null} mutation MarkRead { updateArchieNotificationsInApp( id: "item-id…" input: { isRead: true, readAt: "2026-06-24T15:00:00Z" } ) { id isRead readAt } } ``` ## Receive notifications in real time To make new notifications appear instantly — without polling — use Archie's subscriptions. There are two steps: **register** a subscription configuration, then **connect** over a WebSocket to receive events. ### 1. Register the subscription Use the `system { createSubscription }` mutation to declare which table and operations to watch. For an inbox, watch the in-app table for new rows (`CREATE`): ```graphql theme={null} mutation CreateInboxSubscription($input: SubscriptionInput!) { system { createSubscription(input: $input) { id active name } } } ``` **Variables** ```json theme={null} { "input": { "name": "in_app_inbox", "description": "New in-app notifications", "active": true, "tables": [ { "table": "archie_notifications_in_app", "operations": ["CREATE"], "fields": ["id", "receiver_id", "notification_key", "params", "action_url", "action_text", "is_read", "created_at"] } ] } } ``` > [!NOTE] > > Inside a subscription's `fields` list, use the raw column names in `snake_case` (e.g., `receiver_id`, `notification_key`, `is_read`) — these are the database column names, not the camelCase API fields. ### 2. Connect and listen Open a WebSocket to the project's subscription endpoint and you'll receive an event each time a matching row is created: ``` wss://archie-core.archie-platform.com/subscriptions?project_id= ``` When a new notification is delivered to a user, your client receives the row described by the `fields` you registered. Filter client-side by `receiver_id` so each user only reacts to their own items, then update the inbox UI (prepend the item, bump the unread badge). > [!TIP] > > Watch `["CREATE", "UPDATE"]` if you also want live updates when an item is marked as read on another device. ## Putting it together A complete in-app experience usually looks like this: 1. On load, **query** the inbox and the **unread count**. 2. **Register** (once) the `in_app_inbox` subscription and **connect** the WebSocket. 3. When an event arrives, **prepend** the new item and increment the badge. 4. When the user opens an item, **mark it read** and decrement the badge. For email and other channels, no inbox handling is needed — Archie delivers those directly through the configured integration. # Notification types & categories Source: https://archie.com/docs/backend/app-services/notifications/notification-types Define the reusable catalog of notifications your app can send — event keys, default channels, templates, and opt-out rules — and organize them into categories. A **notification type** is the reusable definition of one kind of notification your application can send — for example `build_failed`, `invoice_payment`, or `chat_direct_mentions`. Your app never hard-codes message text; instead it refers to a type by its **event key**, and the type decides the default channels, the title and message templates, and whether users are allowed to turn it off. **Categories** simply group related types together (e.g., *Billing*, *Collaboration*, *Security*) so they are easier to present in a preferences screen. > [!NOTE] > > Types and categories are scoped per environment. Configure them independently for `development`, `staging`, and `production`. ## Ready-made types Every project starts with a catalog of common notification types already created for you (such as `password_reset`, `application_deployed`, `billing_payment_reminders`, and many more). You can use them as-is, edit them, or add your own. Re-activating the module never overwrites your edits. ## Anatomy of a notification type | Field | Meaning | | - | - | | **Event key** (`eventKey`) | The stable identifier your app passes to `emitNotification`. Unique per environment. Use `lower_snake_case` (e.g., `order_shipped`). | | **Name** | A human-readable label shown in the UI. | | **Description** | Optional explanation of when this notification fires. | | **Category** | The category this type belongs to. | | **Default channels** (`defaultChannels`) | The channels used when a recipient has no specific preference (e.g., `["in_app", "email"]`). | | **Title template** (`titleTemplate`) | Template for the notification title. Supports variables from `params` (e.g., `Build {{.buildId}} failed`). If left empty, the event key is used. | | **Message template** (`messageTemplate`) | Template for the body. If left empty, the title is reused. | | **Mandatory** (`isMandatory`) | If `true`, every user receives it — preferences cannot switch it off (use for security or legal notices). | | **Can be disabled** (`canBeDisabled`) | If `false`, users cannot opt out (similar effect to mandatory). | | **Default subscribed** (`defaultSubscribed`) | Whether users are opted in by default when they have no preference row yet. | | **Default expiry (hours)** (`defaultExpiresHours`) | How long an in-app item stays before it expires, unless overridden when sending. | | **Channel templates** (`channelTemplates`) | Per-channel delivery settings — for example the email provider template id and its required variables. | | **Active** (`isActive`) | Turn the whole type on or off. Sending an inactive type is rejected. | ## Configure from the Archie web app **1. Open the Types tab** Navigate to **App Services → Notifications** and open the **Types** tab. **2. Create a category (optional)** Open **Categories** first and click **+ Add Category** to create a group such as *Billing*. Give it a **key** and a **name**. **3. Add a type** Back in **Types**, click **+ Add Type** and fill in: * **Event Key** — e.g., `order_shipped`. * **Name** and **Description**. * **Category** — pick the group it belongs to. * **Default Channels** — e.g., *In-app* and *Email*. * **Title / Message templates** — optionally with `{{.variable}}` placeholders. * Toggles for **Mandatory**, **Can be disabled**, **Default subscribed**, and **Active**. **4. Save** Click **Save**. You can edit or deactivate a type at any time; changes take effect immediately for the next notification sent. ## Configure through the GraphQL API Notification types and categories are regular tables in your project, so they are available through the standard auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) operations on your project endpoint. **Endpoint** ``` https://archie-core.services.archie.com/graphql ``` **Required headers** | Header | Value | | - | - | | `X-Project-Id` | Your project id | | `X-Environment` | The target environment (e.g., `master`, `staging`, `production`) | | `Authorization` | `Bearer ` | ### Create a category ```graphql theme={null} mutation CreateCategory { createArchieNotificationCategories( input: { key: "billing" name: "Billing" description: "Invoices, payments and credit alerts" sortOrder: 10 isActive: true } ) { id key name } } ``` ### Create a notification type ```graphql theme={null} mutation CreateType { createArchieNotificationTypes( input: { eventKey: "order_shipped" name: "Order Shipped" description: "Sent when a customer's order leaves the warehouse" defaultChannels: ["in_app", "email"] titleTemplate: "Your order {{.orderId}} is on its way" messageTemplate: "Order {{.orderId}} shipped and should arrive on {{.eta}}." isMandatory: false canBeDisabled: true defaultSubscribed: true defaultExpiresHours: 720 isActive: true } ) { id eventKey name isActive } } ``` > [!TIP] > > Any variable you reference in a template (e.g., `{{.orderId}}`) must be supplied in the `params` object when you send the notification — see [Sending notifications](/docs/features/backend/app-services/notifications/sending-notifications). ### List the catalog ```graphql theme={null} query ListTypes { archieNotificationTypes( filter: { isActive: { equals: true } } orderBy: { sortOrder: ASC } ) { items { id eventKey name defaultChannels isMandatory defaultSubscribed } } } ``` > [!NOTE] > > **Soft-deleted records.** List queries exclude soft-deleted rows by default. To include records that have been soft-deleted, pass the `withDeleted: true` argument on the list query (e.g., `archieNotificationTypes(withDeleted: true) { items { id eventKey } }`). Omit it — or set it to `false` — to return only active rows. ### Update or deactivate a type ```graphql theme={null} mutation DeactivateType { updateArchieNotificationTypes( id: "b2c8…" input: { isActive: false } ) { id eventKey isActive } } ``` > [!NOTE] > > In the API, fields are shown in `camelCase` (e.g., `eventKey`, `defaultChannels`, `isActive`). The API Explorer's autocomplete and documentation panel always show the exact names and arguments available for your project. ## Next step Once your catalog exists, let users choose what they receive in [User preferences](/docs/features/backend/app-services/notifications/user-preferences), or jump straight to [Sending notifications](/docs/features/backend/app-services/notifications/sending-notifications). # Sending notifications Source: https://archie.com/docs/backend/app-services/notifications/sending-notifications Emit a notification to specific users or a whole audience with a single emitNotification mutation. Archie resolves recipients, checks preferences, and delivers on each channel. To send a notification, your application calls a single mutation — `emitNotification` — with the **event key** of a notification type and a **target** (who should receive it). Archie does the rest: it resolves the recipients, checks their preferences, renders the templates, and delivers on each channel. You describe *intent* ("notify these users that their build failed"); you never build per-recipient rows yourself. **Endpoint** ``` https://archie-core.services.archie.com/graphql ``` **Required headers** | Header | Value | | - | - | | `X-Project-Id` | Your project id | | `X-Environment` | The target environment (e.g., `master`, `staging`, `production`) | | `Authorization` | `Bearer ` | ## The input at a glance | Field | Required | Description | | - | - | - | | `eventKey` | Yes | The notification type to send (must exist and be active in this environment). | | `target` | Yes | **Exactly one** of `userIds` or `audience` (see below). | | `sourceService` | Yes | A short label identifying who is sending (e.g., `"billing"`, `"ci"`). Used for auditing. | | `params` | No | A JSON object of variables for the templates (e.g., `{ "buildId": "42" }`). | | `channels` | No | Override the type's channels for this send. In v1 the override may only contain `email` and/or `in_app`. Omit it to use each recipient's resolved channels. | | `action` | No | An optional call-to-action with `url` and/or `text` (e.g., a "View build" link). | | `dedupKey` | No | A key that makes the send idempotent — repeating the same `dedupKey` will not deliver twice. | | `expiresIn` | No | How long the in-app item lives, as a duration string (e.g., `"720h"`). Clamped to a maximum of 365 days. | | `broadcastConfirm` | No | Set to `true` to confirm an intentionally large broadcast (see *Large broadcasts* below). | ## Choosing a target A target is **exactly one** of: * **`userIds`** — an explicit list of user ids. Use this for transactional notifications ("notify *this* user"). * **`audience`** — a named group: * `SUBSCRIBERS` — everyone whose preference for this type is enabled (respecting per-user opt-in). * `ALL_PROJECT_USERS` — every user in the project (use sparingly; respects mandatory and anomaly rules). ## Examples ### Notify specific users ```graphql theme={null} mutation NotifyUser { emitNotification( input: { eventKey: "order_shipped" sourceService: "fulfillment" target: { userIds: ["2685ec12-a4c7-491d-a155-d0b09190993b"] } params: { orderId: "A-1029", eta: "Friday" } action: { text: "Track package", url: "https://app.example.com/orders/A-1029" } } ) { accepted targetingMode recipientCount } } ``` **Response** ```json theme={null} { "data": { "emitNotification": { "accepted": true, "targetingMode": "user_ids", "recipientCount": 1 } } } ``` ### Notify everyone subscribed to a type ```graphql theme={null} mutation NotifySubscribers { emitNotification( input: { eventKey: "platform_updates" sourceService: "product" target: { audience: SUBSCRIBERS } params: { version: "2.4.0" } channels: [in_app, email] } ) { accepted targetingMode fanoutJobId } } ``` For an audience send, delivery happens **asynchronously**: `accepted: true` means the request was queued. `recipientCount` is therefore `null` and a `fanoutJobId` is returned that identifies the broadcast. ### Make a send idempotent If your service might retry, pass a stable `dedupKey`. The same key is never delivered twice: ```graphql theme={null} mutation NotifyOnce { emitNotification( input: { eventKey: "invoice_payment" sourceService: "billing" target: { userIds: ["2685ec12-…"] } params: { invoiceId: "INV-77" } dedupKey: "invoice-INV-77-paid" } ) { accepted recipientCount } } ``` ## Understanding the result `EmitNotificationResult` tells you what happened **synchronously**: | Field | Meaning | | - | - | | `accepted` | `true` if the request was accepted (delivered for explicit users, or queued for an audience). | | `targetingMode` | `user_ids`, `subscribers`, or `all_project_users`. | | `recipientCount` | Number of recipients for an explicit `userIds` send; `null` for an audience (resolved asynchronously). | | `fanoutJobId` | Identifier for an audience broadcast. | | `rejectedReason` | When `accepted` is `false`, a short reason code explaining why. | ### When a send is rejected If `accepted` is `false`, check `rejectedReason`. Common reasons: | `rejectedReason` | What to do | | - | - | | `invalid_input` | A required field is missing or invalid, or the explicit `userIds` list is too large — split it or use an audience. | | `target_validation_failed` | You must provide **exactly one** of `userIds` or `audience`. | | `override_channel_not_permitted` | The `channels` override contains a channel that can't be forced in v1 (only `email` and `in_app` are allowed). | | `broadcast_quota_exceeded` | Too many broadcasts for this type in the current window — slow down or batch. | > [!NOTE] > > A rejection is a normal, expected response (the mutation still returns `200`); it is not a server error. ## Large broadcasts To protect your users from accidental mass-sends, an audience broadcast that resolves to an unusually large number of recipients is **held** unless you explicitly confirm it. If you *intend* to reach a very large audience, set `broadcastConfirm: true`: ```graphql theme={null} mutation BigAnnouncement { emitNotification( input: { eventKey: "platform_updates" sourceService: "product" target: { audience: ALL_PROJECT_USERS } params: { headline: "New dashboard is live" } broadcastConfirm: true } ) { accepted fanoutJobId rejectedReason } } ``` ## Next step See how recipients read and stream their notifications in [In-app inbox & real-time updates](/docs/features/backend/app-services/notifications/in-app-inbox). # User preferences Source: https://archie.com/docs/backend/app-services/notifications/user-preferences Let each user decide which notifications they receive and on which channels. Preferences link one user to one notification type and drive who gets notified. Preferences let each of your users decide which notifications they receive and on which channels. A preference is the link between **one user** and **one notification type**: it records whether that type is enabled for them and which channels to use. When a notification is sent, Archie looks at the recipient's preference for that type: * **No preference row yet?** The type's **Default subscribed** setting decides whether they receive it. * **Preference exists and is enabled with at least one channel?** They receive it on those channels. * **Preference exists but is disabled?** They are skipped — *unless* the type is **Mandatory**, which always reaches everyone. > [!NOTE] > > Preferences are scoped per environment, like everything else in the Notifications module. ## Anatomy of a preference | Field | Meaning | | - | - | | **User** (`userId`) | The user this preference belongs to. | | **Notification type** (`notificationType`) | The type being configured. | | **Enabled** (`isEnabled`) | Whether the user wants this notification at all. | | **Channels** (`channels`) | Which channels to deliver on (e.g., `["in_app", "email"]`). An enabled preference with no channels delivers nothing. | | **Email / Phone / WhatsApp** | Optional per-user contact overrides for the relevant channels. | A user has at most **one** preference per type — the pair *(user, type)* is unique. ## Configure from the Archie web app Most teams expose preferences inside their own application's "Notification settings" screen, but you can also manage them directly: **1. Open the Preferences tab** Navigate to **App Services → Notifications** and open the **Preferences** tab. **2. Find a user** Search for a user, then click **+ Add Preference** (or edit an existing row). **3. Configure the preference** Choose the **Notification type**, toggle **Enabled**, and select the **Channels**. **4. Save** Click **Save**. ## Configure through the GraphQL API Preferences are a standard table, available through the auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) operations on your project endpoint (`https://archie-core.services.archie.com/graphql`) with the `X-Project-Id`, `X-Environment`, and `Authorization` headers. ### Set (or update) a user's preference The most common pattern is "create it if missing, update it if present." You can do that with a single upsert-style call keyed on *(user, type)*: ```graphql theme={null} mutation SavePreference { createArchieUserNotificationPreferences( input: { userId: "2685ec12-a4c7-491d-a155-d0b09190993b" notificationType: "b2c8…" # the notification type id isEnabled: true channels: ["in_app", "email"] } ) { id userId isEnabled channels } } ``` To change channels or turn a type off later: ```graphql theme={null} mutation DisableType { updateArchieUserNotificationPreferences( id: "pref-id…" input: { isEnabled: false } ) { id isEnabled } } ``` ### Read a user's preferences ```graphql theme={null} query MyPreferences { archieUserNotificationPreferences( filter: { userId: { equals: "2685ec12-a4c7-491d-a155-d0b09190993b" } } ) { items { id isEnabled channels notificationType { eventKey name } } } } ``` > [!NOTE] > > **Soft-deleted records.** List queries exclude soft-deleted rows by default. To include preferences that have been soft-deleted, pass the `withDeleted: true` argument on the list query (e.g., `archieUserNotificationPreferences(withDeleted: true) { items { id } }`). Omit it — or set it to `false` — to return only active rows. ### Build a "notification settings" screen A typical settings page combines the two tables: 1. Read the catalog of **active, user-configurable** types (`archieNotificationTypes` where `isActive = true` and `canBeDisabled = true`). 2. Read the user's existing **preferences**. 3. For each type, show a toggle and channel pickers, pre-filled from the preference (or from the type's defaults when the user has no row yet). 4. On save, create or update the matching preference. > [!NOTE] > > **Mandatory types** (`isMandatory = true`) should be shown as read-only — the user always receives them, so don't offer an off switch. ## Next step With your catalog and preferences in place, you're ready to [send notifications](/docs/features/backend/app-services/notifications/sending-notifications). # Overview Source: https://archie.com/docs/backend/app-services/queues-and-event-bus Move work into the background and broadcast events across your app. Use Queues to process jobs reliably one at a time, the Event Bus to fan one event out to many places, or both together — all scoped per environment. Queues and the Event Bus are Archie's two building blocks for **asynchronous work** — anything your app should do *after* responding to a user, or *in reaction to* something that happened elsewhere. They let you decouple the moment something happens from the work it triggers, so a slow task never blocks a request and one event can reach many places at once. To access this section, open a project, switch to the **Backend Console**, and look under **App Services → Queues** and **App Services → Event Bus** in the left-hand sidebar. > [!NOTE] > > Queues and the Event Bus are scoped **per environment**. Each [environment](/docs/features/backend/environments/overview) keeps its own queues, topics, routes, and message history, so what you set up in `development` never touches `production`. ## The two building blocks in plain language > **[Queues](/docs/features/backend/app-services/queues-and-event-bus/queues)** > > A **to-do list for your app**. Each message is picked up by exactly one worker, processed, and removed. Perfect for background jobs: sending an email, resizing an image, charging a card. > **[Event Bus](/docs/features/backend/app-services/queues-and-event-bus/event-bus)** > > A **loudspeaker**. You publish one event to a *topic*, and every matching *route* gets its own copy — a queue, a REST endpoint, or GraphQL. Perfect for "when X happens, do A, B, and C". If you have used AWS before: a **Queue** is Archie's equivalent of **SQS**, and the **Event Bus** is the equivalent of **SNS / EventBridge**. You don't need to know either to use them — the panels speak in product terms, with the technical name in parentheses. ## When to use which | You want to… | Use | Why | | - | - | - | | Run a slow task without making the user wait | **Queue** | The request returns immediately; a worker picks up the job later. | | Guarantee each job is handled by exactly one worker | **Queue** | A message is delivered to a single consumer at a time, and retried if it isn't confirmed. | | Smooth out spikes so a burst doesn't overwhelm a downstream service | **Queue** | Messages wait in line and are consumed at a steady pace. | | React to something with **several** independent actions | **Event Bus** | One published event fans out to every route, each with its own copy. | | Notify an external system when data changes | **Event Bus** | A route can deliver to an external REST or GraphQL endpoint, signed and retried. | | Both: broadcast an event **and** process each copy reliably in the background | **Both, together** | A topic fans out to one or more queues; workers drain each queue at their own pace. See [Using them together](/docs/features/backend/app-services/queues-and-event-bus/using-them-together). | ## Activate one, the other, or both There is nothing to "install". Both services live under **App Services** and become available per environment: **1. Open the panel** Go to **Backend → App Services → Queues** or **App Services → Event Bus** in the environment you want to work in. **2. Create your first resource** Create a **queue** (Queues panel) or a **topic** (Event Bus panel). The underlying infrastructure is provisioned for you automatically, scoped to this `(project, environment)`. **3. Use them independently or connect them** A queue works on its own — send and receive messages. A topic works on its own — publish events. To combine them, add a **route** on a topic whose target is a queue. > [!NOTE] > > You can adopt these features **incrementally**. Start with a single queue for one background job, or a single topic for one broadcast. Nothing else in your project changes, and neither service is required by the other. ## How a message flows Understanding the path a message takes makes the panels self-explanatory: **Queue (point-to-point).** 1. Your app **sends** a message to a queue. 2. The message waits as **Pending** until a worker asks for it. 3. A worker **receives** the message; it becomes **In flight** (invisible to other workers) for a limited time. 4. The worker finishes and **confirms** (acknowledges) it — the message is removed. 5. If the worker never confirms (crash, timeout), the message becomes visible again and is **retried**. After too many failed attempts it moves to the **error list (dead-letter queue)** for inspection. **Event Bus (fan-out).** 1. Your app **publishes** an event to a topic (an *event type* plus a JSON payload). 2. The topic checks each **route's filter** to see whether the event matches. 3. Every matching route receives its **own independent copy** and delivers it to its target — a queue, your project's own GraphQL API, or an external REST/GraphQL endpoint. 4. Endpoint deliveries are **retried** on failure and land in that route's own **error list** if they keep failing. You can watch every attempt in the **Activity** tab. ## Key ideas that apply to both > **Everything is scoped to a project and environment** > > A queue or topic created in `development` is completely separate from one with the same name in `production`. Switching the environment selector refreshes everything; the two never mix. > **Delivery is 'at least once'** > > Both services guarantee a message is delivered **at least once**, and may occasionally deliver a duplicate (for example, if a worker processed a job but crashed before confirming). Design your workers to be **idempotent** — safe to run twice with the same input. Sending a message with a **dedup key** also suppresses accidental duplicates within a short window. > **Failed work is never lost silently** > > When a message can't be processed after its configured number of attempts, it moves to a **dead-letter queue (DLQ)** — an "error list" — instead of disappearing. You can inspect it, fix the cause, and **redrive** (retry) it. A red badge on a queue, or a non-zero **Failed** count on a topic, tells you something needs attention. Every REST or GraphQL route has its own dead-letter queue, separate from any Queues-panel queue. > **You can drive everything from the UI or the API** > > Every action in the Queues and Event Bus panels has a matching **GraphQL** operation, so you can automate the same tasks from your app or scripts. The panel is the friendly front door; the API is the same door for machines. Note that the API still calls a route a **subscription** — the UI renamed the concept, the schema didn't. See the [API reference](/docs/features/backend/app-services/queues-and-event-bus/reference). > **Internal targets still need a token; external targets need a URL too** > > Delivering to **your own project's API** (GraphQL, the default) skips the URL — you pick an existing mutation instead of writing one — but you still provide a **Token / API key** for authentication. Delivering to an **external** system needs the HTTPS URL and, for REST, is the only option today (internal REST delivery is coming). Archie signs every external request so the receiver can verify it came from you. ## Where to go next > **[Queues](/docs/features/backend/app-services/queues-and-event-bus/queues)** > > Create a queue, tune its settings in plain language, send a test message, watch its health, and handle errors. > **[Event Bus](/docs/features/backend/app-services/queues-and-event-bus/event-bus)** > > Create a topic, publish events, and add routes with the two-step wizard — to a queue, a REST endpoint, or GraphQL. > **[Using them together](/docs/features/backend/app-services/queues-and-event-bus/using-them-together)** > > The fan-out-then-process pattern: broadcast one event and have several queues handle their copies independently. > **[Reference & FAQ](/docs/features/backend/app-services/queues-and-event-bus/reference)** > > The GraphQL operations, limits and defaults, message statuses, and answers to common questions. # Event Bus Source: https://archie.com/docs/backend/app-services/queues-and-event-bus/event-bus Publish an event once and deliver it to many places at the same time. Create topics, add routes with a two-step wizard — to a queue, a REST endpoint, or GraphQL — filter by event type, and watch every delivery in the Activity tab. The **Event Bus** lets your app announce that something happened — an order was created, a user signed up — and have that single announcement delivered to **every interested route at once**. You publish one event to a **topic**; each **route** on that topic receives its own copy and delivers it to its target. Open **Backend → App Services → Event Bus** to manage it. > [!NOTE] > > Topics and routes are per **environment**. Publishing to the `orders` topic in `development` never reaches routes in `production`. > [!NOTE] > > The panel calls these connections **routes**. The underlying GraphQL API still names the operations after the older term, **subscription** (`createSubscription`, `subscriptions`, and so on) — see the [reference](/docs/features/backend/app-services/queues-and-event-bus/reference) if you're calling the API directly. ## The three pieces | Piece | What it is | | - | - | | **Topic** | A named channel you publish events to (e.g., `orders`, `users`). | | **Event** | A single announcement: an **event type** (e.g., `orders.created`) plus a JSON **payload**. | | **Route** | A rule attached to a topic that says *"when an event matches this filter, deliver it here"* — to a queue, a REST endpoint, or GraphQL. | One topic can have many routes, and each gets an **independent copy** of every matching event. That independence is the whole point: a slow or failing route never affects the others. ## Topics The Event Bus landing view lists your topics in a table — **Name**, **Events (24 h)**, **Routes**, and **Failed** (the number of failing deliveries, so an unhealthy topic stands out at a glance) — plus a per-row actions menu. Creating a topic needs only a name (retention lives under Advanced settings). ![Event Bus panel topic list with the Name, Events (24 h), Routes, and Failed columns and the New event button](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/event-bus-topics-list.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=8909f6d4fd6d6419199039bf7d22bd63) **1. Create a topic** Click **New event**, give it a name, and create it. The button is labeled "New event," but what it creates — and what the drawer calls it throughout — is a **topic**: "the publish point that routes connect to." The infrastructure is provisioned automatically for this environment. **2. Open the topic** Click the topic to open its detail, which has four tabs: **Routes**, **Tests**, **Connect**, and **Activity**. ## Add a route (the two-step wizard) The route wizard is the heart of the Event Bus. It answers two questions. ### Step 1 — Which events? Choose which events this route reacts to: | Mode | Matches | Example | | - | - | - | | **All** | Every event on the topic. | `orders.created`, `orders.shipped`, … | | **An exact type** | Only the type you name. | only `orders.created` | | **Pattern** | A single-segment wildcard. | `orders.*` matches `orders.created` and `orders.shipped` | ![Route wizard Step 1 — the three filter modes (All / An exact type / Pattern), with the event type field revealed for "An exact type"](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/route-wizard-step1.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=ecef425003a9918c9665a072536856fe) ### Step 2 — Where should it go? Pick a target: **Queue**, **REST**, or **GraphQL**. ![Route wizard Step 2 — the target choice (Queue / REST / GraphQL)](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/route-wizard-step2-queue.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=dab4e1f120090ba52f1212981cda4bdc) > **Queue** > > Deliver each matching event into one of your existing queues (or create one inline), to be processed by a worker in the background. > **REST** > > Deliver to an **external** HTTPS endpoint you own. Internal delivery to your own REST API isn't available yet — that option is shown but disabled ("coming soon"). > **GraphQL** > > Deliver to **your own project's** GraphQL API (the default), or to an external GraphQL endpoint with a mutation template you write. #### Target: Queue Select one of your existing queues in the same environment (or create one inline). Each routed queue receives its own copy of every matching event — this is the classic **fan-out then process** pattern covered in [Using them together](/docs/features/backend/app-services/queues-and-event-bus/using-them-together). Delivery attempts and the dead-letter queue for this path are configured on the queue itself, not on the route. #### Target: REST REST routes currently deliver to an **external** service only — "My archie-core API" is shown as an option but disabled while that path is built. You provide: * **Endpoint URL** — must be HTTPS and publicly reachable. It can include dynamic parameters filled in from each event's payload at delivery time — `{field}`, `[field]`, and `:field` are all replaced by the matching top-level field. For example `https://api.partner.com/orders/{orderId}/status` with payload `{ "orderId": 42 }` delivers to `…/orders/42/status`. * **Method** — `POST` (default), or another verb from the dropdown. * **Signing secret (HMAC)** — generated automatically when you save and shown **once**; used to sign every delivery (HMAC-SHA256) so your receiver can verify it came from Archie. * **Token / API key (optional)** — sent as `Authorization` if your endpoint needs it. * **Custom headers** — additional name/value pairs sent on every delivery, encrypted at rest; only the names are shown afterward. * **Delivery attempts** — 1–20, per route. Once exhausted, the event is parked in **this route's own dead-letter queue** — each endpoint route has one, separate from any queue's. #### Target: GraphQL Choose **where it runs**: * **My archie-core API (internal, default)** — search-select an existing **mutation** from your project's own schema (the same one the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) uses) instead of writing one by hand. You still provide a **Token / API key**, used as the `Authorization` for that mutation call — internal delivery is not secret-free, unlike a queue target. * **External endpoint** — deliver to a GraphQL endpoint you don't own. You write the **mutation** yourself; each event field arrives as a `$` variable, and `$event` (full payload) and `$payload` (event + metadata) are also available. The endpoint URL, signing secret, optional token, and delivery attempts work the same way as the REST target. ![New route panel with an external endpoint target selected — Endpoint URL, Signing secret (HMAC), Token / API key, and Delivery attempts](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/route-target-external-endpoint.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=35b7496f99607067c2e9fd2c33fcec6d) > [!WARNING] > > The external signing secret is displayed **only at creation time**. If you lose it, rotate it by recreating the route. Verify the signature on your side before trusting a delivery. ## The routes list Each topic's **Routes** tab lists every route with its filter, type, target, delivery-attempts setting, and an **Active** toggle plus a delete action: ![Topic Routes tab listing a route with its Filter, Type, Target, Delivery, and Active columns](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/topic-routes-list.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=54bd0297d524f069b8892f4d5757a124) | Column | What it shows | | - | - | | **Filter** | The event type or pattern this route matches. | | **Type** | The target kind — Queue, REST, or GraphQL. | | **Target** | The queue name or endpoint the route delivers to. | | **Delivery** | Delivery attempts for endpoint routes; `—` for queue routes (configured on the queue instead). | | **Active** | Pause or resume delivery without deleting the route. Paused routes stop receiving new events until switched back on. | ## Publish a test event The **Tests** tab lets you fire an event by hand to see the whole chain work end to end: ![Topic Tests tab — Event type and Payload (JSON) fields with the Publish button](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/topic-tests-tab.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=64d3f34ef78971878e46194d94747f53) **1. Choose an event type** Type an event type, e.g., `orders.created`. **2. Write the payload** Provide a JSON payload in the editor. **3. Publish** Click **Publish**. Archie confirms with the message ID once it's accepted — check the routes it should reach (a queue's Pending count, or the **Activity** tab for endpoint targets) to confirm delivery. ## Connect your app to a topic The **Connect** tab gives you the GraphQL endpoint and a ready-to-paste snippet for publishing from your own code — the same pattern as a queue's connect panel, with **GraphQL** as the default tab and **REST (curl)** as the second: ![Topic Connect tab with the GraphQL endpoint and a copyable publishEvent snippet](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/topic-connect-tab.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=3cad6b5108bf0a73c79883be48121de4) ```graphql theme={null} mutation { publishEvent( topic: "orders" eventType: "orders.created" body: "{\"orderId\":\"A-1024\",\"total\":51.25}" # dedupKey: "order-A-1024" # optional: suppress duplicates ) { messageId } } ``` ## Watch deliveries in Activity The **Activity** tab is your audit trail of what actually happened for **endpoint** routes (REST and GraphQL). Queue routes don't produce delivery rows here — instead, watch the **Pending** count and error list on the destination [queue](/docs/features/backend/app-services/queues-and-event-bus/queues). ![Topic Activity tab, empty state: "No deliveries recorded yet"](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/topic-activity-tab.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=aed9bc97ab134235e43e25d796d5cfe1) Once a REST or GraphQL route has delivered at least once, this tab fills in with the delivery history — the event, the status returned, and how many attempts it took. Retried automatically with increasing back-off; exhausted deliveries land in that route's own dead-letter queue. ## Common questions > **What's the difference between an event type and a topic?** > > A **topic** is the channel (e.g., `orders`). An **event type** is the specific thing that happened on it (e.g., `orders.created`). Routes filter by event type within a topic. > **Do all routes get the event at the same time?** > > Each matching route gets its **own copy**, delivered independently. If one route is slow or failing, the others are unaffected. > **When should I deliver to my own API vs an external endpoint?** > > Use **GraphQL → My archie-core API** when the reaction is a write in your own project — no URL to manage, though you still need a token. Use an **external endpoint** (REST or GraphQL) when you're notifying a system outside Archie that you own. Note that REST currently only supports external delivery; internal REST delivery is coming. > **A route shows as failing — what do I check?** > > Open **Activity** and read the status codes for that route (endpoint routes only — queue routes show their health on the queue itself). A `4xx` usually means the target rejected the request (auth, validation); a `5xx` or timeout usually means the target was unavailable. Fix the cause; the route retries automatically up to its configured delivery attempts. > **Can I temporarily stop a route without losing it?** > > Yes — switch its **Active** toggle off in the Routes list. It stops receiving new events and keeps its configuration and history; switch it back on to resume. ## Next > **[Using them together](/docs/features/backend/app-services/queues-and-event-bus/using-them-together)** > > Fan an event out to several queues and process each copy in the background. > **[Reference & FAQ](/docs/features/backend/app-services/queues-and-event-bus/reference)** > > Operations, limits, statuses, and troubleshooting. # Queues Source: https://archie.com/docs/backend/app-services/queues-and-event-bus/queues Create a queue, send messages to it, and have workers process them one at a time in the background. Tune retries and timing in plain language, watch live health, and recover failed jobs from the error list. A **queue** is a to-do list for your app. Your app drops a message in; a single worker later picks it up, does the work, and confirms it. If the worker fails, the message comes back and is retried. Queues are how you run slow or spiky work **in the background** without making users wait and without losing anything. Open **Backend → App Services → Queues** to manage them. > [!NOTE] > > Queues are per **environment**. A queue named `order-processing` in `development` is a different queue from one with the same name in `production`. ## The queue list The landing view lists every queue in the current environment, with live health that refreshes automatically: ![Queues panel list view with several queues showing the Name, Pending, In flight, Oldest, and Errors columns and the New queue button](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/queues-list.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=b812a35571cf8d3b5b8f37ee01096bf7) | Column | Plain meaning | Technical term | | - | - | - | | **Name** | The queue's identifier (with a copy button). | — | | **Pending** | Messages waiting to be picked up. | Queue depth | | **In flight** | Messages a worker has taken but not yet confirmed. | In-flight | | **Oldest** | How long the oldest waiting message has been sitting there (e.g., "3 d ago"). | Age of oldest message | | **Errors** | Messages that failed too many times and moved to the error list. A red badge appears when this is above zero. | Dead-letter queue (DLQ) size | A steadily growing **Pending** count means messages arrive faster than your workers drain them — add workers or speed up processing. A growing **Errors** count means something is repeatedly failing — open the error list to investigate. ## Create a queue Creating a queue is meant to take about ten seconds — the only required field is the name. **1. Click New queue** In the Queues panel, click **New queue**. **2. Name it** Type a name. Names are lowercase letters, numbers and hyphens, with no spaces (they become part of an internal address). If what you type contains other characters, the panel shows the cleaned-up version it will use, and warns you immediately if the name is already taken. **3. (Optional) Add a description** A short description of what the queue is for. It shows up in the queue list and detail view — useful once you have more than a couple of queues. **4. (Optional) Adjust the configuration** The **Configuration (recommended defaults)** section is pre-filled with sensible values. Change them only if you need to — see the table below. **5. Create the queue** Click **Create queue**. You land directly on the new queue's detail view, ready to receive messages. ![New queue drawer open, showing the Name field, an optional Description, and the "Configuration (recommended defaults)" section with Processing time (s), Attempts before moving to errors, and Message retention (days)](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/new-queue-drawer.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=d71cad18c4371a2fb6d5cef817b17403) ### Settings, explained Every setting has a safe default, shown under **Configuration (recommended defaults)**. You can change them later from the queue's **Settings** tab. | Setting (panel label) | Default | Range | What it controls (plain language) | Technical term | | - | - | - | - | - | | **Processing time (s)** | 30 seconds | up to 12 hours | How long a consumer has to process a message before it's retried. Set it a little longer than your slowest job. | Visibility timeout | | **Attempts before moving to errors** | 5 | 1–20 | How many times a message is retried before it goes to the dead-letter queue (error list). | Max delivery / max receives | | **Message retention (days)** | 4 days | up to 14 days | How long an unprocessed message is kept before it expires. | Message retention | > [!WARNING] > > **Processing time** is the most common source of surprises. If a job legitimately takes longer than this window, another worker may start processing the same message in parallel. Set it comfortably above your slowest realistic job, or have the worker extend it while it works. ## The queue detail view Click any queue to open its detail — creating a queue drops you here directly. It's organized into tabs: **Overview**, **Errors**, **Test**, and **Settings**. ### Overview Six stat cards — three from your configuration (**Processing time**, **Retries**, **Retention**) and three live (**Pending**, **In flight**, **Failed**) — plus an **Oldest message** line and a **How to connect** panel. The connect panel shows your project's **GraphQL endpoint** with a copy button and ready-to-paste snippets for sending and receiving messages, with **GraphQL** as the default tab and **REST (curl)** as the second. This is the fastest path from "queue created" to "first message flowing". ![Queue detail Overview tab — six stat cards, the Oldest message line, and the "How to connect" panel with the GraphQL endpoint and copyable send/receive snippets](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/queue-detail-overview.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=e08529417ea71f1665042bbcf1aec61b) ### Errors (dead-letter queue) Messages that exhausted their attempts land here so they're never lost. The tab shows a **Failed** count and a single **Retry all** action — there is no per-message browser, so use this to confirm something needs attention and to recover once you've fixed the cause. ![Queue Errors tab showing a Failed count and the "Retry all" action](https://mintcdn.com/archie-e998dbf6/C7VAvypdgbF8SE1m/features/backend/app-services/queues-and-event-bus/queue-errors-tab.png?fit=max\&auto=format\&n=C7VAvypdgbF8SE1m\&q=85\&s=8e19c60bacf1a62224251860c6e33698) * **Retry all** — moves every failed message back to the main queue to be processed again. Use this after you've fixed the cause. ### Test Send a message by hand to confirm your worker consumes it: **1. Write a JSON message** Use the JSON editor. It validates as you type and enforces a 256 KB limit with a live size counter. **2. (Optional) Add a dedup key** A **dedup key** suppresses accidental duplicates: sending the same key twice within a short window enqueues the message only once. **3. Send** Click **Send**. You'll see confirmation that the message was enqueued and the **Pending** count tick up. ### Settings Change the description and the mutable settings (processing time, max attempts, retention), and find the **danger zone** to delete the queue. Deleting asks you to type the queue name and warns how many messages will be discarded. ## Connect your app to a queue Everything the panel does is available through the **GraphQL API**, so your application code sends and processes messages the same way. The connect panel gives you copy-paste snippets; the examples below show the shape. **Send a message.** ```graphql theme={null} mutation { sendMessage( queue: "order-processing" body: "{\"orderId\":\"A-1024\",\"total\":51.25}" # dedupKey: "order-A-1024" # optional: suppress duplicates ) { messageId } } ``` **Receive messages** (a worker pulls a batch, then confirms each once done). Receiving supports **long polling** — the call waits up to a few seconds for messages rather than returning empty immediately. ```graphql theme={null} mutation { receiveMessages(queue: "order-processing", max: 10, waitSeconds: 5) { messageId body receiptHandle } } ``` **Confirm (delete) a processed message** using the `receiptHandle` from the message you received: ```graphql theme={null} mutation { deleteMessage(queue: "order-processing", receiptHandle: "…") { ok } } ``` A typical worker loop is: `receiveMessages` → do the work → `deleteMessage`. If your worker crashes before `deleteMessage`, the message reappears after the **processing time** and is retried automatically. > [!NOTE] > > You can also connect over **REST** — the connect panel's second tab shows the equivalent `curl` calls. GraphQL is the recommended default because it's the same endpoint as the rest of your project's API. ## Recover from failures > **My Errors count is going up** > > A message is failing every attempt. Open the **Errors** tab to confirm how many are stuck, and check your worker's logic and logs. Once fixed, click **Retry all** to move the error list back into the main queue. > **The same message seems to be processed twice** > > This is expected under "at least once" delivery. Make your worker **idempotent** (safe to run twice for the same input) — for example, key writes on the `orderId` so a repeat is a no-op — and use a **dedup key** when sending to suppress accidental duplicate sends. > **Messages get retried while the worker is still running** > > Your job takes longer than the queue's **Processing time**. Increase it on the **Settings** tab, or have the worker extend the window while it processes. > **Pending keeps climbing and never drains** > > Messages arrive faster than they're processed. Run more workers in parallel (they compete for the same queue safely), make each job faster, or both. ## Next > **[Event Bus](/docs/features/backend/app-services/queues-and-event-bus/event-bus)** > > Broadcast one event to many routes — including feeding one or more queues. > **[Using them together](/docs/features/backend/app-services/queues-and-event-bus/using-them-together)** > > Fan an event out to several queues and process each copy independently. # Reference & FAQ Source: https://archie.com/docs/backend/app-services/queues-and-event-bus/reference A quick reference for Queues and the Event Bus: the GraphQL operations, default settings and limits, message and delivery statuses, a glossary that maps plain-language terms to their technical names, and troubleshooting. A one-page reference for everything in the Queues and Event Bus panels. For step-by-step guidance, start from the [overview](/docs/features/backend/app-services/queues-and-event-bus/overview). ## GraphQL operations Every panel action maps to a GraphQL operation you can run from the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) or your app. The exact fields and arguments are documented live in the Explorer's schema; the tables below list what each operation is for. ### Queues | Operation | Type | What it does | | - | - | - | | `queues` | Query | List the queues in the current environment. | | `queueStats` | Query | Live counts for one queue: pending, in-process, oldest-message age, error-list size. | | `createQueue` | Mutation | Create a queue (name, plus optional processing time, max attempts, retention). | | `updateQueue` | Mutation | Change a queue's mutable settings. | | `deleteQueue` | Mutation | Delete a queue. | | `sendMessage` | Mutation | Put a message on a queue (with an optional dedup key). | | `receiveMessages` | Mutation | Pull a batch of messages for a worker (supports long polling). | | `deleteMessage` | Mutation | Confirm a processed message using its receipt handle. | | `redrive` | Mutation | Move a queue's error list (dead-letter queue) back to the main queue. | ### Event Bus The panel calls these connections **routes**; the API still names the operations after the older term, **subscription**. | Operation | Type | What it does | | - | - | - | | `topics` | Query | List topics in the current environment. | | `topicStats` | Query | Event volume, bytes, and retention for a topic. | | `createTopic` | Mutation | Create a topic (the panel's "New event" button). | | `deleteTopic` | Mutation | Delete a topic. | | `subscriptions` | Query | List a topic's routes. | | `createSubscription` | Mutation | Add a route (filter + target: queue, REST, or GraphQL). | | `deleteSubscription` | Mutation | Remove a route. | | `setSubscriptionActive` | Mutation | Pause or resume an endpoint route. | | `publishEvent` | Mutation | Publish an event (event type + JSON body, optional dedup key) to a topic. | | `topicDeliveries` | Query | Delivery history for a topic's endpoint routes (powers the **Activity** tab). | > [!NOTE] > > The API is scoped to the current project and environment the same way the panels are. Calls made with an API key follow your [Role-Based Access](/docs/features/backend/app-services/role-based-access) permissions. ## Defaults and limits ### Queue settings | Setting (panel label) | Default | Maximum | Technical term | | - | - | - | - | | Processing time (s) | 30 seconds | 12 hours | Visibility timeout | | Attempts before moving to errors | 5 | 20 | Max delivery / max receives | | Message retention (days) | 4 days | 14 days | Message retention | | Message size | — | 256 KB | Max message size | ### Event Bus settings | Setting | Note | | - | - | | Target types | **Queue** (one of your queues), **REST** (external endpoint only, for now), **GraphQL** (your own project's API by default, or an external endpoint). | | Filter modes | All (`>`), an exact event type, or a single-segment wildcard pattern (`*`). | | Description | Topics take an optional description. | | GraphQL → My archie-core API | Search-select an existing mutation from your schema instead of writing one. Still requires a **Token / API key**, sent as `Authorization` — internal delivery is not secret-free. | | REST → My archie-core API | Not available yet; shown as disabled in the target picker. | | External targets (REST or GraphQL) | HTTPS required; blocked for private/loopback/metadata hosts (SSRF safeguard); credentials and custom headers encrypted at rest; one-time HMAC signing secret shown on save. | | HTTP method | REST targets expose a method dropdown (`POST` default). GraphQL targets always `POST` the mutation. | | Dynamic URL parameters | The external URL may contain `{field}`, `[field]`, or `:field` placeholders, resolved from each event's payload at delivery; SSRF checks run on the final address. | | Custom headers | Multiple name/value headers sent on every delivery, encrypted at rest; only the names are shown afterward. Platform headers (signature, `Content-Type`, `X-Archie-*`) always win. | | Delivery attempts | 1–20, set **per route** for REST/GraphQL targets. Exhausted deliveries move to that route's own dead-letter queue. Queue targets use the destination queue's own settings instead. | | Endpoint retries | Automatic, with increasing back-off, up to the route's delivery attempts. | > [!NOTE] > > Per-tenant quotas (how many queues, topics, and routes an environment may create) apply and are enforced when you create a resource. If you hit a limit, the panel tells you in plain language. ## Statuses ### Queue message lifecycle | State | Meaning | | - | - | | **Pending** | Waiting to be picked up. | | **In flight** | Received by a worker and temporarily invisible to others. | | **Confirmed** | Acknowledged and removed. | | **Error list (DLQ)** | Failed its maximum attempts and moved aside for inspection. | ### Delivery statuses (Activity tab) | Status | Meaning | | - | - | | Green `2xx` | The target accepted the delivery. | | Red `4xx` | The target rejected it (authentication or validation). Retrying rarely helps until the cause is fixed. | | Red `5xx` / timeout | The target was unavailable. Retried automatically. | | `failed` / `dead_letter` | The delivery exhausted its retries and moved to the route's own error list. | ## Glossary Product terms in the panels, mapped to their industry names. | In Archie | Industry term | Meaning | | - | - | - | | Queue | SQS queue | A point-to-point message buffer consumed by one worker at a time. | | Event Bus / Topic | SNS topic / EventBridge | A publish/subscribe channel that fans one event out to many routes. | | Route (panel) / Subscription (API) | SNS subscription | A rule that delivers matching events from a topic to a target. The UI calls it a route; `createSubscription` and related GraphQL operations still use the older name. | | Processing time | Visibility timeout | How long a worker has to confirm a message before it's retried. | | Max attempts | Max delivery / receive count | Retries before a message moves to the error list. | | Error list | Dead-letter queue (DLQ) | Where messages that keep failing are preserved. | | Redrive | Redrive | Moving the error list back to the main queue to retry. | | Dedup key | Deduplication ID | Suppresses accidental duplicate sends within a window. | | In flight | In-flight | A message currently held by a worker. | | Pending | Queue depth | Messages waiting to be consumed. | ## Troubleshooting > **I created a queue/topic but don't see it** > > Confirm you're viewing the **same environment** you created it in. Queues and topics are per-environment; switching the environment selector changes the whole list. > **My worker processes messages twice** > > Delivery is *at least once*. Make the worker **idempotent** (safe to run twice for the same input) and use a **dedup key** on send to suppress accidental duplicate sends. > **Messages are retried while my worker is still running** > > The job exceeds the queue's **Processing time**. Increase it on the queue's **Settings** tab, or extend it from the worker while it runs. > **A route keeps failing** > > Open the topic's **Activity** tab and read the status codes for that route (queue-target routes show their health on the queue itself, not here). Fix the cause (auth for `4xx`, availability for `5xx`) — the route retries automatically up to its configured delivery attempts, then parks the event in its own dead-letter queue. > **I published an event but a route didn't receive it** > > Check the route's **filter** on the topic's Routes tab — the event type may not match "An exact type" or the Pattern prefix. Also confirm the route is **Active**. > **My external endpoint rejects the delivery** > > Verify you're checking the **HMAC signature** with the secret shown at creation time, and that any **token or custom headers** you configured are what your endpoint expects. External URLs must be HTTPS and publicly reachable. > **I lost the external signing secret** > > The secret is shown only once. Recreate the route to generate a new one, and update your endpoint to verify against it. ## Related > **[Queues](/docs/features/backend/app-services/queues-and-event-bus/queues)** > > Create, monitor, and recover queues. > **[Event Bus](/docs/features/backend/app-services/queues-and-event-bus/event-bus)** > > Topics, routes, and delivery history. > **[GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview)** > > Run the operations above against your live schema. > **[Environments](/docs/features/backend/environments/overview)** > > How per-environment scoping works. # Using them together Source: https://archie.com/docs/backend/app-services/queues-and-event-bus/using-them-together Combine the Event Bus and Queues into the fan-out-then-process pattern: publish one event, deliver an independent copy to several queues, and let workers drain each queue at their own pace. Includes a full walkthrough and common recipes. Queues and the Event Bus are useful on their own, but they're most powerful **together**. The Event Bus is great at *broadcasting* an event; queues are great at *reliably processing* work one item at a time. Connect them and you get the best of both: one event fans out to several independent workstreams, each of which is buffered, retried, and processed at its own pace. This is the classic **fan-out then process** pattern (in AWS terms, SNS delivering to several SQS queues). ## The pattern in one picture When you publish `orders.created` once, three teams react without knowing about each other: ``` ┌────────────────────┐ worker ┌──▶ │ queue: fulfillment │ ─────────▶ pick & pack │ └────────────────────┘ publish │ ┌────────────────────┐ worker orders.created ──▶│──▶ │ queue: billing │ ─────────▶ charge card (topic: orders) │ └────────────────────┘ │ ┌────────────────────┐ worker └──▶ │ queue: analytics │ ─────────▶ record metrics └────────────────────┘ ``` Each queue holds **its own copy** of the event. If the billing worker is down for an hour, its copies wait safely in the billing queue and are processed when it returns — fulfillment and analytics are unaffected. ## Why not deliver straight to your services? You *can* route your API directly from a topic. Putting a **queue in the middle** adds three things that matter in production: * **Buffering** — a spike of events is absorbed by the queue instead of overwhelming a downstream service. * **Retries and an error list** — a failed job is retried and, if it keeps failing, preserved in the queue's error list instead of being lost. * **Independent pace** — each consumer drains its queue as fast as it can, fully isolated from the others. ## Walkthrough: order created → three reactions **1. Create the queues** In the **Queues** panel, create the queues that will do the work — for example `fulfillment`, `billing`, and `analytics`. Accept the defaults unless a job needs special timing. **2. Create the topic** In the **Event Bus** panel, create a topic — for example `orders`. **3. Route each queue from the topic** On the `orders` topic, add three routes. For each one: * **Step 1 (filter):** choose **An exact type** and enter `orders.created` (or a pattern like `orders.*` if the queue should react to more). * **Step 2 (target):** choose **Queue** and select `fulfillment`, then repeat for `billing` and `analytics`. **4. Publish an event** From your app, publish once: ```graphql theme={null} mutation { publishEvent( topic: "orders" eventType: "orders.created" body: "{\"orderId\":\"A-1024\",\"total\":51.25}" ) { messageId } } ``` Or use the topic's **Tests** tab. **5. Watch the copies arrive** Open each queue — the **Pending** count on `fulfillment`, `billing`, and `analytics` each increases by one. Every routed queue got its own copy. **6. Let workers process each queue** Each worker runs its own loop — `receiveMessages` → do the work → `deleteMessage` — against its queue. They run independently and at their own pace. See [Queues](/docs/features/backend/app-services/queues-and-event-bus/queues#connect-your-app-to-a-queue) for the worker loop. That's the whole pattern: **publish once, process in three isolated, retryable streams.** ## Together or separate — choosing per case You don't have to combine them. Pick the shape that fits each need: | Scenario | Shape | Setup | | - | - | - | | A single background job triggered directly by your app | **Queue only** | Send to a queue; a worker drains it. | | Notify one external partner when data changes | **Event Bus only** | A topic with one external-endpoint route. | | One event, several **internal** reactions, each buffered and retried | **Together (topic → queues)** | One topic, one queue + route per reaction. | | One event, a mix of internal writes and external notifications | **Event Bus, mixed targets** | One topic with several routes (some GraphQL-internal, some external, some Queue). | | Broadcast **and** guarantee reliable processing of each copy | **Together** | Fan out to queues; workers provide the reliability. | ## Recipes > **Add a new reaction later, with zero risk** > > To make the system do one more thing when `orders.created` fires, create a new queue and route it from the topic. Existing routes are untouched — they never know a new one was added. This is the safest way to grow an event-driven system. > **Give one reaction its own retry policy** > > Because each reaction has its own queue, you can tune them independently. Give `billing` a longer **processing time** and more **max attempts** than `analytics`, for example — one queue's settings never affect another. > **Mix a queue and a direct API write on the same topic** > > A topic can have both a **Queue** route (for heavy background work) and a **GraphQL → My archie-core API** route (for a quick synchronous write) on the same event type. Each is independent. > **Fan out to both internal and external systems** > > Route a queue for your own processing and an **external endpoint** for a partner, both from `orders.created`. Your internal work and the partner notification happen in parallel, each retried on its own. > **Replay a burst safely** > > If a downstream service was down, its queue simply accumulated the copies. When it recovers, its workers drain the backlog — no events were lost and nothing needs to be re-published. ## Keep it reliable > [!NOTE] > > Two habits make an event-driven system dependable: > > 1. **Make workers idempotent.** Delivery is *at least once*, so a copy may occasionally arrive twice. Key your writes on a stable identifier (like `orderId`) so a repeat is harmless. > 2. **Watch the error lists.** A non-empty dead-letter queue (a red badge on a queue) or a failing route in **Activity** is your early warning. Fix the cause, then redrive. ## Next > **[Reference & FAQ](/docs/features/backend/app-services/queues-and-event-bus/reference)** > > The full list of operations, limits, statuses, and troubleshooting. > **[Back to overview](/docs/features/backend/app-services/queues-and-event-bus/overview)** > > The concepts and when to use each service. # Role-Based Access Source: https://archie.com/docs/backend/app-services/role-based-access Define roles and the read, write, update, and delete permissions they grant on every table — including row-level filtering. Role-Based Access is where you decide what every authenticated user (or [API key](/docs/features/backend/settings/api-keys)) is allowed to do. You create roles, attach **Read / Write / Update / Delete** permissions per resource, and optionally add a row-level filter that scopes queries to a subset of records. The auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs enforce those rules on every request. To open it, navigate to **App Services → Role-Based Access** in the Backend Console. ![Role-based access overview](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/role-based-access-overview.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=40c152f1e5a67dd996a40619cedc0600) ## Per-environment scoping Roles are scoped per environment. Each [environment](/docs/features/backend/environments/overview) has its own `roles` table — switch environments and the roles list reflects whatever's defined there. Branching an environment copies the roles in the source environment as a starting point. ## Creating a role **1. Click + Add Role** The button sits in the top-right of the dashboard. **2. Name the role** Use a unique, snake-cased identifier — `admin`, `editor`, `content_manager`. The name appears in API responses and tokens. **3. Add a description** Briefly describe what the role can do. Click **Auto-generate** to have Archie propose one based on the name. **4. Save** The role appears in the dashboard with zero assigned users and a blank permissions grid. ![Roles configuration panel](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/role-based-access-roles-config.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=51ed116e3ab2868aa9ca22a97e745583) ## The permission matrix Click any role from the dashboard to open its permissions grid. For every resource — every [table](/docs/features/backend/data-model/tables) and every system resource — you toggle four operations: | Operation | What it grants | | - | - | | **Read** | Run list and single-record queries against the resource. | | **Write** | Create new records (POST/`createXxx`). | | **Update** | Modify existing records (PATCH/`updateXxx`). | | **Delete** | Remove records (DELETE/`deleteXxx`). | The grid is the contract for both APIs — there is no separate "API permission" layer. Granting **Read** on `orders` means a holder of this role can run `query { orders { ... } }` on GraphQL and `GET /api/rest/orders` on REST. Without it, both fail with `403 Forbidden`. ![Manage Permissions Grid](https://mintcdn.com/archie-e998dbf6/Qol4EJyOu33nszYZ/features/backend/app-services/role-based-access-permissions-grid.png?fit=max\&auto=format\&n=Qol4EJyOu33nszYZ\&q=85\&s=603f0686e3c029af192a6319e4e8e367) ## Row-level filtering The **Filter** column on each table scopes a role's queries to a *subset of records*. You author it as a structured **Custom Filter** (a small JSON object) — for example, an `employee` role that can only read records they own: ```json theme={null} { "version": "1", "logic": "AND", "applyAs": "row_filter", "rules": [ { "type": "field", "table": "order", "field": "owner_id", "operator": "equals", "value": "$user.id" } ] } ``` The filter is applied to every read, list, count, update, and delete for that role, so a query like `GET /api/rest/orders` returns only the matching subset — the user never has to add anything to their request. Rules can reference the signed-in user (`$user.id` / `$user.email`), combine with **AND** / **OR**, and a user with multiple filtered roles sees the **union** of their filters. Filters apply on top of the base permission: a role with **Read** on `orders` and a row-level filter can read its own orders; without **Read**, the filter doesn't grant anything. > **[Row-Level Filters — full reference](/docs/features/backend/app-services/row-level-filters)** > > All operators, per-user tokens, AND/OR logic, multi-role behavior, examples, and troubleshooting. ## Roles and authenticated users When a user signs in via an [authentication provider](/docs/features/backend/app-services/authentication-providers/overview), the issued token carries their assigned role(s). Every subsequent request is checked against those roles. Users can hold multiple roles; permissions are the union. Assignment happens in two places: * **In the dashboard** — open a user record (or the auth provider's user list) and pick roles from the dropdown. * **At signup** — pass a `roleId` to the [Archie Auth signup endpoint](/docs/features/backend/app-services/authentication-providers/archie-auth/rest-api). New users without an explicit role get the project's default. ## Roles and API keys External clients calling the API use tokens generated under [Backend → Settings → API Keys](/docs/features/backend/settings/api-keys). When you create a key you attach one or more roles to it, and the key is bound to those permissions for its entire lifetime. To rotate the role attached to a key, generate a new key with the new role and revoke the old one. Roles assigned at key-creation time can't be edited later — that's deliberate, because a long-lived key whose permissions silently change is a security trap. ## Cross-cutting effects | Surface | What RBAC enforces | | - | - | | [GraphQL API](/docs/features/backend/graphql-api-explorer/overview) | Filters records the role can't read out of every list and nested query; rejects mutations the role can't perform. | | [REST API](/docs/features/backend/rest-api-explorer/overview) | Same checks on every endpoint, returning `403 Forbidden` on disallowed operations. | | [File Manager](/docs/features/backend/app-services/file-manager) | Per-role rules on `/api/rest/files` upload, download, delete. | | [Custom APIs](/docs/features/backend/app-services/custom-apis) | Custom routes inherit auth and can be protected per-role. | | [Data Viewer](/docs/features/backend/data-model/data-viewer) | Hides fields and rows the role can't read; disables edit/delete actions the role can't perform. | ## FAQ > **What's the default role for new users?** > > Each project has a default role assigned at signup time. By convention it's a low-privilege role like `member` — verified users get baseline read access to user-facing tables, nothing more. Configure it in the auth provider settings. > **Can I bypass RBAC for a custom function?** > > Custom functions run with their own service identity. They can read and write tables regardless of the caller's role — that's typically the right place to enforce business-level rules that go beyond CRUD. The auto-generated APIs always enforce RBAC. > **What does a row-level filter look like in practice?** > > `owner_id = $userId` on the `orders` table means the user only sees their own orders, even when they call `GET /api/rest/orders` with no filter. The platform appends the filter to the WHERE clause server-side. The user can't override it. > **If a user has multiple roles, what permissions do they get?** > > The union — the most permissive across all assigned roles. If `editor` has Read on `posts` and `moderator` has Update, a user with both can read and update. > **Can I copy roles from one environment to another?** > > Yes — branching an environment optionally copies the roles. After branching, edits to roles in either environment are independent. # Row-Level Filters Source: https://archie.com/docs/backend/app-services/row-level-filters Scope which records a role can read, update, and delete with a structured Custom Filter — including per-user filters, operators, and AND/OR logic. A **Row-Level Filter** limits *which records* a role can see — not just which tables. You attach it to a role's permission on a table, and every read, list, count, update, and delete for that role is automatically scoped to the matching rows. Users never see (or touch) records outside their filter, and they don't have to add anything to their queries — it's enforced server-side on every request. You define it as a small JSON object in the **Custom Filter** editor of the [Role-Based Access](/docs/features/backend/app-services/role-based-access) permission matrix. > [!NOTE] > > A row-level filter **narrows** what a role can access; it never grants access. A role still needs the base **Read / Update / Delete** permission on the table. Without the base permission, the filter does nothing. ## Opening the Custom Filter **1. Go to Role-Based Access** **Backend Console → App Services → Role-Based Access**, then click the role you want to scope. **2. Open the Filter for a table** In the permission matrix, find the table's row and open its **Custom Filter**. A small JSON editor appears. **3. Paste your filter and Save** Paste the JSON (see the shapes below) and click **Save**. It takes effect on the next request for that role. ## The two modes: `applyAs` The Custom Filter has an `applyAs` field that decides *how* the filter is used: | `applyAs` | Behavior | | - | - | | `"row_filter"` | **Row-level scope** (this page). The rules are turned into a `WHERE` condition and applied to every query, so the role only ever sees matching rows. | | `"gate"` *(or omitted)* | The default all-or-nothing check: the filter decides whether the whole operation is allowed, not which rows come back. | To scope rows, set **`"applyAs": "row_filter"`**. If you leave `applyAs` out, the filter behaves as a gate (the existing default) — so adding row-level scoping is always an explicit choice and never changes existing filters. ## Filter shape A row-level filter is a JSON object with four keys: ```json theme={null} { "version": "1", "logic": "AND", "applyAs": "row_filter", "rules": [ { "type": "field", "table": "order", "field": "owner_id", "operator": "equals", "value": "$user.id" } ] } ``` | Key | Required | Value | | - | - | - | | `version` | ✅ | Always `"1"` for now. | | `applyAs` | ✅ (for row scoping) | `"row_filter"`. | | `logic` | ✅ | `"AND"` or `"OR"` — how multiple rules combine. | | `rules` | ✅ | A list of one or more **field rules** (see below). | ## Field rules Each entry in `rules` is a single condition on one column: ```json theme={null} { "type": "field", "table": "order", "field": "status", "operator": "equals", "value": "PENDING" } ``` | Key | Required | Notes | | - | - | - | | `type` | ✅ | Always `"field"` for row-level filters (see [Rules & limits](#rules-and-limits)). | | `field` | ✅ | The **column** the condition applies to (e.g. `owner_id`, `status`, `total_amount`). | | `table` | – | The table the filter belongs to. Informational — matches the table you attached the filter to. | | `operator` | ✅ | One of the [supported operators](#operators). | | `value` | ✅ for scalar operators | A single value. | | `values` | ✅ for `in` / `not_in` | A **list** of values. | ## Operators > [!NOTE] > > `in` and `not_in` use **`values`** (a list). Every other operator uses **`value`** (a single value). | Operator | Meaning | Example | | - | - | - | | `equals` | equal to | `{ "field": "status", "operator": "equals", "value": "PENDING" }` | | `not_equals` | not equal to | `{ "field": "status", "operator": "not_equals", "value": "CANCELLED" }` | | `greater_than` | `>` | `{ "field": "total_amount", "operator": "greater_than", "value": 100 }` | | `greater_than_or_equal` | `>=` | `{ "field": "total_amount", "operator": "greater_than_or_equal", "value": 100 }` | | `less_than` | `<` | `{ "field": "age", "operator": "less_than", "value": 18 }` | | `less_than_or_equal` | `<=` | `{ "field": "age", "operator": "less_than_or_equal", "value": 65 }` | | `in` | in a list | `{ "field": "status", "operator": "in", "values": ["PENDING", "IN_PROGRESS"] }` | | `not_in` | not in a list | `{ "field": "status", "operator": "not_in", "values": ["CANCELLED"] }` | | `contains` | text contains | `{ "field": "title", "operator": "contains", "value": "invoice" }` | | `starts_with` | text starts with | `{ "field": "sku", "operator": "starts_with", "value": "AB-" }` | | `ends_with` | text ends with | `{ "field": "email", "operator": "ends_with", "value": "@acme.com" }` | ## Dynamic values (the signed-in user) Instead of a fixed value, a rule can reference the **authenticated user** so the same filter scopes each user to their own records: | Token | Resolves to | | - | - | | `$user.id` | The signed-in user's identifier. | | `$user.email` | The signed-in user's email. | ```json theme={null} { "version": "1", "logic": "AND", "applyAs": "row_filter", "rules": [ { "type": "field", "table": "order", "field": "owner_id", "operator": "equals", "value": "$user.id" } ] } ``` > [!TIP] > > Use the token that matches how your column stores the user. If your `owner_id` column holds emails, `$user.email` is the natural choice; if it stores the user identifier, use `$user.id`. In most projects the user's identifier **is** their email, so the two are often interchangeable — pick the one whose value matches your column so rows actually match. ## Combining rules: `logic` `logic` controls how multiple rules combine: * **`"AND"`** — a row must match **every** rule. * **`"OR"`** — a row must match **at least one** rule. ```json theme={null} { "version": "1", "logic": "AND", "applyAs": "row_filter", "rules": [ { "type": "field", "table": "order", "field": "owner_id", "operator": "equals", "value": "$user.id" }, { "type": "field", "table": "order", "field": "status", "operator": "not_equals", "value": "ARCHIVED" } ] } ``` The example above scopes a user to **their own, non-archived** orders. ## Multiple roles combine as a union If a user holds **several roles** and more than one has a row-level filter on the same table, the results are the **union (OR)** of all their filters. A user is never *more* restricted for having an extra role — each role's filter can only *add* rows they're allowed to see. **Example:** role `stylist` filters appointments to `status = PENDING`, role `receptionist` filters to `status = IN_PROGRESS`. A user with **both** roles sees appointments that are `PENDING` **or** `IN_PROGRESS`. ## Where the filter applies Once attached to a role's table permission, a row-level filter is enforced on: | Operation | Effect | | - | - | | **Read / list / count** | Only matching rows are returned (and counted). | | **Get by id** | Returns the record only if it matches the filter. | | **Update** | Only matching rows can be updated — a row outside the filter is untouched, even in bulk updates. | | **Delete** | Only matching rows can be deleted — including bulk deletes. | ### Writes are scoped too The filter isn't only for reads — it also protects **updates** and **deletes**, so a role can never modify or remove a record it isn't allowed to see. This holds for single-record *and* bulk operations, and you don't change the mutation at all — the scope is applied automatically. Take a role scoped to its own orders: ```json theme={null} { "version": "1", "logic": "AND", "applyAs": "row_filter", "rules": [ { "type": "field", "table": "order", "field": "owner_id", "operator": "equals", "value": "$user.id" } ] } ``` With that filter, the same mutations a user already runs are silently limited to their own rows: ```graphql Bulk update theme={null} # "Cancel all pending orders" — only the CALLER's pending orders are updated. # Someone else's pending order matches the filter's status but not owner_id, # so it is left untouched. mutation { updateOrders( filter: { status: { equals: "PENDING" } } data: { status: "CANCELLED" } ) { count # counts only the caller's rows that were updated } } ``` ```graphql Bulk delete theme={null} # "Delete my archived orders" — rows outside the filter are never deleted, # even if their id is included in the request. mutation { deleteOrders(filter: { archived: { equals: true } }) { count } } ``` ```graphql Single record theme={null} # Updating one order by id: if that order is not the caller's (fails the filter), # nothing is updated — the same as if the record didn't exist for this role. mutation { updateOrder( id: "b91c70bf-09b4-41f6-8d0c-a5924294f451" data: { notes: "reviewed" } ) { id } } ``` > [!NOTE] > > The write scope is enforced at the database level, so it holds even under concurrent > activity: a row that leaves the filter between the request starting and the write > committing is still not modified. The same guarantee applies on both the > [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and > [REST](/docs/features/backend/rest-api-explorer/overview) APIs. ## Examples > **Users see only their records** > > ```json theme={null} > { > "version": "1", > "logic": "AND", > "applyAs": "row_filter", > "rules": [ > { "type": "field", "table": "order", > "field": "owner_id", "operator": "equals", > "value": "$user.id" } > ] > } > ``` > **Only active, recent records** > > ```json theme={null} > { > "version": "1", > "logic": "AND", > "applyAs": "row_filter", > "rules": [ > { "type": "field", "table": "order", > "field": "status", "operator": "in", > "values": ["PENDING", "IN_PROGRESS"] } > ] > } > ``` > **Domain-scoped by email** > > ```json theme={null} > { > "version": "1", > "logic": "AND", > "applyAs": "row_filter", > "rules": [ > { "type": "field", "table": "client", > "field": "email", "operator": "ends_with", > "value": "@acme.com" } > ] > } > ``` > **Everything except archived** > > ```json theme={null} > { > "version": "1", > "logic": "AND", > "applyAs": "row_filter", > "rules": [ > { "type": "field", "table": "order", > "field": "archived", "operator": "equals", > "value": false } > ] > } > ``` ## Rules & limits > [!WARNING] > > A row-level filter only accepts **`"type": "field"`** rules. Free-form SQL and expression rules are intentionally not allowed — you describe *what* to match with columns, operators, and values, and the platform builds a safe, parameterized query for you. * **Values are always bound safely.** Nothing you type in `value` / `values` is ever executed as code, so filters can't be used for injection. * **Match your value types exactly:** * **Enums / status columns are case-sensitive.** If the stored value is `PENDING`, filter on `"PENDING"`, not `"pending"`. * **Numbers go without quotes** (`"value": 100`, not `"value": "100"`), otherwise they're treated as text. * **Booleans** use `true` / `false` (no quotes). * **`in` / `not_in` need `values`** (a list); every other operator needs `value`. * **Unknown column or operator ⇒ the filter is rejected safely.** If a rule references a column that doesn't exist or an operator that isn't supported, the role sees **no rows** for that table (fail-safe) rather than accidentally exposing everything. Double-check `field` and `operator` spelling if a filter returns nothing unexpectedly. ## Troubleshooting > **The role still sees all rows** > > * Confirm the filter is saved on the **correct role**, the **correct table**, and that `applyAs` is exactly `"row_filter"`. > * You may be querying as a **project owner / administrator**, which is not scoped by design. Test with a user that only holds the filtered role. > * Check the base **Read** permission is enabled for that table (the filter narrows Read; it doesn't replace it). > **The role sees no rows (but you expected some)** > > * **Value mismatch** is the most common cause — usually enum **casing** (`"PENDING"` vs `"pending"`) or a number sent as text. > * A `$user.id` / `$user.email` token that doesn't match how the column stores the user returns nothing. Use the token whose value matches your column. > * A misspelled `field` or `operator` makes the filter fail safe to **no rows**. > **A numeric filter returns nothing** > > That's usually correct, not a bug: `greater_than: 10.5` returns nothing if every row is `10.2`. Flip the threshold (e.g. `greater_than: 10`) to confirm the filter is applied — matching rows should reappear. > [!NOTE] > > Changes to a role's filter take effect within a short window (typically under a minute) as the new rules propagate. If a change doesn't seem to apply immediately, wait a moment and retry. # Overview Source: https://archie.com/docs/backend/custom-functions Server-side functions in Archie. This page covers custom functions. Detailed content arrives with the section build-out. # Deploying Custom Functions Source: https://archie.com/docs/backend/custom-functions/deploying Ship your custom functions to your app. This page covers deploying custom functions. Detailed content arrives with the section build-out. # Writing Custom Functions Source: https://archie.com/docs/backend/custom-functions/writing Author server-side logic for your app. This page covers writing custom functions. Detailed content arrives with the section build-out. # Overview Source: https://archie.com/docs/backend/data-model The Data Model is where you design your app's database schema, define relationships, and configure data validation. The Data Model is where you design your app's database schema, define relationships, and configure data validation. Every change is published in real time and stays in sync with the auto-generated GraphQL and REST APIs that your frontend and integrations consume. ## What lives in the Data Model The Data Model sidebar groups the schema into three item types: | Item type | What it is | Where to learn more | | - | - | - | | **Tables** | The standard database tables that store your records. | [Tables](/docs/features/backend/data-model/tables) | | **Views** | Virtual tables defined by a saved SQL `SELECT` query. | [Views](/docs/features/backend/data-model/views) | | **Data Types** | Reusable named enums you can apply to fields across tables. | [Data Types](/docs/features/backend/data-model/data-types) | ## The two tabs on every table When you open a table, you switch between two tabs: * **Schema** — the visual schema designer. Add and remove fields, configure validation, define relationships, and create indexes. See [Tables](/docs/features/backend/data-model/tables) and [Fields](/docs/features/backend/data-model/fields). * **Data** — a spreadsheet-style viewer for the records inside the table. Add, edit, filter, sort, import, and export rows. See [Data Viewer](/docs/features/backend/data-model/data-viewer). ## Auto-generated APIs Every table and view in the Data Model is automatically exposed through: * A **GraphQL API** with type-safe queries, mutations, and real-time subscriptions. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview). * A **REST API** with CRUD endpoints per table. See the [REST API Explorer](/docs/features/backend/rest-api-explorer/overview). When you add, rename, or remove a field, both APIs update immediately. You don't write resolvers, controllers, or routes by hand. ## Schema is stored as structured JSON The Data Model is persisted as a structured JSON representation of your schema, not flat SQL files. Code generation reads from this representation so the rest of your project — types, queries, validation — stays in sync. You configure the model through the UI; you don't edit raw SQL files. ## Permissions live elsewhere Per-table access control is **not** configured inside the Data Model. Every table is governed by role-based permissions defined in **App Services → Role-Based Access**. See [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## Where to go next > **[Tables](/docs/features/backend/data-model/tables)** > > Create, rename, and delete tables. > **[Fields](/docs/features/backend/data-model/fields)** > > The 9 field types and their configuration options. > **[Data Types](/docs/features/backend/data-model/data-types)** > > Reusable enums shared across tables. > **[Views](/docs/features/backend/data-model/views)** > > Virtual tables defined by a SQL query. > **[Indexes](/docs/features/backend/data-model/indexes)** > > Speed up lookups and enforce uniqueness. > **[Relationships](/docs/features/backend/data-model/relationships)** > > Foreign keys and cardinality between tables. > **[Data Viewer](/docs/features/backend/data-model/data-viewer)** > > Browse and edit records. > **[Importing & exporting](/docs/features/backend/data-model/importing-data)** > > Move data in and out via CSV. > **[SQL Playground](/docs/features/backend/data-model/sql-playground)** > > Run raw SQL against your database. > **[ERD](/docs/backend/data-model/erd)** > > A live diagram of your schema and its relationships. ## FAQ > **Do I need to write SQL to use the Data Model?** > > No. You design tables, fields, relationships, and indexes through the visual UI. The only place SQL is required is when you create a [View](/docs/features/backend/data-model/views), which is defined by a saved `SELECT` query. > **What database is Archie running under the hood?** > > Each workspace gets a dedicated PostgreSQL database. The Data Model maps directly to PostgreSQL tables, columns, indexes, and enum types. > **Where do I configure who can read or write each table?** > > Permissions live in [App Services → Role-Based Access](/docs/features/backend/app-services/role-based-access), not in the Data Model. The Data Model defines the shape of your data; Role-Based Access governs which user types can read, write, or administer it. > **Do I have to update my GraphQL or REST endpoints when I change the schema?** > > No. Both APIs are generated from the Data Model and refresh automatically whenever the schema changes. > **Can I edit the schema as raw SQL files in my repo?** > > No. The schema is stored as structured JSON, not SQL files. Use the Data Model UI to make changes — code generation and the auto-generated APIs read from that representation. # Data Types Source: https://archie.com/docs/backend/data-model/data-types Data Types are reusable named enums you can apply to fields across tables — defined once, applied everywhere. A Data Type is a reusable named enum — a fixed list of values that you define once and apply to fields across your data model. Instead of declaring `DRAFT`, `PUBLISHED`, `ARCHIVED` separately on every field that needs those statuses, you create one `post_status_enum` Data Type and reference it wherever it's needed. When you update a Data Type, every field that uses it inherits the change. ## When to use a Data Type Use a Data Type when the same list of values needs to stay consistent across multiple fields or tables: * A `status_enum` shared by `posts.status`, `comments.status`, and `pages.status` * A `priority_enum` shared across every table that has a priority column * A `currency_enum` shared by `orders.currency`, `invoices.currency`, and `payments.currency` If the list lives on exactly one field and you don't expect to reuse it, use an [inline enum](/docs/features/backend/data-model/fields/inline-enum) instead. ## Creating a Data Type **1. Open the Data Model sidebar** Find the **Data Types** section of the sidebar. **2. Click + next to Data Types** The **Add Data Type** dialog opens. **3. Name and describe** Give the Data Type a unique, lowercase identifier. The convention is to suffix the name with `_enum` (for example, `course_status_enum`) so it's clear at a glance what it is. **4. Add values** Enter the allowed values in upper snake case (for example, `IN_PROGRESS`, `COMPLETED`). Add as many rows as you need. **5. Save** The Data Type appears in the **Data Types** section of the sidebar and becomes available to any user-defined field. ![Add Data Type button and modal](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-add-data-type.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=43e9a8953f49ea59c956aa67db7f0d5b) ![Data Type sidebar listing](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-data-type-sidebar.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=a651f2fcb415bef0385e625c107b0395) ![Data Type enum values configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-data-type-enum.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=6e4ebf6f2fe29d9968fa9044cde710e0) ## Applying a Data Type to a field On any table's **Schema** tab, add a [user-defined field](/docs/features/backend/data-model/fields/user-defined) and pick the Data Type from the **Enum Type** dropdown. The field will only accept values from that Data Type's list. ## Examples | Data Type name | Sample values | | - | - | | `course_status_enum` | `OPEN`, `CLOSED`, `IN_PROGRESS`, `CANCELLED` | | `grade_type_enum` | `A`, `B`, `C`, `D`, `F`, `INCOMPLETE` | | `resource_type_enum` | `VIDEO`, `PDF`, `QUIZ`, `ASSIGNMENT` | | `attendance_status_enum` | `PRESENT`, `ABSENT`, `LATE`, `EXCUSED` | ## Why Data Types over inline enums * **Standardization** — every field using `status_enum` accepts the exact same list of values, eliminating drift between tables. * **Single source of truth** — when business rules change, you update the Data Type once and every field that uses it is updated. * **Predictable APIs** — the GraphQL schema exposes one shared enum type instead of many lookalike ones. ## How it appears in the API The Data Type is generated as a single enum type in the GraphQL schema. Every field that uses it gets the same typed value, with autocompletion and validation on the client. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated types. ## Permissions Data Types are not access-controlled — they're shape definitions. Read and write access to fields that use them is governed in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **What happens if I add a value to a Data Type?** > > Every field using the Data Type immediately accepts the new value. Existing rows are untouched. This is safe and the most common kind of change. > **What happens if I remove a value?** > > Any rows that still hold the removed value will fail validation on update. Migrate affected rows to a different value before removing it from the Data Type. > **Can I rename a value?** > > Renaming changes the Data Type but does not rewrite existing rows. Update affected rows to the new value after renaming. > **Can I convert an inline enum into a reusable Data Type later?** > > Yes. Create the Data Type with the same values, then change the field's type from inline enum to user-defined and pick the new Data Type. Existing values that match carry over. > **Can two Data Types share the same name?** > > No. Data Type names are unique within a project. Pick names that describe the domain meaning (`order_status_enum`) rather than the column they happen to be attached to. # Data Viewer Source: https://archie.com/docs/backend/data-model/data-viewer The Data tab is a spreadsheet-like editor for the records inside a table. Browse, edit, filter, sort, and customize columns. The Data Viewer is a spreadsheet-like interface for the rows in a table. Open any table in the Data Model and click the **Data** tab to switch from schema design to record editing. It's where you add seed data during development, eyeball the contents of a table, or fix a bad row by hand. Schema-level validations apply here too — uniqueness, mandatory fields, type constraints, and enum values are enforced just like they are through the GraphQL or REST API. ## Adding a row **1. Click + Add row** The button sits in the top-left of the toolbar. A form opens with all the table's fields. **2. Fill in the fields** Required fields are marked. Validation runs as you go — uniqueness, type, and constraint errors appear inline. **3. Click Add Row** The row is inserted and appears in the grid immediately. ![Add row form in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-add-row-form.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=5c46f9178cdb793b5f364ddb226ebec0) ## Editing a row Click any row in the grid to open the edit form, update the fields, and click **Update Row**. As with adding a row, schema validations run before the change is saved. ![Edit record form in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-edit-record-form.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=7b959eaffaddf1487f6bf9f3b4de209b) ## Deleting a row Right-click the row you want to delete and choose **Delete Row** from the context menu. Confirm in the dialog that appears. ![Delete row confirmation in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-delete-row.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=ac1937fe7b8e6dcd27c0ff035e75c49d) > [!WARNING] > > Deleting a row is permanent. There is no undo from the Data Viewer. ## Filtering Click the **Filter** icon (funnel) in the toolbar to add filters. You can stack multiple filters — for example, "is\_active = true AND created\_at > 2025-01-01". The grid updates as you add and remove filters. ![Filter records in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-filter-records.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=e3a12a8e190ccf877359de987725a7ec) The Data Viewer's filter UI doesn't span related tables. For filters that need joins, use the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) or the [SQL Playground](/docs/features/backend/data-model/sql-playground). ## Sorting Click the sort icon (up/down arrows) in the toolbar to add sort attributes. You can sort on multiple columns; the order in which you add them determines the priority. ![Sort records in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-sort-records.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=d9a6feb4e2c97c4abae7d0e4a46e492e) ## Showing and hiding columns Click **View** in the toolbar to toggle which columns are visible in the grid. Audit columns — `created_by`, `updated_by`, `created_at`, `updated_at` — are hidden by default. Use **Show All** to bring them back, or **Reset** to return to the default view. ![Column visibility toggle in Data Viewer](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-column-visibility.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=359d2403db87ccb00a98e41718838be9) The current column selection also drives **what gets exported** when you run a CSV export — see [Exporting data](/docs/features/backend/data-model/exporting-data). ## Preview a referenced record A foreign key cell links a row to a record in another table. To read that record without leaving the current table, use the preview. Hover over a foreign key cell to reveal the **View referenced record** button at the right edge of the cell. Click it to open a card with the record the cell points to. The button doesn't appear when the cell is empty. The card is titled "Table:" followed by the table name and lists every field of the referenced record. Each field shows an icon for its type, its name, and its value. The field the foreign key points to is listed first. Dates and timestamps appear in a readable format. * An empty value shows as `null`. * An [encrypted field](/docs/backend/app-services/encryption) that Archie hasn't revealed shows a lock icon and dots instead of the value. Two buttons sit at the bottom of the card: * **Copy ID** copies the foreign key value to your clipboard. The button reads **Copied** for a moment afterward. * **View in table** opens the referenced table in a new browser tab on its **Data** tab, filtered to that record. While the record loads, the card shows placeholders. If it can't load the record, it shows "Couldn't load the referenced record. You may not have access to it." If no matching record exists, it shows "Referenced record not found." The preview follows the same [permissions](#permissions) as the rest of the Data Viewer. ## Importing and exporting The Data Viewer toolbar has dedicated controls for moving data in and out via CSV: * [Importing data](/docs/features/backend/data-model/importing-data) — upload a CSV, map columns, validate against the schema. * [Exporting data](/docs/features/backend/data-model/exporting-data) — download the current filtered view as CSV. ## Permissions Whether a user can read, add, edit, or delete rows — and whether specific fields are visible to them — is governed by [Role-Based Access](/docs/features/backend/app-services/role-based-access). The Data Viewer respects those rules: a field hidden from your role is hidden in the grid; a delete action you don't have permission for is unavailable. The same applies to the [referenced record preview](#preview-a-referenced-record): if you can't read the related record, the card tells you. ## FAQ > **Why are created\_by, updated\_by, created\_at, and updated\_at hidden by default?** > > They're audit columns the platform manages automatically. Hiding them by default keeps the grid focused on the fields you're likely to edit. Toggle them on from the **View** dropdown when you need them. > **Can I filter by a field on a related table?** > > Not from the Data Viewer's filter UI. Use the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) or the [SQL Playground](/docs/features/backend/data-model/sql-playground) for filters that span tables. > **What happens if my edit violates a uniqueness constraint?** > > The save fails and an inline error tells you which constraint was violated. The row stays in edit mode so you can fix the value. > **Is the Data Viewer safe to use against production?** > > Edits go through the same validations as the API, but anything you delete is gone. Use environments to keep production isolated — see [Environments](/docs/features/backend/environments/overview). > **Can I bulk-edit rows from the Data Viewer?** > > The Data Viewer edits one row at a time. For bulk updates, use a custom function or the SQL Playground. # ERD Source: https://archie.com/docs/backend/data-model/erd A live, auto-generated entity-relationship diagram of your project's database. See every table grouped by domain, follow relationships, inspect columns, and export the diagram as an image. The **ERD** (Entity-Relationship Diagram) is a visual map of your project's database. It's generated from your schema, so it stays current without a separate diagramming tool. ## Open the ERD Go to **Backend → Data**. You can open the diagram two ways: * In the page header, click **Diagram** under **DB-LEVEL TOOLS**, next to **SQL Playground**. * On the **Your Data** home, click **View Diagram**. This page appears when you open Data without selecting a table, and it shows how many tables, views, and data types the project has. ![The Data page with the Your Data home, the View Diagram button, and the Diagram button in the page header.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/32-erd-your-data.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=2920832db347400b41d2139946c79cdd) The diagram opens in a full-view dialog titled **Entity-Relationship Diagram (ERD)**. Click the close icon in the top-right corner to return to the Data page. The ERD shows the [environment](/docs/backend/environments) selected in the Backend Console. Switch environments to compare a branch's schema against `master` before you merge. ## The canvas The ERD arranges tables in colored bands, one band per domain, which groups related tables together. The diagram opens in a vertical layout and fits the whole schema on screen. The header shows the number of tables and relationships and the detail level, such as "21 of 21 tables · 28 relationships · keys + types". ![The ERD canvas showing tables as cards inside four colored domain bands, connected by relationship lines, with the Domains panel on the left and a minimap in the corner.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/10-erd-canvas.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=a575ff1b7e50846f588cfe50f95b9c14) Each table is a card: * The header shows the table name and a summary such as "12 columns · 3 FK". * Primary keys carry a **PK** badge. Foreign keys carry an **FK** badge and a link such as `→ users` to the table they reference. * When a table has more columns than the card shows, a **+N columns** button expands the card to list them all. Audit columns sit in a collapsible **Audit** section. * Hover over a card and select the arrow icon to open the table's data in a new tab. Lines connect related tables. A badge on each line shows the cardinality: **1:1**, **1:N**, or **N:M**. Hover over a badge to read it in words, such as "one-to-many". To move around, scroll to zoom and drag to pan. The controls in the bottom-left corner zoom in and out and fit the view. A minimap lets you jump around a large diagram. The status bar at the bottom shows how many tables are visible, the selected table, and the zoom level. ## Toolbar The toolbar sits in the top-right corner of the dialog: ![The ERD toolbar with the Detail selector set to Keys, the All tables button, and the Search, Layout, Fit, Export, and Close icon buttons.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/11-erd-toolbar.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=aced649dbf369a4d9e0c2f7b9d74ac17) | Control | What it does | | - | - | | **Detail** | Sets how much each table card shows. **Names** shows the table name only. **Keys** shows primary and foreign keys and is the default. **All** shows every column. | | **All tables** | Opens a list of tables with a checkbox for each one. See [Filter tables](#filter-tables). | | **Search** (magnifier icon) | Opens a search box for tables and columns. The box shows the `⌘K` shortcut. See [Search](#search). | | **Layout** (columns icon) | Switches between the vertical and horizontal layouts. The tooltip names the layout you switch to. | | **Fit** (corners icon) | Clears your selection and fits the whole diagram on screen. | | **Export** (download icon) | Copies the diagram as an image or downloads a PNG. See [Export the diagram](#export-the-diagram). | | **Close** (X icon) | Closes the dialog and returns to the Data page. | ### Search Click the search icon and type a table or column name. Results list matching tables, and matching columns appear as `table.column`. Use the arrow keys to move through results and press `Enter`, or click a result, to focus that table. If nothing matches, the list shows **No matching tables**. ![The ERD search box with the term ticket typed, listing the matching ticket tables and ticket\_attachment.ticket\_id and ticket\_comment.ticket\_id column results.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/12-erd-search.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=16620aa024aaf842e3fba744f3bfabc7) ### Filter tables Open **All tables** to choose which tables appear: * Uncheck a table to remove it from the diagram. The button then reads **Filtered tables**. * Select **Clear all** or **Select all** to hide or show every table at once. At least one table always stays visible. * Hover over a table and select the focus icon to show only that table and the tables it relates to. * Select **Rebalance diagram** to re-lay out the remaining tables. ![The All tables list open, headed "Tables in diagram", showing a checkbox for each table, a Clear all link, and a Rebalance diagram button.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/13-erd-filter-tables.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=be976d61ed7fa0043a82a1d4f2de446a) Relationships to hidden tables aren't drawn. ### Export the diagram Open **Export** and choose a scope: * **Current view** exports what's on screen. * **All sources** exports every table regardless of the current filter. ![The Export menu open, with the Current view and All sources scopes and the Copy image and PNG options.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/14-erd-export.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=1dcbd5bd200122a618bd598ff12ad266) Then choose a format: * **Copy image** sends the diagram to your clipboard. While the export menu is open, `Cmd+C` or `Ctrl+C` does the same. * **PNG** downloads a 2x, retina-resolution image. The image follows the **Detail** level you selected and the tables you chose to show. Domains you hide in the **Domains** panel are left out. ## Domains panel The **Domains** panel on the left lists each domain with a colored dot, a checkbox, and its table count. ![The Domains panel on the left of the ERD, listing the System, Archie, Ticket, and Events & other domains with the Ticket domain expanded to show its four tables.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/15-erd-domains.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=3f6f3ff595f603aa3d6703711d128bf5) * Check or uncheck a domain to show or hide its tables. Unchecked domains are left out of the diagram and the export. * Select **All** or **None** to show or hide every domain. * Click a domain name to expand it and see its tables. Click a table to focus it. The panel footer shows how many tables are visible out of the total, and how many relationships. > [!NOTE] > > Table and relationship counts leave out hidden permission tables. The **All tables** list still shows every table, so its count can be higher than the footer. ## Inspector Click a table on the canvas to focus it. The diagram narrows to the table and the tables related to it, and the status bar shows how many are related. The **Inspector** opens on the right. ![The ERD focused on the ticket table, with the Inspector open on the right listing its columns and the tables that reference it.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/16-erd-inspector.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=40b0f43e26eecae45892cef365826799) The Inspector shows: * The table name, its domain, and a summary of columns, foreign keys, and references. * **Columns** — every column with its type. **PK** and **FK** badges mark keys. * A `→ table` link under each foreign key. Click it to jump to the referenced table. * **Referenced by** — the tables that point to this one, with the column they use (for example, "via `user_id`") and the cardinality. Click a row to jump to that table. If nothing points here, the section reads **No tables point here.** To leave focus mode, press `Esc`, click an empty part of the canvas, or select **Fit**. The status bar reminds you: "Click a table to focus it. Esc resets." ## When the ERD doesn't load * If the project has no tables yet, the ERD shows a button to generate the data model. * If your backend is generated but its data model can't be loaded, the Data page shows **We couldn't load your database** with a **Retry** button. This is a temporary problem, not a missing backend. * If the diagram itself fails to load, you see an error with a **Try Again** button. ## FAQ > **Can I edit the schema from the ERD?** > > No. The ERD is read-only. Make schema changes on the [Data](/docs/backend/data-model) page's **Schema** tab. > **Does the ERD update automatically when I change a table?** > > Yes. It's generated from the current schema, so a new table, field, or relationship appears the next time you open or refresh the ERD. > **What do the 1:1, 1:N, and N:M badges mean?** > > They show the cardinality of the relationship between the two connected tables: one-to-one, one-to-many, or many-to-many. The cardinality comes from how the [relationship field](/docs/backend/data-model/fields/relationship) is configured. > **Can I export just part of my schema?** > > Yes. Hide tables with the **All tables** filter or uncheck domains in the **Domains** panel, then export with the scope set to **Current view**. > **Can I rearrange the cards?** > > Yes. Drag a card to move it. Select **Rebalance diagram** in the **All tables** list to re-lay out the remaining tables. # Exporting data Source: https://archie.com/docs/backend/data-model/exporting-data Download the rows in a table as a CSV file. Filters and visible columns control exactly what gets exported. The CSV exporter downloads the rows in a table as a CSV file. The **columns currently visible** in the Data Viewer control what gets included, and the export dialog lets you choose between the rows matching your active filters or every row in the table. ## Step-by-step **1. Filter the rows you want (optional)** Open the table on the **Data** tab and apply filters until the grid shows the rows you're interested in. This step is optional — you can also export everything regardless of filters. ![Filter records for export](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-export-filter-records.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=d63e6c6f29d5fd407dc9c7092571eee4) **2. Choose which columns to include** Click the **View** dropdown in the toolbar and toggle off any fields you don't want in the file. Only the columns currently visible in the grid are included in the export. ![Select columns for export](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-export-select-columns.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=8d0d1456ac7956b5b01b4a99fa6cbeee) **3. Click Export** The **Export Data** dialog opens. Edit the **File Name** if you want, then choose **Filtered Records Only** (matches your active filters and selected columns) or **All Records** (every row in the table, still limited to the visible columns). Click **Download** to save the CSV to your machine. ![Export Data dialog with File Name and a choice between Filtered Records Only and All Records](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-export-data.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=ad5f41e061de49b9976d8ef77bca8277) ## What the file contains * **Header row** — the field names of the included columns. * **One row per record** — matching the current filtered view. * **Date and timestamp formatting** — ISO 8601 (`2025-03-14T10:30:00Z` for timestamps with time zone). * **Relationship fields** — the primary key of the related row, not the related row's full content. * **JSONB fields** — the JSON value as a quoted string. If you need a different shape — for example, joining in fields from a related table — define a [view](/docs/features/backend/data-model/views) that returns the columns you want, then export the view. ## Permissions Exports respect the per-role read permissions configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). A user can only export rows and fields they're allowed to read. ## FAQ > **How do I export only some columns?** > > Toggle off the columns you don't want from the **View** dropdown. The export uses the visible-column set as its column list. > **How do I export only some rows?** > > Apply filters in the toolbar, then pick **Filtered Records Only** in the Export Data dialog. Pick **All Records** to ignore active filters and export the whole table. > **Can I export rows along with their related records?** > > Define a [view](/docs/features/backend/data-model/views) whose `SELECT` joins the related tables, then export the view. The CSV exporter operates on a single table or view at a time. > **Are there any size limits?** > > Very large exports may take a while to generate. For multi-million-row exports, consider scripting the dump through a custom function or the SQL Playground. > **Are audit columns included?** > > `id` is visible by default, so it's included unless you hide it. `created_by`, `updated_by`, `created_at`, and `updated_at` are hidden by default — toggle them on from the **View** dropdown if you want them in the file. # Boolean field Source: https://archie.com/docs/backend/data-model/fields/boolean Boolean fields store true / false flags. Use them for yes/no, on/off, and any binary state. A Boolean field stores a binary value: `true` or `false`. It's the most efficient way to model yes/no, on/off, or any state with exactly two possibilities. In the data viewer, the field renders as a switch. ## When to use it Reach for a Boolean when a record can be in exactly one of two states with no expectation that you'll add a third. Common examples: * `is_active`, `is_archived`, `is_published` * `has_paid`, `has_verified_email`, `has_consented` * `accepts_marketing`, `is_admin`, `is_default` If a field could grow into a third state ("draft", "in review"), use an [inline enum](/docs/features/backend/data-model/fields/inline-enum) or a reusable [Data Type](/docs/features/backend/data-model/data-types) instead. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `is_enrolled`). | | **Description** | An optional internal note explaining the flag's purpose. | | **Default Value** | One of: **No default** (the field starts `null`), **True**, or **False**. | | **Mandatory** | If on, the field cannot be `null` — every record must commit to `true` or `false`. | | **Unique** | If on, only one row in the table can hold each value. Rarely useful for booleans, since it caps the table at two rows. | ![Boolean field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-boolean.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=06377747733f87114a106ecd66b77bd4) ## How it appears in the API The field is generated as a non-nullable `Boolean!` in GraphQL when **Mandatory** is on, and as a nullable `Boolean` otherwise. The same shape is reflected in the auto-generated REST endpoints. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated queries and mutations. ## Permissions Whether a Boolean field is readable or writable per role is governed in [Role-Based Access](/docs/features/backend/app-services/role-based-access), not on the field itself. ## FAQ > **Should I leave Mandatory off if I want a default of false?** > > Set **Mandatory** on and **Default Value** to **False**. That way every record is guaranteed to be either true or false, with new records defaulting to false. > **Can I use a Boolean to model a three-state value like 'unknown / yes / no'?** > > Leave **Mandatory** off — `null` then represents "unknown". For anything more expressive, use an [inline enum](/docs/features/backend/data-model/fields/inline-enum) instead. > **Why would I ever turn on Unique for a Boolean?** > > Almost never. A unique Boolean limits the table to at most two rows total (one `true`, one `false`). It exists for completeness, not because it's a common pattern. > **What does the default value look like in the Data Viewer?** > > A new row pre-fills the switch to the default state. You can override it before saving the row. # Date field Source: https://archie.com/docs/backend/data-model/fields/date Date fields store calendar dates and timestamps. Pick the timestamp type to match how you'll use the value. A Date field stores temporal data. Depending on which timestamp type you choose, it can hold a plain calendar date (a birthday), a precise moment in time (when a transaction posted), or a wall-clock time (when a shop opens). Picking the right type up front avoids time-zone surprises later. ## Timestamp types | Type | What it stores | When to use it | | - | - | - | | **Date** | Year, month, day. No time component. | Birthdays, holidays, due dates, anything where the time of day is irrelevant. | | **Timestamp with time zone** | A precise moment in time, normalized to UTC. | Records of when something happened (logins, payments, message sends). The default for most timestamps. | | **Timestamp without time zone** | A wall-clock date and time, ignoring time zones. | Concepts that apply identically everywhere — "shop opens at 9:00 AM", recurring schedules. | If you're not sure, use **Timestamp with time zone**. It's the safest choice for most "when did X happen" fields and avoids ambiguity when records are created from different regions. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `created_at`, `due_date`). | | **Description** | An optional internal note explaining the field's purpose. | | **Timestamp Type** | One of **Date**, **Timestamp with time zone**, or **Timestamp without time zone**. | | **Default Value** | A value automatically assigned if no data is provided. Often used to capture creation time. | | **Mandatory** | If on, enforces `NOT NULL` — the record cannot be saved without a date. | | **Unique** | If on, no two rows can share the same date or timestamp. | ![Date field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-date.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=1ce9b54a57d2eaa61858ba8d17fa86e9) ## How it appears in the API The field is generated as a `Date` or `DateTime` scalar in the GraphQL schema, and as an ISO 8601 string in the REST API. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) for the exact generated types. ## Permissions Date fields, like any other field, are subject to the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Should I use timestamp with or without time zone?** > > Use **Timestamp with time zone** unless you have a specific reason not to. It stores values in UTC and converts to the viewer's time zone on read, which is what you want for "when did this event happen". Use **without time zone** only for wall-clock times that are the same everywhere (for example, store hours). > **Can I auto-fill a created\_at field?** > > Yes. Set the **Default Value** to the current time so new records capture their creation moment automatically. > **What happens if I switch a Date field to a Timestamp later?** > > Existing values are converted automatically where possible — a date becomes the start of that day. Switching the other direction truncates the time component. > **How do I store just a time of day with no date?** > > A Date field always carries at least a calendar date. If you only need a time of day, store it as **Text** in `HH:MM` format or use a JSONB field with a structured shape. # Inline enum field Source: https://archie.com/docs/backend/data-model/fields/inline-enum Inline enum fields constrain a column to a fixed list of values defined directly on the field — no shared Data Type required. An inline enum field constrains a column to a fixed list of allowed values, defined directly on the field. It's the right choice when the list is specific to one column on one table and you don't need to share it elsewhere. Common examples: * A `priority` field with `LOW`, `MEDIUM`, `HIGH` * A `visibility` field with `PUBLIC`, `PRIVATE` * A `payment_method` field with `CARD`, `BANK_TRANSFER`, `CASH` ## Inline enum vs. user-defined enum Both options give you the same protection — only listed values are accepted. The difference is reuse: | Choose this | When | | - | - | | **Inline enum** | The list of values lives on exactly one field. Editing the list edits only that field. | | **[User-defined](/docs/features/backend/data-model/fields/user-defined)** | The list needs to stay consistent across multiple fields or tables. Defined once as a [Data Type](/docs/features/backend/data-model/data-types). | If you find yourself copy-pasting the same enum values onto multiple fields, switch to a [Data Type](/docs/features/backend/data-model/data-types) instead. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `priority`, `status`). | | **Description** | An optional internal note explaining what the field captures. | | **Values** | The list of allowed values. Add and remove rows as needed. | | **Default Value** | One of the listed values, assigned to new records when no value is provided. | | **Mandatory** | If on, the record cannot be saved without one of the listed values. | | **Unique** | If on, no two rows can share the same value. Rarely useful for enums. | ## How it appears in the API The field is generated as a typed enum in GraphQL — clients get autocompletion of allowed values and the API rejects any value outside the list. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) for the generated enum type. ## Permissions Inline enum fields are subject to the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **When should I switch from inline to a Data Type?** > > The moment you want the same list on a second field. Inline enums are independent — editing one doesn't update any others. A [Data Type](/docs/features/backend/data-model/data-types) is shared, so editing it once propagates everywhere. > **What happens if I remove a value that existing records use?** > > Records that hold the removed value will fail validation on update until you reassign them. Plan removals carefully — consider migrating affected rows first, then removing the value. > **Can I add new values without breaking existing rows?** > > Yes. Adding a value is safe; existing records keep their current values and the new option becomes available going forward. > **Can I rename a value?** > > Renaming changes the enum on the field but does not rewrite existing rows. After renaming, update any rows holding the old value to the new one. # JSONB field Source: https://archie.com/docs/backend/data-model/fields/jsonb JSONB fields store structured JSON objects and arrays. Validated on write, indexable, and queryable by key. A JSONB field stores structured JSON — objects, arrays, or scalars wrapped in JSON. Unlike a Text field, the database validates that the input is well-formed JSON before saving and stores it in a binary format that allows efficient indexing and key-level queries. ## JSONB vs. JSON The field offers two storage modes: | Mode | What it does | When to use it | | - | - | - | | **JSONB** (default) | Decomposes and stores JSON in a binary format. Supports indexing (GIN). Faster to query. | Almost always. Anything you'll filter or read often. | | **JSON** | Stores an exact copy of the input text, preserving whitespace and key order. Faster to write, slower to read (re-parsed on every access). | Only when you need to round-trip the input byte-for-byte. | If you're not sure, pick **JSONB**. ## When to use a JSONB field Reach for JSONB when the data is structured but the structure varies between records or evolves frequently: * **Configuration and settings** — `{"notifications": true, "theme": "dark"}` * **Third-party API payloads** — raw responses from Stripe, webhooks, logging payloads * **Dynamic attributes** — products where a T-shirt has `size` and `color` but a laptop has `ram` and `cpu` * **Lists of strings or numbers** — `["tag1", "tag2", "tag3"]` If the shape is fixed and shared across all records, model it with explicit fields and relationships instead. JSONB shines when columns would balloon or change too often. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `metadata`, `settings`, `payload`). | | **JSON Type** | **JSONB** (binary, default) or **JSON** (raw text). | | **Description** | An optional internal note describing the schema or purpose of the data. | | **Default Value** | A valid JSON object or array assigned if no value is provided (for example `{}`). | | **Mandatory** | If on, the record cannot be saved without valid JSON. | | **Unique** | If on, no two rows can share the exact same content. | ![JSONB field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-jsonb.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=94afd2831b4830ccfd21cdf390ca400a) ## How it appears in the API The field is generated as a `JSON` scalar in GraphQL and as a nested JSON value in the REST API. Clients receive and send the value as a structured object, not a string. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated types. ## Permissions JSONB fields are subject to the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Why JSONB instead of JSON?** > > JSONB is decomposed and indexable, which makes filtering on keys (for example, `attributes->>'color' = 'blue'`) fast. Plain JSON has to re-parse the value on every read. The only reason to pick JSON is if you need to preserve the exact input text — whitespace, duplicate keys, key order. > **When should I use JSONB instead of separate columns?** > > When the shape varies between records or changes often. If every record has the same fields and you'll filter on them, model them as explicit columns — you'll get better validation and clearer types in the generated APIs. > **Can I query inside a JSONB value?** > > Yes. Use the SQL Playground or a custom function for ad-hoc PostgreSQL JSONB operators (`->`, `->>`, `@>`). The auto-generated GraphQL API surfaces the value as a whole; deep filtering inside JSONB is best done with a [view](/docs/features/backend/data-model/views) or a custom resolver. > **What does the Unique constraint compare?** > > Exact binary equality of the stored JSONB value. Note that JSONB normalizes — `{"a":1,"b":2}` and `{"b":2,"a":1}` collapse to the same value, but `{"a":1}` is distinct from `{"a":1,"b":null}`. > **What's a sensible default value?** > > `{}` for an object-shaped field or `[]` for an array. Defaults keep clients from having to handle `null` in addition to "empty". # Number field Source: https://archie.com/docs/backend/data-model/fields/number Number fields store integers, decimals, and floats. Pick the number type to match the precision and range you need. A Number field stores numerical data. Picking the right number type matters: it controls how much storage the field uses, what range of values it can hold, and whether decimal precision is exact or approximate. ## Number types | Type | Stores | Range / precision | Use it for | | - | - | - | - | | **Small Integer (SMALLINT)** | Whole numbers | -32,768 to 32,767 | Small counters, ages, ratings on a fixed scale. | | **Integer (INT)** | Whole numbers | About -2.1 billion to 2.1 billion | Most counts, quantities, scores. The default choice. | | **Big Integer (BIGINT)** | Whole numbers | About -9.2 quintillion to 9.2 quintillion | High-traffic counters, very large IDs, file sizes in bytes. | | **Float (REAL)** | Decimal numbers | \~6 decimal digits of precision | Weights, percentages, scientific measurements where some imprecision is fine. | | **Numeric / Decimal (NUMERIC)** | Exact decimal numbers | Configurable precision and scale | Money, anything that must round predictably. | | **Double Precision** | Decimal numbers | \~15 decimal digits of precision | Coordinates, scientific calculations needing more precision than Float. | > [!WARNING] > > For monetary values, always use **Numeric / Decimal**. Floats and doubles are approximate and accumulate rounding errors. A `0.1 + 0.2 = 0.30000000000000004` mistake in a financial column is a real problem. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `price`, `quantity`, `score`). | | **Description** | An optional internal note explaining the field's purpose. | | **Number Type** | One of the six types listed above. | | **Default Value** | A value automatically assigned if no number is provided (for example `0` for a counter). | | **Mandatory** | If on, the record cannot be saved without a number. | | **Unique** | If on, no two rows can share the same number. Useful for serial numbers or rankings. | ![Number field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-number.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=6f08d418777f36c99caccd5606ffbd81) ## How it appears in the API Integer types are exposed as `Int` in GraphQL. Float, Double Precision, and Numeric are exposed as `Float` (or a custom `Decimal` scalar where exactness matters). See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) for the generated schema. ## Permissions Number fields obey the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Which type should I use for money?** > > **Numeric / Decimal**. It stores exact values with a configurable precision and scale. Floats and doubles are inexact and will produce rounding errors over time. > **Integer vs. Big Integer — does it matter?** > > For most app data, **Integer** is more than enough (about ±2.1 billion). Reach for **Big Integer** only when you genuinely expect very large counts or are storing identifiers that exceed Integer's range. > **What's the difference between Float and Double Precision?** > > Both are approximate. **Float** has about 6 digits of precision; **Double Precision** has about 15. Use Double when you need more precision; Float when storage size matters and 6 digits is enough. > **Can I switch a number type later?** > > Yes. Existing values are auto-converted where the new type can hold them. Narrowing the type — for example, from Integer to Small Integer — will fail if any value is out of range. # Relationship field Source: https://archie.com/docs/backend/data-model/fields/relationship Relationship fields connect two tables with a foreign key. Archie generates the inverse query in GraphQL automatically. A Relationship field links a record in one table to a record (or records) in another. Unlike a UUID or a Text field, it doesn't store a free-form value — it stores a reference to a row in another table and enforces that the reference is valid. When you add a relationship, Archie creates the corresponding inverse field on the other side automatically, so the connection works in both directions. The auto-generated GraphQL API exposes the relationship as a nested field, letting you fetch a record and its related records in a single query. ## Cardinality | Cardinality | What it means | Example | | - | - | - | | **One-to-One** | Record A links to exactly one Record B, and vice versa. | `User` ↔ `Profile` — every user has one profile. | | **One-to-Many** | Record A is linked to many Record Bs, but each Record B belongs to one Record A. | `Author` ↔ `Books` — an author writes many books, each book has one author. | For more on choosing cardinality and how Archie models many-to-many connections, see [Relationships](/docs/features/backend/data-model/relationships). ## Creating a relationship **1. Add a field** On the **Schema** tab, click **+ Add Field** and pick **Relationship** from the type dropdown. **2. Open the configuration** Click the field to open its settings in the right-hand panel. **3. Pick the related table** Choose the target table from the **Related Table** dropdown. **4. Choose the cardinality** Pick **One-to-One** or **One-to-Many** based on how the two tables relate. **5. Mark mandatory if required** Turn on **Mandatory** if every record must have a related record. Leave it off to allow the link to be empty. ![Relationship field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-relationship.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=84229b3448eb9a90f0e3aeb4c43111ce) > [!NOTE] > > When you create a relationship, Archie automatically adds the inverse field on the related table — for example, adding `author` on `books` also exposes `books` as a nested field on `author`. You don't need to set it up on both sides. ## How it appears in the API The relationship is exposed in GraphQL as a nested field. Given an `Author` with a one-to-many `books` relationship, you can fetch both in one query: ```graphql theme={null} query { author(id: "...") { id name books { id title } } } ``` The reverse direction works too — given a book, you can resolve its author: ```graphql theme={null} query { book(id: "...") { id title author { id name } } } ``` See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated queries and mutations for your specific tables. ## Permissions A relationship field is governed by the read and write permissions on **both** tables. A user has to be able to read a related table for it to appear in the nested query. Configure this in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **How do I model many-to-many?** > > Create a join table with one-to-many relationships pointing to each side. For example, a `students_courses` join table linking `students` and `courses` gives you the many-to-many. See [Relationships](/docs/features/backend/data-model/relationships) for the full pattern. > **What happens if I delete a record that other records reference?** > > The default behavior is to prevent the deletion when there are referencing rows. See [Relationships](/docs/features/backend/data-model/relationships) for cascade and nullify options. > **Do I need to add a UUID field for the foreign key separately?** > > No. The relationship field is itself the foreign key — it stores the reference to the related table's primary key. > **Can I rename a relationship after creating it?** > > Yes. Rename the field on the **Schema** tab. The GraphQL nested field name updates immediately; any client code that uses the old name will need to be updated. # Text field Source: https://archie.com/docs/backend/data-model/fields/text Text fields store strings of any length. The most versatile field type, suitable for names, titles, descriptions, and free-form content. A Text field stores alphanumeric string data. It's the most versatile field type and handles everything from short identifiers (a username) to long-form content (a blog post body). ## Text vs. Varchar By default, a Text field is stored as PostgreSQL's `TEXT` type — a variable-length string with no specific limit (up to 1 GB per value). Toggling **Is Varchar** stores it as `VARCHAR` instead, which lets you specify a maximum character length. | Mode | Underlying type | When to use it | | - | - | - | | **Default (Is Varchar off)** | `TEXT` | Most fields. No length limit, no measurable performance difference for typical content. | | **Is Varchar on** | `VARCHAR(n)` | When you want to enforce a maximum length at the database level — for example, a 2-letter country code or a 280-character message. | ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `first_name`, `email`, `title`). | | **Description** | An optional internal note explaining the field's purpose. | | **Is Varchar** | Off (default) stores the value as `TEXT`. On stores it as `VARCHAR` with an optional length limit. | | **Default Value** | A value automatically assigned if no string is provided. | | **Mandatory** | If on, enforces `NOT NULL` — the record cannot be saved with this field empty. | | **Unique** | If on, no two rows can share the same string. Useful for emails, usernames, slugs. | ![Text field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-text.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=60409d8a8848810d3f4180ec4a72a326) ## How it appears in the API The field is generated as a `String` in GraphQL and as a JSON string in the REST API. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) for the generated queries and mutations. ## Permissions Text fields are subject to the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). Use that page to hide sensitive fields (for example, internal notes) from specific user types. ## FAQ > **When should I turn on Is Varchar?** > > Only when you want the database to enforce a maximum character length — for example, a fixed-width code or a tweet-style message cap. For everything else, leave it off. > **Should I use Text or JSONB for a list of values?** > > Use [JSONB](/docs/features/backend/data-model/fields/jsonb). Text stores the list as one opaque string; JSONB lets you query specific elements and is faster to filter on. > **How do I make a Text field case-insensitive unique (for emails)?** > > Enable **Unique**. The auto-generated mutations validate uniqueness; for case-insensitive matching at the application layer, normalize values to lowercase before saving. > **Is there a performance penalty for storing long Text values?** > > Not for typical content. PostgreSQL stores large text values out-of-line automatically. Indexing very long strings is what costs — keep `UNIQUE` and indexed Text fields short. # User-defined field Source: https://archie.com/docs/backend/data-model/fields/user-defined User-defined fields use a reusable Data Type (enum) that's shared across tables. Define the list once, apply it everywhere. A user-defined field uses an existing reusable [Data Type](/docs/features/backend/data-model/data-types) — typically a named enum — as the field's type. Define the list of allowed values once as a Data Type, then apply it to as many fields as you need. Editing the Data Type updates every field that uses it. Common examples: * A `course_status_enum` shared by `courses.status` and `applications.course_status` * A `priority_enum` shared by every table that has a priority column * A `country_code_enum` shared by `users.country` and `addresses.country` ## User-defined vs. inline enum Both constrain a column to a fixed list of values. Pick based on whether the list is shared: | Choose this | When | | - | - | | **User-defined** | Multiple fields or tables need the same set of values. Single source of truth. | | **[Inline enum](/docs/features/backend/data-model/fields/inline-enum)** | The list is unique to one field and doesn't need to be reused. | If you don't have a Data Type yet, create one first — see [Data Types](/docs/features/backend/data-model/data-types). ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `status`, `role`). | | **Description** | An optional internal note explaining what the field captures. | | **Enum Type** | Pick from the list of existing [Data Types](/docs/features/backend/data-model/data-types) in the project. | | **Default Value** | One of the values defined on the chosen Data Type. | | **Mandatory** | If on, the record cannot be saved without one of the allowed values. | | **Unique** | If on, no two rows can share the same value. Rarely useful for enums. | ## How it appears in the API The field is generated as the same enum type wherever the Data Type is referenced. Updating the Data Type — adding, removing, or renaming values — propagates to every field that uses it and to every place the type appears in the GraphQL schema. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated types. ## Permissions User-defined fields are subject to the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **What happens if I edit the Data Type after applying it to fields?** > > Every field that uses the Data Type sees the change immediately. Adding a value is safe; removing one will fail validation on rows that still hold the removed value, so plan removals carefully. > **Can I switch a field from inline enum to user-defined later?** > > Yes — change the field type and pick the matching Data Type. Existing values that match the Data Type's allowed list carry over; values that don't will need to be migrated. > **How do I create a new Data Type from inside the field configuration?** > > In the Data Model sidebar, click **+** next to **Data Types**. Once created, it becomes available in the Enum Type picker on any user-defined field. > **Can I use a Data Type that isn't an enum?** > > Today, Data Types are enums — that's the built-in shape. The user-defined field is the way to apply that shape to a column. # UUID field Source: https://archie.com/docs/backend/data-model/fields/uuid UUID fields store 128-bit unique identifiers. Globally unique, hard to guess, and safe to expose in URLs. A UUID field stores a 128-bit Universally Unique Identifier — a standardized string in the form `123e4567-e89b-12d3-a456-426614174000`. UUIDs are guaranteed to be unique across systems, databases, and servers without any central coordination, which makes them the right choice for identifiers that travel outside your database. ## When to use UUIDs * **Public-facing identifiers in URLs** — `/orders/123e4567-...` reveals nothing about the order's age or volume the way `/orders/47` would. * **Distributed identifiers** — IDs generated on the client or in another system that need to be unique when merged. * **Tokens** — invite tokens, share links, password reset tokens. * **External references** — IDs that must be unique across multiple workspaces or environments. If you just need an integer primary key for a single table, the auto-generated `id` column is enough. Reach for UUID when the ID needs to be globally unique or hard to guess. ## Configuration options | Option | Description | | - | - | | **Name** | The system identifier used in the GraphQL and REST APIs (for example `transaction_id`, `invite_token`). | | **Description** | An optional internal note explaining the field's purpose. | | **Default Value** | An optional specific UUID string. Usually left blank — UUIDs are typically generated by the application or API at creation time. | | **Mandatory** | If on, the record cannot be saved without a valid UUID. | ![UUID field type configuration](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/field-type-uuid.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=458c0d6b66d6236b5342cb3132d2f4cd) ## How it appears in the API The field is generated as a `UUID` scalar (or `String` in stacks that don't have a dedicated UUID scalar) in GraphQL, and as a string in JSON. Validation of the UUID format runs on insert and update. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the generated mutations. ## Permissions UUID fields obey the per-role read and write rules configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **UUID vs. relationship — which should I use for a foreign key?** > > Use a [Relationship field](/docs/features/backend/data-model/fields/relationship). It enforces referential integrity, generates the inverse query in GraphQL, and visually links the two tables. UUID is for free-form identifiers that aren't tied to a specific Archie table. > **Should I generate UUIDs on the client or the server?** > > Either works. Server-side generation is the default — leave **Default Value** blank and let the API assign the UUID on insert. Client-side generation is useful when you need to know the ID before the record is saved (for offline-first apps or correlation across systems). > **Is there a performance cost to using UUIDs?** > > UUIDs are 16 bytes vs. 4 bytes for a standard integer ID, so indexes are slightly larger. The trade-off is rarely meaningful at typical scales, and it's almost always worth it for IDs you expose externally. > **Why isn't there a Unique toggle on UUID?** > > UUIDs are designed to be unique by construction — collisions are statistically negligible. If you need a strict database-level constraint, use the auto-generated primary key on the table or add a unique [index](/docs/features/backend/data-model/indexes). # Importing data Source: https://archie.com/docs/backend/data-model/importing-data Bulk-load records into a table from a CSV file with column mapping and schema-aware validation. The CSV importer loads records into a table from a CSV file. The importer maps your CSV columns to the table's fields, runs every schema-level validation on each row, and rejects rows that would violate a constraint. ## Validations the importer runs Every constraint defined on the table applies during import: * **Type checks** — strings can't go into number fields, invalid dates are rejected. * **Mandatory fields** — every required field must be present in the CSV (or mapped to a column that has a value). * **Unique constraints** — rows with duplicate values for unique fields are rejected. * **Enum values** — values for [inline enum](/docs/features/backend/data-model/fields/inline-enum) and [user-defined](/docs/features/backend/data-model/fields/user-defined) fields must match the allowed list. * **Foreign keys** — relationship fields must reference existing rows in the related table. Rows that fail validation are reported back to you with the offending field and reason; the rest of the import proceeds. ## Step-by-step **1. Open the Data tab** Open the table in the Data Model and switch to the **Data** tab. **2. Click Import** The button sits in the toolbar. The **Import Data** dialog opens. **3. Upload your CSV file** Drag and drop a CSV file or click **Choose File**. Need a starting point? Click **Download Table Template** for a CSV pre-headered with the table's field names. ![Upload a CSV file to the Import Data dialog](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-import-upload-file.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=1d7f740fa18efba710df197ec3572ff2) **4. Review the preview** The dialog shows a **Data Import Preview** — up to 5 rows, matched to fields by column header. System columns (`id`, timestamps, and so on) are hidden here since the importer generates them automatically. ![Data Import Preview showing the CSV rows matched to table fields](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-viewer-import-map-columns.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=874e2ac819b4b60eba3dec089fff965a) **5. Click Import** The importer inserts the rows and runs the same validations as adding a row by hand. Rows that fail are reported with the offending field and reason; valid rows are still inserted. ## Preparing the CSV A few small things make imports go smoothly: * **Match column headers to field names.** The auto-mapper relies on header names; matching field names exactly removes a manual step. * **Use ISO 8601 for dates.** `2025-03-14` for dates, `2025-03-14T10:30:00Z` for timestamps. * **Use the exact enum values.** Enum and user-defined fields are case-sensitive — match the casing defined on the [Data Type](/docs/features/backend/data-model/data-types). * **Don't include id, created\_at, or audit columns.** Archie generates these automatically. If they're in the CSV, leave them unmapped. * **Reference rows by their primary key for relationships.** If a CSV column maps to a relationship field, populate it with the related row's `id` (typically a UUID). ## Permissions Importing data uses the same write permissions as adding rows through the [Data Viewer](/docs/features/backend/data-model/data-viewer). A user who can't write to a field through the API can't import into it either. Configure access in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **What happens if a row in my CSV fails validation?** > > The failing row is skipped and reported back with the field and reason. The remaining valid rows are still imported. Fix the offending rows and re-run the import for them only. > **Can I import into a table that has relationships?** > > Yes. Map the relationship column in your CSV to the primary key (usually the UUID `id`) of the related row. The importer enforces referential integrity, so the related row must already exist. > **Should I include the id column in my CSV?** > > Generally no — Archie generates IDs automatically. Include the id column only when you're explicitly upserting rows with known IDs (for example, restoring an export). > **What's the maximum file size?** > > The importer batches rows so size isn't a hard ceiling, but very large files can take a while. For multi-million-row imports, consider splitting the file or scripting the load through a custom function. > **Will the import overwrite existing rows?** > > No. The importer inserts new rows. To update existing rows in bulk, use the SQL Playground or a custom function. # Indexes Source: https://archie.com/docs/backend/data-model/indexes Indexes speed up lookups on frequently queried fields and can enforce uniqueness across one or more columns. An index is a database structure that speeds up lookups on a column (or combination of columns). Without an index, the database has to scan every row of a table to find matches; with one, it can jump directly to the relevant rows. Indexes also have a second job: **unique indexes** prevent duplicate values from being inserted, which is the standard way to enforce uniqueness across multiple columns at once. ## When to add an index Add an index when: * A column is frequently used in lookups — for example, an `email` field on `users` or a `slug` on `posts`. * A column is used to filter or sort large result sets. * You need to enforce uniqueness across a combination of columns (use a unique index). Don't add indexes preemptively to every column. Indexes take up storage and slow down inserts and updates. Add them when a query is measurably slow, or when uniqueness needs to be enforced. ## Unique vs. non-unique | Type | What it does | | - | - | | **Non-unique index** | Speeds up lookups on the indexed columns. Multiple rows can share the same value. | | **Unique index** | Speeds up lookups **and** rejects inserts or updates that would create a duplicate combination of indexed values. | A unique index across `(workspace_id, slug)` is the right way to say "every workspace must have unique slugs, but slugs can repeat across workspaces." ## Creating an index **1. Open the table** Click the table you want to index in the Data Model sidebar. **2. Scroll to the Indexes section** Below the field list, the **Indexes** section lists every index already configured on the table. **3. Click + Add Index** The index configuration dialog opens. **4. Pick the fields** Select one or more fields. The order matters for multi-column indexes — pick the order that matches how you'll query. **5. Choose unique or non-unique** Toggle the **Unique** option if you want the index to enforce uniqueness across the chosen fields. **6. Name the index** Index names cannot contain spaces. Use snake\_case (for example, `users_email_unique`). **7. Save** Click **Add**. The index is created immediately. For large tables, the operation runs in the background. ![Table indexes panel](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-table-indexes.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=3b110760efd59bb740b84abc0bb6441f) ![Index configuration dialog](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-index-configuration.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=b3855c0a0f95108cab51f9dae3123e59) ## Single-column vs. multi-column indexes A single-column index speeds up lookups on that column alone: * `users.email` — speeds up `WHERE email = '...'` A multi-column index speeds up lookups on the **leading** columns. The order matters: * An index on `(workspace_id, created_at)` speeds up queries that filter by `workspace_id`, by `workspace_id` and `created_at`, but not queries that filter only by `created_at`. When in doubt, start with a single-column index. Add a multi-column index only when your query plan shows it would help. ## Unique constraints on a single field vs. a unique index A field with **Unique** turned on is implemented under the hood as a single-column unique index. The two are equivalent for a single column — use the field-level toggle for clarity. Use the **Indexes** panel when: * Uniqueness needs to span multiple columns. * You want to add a non-unique index for performance. * You're naming the index explicitly to match a convention. ## How it appears in the API Indexes don't change the shape of the GraphQL or REST APIs — they affect query performance only. Unique indexes do surface as validation: inserts and updates that would violate uniqueness are rejected with a clear error. ## Permissions Indexes are infrastructure-level — they don't have their own permissions. Read and write permissions on the underlying table are governed by [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Should I index every column I'll filter on?** > > No. Indexes have a write cost — every insert and update has to maintain them. Add an index when a query is measurably slow on real data. Most tables only need a few indexes. > **Index column order — does it matter?** > > Yes for multi-column indexes. The index helps queries that filter on a leading prefix of the columns. `(a, b)` helps `WHERE a = ?` and `WHERE a = ? AND b = ?`, but not `WHERE b = ?` alone. > **What's the difference between a unique field and a unique index?** > > A field with **Unique** on is a unique index on that single column. The Indexes panel is the right tool when uniqueness spans multiple columns. > **Can I drop an index later?** > > Yes. The Indexes panel lists each index with a delete control. Dropping a non-unique index is safe; dropping a unique index removes the uniqueness guarantee. > **Will adding an index lock the table?** > > Indexes on small tables are nearly instant. On large tables, the database creates the index without blocking reads or writes — but creation itself takes longer. # Relationships Source: https://archie.com/docs/backend/data-model/relationships Relationships connect tables with foreign keys. Choose between one-to-one, one-to-many, and many-to-many. A relationship connects two tables with a foreign key. When you add a [relationship field](/docs/features/backend/data-model/fields/relationship), Archie creates the foreign key in the database, generates the inverse field on the other table automatically, and exposes both sides as nested fields in the GraphQL API. This page covers the cardinalities you can model, how to set up many-to-many, and how the relationships look in the auto-generated APIs. ## Cardinality | Cardinality | What it means | Real-world example | | - | - | - | | **One-to-One** | Record A ↔ exactly one Record B (and vice versa). | A `User` has one `Profile`. | | **One-to-Many** | Record A ↔ many Record Bs, but each B belongs to exactly one A. | An `Author` has many `Books`; each `Book` has one `Author`. | | **Many-to-Many** | Record A ↔ many Record Bs **and** Record B ↔ many Record As. | A `Student` takes many `Courses`; a `Course` has many `Students`. | The Relationship field type supports **One-to-One** and **One-to-Many** directly. Many-to-many is modeled with a join table — see below. ## One-to-many The most common cardinality. The "many" side holds the foreign key. **Example:** an `author` has many `books`. The `books` table holds an `author_id` relationship field pointing to `authors`. When you add the `author` relationship on `books` with cardinality **One-to-Many** (one author, many books), Archie automatically: * Creates the `author_id` foreign key column on `books`. * Generates a `books` nested field on `Author` in the GraphQL schema. * Generates an `author` nested field on `Book` in the GraphQL schema. ```graphql theme={null} query { author(id: "...") { name books { title } } } query { book(id: "...") { title author { name } } } ``` ## One-to-one A `user` has exactly one `profile`, and a `profile` belongs to exactly one `user`. Add a relationship field on the side that owns the foreign key (typically `profile.user_id`), pick **One-to-One** as the cardinality, and Archie enforces the uniqueness so that no two profiles can point to the same user. ```graphql theme={null} query { user(id: "...") { profile { avatar_url } } } ``` ## Many-to-many For many-to-many, create a **join table** that holds two relationship fields — one to each side. **Example:** students enrolling in courses. 1. Create a `students` table. 2. Create a `courses` table. 3. Create a `student_courses` join table with two fields: * `student` — relationship to `students`, cardinality **One-to-Many**. * `course` — relationship to `courses`, cardinality **One-to-Many**. 4. Optionally, add a unique [index](/docs/features/backend/data-model/indexes) across `(student, course)` to prevent duplicate enrollments. The auto-generated GraphQL API now lets you traverse from a student to all their courses (via the join table) and from a course to all its students. ```graphql theme={null} query { student(id: "...") { name student_courses { course { title } } } } ``` The join table itself is a first-class table — you can attach extra fields to it (for example `enrolled_at`, `grade`) to record per-link metadata. ## Mandatory vs. optional relationships Toggle **Mandatory** on the relationship field to require a related record. A non-mandatory relationship can be `null` — for example, a `comment` can optionally belong to a `parent_comment` for threaded replies. ## Cascade behavior on delete By default, the database prevents you from deleting a record that other records reference — this protects against orphaned data. If you have an `author` with `books`, deleting the author fails until the books are reassigned or deleted. Common alternatives: | Behavior | What it does | When to use it | | - | - | - | | **Restrict** (default) | Block the delete while related records exist. | Default. Forces you to handle related data explicitly. | | **Cascade** | Delete related records too. | Strong ownership — deleting a `post` should delete its `comments`. | | **Set null** | Clear the reference; keep the related records. | Loose ownership — deleting a `category` shouldn't delete its `posts`, just unlink them. | ## How it appears in the API Every relationship is exposed in the auto-generated GraphQL API as a nested field on each side. Filtering, sorting, and pagination on related collections is supported. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to inspect the exact shape generated for your tables. The [REST API](/docs/features/backend/rest-api-explorer/overview) exposes related records via standard endpoint patterns and query parameters. ## Permissions Reading or writing a relationship requires permissions on **both** sides. A user can only see related records on a table they're allowed to read. Configure this in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Why is there no Many-to-Many option in the relationship field?** > > Many-to-many is always implemented under the hood as a join table with two one-to-many relationships. Modeling it explicitly gives you a place to attach metadata to each link (for example, when a student enrolled, what grade they got). The Data Model exposes that pattern directly rather than hiding it. > **Do I need to manually add a foreign key column?** > > No. The relationship field is the foreign key. Adding it creates the column, the database constraint, and the inverse query in GraphQL all at once. > **What happens to the inverse if I rename a relationship field?** > > The inverse field updates to match the new name. Any client code that referenced the old nested field name needs to be updated. > **Can I have multiple relationships between the same two tables?** > > Yes. For example, a `messages` table could have both a `sender` and a `recipient` relationship pointing to `users`. Pick distinct field names so the two relationships are clearly distinguishable in the GraphQL schema. > **What does cascade delete look like in practice?** > > When you delete a `post` with `comments` configured to cascade, the database deletes the post and every comment that points to it as part of the same transaction. Choose cascade only when child records have no meaning without the parent. # SQL Playground Source: https://archie.com/docs/backend/data-model/sql-playground Run SQL against your app's database. The SQL Playground is a direct query interface to your project's database. It runs in the same connection your app uses, so what you can do depends on the role you are signed in as. To access this tool, open a table on the **Data** page (**Rows** or **Schema** tab) and click **SQL Playground** under **DB-Level Tools**, next to [Diagram](/docs/backend/data-model/erd). ![SQL Playground overview](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/sql-playground-overview.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=971093ee23b2dce1c1714546c6f94ce8) ## Query Editor The main "Playground" area allows you to write standard SQL statements. You can perform simple `SELECT` queries or complex `JOIN` operations (e.g., linking `students` with `cities`). ### Execution Methods There are multiple ways to execute your code, giving you flexibility depending on your workflow: 1. **Line Execution:** Click the **Blue Play Triangle (▶)** located in the gutter next to a specific line number to run just that statement. 2. **Context Menu (Right-Click):** Right-click anywhere in the editor to open a menu where you can select **Execute SQL Query**. 3. **Keyboard Shortcut:** Press **`Ctrl + Enter`** (**`⌘ + Enter`** on Mac) to execute the query immediately without using the mouse. 4. **Execute Button:** Click **Execute** in the top-right corner of the editor. ![SQL Playground execution methods: line execution, right-click, Ctrl+Enter](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/sql-playground-execution-methods.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=7fbacf09710894abda55591eced95911) ### Toolbar Actions The toolbar at the top of the editor provides tools to manage your code: ![SQL Playground toolbar with Format, History, Download, Copy, Clear actions](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/sql-playground-toolbar-actions.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=8a2ad22dee8eabed5fae822a092614b1) * **Format SQL:** Use the **Magic Wand** icon to automatically format and indent your SQL code for better readability. * **Query History:** Access a log of your previously executed queries. * **Download SQL:** Download the current queries to your local machine. * **Copy SQL:** Use the **Copy** icon to copy the editor's content to your clipboard. * **Clear Editor:** Use the **Broom** icon to clear or delete all queries from the editor. ## Results Panel The **Results** section at the bottom displays the output of your execution. * **Data Grid:** The data is presented in a table format with columns corresponding to your query. * **Row Count and Timing:** The number of records returned and the query runtime are displayed in the top right (e.g., "5 rows found | 313ms"). * **Column Visibility:** You can customize the view by clicking the column selector icon in the results header to toggle specific columns on or off. ![SQL Playground Results panel with data grid and column visibility](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/sql-playground-results-panel.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=3fba48e5095d5ef23dc879108b13b5c8) > [!NOTE] > > The playground is a tool, not an artifact — it reads the same schema the API is generated from, so a query that works here describes an operation Archie can generate for you. ## DDL statements Schema changes (CREATE, ALTER, DROP) are not executed in the playground — they belong in the [Data](/docs/features/backend/data-model/overview) workflow which produces tracked migrations. ## Per-environment data The playground binds to the active environment. Switch environments to query staging or production data. Production access is typically restricted to admin roles. ## Best practices * **Use transactions** for multi-statement writes — `BEGIN; ...; COMMIT;` * **Limit destructive queries** — always test with a `WHERE` clause first * **Use `EXPLAIN`** to inspect query plans for slow reads * **Avoid manually inserting** rows that should go through the application layer (validation, hooks) ## FAQ > **Are my queries logged?** > > Yes. Audit logs include the actor, query, and runtime. > **Can I run reports on a schedule?** > > Use a custom function with a scheduled trigger and put the SQL there. # Tables Source: https://archie.com/docs/backend/data-model/tables Create, rename, and delete tables in your Data Model. Every table generates a GraphQL and REST API automatically. A table is a named collection of records with a fixed set of fields. When you add a table, Archie provisions it in your workspace's PostgreSQL database and generates the corresponding GraphQL and REST endpoints automatically. ## Creating a table You can create a table manually or describe what you need in plain language and let the AI generate it. **1. Open the Data Model** Click **Data** in the Backend header. The list of existing tables appears in the sidebar. **2. Click + Add Table** The **Add Table** modal opens. **3. Name the table** Enter a unique, lowercase identifier (for example `products`, `orders`, `members`). This is the name that will appear in the GraphQL and REST APIs. **4. Add a description (optional)** Briefly describe what the table stores. This helps your team and any AI agents working on your project understand its purpose. **5. Build with AI (optional)** Instead of adding fields by hand later, describe the table in plain language — for example, *"Create a loans table with customer, book, loan date, due date, and status"*. You can also pick one of the suggestion chips to start from a template. **6. Create the table** Click **Create Table**. The table appears in the sidebar, ready for fields. ![Add Table modal dialog](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-add-table-modal.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=823e00abb904fd87adf93f101a234031) ## The Schema and Data tabs Every table has two tabs: * **Schema** — design the table: add fields, set defaults, configure validation, define relationships, and create indexes. ![Schema tab showing a table's field list](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-table-schema.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=20591c2fd012a538be149043bcb892a9) * **Data** — view, edit, import, and export the records inside the table. See [Data Viewer](/docs/features/backend/data-model/data-viewer). ![Data tab showing a table's records](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-model-overview.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=75a1813f318e55e471b13267a3faab5e) ## Renaming and describing a table Click the **•••** menu next to the table name and select **Table settings** to edit the table's name and description. * Renaming the table propagates the change to the auto-generated GraphQL and REST APIs immediately. * The description helps your team and any AI agents working on your project understand what the table stores. ## Adding and configuring fields Click **+ Add Field** below the field list to add a column. Each field has a type — boolean, date, number, text, JSONB, UUID, relationship, inline enum, or a reusable Data Type. See [Fields](/docs/features/backend/data-model/fields) for the full list and configuration options. ![Field configuration panel with data type, default value, and constraints](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-field-configuration.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=0c559f3acf98fadc602ad94ab6a5b637) ## Deleting a table You can delete a table from two places. **From the sidebar:** hover the table in the **Tables** list, click its **•••** menu, and select **Delete**. **From the table header:** open the table, click the **•••** menu next to the table name, and select **Delete this Table**. ![Table options menu with Table settings and Delete this Table](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-delete-table.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=6c3b8d86a48bddb5c0b14f79c5c46d27) > [!WARNING] > > Deleting a table permanently removes its records and drops it from the GraphQL and REST APIs. There is no undo. ## System tables Archie ships with system tables that the platform itself depends on — most notably **`users`**, **`roles`**, and **`user_roles`**. These tables appear in the Data Model so you can extend them with custom fields, but their core fields are read-only and the table itself cannot be renamed or deleted. The trash can icon is hidden on system tables. You can still: * Add new fields alongside the system fields. * Read and write rows through the Data Viewer and the auto-generated APIs. * Govern access to them in [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## Safety checks when updating a table Archie protects against changes that would silently break existing data: * Switching a field from optional to mandatory prompts you for a default value to backfill existing rows. * Changing the type of a Date, Number, or Text field automatically converts existing values where possible. * Switching a field to **Unique** validates that the current values are already unique before applying the change. ## Permissions Who can read or write each table is configured in **App Services → Role-Based Access**, not in the Data Model. See [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Why can't I delete the users table?** > > `users`, `roles`, and `user_roles` are system tables. The platform's authentication and access control depend on them, so the delete and rename actions are disabled. You can still add custom fields to them. > **What happens to the GraphQL and REST APIs when I rename a table?** > > Both APIs regenerate immediately. Any client code or integrations that referenced the old name will need to be updated to the new name. > **Can I undo a table deletion?** > > No. Deleting a table drops the underlying records and the generated API endpoints. Export the data first if you might need it again. > **How do I let some user types read a table but not write to it?** > > Configure that in [Role-Based Access](/docs/features/backend/app-services/role-based-access). The Data Model defines the shape of the table; permissions are enforced separately on every auto-generated query and mutation. > **Can I generate a table from a description instead of adding fields by hand?** > > Yes. The **Build with AI** section of the Add Table modal accepts a plain-language description and generates a starter table with suggested fields, which you can then refine. # Views Source: https://archie.com/docs/backend/data-model/views A View is a virtual table defined by a saved SQL SELECT query. Views appear in the GraphQL API like a standard table. A View is a virtual table whose contents are defined by a saved SQL `SELECT` query. Views don't store data themselves — every time you query a view, the underlying SQL runs and returns a fresh result set built from the real tables. In the auto-generated GraphQL API, a view appears as a regular table. Clients query it the same way they query any other table. ## When to use a View | Use case | Why a View fits | | - | - | | **Pre-joined data** | Combine `students`, `courses`, and `grades` into one query so the frontend doesn't have to. | | **Computed columns** | Concatenate `first_name` and `last_name` into a `full_name` field without storing it. | | **Hide sensitive columns** | Expose only public fields to a specific API consumer; keep internal IDs and PII in the underlying tables. | | **Live aggregates** | Use `COUNT`, `SUM`, or `AVG` to surface statistics like "monthly sales total" automatically. | | **Schema stability** | Refactor underlying tables and update the view's SQL to keep the API output stable for clients. | If you need to run ad-hoc queries that don't need to be exposed in the API, use the [SQL Playground](/docs/features/backend/data-model/sql-playground) instead. ## Creating a View **1. Open the Data Model sidebar** Find the **Views** section of the sidebar. **2. Click + next to Views** An inline SQL playground opens alongside a **View details** panel. **3. Write the query** Enter a `SELECT` statement that returns the columns and rows you want to expose. ```sql theme={null} SELECT first_name, last_name, email FROM students WHERE is_active = true; ``` **4. Run the query** Click the **Play** button (▶) to preview the result set immediately. This catches syntax errors and verifies the shape of the output before saving. **5. Name and describe the view** Enter a unique system identifier (for example `active_students`). This name is what the GraphQL API exposes. Add a description so your team knows what the view returns. **6. Save** The view appears in the **Views** section of the sidebar and becomes immediately queryable. ![View creation interface](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-view-creation.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=a4ffaa4a197d756b42d62184d6aa268b) ![View configuration panel](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-view-configuration.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=6766d4f2c6d70ba9add2f3adfe2ccb4f) ![View result preview](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-view-result.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=e619d038311bd03e8a73056691f2d7de) ## Editing a View To modify an existing view's query or description: 1. Open the **Views** section in the **Data Model** sidebar and select the view. 2. Click the **•••** menu next to the view name and select **Edit**. 3. Update the SQL or metadata, run the query to verify, and save. ![View schema definition](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/data-model/data-builder-view-schema.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=70087f09b942439c317c7bde3585221d) The same **•••** menu has **Delete this View** to remove it permanently. ## How it appears in the API Each view is exposed in GraphQL as a queryable type with the columns from the `SELECT` clause. Because views are read-only by nature, the API surfaces queries — but not mutations — for them. See the [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview) to confirm the exact shape generated for your view. ## Permissions Views obey [Role-Based Access](/docs/features/backend/app-services/role-based-access). You can grant read access to a view independently of the underlying tables — that's one reason to use a view to expose a sanitized projection of a sensitive table. ## FAQ > **Are views read-only?** > > Yes. Views compute their result set from the underlying tables on every query, so they don't accept inserts or updates through the auto-generated API. Mutate the underlying tables directly. > **How do I get a calculated column without storing the value?** > > Define a view whose `SELECT` includes the calculation — for example, `SELECT id, first_name || ' ' || last_name AS full_name FROM users`. Clients query the view and get the computed value without it ever being stored. > **Can I write a view that joins tables from multiple schemas?** > > Stick to tables in your project's schema. The auto-generated API works against your project's namespace, and cross-schema joins fall outside the supported surface area. > **What happens to a view if I rename a column in an underlying table?** > > The view's query may break. Edit the view to reference the new column name. Test by clicking the play button before saving — the editor will surface SQL errors. > **Should I use a view or a custom function for complex reads?** > > Views are best for SQL-only logic that maps cleanly to a `SELECT`. For logic that needs procedural code, third-party API calls, or complex authorization, write a custom function instead. # Overview Source: https://archie.com/docs/backend/environments Environments are isolated copies of your backend that let you develop, test, and ship changes safely without affecting production. Environments are isolated copies of your backend that let you develop, test, and ship changes safely without affecting production. Every project starts with a single **master** environment. From there, you branch new environments — `staging`, `feature-auth`, `qa-sprint-12` — that inherit the schema (and optionally the data and configuration) of their parent and run in complete isolation. Changes in one environment don't propagate to another until you explicitly [merge](/docs/features/backend/environments/merge) them. ## How you reach Environments Environments don't have a dedicated entry in the Backend sidebar. You access them through the **environment switcher** at the top of the project chrome — the dropdown that shows the active environment's name. From there you can: * Switch to a different environment (the entire Backend Console reloads in that environment's context). * Open the create-environment modal. * Open the [environment tree](/docs/features/backend/environments/tree), [diff](/docs/features/backend/environments/diff), [merge](/docs/features/backend/environments/merge), [history](/docs/features/backend/environments/history), and [backups](/docs/features/backend/environments/backups) views. ## The branching model Environments form a parent-child tree rooted at `master`. Each branch carries metadata — name, status, branch mode, parent, branched-at timestamp, and the list of children — that drives the rest of the surface (diff target selection, merge direction, restore scope). | Property | What it tracks | | - | - | | **Name** | Identifier shown in the switcher (`master`, `staging`, `feature-auth`). | | **Parent** | The environment this one was branched from. Master has no parent. | | **Branch mode** | `full` (schema + data) or `system` (schema only). | | **Status** | One of `active`, `branching`, `merging`, `error`, `archived`. | | **Branched at** | When this environment was created. | | **Children** | Environments branched from this one. | ## Branch modes When you create a new environment, you pick how the source database is cloned: | Mode | What's copied | When to use it | | - | - | - | | **Full** | Schema **and** all row data. | Realistic data needed — QA, staging, performance testing. | | **System** | Schema only. No row data. | Clean dev environment, or you'll seed the data yourself. | For full coverage of branch modes and the configuration-copy flags, see [Creating environments](/docs/features/backend/environments/creating). ## Status lifecycle Environments transition through five states. The platform uses these to prevent conflicting operations — you can't delete an environment that's `merging`, can't merge into one that's `branching`. | Status | Meaning | | - | - | | `active` | Fully operational and available. | | `branching` | Being created — database cloning in progress. | | `merging` | A merge is being applied to this environment. | | `error` | An operation failed; the environment may be inconsistent. | | `archived` | Soft-deleted; data preserved but the environment is hidden from the switcher. | ## Isolation guarantees Environments are designed for hard isolation between development stages: 1. **Storage isolation** — each environment has its own dedicated PostgreSQL database. A bug or destructive operation in `dev` cannot reach `production`. 2. **Configuration scoping** — every backend setting is per-environment. Auth providers, API keys, RBAC roles, custom APIs, file storage, environment variables, and integration credentials are all independent. 3. **Context propagation** — when you switch environments, the entire Backend Console reloads in that environment's context. The same applies to API requests via the `environment` header. There's no path by which `dev` traffic accidentally touches `production` data. For the full list of what's scoped per-environment, see [Configuration](/docs/features/backend/environments/configuration). ## Where to go next > **[Creating](/docs/features/backend/environments/creating)** > > Branch a new environment with the right mode and copy flags. > **[Tree](/docs/features/backend/environments/tree)** > > Visualize the parent-child hierarchy and plan merge strategies. > **[Diff](/docs/features/backend/environments/diff)** > > Compare two environments' schemas with forward-only semantics. > **[Merge](/docs/features/backend/environments/merge)** > > Cherry-pick changes from one environment to another, transactionally. > **[History](/docs/features/backend/environments/history)** > > Audit trail of every merge and migration. > **[Backups](/docs/features/backend/environments/backups)** > > Automatic pre-merge backups, manual snapshots, and restore. > **[Deleting](/docs/features/backend/environments/deleting)** > > Permanently remove an environment. > **[Configuration](/docs/features/backend/environments/configuration)** > > What's scoped per-environment, with cross-links to each setting. ## FAQ > **Can I have more than three environments?** > > Yes — branch as many as you want. Each one is independent, so it's reasonable to have a long-lived `master` and `staging` plus a handful of short-lived `feature-*` environments for in-progress work. > **Are changes in `staging` automatically reflected in `master`?** > > No. Environments are isolated. Schema changes propagate only when you explicitly [merge](/docs/features/backend/environments/merge) — the cherry-pick UI lets you pick which changes to apply. > **Why is the `master` environment special?** > > It's the root of the tree and can't be deleted or renamed. Other environments can be deleted; `master` always exists so the project always has a baseline. > **What happens to the active environment when I switch?** > > Nothing. Switching only changes which environment your console session is pointed at. The previous environment continues running, holds open connections, and serves traffic from any clients still using it. > **Can I see Environments in the Backend sidebar?** > > Not as a dedicated menu entry — environments are surfaced through the switcher at the top of the project chrome. The docs cover the full surface; the product UI threads the actions through the switcher dropdown. # Backups Source: https://archie.com/docs/backend/environments/backups Automatic pre-merge snapshots, manual backups on demand, and restore — including to environments that have been deleted. Every environment has its own backups. They come in two flavors — **automatic pre-merge** snapshots taken before each [merge](/docs/features/backend/environments/merge), and **manual** snapshots you take on demand. Either kind can be restored into any target, including a new environment that takes over the original's name if it was deleted. ## Backup types | Type | When it's taken | Why it exists | | - | - | - | | **Automatic pre-merge** | Right before every merge runs, on the target environment. | A safety net. If a merge succeeds but turns out to be a mistake, restore this snapshot to revert. | | **Manual** | Whenever you click **Create backup** in the Backups view (or call the API). | Snapshot before a risky operation, or to capture a known-good state. | Both kinds are full backups — schema **and** data. There's no incremental or schema-only mode. ## What's in a backup | Component | Included | | - | - | | Database schema | Yes — every table, column, index, relationship, enum, view. | | Row data | Yes — every row in every table. | | RBAC roles and permissions | Yes — they live in the database. | | Auth provider configuration | Provider records are included. **Secrets aren't restored** — they're re-encrypted with each environment's key. | | File storage provider configuration | Yes. | | Custom API gateway routes | Yes. | | Environment variables | Yes. | | Files in the storage backend | No — files live in the configured object store and are referenced by ID. The backup captures references, not blobs. | ## Taking a manual backup **1. Switch to the environment you want to back up** Use the environment switcher at the top of the project chrome. **2. Open Backups** From the switcher dropdown, open the **Backups** view. **3. Click Create backup** The backup runs as a background job. The status moves through `pending` → `running` → `completed`. **4. Optionally name the backup** Add a label like `pre-stripe-migration` or `release-v2` to make the snapshot easy to find later. The backup appears in the list with its type (`Manual`), the user who created it, the timestamp, and the size. ## Restoring a backup A restore reverts the **entire** environment — schema and data — to the snapshot point. It's not a per-change undo. **1. Find the backup** In the Backups view, locate the entry to restore. Pre-merge backups are linked from the corresponding [history](/docs/features/backend/environments/history) entry. **2. Pick the target environment** By default, restoring puts the snapshot back in the same environment it was taken from. You can also restore into a different environment — including a new environment that takes over a deleted one's name (see below). **3. Confirm** The platform locks the target, drops its current state, and replays the backup. The status transitions through `restoring` and back to `active` when complete. > [!WARNING] > > A restore replaces the target environment's contents. Anything written to the target after the backup was taken is gone. If you might need that recent data, take a fresh manual backup of the current state first. ## Restoring to a deleted environment A backup outlives its environment — even if you delete `feature-x`, its backups stick around through the [retention](#retention) window. To restore them, the platform recreates the environment as part of the restore: **1. Find the backup** Backups for deleted environments are still listed in the project's Backups view, with their original environment name shown. **2. Restore** Click Restore. If no environment with that name currently exists, the platform recreates the environment as part of the operation. The new environment inherits the backup's schema, data, and configuration. **3. Reconfigure secrets** Auth provider secrets and OAuth credentials are not in the backup — re-enter them in the recreated environment before exposing it. This is the recovery path when a deleted environment turns out to have held a critical migration or test fixture you need back. ## Pre-merge backups in the merge flow Every merge automatically produces one pre-merge backup on the target. The backup ID is recorded in the [history](/docs/features/backend/environments/history) entry alongside the merge — you don't have to hunt for it. Common pattern: after a merge into `master`, if a regression surfaces, open History → find the merge → follow the `backupId` to the Backups view → restore. ## Retention Backups are retained for a configurable window per project. Once a backup ages out, it's pruned and can no longer be restored. Manual backups can be marked **retain forever** to opt them out of pruning — useful for capturing release-train snapshots that you want to keep indefinitely. If you need a long rollback window for a specific merge, promote its automatic pre-merge backup to a retained manual backup before the retention window expires. ## Permissions Creating, listing, and restoring backups is governed by [Role-Based Access](/docs/features/backend/app-services/role-based-access). Restore is destructive — typically restricted to admin or operator roles. Read access can be granted more widely if you want broader visibility into snapshots without the ability to restore. ## FAQ > **Are files in S3 / GCS included in the backup?** > > No. The backup captures the file storage **provider configuration** and the **references** records hold to files (file IDs, paths). The actual blobs stay in the configured object store. Treat your storage provider's own snapshot/versioning as the source of truth for file content. > **Can I take a schema-only backup?** > > Backups are full — schema and data. For schema-only operations, use the [diff](/docs/features/backend/environments/diff) and [merge](/docs/features/backend/environments/merge) tools, or branch a new environment in **System** mode. > **What happens during a restore — is the environment offline?** > > The target is locked while the restore runs. Reads continue from the existing data until the restore completes; mutations are rejected with the environment-locked error. Once complete, the target reflects the snapshot's state and reopens for writes. > **Can I restore a backup from one project to a different project?** > > No. Backups are project-scoped — they reference project-specific identifiers (auth keys, project ID, RBAC role IDs) that don't translate across projects. To clone a project, branch within it. > **What if the restore fails?** > > The target stays in `error` state with a failure reason recorded. The platform doesn't automatically partial-restore — either the backup applies cleanly or the environment is left as it was, in `error`. Recovery is to run the restore again, or restore a different snapshot. # Configuration Source: https://archie.com/docs/backend/environments/configuration What's scoped per-environment — the data model, app services, settings, and integrations that each environment maintains independently. Almost everything in your backend is per-environment. When you switch environments using the switcher at the top of the project chrome, the entire Backend Console reloads in that environment's context — different data, different roles, different secrets, different integration credentials. This page is the index of what's scoped per-environment with cross-links to each setting. ## The full per-environment scope Every block below is independent across environments. Editing it in `staging` does not affect `master`. | Resource | Where to configure | Copied at branch time? | | - | - | - | | **Data Model schema** | [Backend → Data Model](/docs/features/backend/data-model/overview) | Always (Full or System mode) | | **Authentication providers** | [App Services → Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview) | Provider records yes; **secrets never** | | **Role-based access** | [App Services → Role-Based Access](/docs/features/backend/app-services/role-based-access) | Always (lives in the cloned database) | | **File storage providers** | [App Services → File Manager](/docs/features/backend/app-services/file-manager) | Optional, per checkbox | | **Custom APIs (gateway routes)** | [App Services → Custom APIs](/docs/features/backend/app-services/custom-apis) | Optional, per checkbox | | **Network policy (CORS, rate limits)** | [Backend → Settings → Network](/docs/features/backend/settings/network) | Optional, per checkbox | | **API keys** | [Backend → Settings → API Keys](/docs/features/backend/settings/api-keys) | Never (issued per environment) | | **Environment variables** | [Backend → Settings → Environment Variables](/docs/backend/settings/environment-variables) | Optional, per checkbox | | **Integrations** | [Backend → Integrations](/docs/features/backend/integrations/overview) | Provider records yes; **credentials never** | | **Migration history** | [Environments → History](/docs/features/backend/environments/history) | Never (audit trail per-environment) | | **Backups** | [Environments → Backups](/docs/features/backend/environments/backups) | Never (snapshots per-environment) | For the branch-time copy mechanics — which checkboxes copy what — see [Creating environments](/docs/features/backend/environments/creating#what-s-copied). ## Why secrets aren't copied Authentication provider secrets, OAuth credentials, and integration API keys (Stripe, SendGrid, etc.) are deliberately excluded from branch-time copying: * A `dev` secret leak shouldn't compromise `production`. * Different environments often use different vendor accounts (test mode vs. live mode for Stripe; sandbox vs. production for OAuth providers). * Re-entering credentials per environment makes the security boundary explicit and reviewable. When you branch a new environment, the auth provider configuration appears with the same fields but the secret values blank — you re-enter them in the new environment before activating the provider. ## Per-environment context propagation When you switch environments, the platform reloads the entire console in the new context. Behind the scenes: * API requests gain an `environment` header that routes to the right database. * The GraphQL and REST API endpoints are per-environment URLs. * Generated API tokens are scoped to a single environment and rejected by others. * Custom function deployments are per-environment. There's no path by which a request to `dev` accidentally touches `production` data — the routing happens server-side based on the `environment` header. ## Common patterns | Pattern | How to configure it | | - | - | | **Test Stripe keys in dev, live keys in prod** | Configure the [Stripe integration](/docs/features/backend/integrations/stripe) with test keys in `dev`, live keys in `master`. The Stripe SDK in your code is the same; the credentials it uses are per-environment. | | **Looser CORS in dev, stricter in prod** | Set permissive origins in `dev`'s [Network settings](/docs/features/backend/settings/network), specific origins in `master`. Branch-time copying lets you start matching, then diverge. | | **A staging email sender** | Configure [SendGrid](/docs/features/backend/integrations/sendgrid) with a staging-only sender address in `staging` so test emails don't appear to come from your production domain. | | **Feature flag for a beta** | Set a `BETA_FEATURE_ENABLED` [environment variable](/docs/backend/settings/environment-variables) to `true` in `staging` and `false` in `master`. Promote when ready. | | **Restricted access to staging** | Use a dedicated [API key](/docs/features/backend/settings/api-keys) per environment. Revoking the staging key doesn't affect production. | ## What's not per-environment A small number of things live at the project level — they're shared across every environment: * The project itself (name, owner, billing). * Workspace memberships and team-level roles. * Project-level settings on the Frontend (UI, branding) that apply across environments. If you need different values for those across environments, model them as per-environment configuration — for example, store frontend variants behind environment variables rather than at the project level. ## FAQ > **Why do my staging API keys not work in production?** > > API keys are environment-scoped — issued for a single environment and rejected by others. Generate a new key in production and use that. Same applies to integration credentials and OAuth secrets. > **If I edit an RBAC role in staging, does master get the change?** > > No. Roles live in each environment's database, so changes don't propagate. To bring a role change from `staging` to `master`, [merge](/docs/features/backend/environments/merge) — RBAC schema travels with the merge. > **Can I bulk-copy configuration after I've already branched?** > > The copy flags only fire at branch time. After branching, edit each environment individually. For values you want to keep in sync, use a workspace-level secret store and reference it from each environment. > **Where do I see what's configured in another environment without switching?** > > The dashboard scopes to the active environment. To inspect another environment's configuration, switch to it. For programmatic access, the GraphQL API accepts an `environment` header — you can query any environment's state without switching the dashboard. > **What's the cleanest way to manage environment-specific config in source control?** > > Treat environment variables as the source-controlled boundary. Commit a `.env.example` listing every key without values, and use the [Environment Variables](/docs/backend/settings/environment-variables) UI to set per-environment values. Avoids checking secrets into git while keeping the variable surface explicit. # Creating environments Source: https://archie.com/docs/backend/environments/creating Branch a new environment from any active source. Pick a branch mode, choose what configuration to copy, and decide what stays per-environment. You create an environment by branching from any **active** source. The platform clones the source database, optionally copies configuration resources, and gives you an isolated workspace for development, testing, or staging changes. ## Branch modes Pick how the source database is cloned: | Mode | What's copied | Typical use | | - | - | - | | **Full** | Schema **and** all row data. | QA, staging, performance testing — anywhere you need realistic data. | | **System** | Schema only — tables, columns, indexes, relationships, enums, views. No data. | Clean dev environments, feature branches you'll seed yourself. | The mode is set at branch time and recorded on the environment. You can always [merge](/docs/features/backend/environments/merge) schema between environments later regardless of the mode each one was created in. ## Creating from the dashboard **1. Open the environment switcher** Click the dropdown showing the active environment's name at the top of the project chrome. **2. Click + Create Environment** The branching modal opens. **3. Pick the source environment** The **Branch From** dropdown lists every environment in `active` status. Environments in `branching`, `merging`, or `error` aren't available as sources until they settle. **4. Name the new environment** Names accept alphanumeric characters, hyphens, and underscores — `staging`, `feature-auth`, `qa-sprint-12`. They have to be unique within the project. **5. Choose the branch mode** **Full** for schema + data, **System** for schema only. **6. Choose what configuration to copy** Tick the configuration checkboxes you want carried over from the source. See [What's copied](#what’s-copied) below for the full list and the security caveat. **7. Click Create environment** The platform validates the name, clones the database, registers the new environment, and copies any selected configuration. Status flips from `branching` to `active` once everything's in place. ![Create new environment modal with branch mode and copy options](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/github-repo-branch-config.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=3520c04ef0973b7184d57de48d621f84) ## What's copied Each configuration block has its own checkbox. The `ProjectOwner` role is required to enable any of these — they involve carrying sensitive configuration across boundaries. | Resource | Checkbox label | Carries over | | - | - | - | | File storage providers | **File storage providers** | S3, GCS, Azure, Filestack credentials. Each copy gets a new ID. | | Custom APIs | **Gateway routes** | Custom API gateway routes and webhooks. | | Network policy | **Security config** | CORS origins and rate-limit settings. | | Environment variables | **Environment variables** | Key-value pairs for runtime configuration. | ## What's never copied > [!WARNING] > > **Authentication provider secrets and OAuth credentials are never copied** when branching, even if you tick every other checkbox. OAuth client secrets, Cognito credentials, Auth0 tenant keys, and similar credentials must be configured independently in each environment. This is a deliberate isolation boundary so a leaked `dev` secret can't be replayed against `production`. For the full per-environment scoping list, see [Configuration](/docs/features/backend/environments/configuration). For the auth-provider configuration flow, see [Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). ## What happens during branching When you click **Create environment**, the platform runs the following steps. If any step fails, everything rolls back — the new database is dropped, credentials are revoked, and no orphaned resources are left behind. | Step | What it does | | - | - | | 1. Validation | Verifies the name is unique and the source is in `active` status. | | 2. Record creation | Registers a new environment record with the project. | | 3. Database cloning | Clones the source PostgreSQL database — full data in `Full` mode, schema only in `System` mode. | | 4. Database users | Provisions fresh database credentials, separate from the source. | | 5. Configuration registration | Links the new environment to the project. | | 6. Resource copying | Copies the configuration blocks you ticked. | | 7. Status activation | Status transitions from `branching` to `active`. | While branching is in progress, the new environment is visible in the switcher with a `branching` status indicator. You can switch to it once it goes `active`. ## Creating via GraphQL Use the `branchEnvironment` mutation to create environments programmatically — useful for codifying environment creation alongside infrastructure-as-code. ```graphql theme={null} mutation BranchEnvironment($input: BranchEnvironmentInput!) { branchEnvironment(input: $input) { success message environment { id name parentId parentName branchMode branchedAt status } } } ``` ```json theme={null} { "input": { "projectId": "f7e4a264-d659-4719-91e8-c2d74654e529", "name": "staging", "sourceEnvironment": "master", "mode": "full", "copyFileProviders": true, "copyGatewayRoutes": true, "copySecurityConfig": false, "copyEnvironmentVariables": true } } ``` | Field | Type | Required | Description | | - | - | - | - | | `projectId` | String | Yes | The project's unique identifier. | | `name` | String | Yes | Name for the new environment. | | `sourceEnvironment` | String | Yes | Name of the source environment. | | `mode` | String | Yes | `"full"` or `"system"`. | | `copyFileProviders` | Boolean | No | Copy file storage configurations. | | `copyGatewayRoutes` | Boolean | No | Copy custom API gateway routes. | | `copySecurityConfig` | Boolean | No | Copy CORS and rate limit settings. | | `copyEnvironmentVariables` | Boolean | No | Copy environment variable key-value pairs. | Enabling any copy flag requires the `ProjectOwner` role. Without copy flags, standard project access is enough. ## FAQ > **Should I pick Full or System mode?** > > Full when you need realistic data — staging, QA, anything resembling production traffic. System when you want a clean, schema-only environment to seed however you like (test fixtures, scripted data). The mode is recorded but doesn't constrain future merges. > **Why aren't auth provider secrets copied?** > > Deliberate isolation. Sharing OAuth client secrets across environments means a leak in `dev` compromises `production`. Re-entering credentials per environment makes the security boundary explicit and reviewable. > **Can I copy environment variables after branching?** > > The copy flags only apply at branch time. After branching, edit each environment's variables individually under [Settings → Environment Variables](/docs/backend/settings/environment-variables). For shared values you don't want to edit twice, look at workspace-level secrets. > **What happens if branching fails halfway through?** > > The platform automatically rolls back. The cloned database and credentials are dropped, and the partially-registered environment record is removed. Your source environment is never touched by a failed branch. > **How long does branching take?** > > Depends on the source database size in **Full** mode. **System** mode is fast — schema-only clones complete in seconds. Full clones scale with row count and can take a few minutes for large databases. The status indicator stays at `branching` until the clone completes. # Deleting environments Source: https://archie.com/docs/backend/environments/deleting Permanently remove an environment, its database, and its scoped configuration. Backups outlive the environment and remain restorable. Deleting an environment removes its dedicated database, its credentials, and every per-environment configuration block. The action is permanent — the database can't be recovered through normal channels — but [backups](/docs/features/backend/environments/backups) outlive the environment and can be restored to recreate it. > [!WARNING] > > Deletion drops the environment's database and revokes its credentials. Anything not captured in a backup is gone. Take a manual backup before deleting if you might want the data back. ## Restrictions Three rules block deletion in the dashboard and the API: | Rule | Why | | - | - | | **`master` cannot be deleted** | It's the root of the [environment tree](/docs/features/backend/environments/tree). Every project must have a baseline. | | **An environment with active children cannot be deleted** | Its children would lose their parent reference. Delete or archive the children first. | | **An environment in the middle of a merge cannot be deleted** | Wait for the merge to complete (or cancel it) before deleting. | ## Deleting from the dashboard **1. Switch to a different environment** You can't delete the environment you're currently viewing. Switch to another active environment first. **2. Open the environment list** From the environment switcher dropdown, open the tree or list view. **3. Choose Delete on the target** The delete control surfaces a confirmation dialog with the environment's name and a final warning. **4. Confirm by typing the name** Type the environment's name to confirm — protection against accidental deletion of the wrong row. **5. Wait for the cleanup** Status moves to `archived` briefly while resources are decommissioned, then the environment disappears from the switcher. ## What happens during deletion The platform runs a cleanup sequence and removes the environment from the project hierarchy: | Step | What it does | | - | - | | 1. Permanent data removal | The dedicated PostgreSQL database is dropped. Database credentials are revoked. | | 2. Configuration cleanup | Auth provider records, file storage providers, RBAC roles, custom API routes, environment variables, and security policies are removed. | | 3. API gateway removal | Any custom API gateway routes scoped to the environment are decommissioned. | | 4. Platform unregistration | The environment is removed from the project hierarchy. The `environment` context is no longer routable. | After deletion, the environment's name is free to be reused — though we recommend not reusing it immediately, since clients with cached endpoints may still try to connect. ## What survives deletion Two things outlive a deleted environment: * **Backups** — both manual and pre-merge backups taken while the environment existed remain in the project's Backups view. Restore one to recreate the environment. See [Restoring to a deleted environment](/docs/features/backend/environments/backups#restoring-to-a-deleted-environment). * **Migration history** — entries in the project-level [history](/docs/features/backend/environments/history) remain. Audit trails don't disappear when environments do. ## Deleting via GraphQL ```graphql theme={null} mutation DeleteEnvironment($input: DeleteEnvironmentInput!) { deleteEnvironment(input: $input) { success message } } ``` ```json theme={null} { "input": { "projectId": "f7e4a264-d659-4719-91e8-c2d74654e529", "environmentName": "staging" } } ``` The mutation enforces the same restrictions as the dashboard — `master` is rejected, environments with children are rejected, and environments in `merging` status are rejected. On success, the database and configuration are gone. ## FAQ > **Can I undo a deletion?** > > Not as an undo. The environment's database is dropped immediately. To recover, restore one of the backups taken while the environment existed — the platform recreates the environment as part of the restore. See [Backups → Restoring to a deleted environment](/docs/features/backend/environments/backups#restoring-to-a-deleted-environment). > **What happens to the children if I delete a parent?** > > You can't. The platform rejects deleting an environment with active children. Delete or archive the children first, then the parent. The [tree view](/docs/features/backend/environments/tree) shows the children to handle. > **What about API keys, integration credentials, and webhooks scoped to the deleted environment?** > > All gone — they're per-environment and cleaned up during deletion. Any external system using credentials issued for that environment will start failing immediately. > **Can I delete an archived environment?** > > Archive is currently a transient state during deletion. Once a deletion completes, the environment disappears entirely. > **What's the right way to retire an environment without losing data?** > > Take a manual backup, mark it **retain forever**, then delete the environment. The backup keeps the data accessible for restore later, while the environment's resources (database, gateway routes, etc.) are released. # Schema diff Source: https://archie.com/docs/backend/environments/diff Compare the schemas of two environments side by side. Forward-only semantics surface exactly the changes that need to flow from source to target. The Environment Diff compares the database schemas of two environments side by side. Use it before a [merge](/docs/features/backend/environments/merge) to see exactly what would flow from source to target, or to audit drift between any two environments. The diff engine introspects both environments' schemas, runs a forward-only comparison, and generates the SQL needed to bring the target up to date. ![Environment schema diff comparison](https://mintcdn.com/archie-e998dbf6/zOlE9kC39qGD3yqx/features/backend/environments/environments-schema-diff.png?fit=max\&auto=format\&n=zOlE9kC39qGD3yqx\&q=85\&s=95e0a3716868569b30daeab60f1c4fb9) ## How the diff works Three stages, all server-side: | Stage | What happens | | - | - | | 1. Schema analysis | The platform introspects both environments — tables, columns, indexes, constraints, relationships, enums, views. | | 2. Comparison | The two structures are compared with **forward-only** semantics (see below). | | 3. Migration generation | For every detected change, the platform produces the DDL needed to apply it to the target. | ## Forward-only semantics The engine compares source against target asymmetrically. Specifically: * Objects in **source but not target** → shown as `CREATED`. They need to be created in the target. * Objects in **both** → compared field by field. Differences are flagged as modifications. * Objects **only in target** → **not** shown as `DROPPED`. The third rule is the key design decision. If a table exists only in the target, it was never present in the source — labeling it `TABLE_DROPPED` would suggest an intentional drop happened in the source. The diff focuses on changes that originated in the source and need to flow to the target. **Exception** — for column-level changes within a table that exists in both environments, `COLUMN_DROPPED` **is** detected, because that's an actual column-removal operation performed on the source. ## Change types | Change type | Description | Breaking | | - | - | - | | `TABLE_CREATED` | Table in source but not target. | No | | `COLUMN_ADDED` | New column added to a source table. | No | | `COLUMN_DROPPED` | Column removed from a source table that still exists in target. | Yes | | `COLUMN_TYPE_CHANGED` | Column data type modified. | Depends\* | | `COLUMN_NULLABLE_CHANGED` | Column's nullable constraint toggled. | Depends | | `COLUMN_DEFAULT_CHANGED` | Column default value modified. | No | | `INDEX_CREATED` | New index added. | No | | `INDEX_DROPPED` | Index removed. | No | | `ENUM_CREATED` | New enum type in source. | No | | `ENUM_VALUE_ADDED` | New value on an enum. | No | | `ENUM_VALUE_REMOVED` | Value removed from an enum. | Yes | | `VIEW_CREATED` | View in source but not target. | No | | `VIEW_MODIFIED` | View SQL definition changed. | No | | `RELATIONSHIP_ADDED` | New foreign key relationship. | No | | `RELATIONSHIP_DROPPED` | Foreign key relationship removed. | Yes | \*Type narrowing (`text` → `varchar(50)`, `int8` → `int4`) is breaking — it can lose data. Type widening (`varchar` → `text`) is safe. `TABLE_DROPPED`, `ENUM_DROPPED`, and `VIEW_DROPPED` aren't generated at the top level — see [forward-only semantics](#forward-only-semantics) above. ## Cherry-pick selection The diff view supports selecting individual changes for a subsequent [merge](/docs/features/backend/environments/merge): * Tick or untick each change individually. * Use the group checkbox on a table to select or deselect all changes for that table at once. * **Breaking changes are not pre-selected by default**. You have to explicitly opt in to drops, type narrowing, and enum-value removals — protection against accidental data loss. The selection set is what flows into the merge. Anything you leave unticked stays in the source. ## Reading the diff output Each entry in the diff has these fields: | Field | What it tells you | | - | - | | `changeType` | One of the change-type identifiers above. | | `objectType` | `table`, `column`, `index`, `enum`, `view`, or `relationship`. | | `objectName` | The name of the affected object (e.g., the table name). | | `fieldName` | For column-level changes, the column name. | | `oldValue` / `newValue` | Before/after values for modifications (e.g., `varchar(255)` → `text`). | | `isBreaking` | Boolean — true if the change can cause data loss or API breakage. | | `sql` | The DDL statement that would apply this change. | ## Use cases | Goal | How to use the diff | | - | - | | Pre-merge review | Diff source → target before clicking Merge to see exactly what will flow. | | Audit drift between environments | Diff `staging` against `master` to surface ad-hoc changes that snuck into one but not the other. | | Generate migration SQL | Read the `sql` field on each change to feed into another tool. | | Verify a merge succeeded | Diff source → target after a merge — should return zero differences if the merge was clean. | ## Running the diff via GraphQL ```graphql theme={null} query EnvironmentDiff($input: EnvironmentDiffInput!) { environmentDiff(input: $input) { success message changes { changeType objectType objectName fieldName oldValue newValue isBreaking sql } summary { totalChanges breakingChanges addedTables droppedTables modifiedTables } } } ``` ```json theme={null} { "input": { "projectId": "f7e4a264-d659-4719-91e8-c2d74654e529", "sourceEnvironment": "master", "targetEnvironment": "staging" } } ``` The `summary` block is a quick at-a-glance view; `changes` carries the detail. ## FAQ > **Why doesn't the diff show TABLE\_DROPPED at the top level?** > > Forward-only semantics. A table that exists only in the target was never in the source, so describing it as "dropped" would be misleading. Drops are surfaced at the column level only, where they reflect an actual removal that happened in the source. > **What does 'breaking' mean exactly?** > > A change that may cause data loss or break consumers of the API. Drops, narrowing type changes, removing enum values, and removing relationships are flagged as breaking. They're never pre-selected for merge — you have to opt in. > **Can I diff three environments at once?** > > The diff is pairwise. To compare three environments, run two diffs and reconcile. Most workflows compare a feature branch against `staging` and `staging` against `master` independently. > **Does the diff include data, or just schema?** > > Schema only — tables, columns, indexes, enums, views, relationships. Data differences aren't surfaced; the diff is about structural drift, not row counts. > **What happens if I diff during a branching or merging operation?** > > Both environments must be in `active` status. Diffs against environments in `branching`, `merging`, or `error` state are rejected — the schemas may not be in a consistent state to compare. # Migration history Source: https://archie.com/docs/backend/environments/history An audit trail of every merge operation — who ran it, when, what changed, and the backup taken alongside. The History view is the audit trail of every [merge](/docs/features/backend/environments/merge) operation that has touched an environment. It records what was changed, who applied it, when it ran, and the SQL that executed — useful for compliance, debugging drift, and understanding how an environment got to its current state. ## What's in a history entry Each merge produces one history entry on the target environment with these fields: | Field | What it records | | - | - | | **Migration ID** | Stable identifier for the merge operation. | | **Source environment** | Where the changes originated. | | **Target environment** | Where the changes were applied (this environment). | | **Status** | `succeeded`, `failed`, or `rolled_back`. | | **Applied by** | The user (or API key holder) who initiated the merge. | | **Applied at** | Timestamp when the merge ran. | | **Changes applied** | The list of selected changes — each with `changeType`, `objectName`, `sql`, and `isBreaking`. | | **Backup ID** | Reference to the pre-merge backup taken automatically. | Failed merges also record the failure reason and the index of the offending change. ## Where history lives History is per-environment. Open an environment from the switcher and look at its History view to see every merge that has been applied to it. The same operation appears in the source environment's history record only as the merge's source, not as a target — history is "what happened to me", not "what I sent elsewhere". For the chronological view across the whole project, query the `migrationHistory` GraphQL endpoint with the project ID and no environment filter. ## Use cases | Goal | How to use history | | - | - | | **Debug drift** | Two environments are out of sync. History shows which merges touched which targets and when. Cross-reference with [diff](/docs/features/backend/environments/diff) to find the gap. | | **Compliance and audit** | Every schema change is recorded with the user who applied it. Export the history for an environment to satisfy audit requirements. | | **Roll back a bad merge** | Find the merge in History, follow the backup ID into [Backups](/docs/features/backend/environments/backups), and restore the pre-merge snapshot. | | **Confirm a merge succeeded** | After running a merge, the new entry should show `succeeded` with the expected change count. | | **Trace an outage** | If something broke in production, the most recent merge in history is usually the first thing to inspect. | ## Inspecting via GraphQL ```graphql theme={null} query MigrationHistory($projectId: ID!, $environment: String) { migrationHistory(projectId: $projectId, environment: $environment) { id sourceEnvironment targetEnvironment status appliedBy appliedAt changes { changeType objectName sql isBreaking } backupId } } ``` | Argument | Behavior | | - | - | | `projectId` | Required. Scopes to the project. | | `environment` | Optional. Returns only the history entries for this target. Omit for the project-wide view. | The `changes` array carries the same shape as the [diff output](/docs/features/backend/environments/diff#reading-the-diff-output) — `changeType`, `objectName`, the executed `sql`, and the `isBreaking` flag. ## Retention History entries are durable and not subject to a retention window — they're part of the project's audit trail. Backups referenced by `backupId` follow the [backup retention policy](/docs/features/backend/environments/backups#retention) and may age out independently. ## Permissions Reading history is governed by [Role-Based Access](/docs/features/backend/app-services/role-based-access). Viewing audit data is typically restricted to admin or operator roles — grant the necessary permissions on the system audit resources to make History visible to additional roles. ## FAQ > **Can I delete a history entry?** > > No. History is append-only by design — that's what makes it useful for audit and compliance. To suppress a misconfigured environment from production audits, archive or delete the environment instead. > **What if the backup referenced by a history entry has been pruned?** > > The history entry remains intact. The `backupId` will resolve to "not found" when you try to restore it. For long-lived rollback windows, copy the pre-merge backup to a manual backup that's exempt from retention pruning before the window expires. > **Does history show data changes too?** > > No — history records schema changes (DDL) only. Data changes go through the auto-generated APIs and aren't audited at the environment level. For data-level audit logging, build it into your custom functions or use database-level triggers. > **Can I get a CSV or JSON export of history?** > > Query `migrationHistory` over GraphQL — the response is JSON. Pipe it through `jq` for a flat dump, or feed it into your audit tooling. > **What does `rolled_back` status mean?** > > The merge succeeded initially but the pre-merge backup was later restored, effectively undoing the merge. Status flips from `succeeded` to `rolled_back` when the matching backup is restored. # Merge Source: https://archie.com/docs/backend/environments/merge Apply schema changes from one environment to another. Cherry-pick the changes you want, run them transactionally, and roll back automatically on failure. Merge applies schema changes from one environment (the **source**) to another (the **target**). It's the operation that propagates structural changes — new tables, new columns, new indexes — across your development lifecycle. The flow is deliberately conservative: every merge is preceded by an automatic backup, runs as a single transaction, and rolls back automatically if anything fails. You pick exactly which changes to apply via the cherry-pick UI; you never commit to "merge everything" without seeing the list first. ## How merge works | Step | What happens | | - | - | | 1. Selection | Pick a source and a target environment. | | 2. Analysis | The platform runs a [diff](/docs/features/backend/environments/diff) and surfaces the changes missing in the target. | | 3. Review and cherry-pick | Tick the changes you want to apply. Breaking changes are never pre-selected — you have to opt in. | | 4. Automatic backup | A pre-merge backup of the target is created automatically. | | 5. Atomic application | Selected changes are applied as a single transaction. All-or-nothing. | | 6. Status update | On success, the target is in its new state and a [history](/docs/features/backend/environments/history) entry is recorded. On failure, the target rolls back to its pre-merge state. | ## Transactional DDL Every merge runs inside a single database transaction: * **All-or-nothing** — every selected change applies, or none does. There's no partial state. * **No partial migrations** — if the third of ten changes fails, the first two are rolled back along with the rest. * **Rollback is automatic** — you don't run a recovery step. The transaction aborts and the target's schema is exactly what it was before you clicked Merge. This applies even when the failing change is the very last one in the batch. ## Environment locking While a merge is in progress, the target environment is **locked**: * Other merges into the same target are rejected. * Branching from the target is rejected. * Schema edits on the target via the Data Model UI are rejected. The lock is automatically released on success, on rollback, or when the merge enters the `error` state. Reads against the target continue to work — only mutating schema operations are blocked. ## Cherry-pick selection The merge UI is a checkbox tree built on top of the [diff](/docs/features/backend/environments/diff): * **Per-change selection** — tick individual changes (a single column added, a single index created). * **Group selection** — tick the parent checkbox on a table to select all changes for that table at once. * **Breaking-change protection** — drops, type narrowing, and enum-value removals are unticked by default. You explicitly opt in. Anything you leave unticked stays in the source. The same change can be merged in a later operation by re-running the merge. ## Pre-merge backup Before any DDL runs, the platform takes a full backup of the target environment — schema and data. The backup is keyed to the merge so it shows up in the [Backups](/docs/features/backend/environments/backups) view as **Pre-merge** alongside the migration record in [History](/docs/features/backend/environments/history). If the merge succeeds but you discover a regression hours or days later, restore from that pre-merge backup to revert the target to its exact pre-merge state. See [Backups → Restoring](/docs/features/backend/environments/backups#restoring-a-backup). ## On failure If any DDL in the batch fails: 1. The transaction aborts. The target's schema goes back to its pre-merge state. 2. The merge record is marked `failed` in [History](/docs/features/backend/environments/history) with the failure reason and the index of the offending change. 3. The target environment's lock is released. 4. The pre-merge backup is preserved (it's a normal backup like any other). A failed merge doesn't leave a half-migrated database. Fix the failing change in the source — usually a constraint conflict, a NOT-NULL backfill issue, or a circular dependency — and re-run. ## Rollback Rolling back a successful merge means restoring the pre-merge backup: 1. Open [Backups](/docs/features/backend/environments/backups). 2. Find the **Pre-merge** entry from the merge you want to undo. 3. Restore it to the same target environment. A restore reverts the **entire** database (schema + data) to the snapshot point. It's not a per-change undo — the whole environment goes back to the moment before the merge ran. Verify changes in `staging` before merging into production to keep this option as a last resort. ## Common patterns | Pattern | How to do it | | - | - | | Promote a feature branch to staging | Merge `feature-x` → `staging`. Cherry-pick only the changes that are ready. | | Promote staging to production | Merge `staging` → `master`. Run the diff first to confirm everything's expected. | | Sync `dev` with `master` after a hotfix | Merge `master` → `dev`. Useful when a hotfix in production needs to flow back. | | Test a destructive change | Merge with the breaking change ticked into a `qa-*` environment, verify, then merge into `staging`. | ## Running merge via GraphQL ```graphql theme={null} mutation MergeEnvironments($input: MergeEnvironmentInput!) { mergeEnvironments(input: $input) { success message migrationId changesApplied backupId } } ``` The `migrationId` is the entry in [History](/docs/features/backend/environments/history); the `backupId` is the pre-merge backup's ID, also visible in [Backups](/docs/features/backend/environments/backups). ## FAQ > **What if I want to merge in only some changes and come back for the rest?** > > Cherry-pick what you want now. Re-run the merge later — the diff regenerates against the current target state, so the unmerged changes will show up again ready to apply. > **Can a merge fail because of data, not schema?** > > Yes. Adding a NOT-NULL column without a default fails if the target has rows that can't satisfy it. Type-narrowing a column fails if existing values don't fit. The transaction aborts, the rollback runs, the merge is marked failed. > **Why didn't the merge include row data?** > > Merge moves schema, not data. To copy data between environments, branch a new environment in **Full** mode (or restore a backup of the source into the target). See [Creating environments](/docs/features/backend/environments/creating). > **What's the difference between rolling back a failed merge and restoring after a successful one?** > > A failed merge rolls back automatically inside the transaction — the target never changes. Restoring after a successful merge means using the pre-merge backup to undo a merge that completed but turned out to be a mistake. > **Can I merge into a target that's locked?** > > No. The target has to be `active`. If a previous merge is still running, wait for it to finish or for it to fail and clear. The status indicator on the environment switcher shows the current state. # Environment tree Source: https://archie.com/docs/backend/environments/tree The parent-child hierarchy of your environments. Visualizes lineage and drives diff target selection, merge direction, and backup restore scope. Environments form a tree rooted at `master`. Each environment has exactly one parent (the source it was branched from) and can have any number of children. The tree visualization is the right starting point for planning merges — it shows lineage at a glance and tells you which environments are eligible targets for a given merge. ![Environment tree structure and status lifecycle](https://mintcdn.com/archie-e998dbf6/zOlE9kC39qGD3yqx/features/backend/environments/environments-tree-structure.png?fit=max\&auto=format\&n=zOlE9kC39qGD3yqx\&q=85\&s=851756e39b0a161c58dacd072c216586) ## What each node shows Each node in the tree displays the metadata recorded when the environment was created: | Field | What it shows | | - | - | | **Name** | Environment identifier — `master`, `staging`, `feature-auth`. | | **Status** | Current lifecycle state — `active`, `branching`, `merging`, `error`, `archived`. | | **Parent** | The environment this branch was created from. | | **Branch mode** | `full` (schema + data) or `system` (schema only). | | **Branched at** | Timestamp when the branch was created. | | **Children** | Other environments branched from this one. | For the full status lifecycle, see [Overview → Status lifecycle](/docs/features/backend/environments/overview#status-lifecycle). ## Tree shape * The tree is rooted at **`master`** — every project starts there and `master` can never be deleted. * Each environment has **exactly one parent**. * Each environment can have **zero or more children**. * Branching is the only way to add a node. Merging schema between environments doesn't change the tree shape. ## When the tree matters The tree shape isn't decorative — it informs three operations: | Operation | How the tree drives it | | - | - | | **Merge target selection** | The [merge UI](/docs/features/backend/environments/merge) lets you pick any source/target pair, but the most common pattern is "merge child → parent" or "merge parent → child". The tree makes those relationships visible. | | **Backup restore scope** | A backup is restored into a target environment. The tree tells you which environments are still around and which have been deleted (deleted nodes can be re-created via [restore](/docs/features/backend/environments/backups#restoring-to-a-deleted-environment)). | | **Deletion ordering** | An environment with active children can't be deleted. The tree shows you the children to delete or archive first. See [Deleting](/docs/features/backend/environments/deleting). | ## Reading the tree via GraphQL Two queries cover the surface: ### `environmentTree` — nested structure Returns the full tree with children nested under parents. Easiest to render directly in a UI. ```graphql theme={null} query EnvironmentTree($projectId: String!) { environmentTree(projectId: $projectId) { success tree { environment { id name status parentName branchMode branchedAt } children { environment { id name status parentName branchMode branchedAt } children { environment { id name status } } } } } } ``` ### `projectEnvironments` — flat list Returns every environment as a flat array. Useful for syncing the list into your own UI or for programmatic operations that don't need nesting. ```graphql theme={null} query GetProjectEnvironments($projectId: String!) { projectEnvironments(projectId: $projectId) { success environments { id name parentId parentName branchMode branchedAt status createdAt updatedAt } } } ``` ## FAQ > **Can an environment have multiple parents?** > > No. Each environment has exactly one parent — the source it was branched from. The tree is strict (no merging of branches into a DAG). Schema changes propagate via [merge](/docs/features/backend/environments/merge), which doesn't change the tree shape. > **What happens to children if I delete the parent?** > > You can't. An environment with active children can't be deleted. Delete or archive every child first, then the parent. See [Deleting](/docs/features/backend/environments/deleting). > **If I branch B from A, then merge changes from C into B, what does the tree look like?** > > Unchanged. B's parent is still A — the tree records lineage at branch time, not merge history. To see merge activity, look at [History](/docs/features/backend/environments/history). > **Can I rename an environment?** > > Names are stable identifiers. Renaming would invalidate every URL, integration credential, and migration record that references the old name. To "rename", branch a fresh environment with the new name and merge changes into it. > **Is the tree visible in the dashboard?** > > Yes — open the environment switcher and choose the tree view. The same data is available via the GraphQL queries above for programmatic use. # Overview Source: https://archie.com/docs/backend/graphql-api-explorer The GraphQL API Explorer is an in-browser IDE for querying and mutating your app's data through the auto-generated GraphQL schema. The GraphQL API Explorer is an in-browser IDE for querying and mutating your app's data through the auto-generated GraphQL schema. Open any project, switch to the Backend Console, and you can build, test, and execute queries, mutations, and subscriptions against real data without leaving the workspace. The schema reflects your [Data Model](/docs/features/backend/data-model/overview) in real time. Add a table, add a field, or rename a column, and the corresponding GraphQL types, queries, and mutations regenerate immediately. ![API Explorer IDE overview with GraphQL editor](https://mintcdn.com/archie-e998dbf6/GdaYz5W-YpQoJXsQ/features/backend/graphql-api-explorer/api-explorer-ide-overview.png?fit=max\&auto=format\&n=GdaYz5W-YpQoJXsQ\&q=85\&s=11a26f002a033e61af42aea755856dfd) ## What's in the Explorer The Explorer ships with the four panels you'd expect from a modern GraphQL IDE: * **Composer** — a visual query builder. Pick a table, check the fields you want, and the Composer writes the operation into the editor for you. * **Editor** — the main code area. Write queries, mutations, and subscriptions with autocomplete, syntax highlighting, and inline error tooltips. Press **Shift + Space** to trigger the autocomplete menu manually. * **Documentation panel** — a searchable schema reference. Browse every query, mutation, type, and field in your schema. Click a type name in the editor to jump straight to its definition. * **Results panel** — the response from the most recent execution, with timing and error information. ![API Explorer Composer with query results](https://mintcdn.com/archie-e998dbf6/GdaYz5W-YpQoJXsQ/features/backend/graphql-api-explorer/api-explorer-composer-query-results.png?fit=max\&auto=format\&n=GdaYz5W-YpQoJXsQ\&q=85\&s=d69fa3cc68db8ed0ece79986fa2fe8a2) ## Introspection and autocomplete Because GraphQL ships introspection in the protocol, the editor knows your schema without any configuration. Start typing a field name and the autocomplete menu shows you the fields that exist on the current type. Hover a squiggly red underline to see why the editor thinks something is wrong — wrong scalar, missing argument, field not on type. ![API Explorer contextual auto-complete when typing in query](https://mintcdn.com/archie-e998dbf6/GdaYz5W-YpQoJXsQ/features/backend/graphql-api-explorer/api-explorer-autocomplete-contextual.png?fit=max\&auto=format\&n=GdaYz5W-YpQoJXsQ\&q=85\&s=038dc9e75229418c165dd06b66080ec6) ## A single endpoint per project Every project gets a single GraphQL endpoint that handles queries, mutations, and subscriptions for every table in the Data Model. Subscriptions use the `wss://` variant of the same path. To target a specific [environment](/docs/features/backend/environments/overview), point requests at that environment's endpoint — each environment has its own. ## Authentication and permissions Both happen at the API layer, not in the Explorer: * **Authentication** — every operation requires a token issued by an authentication provider configured in [App Services → Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). Inside the Explorer you're already authenticated as your workspace user; outside it you'll attach the token to each request. * **Authorization** — read and write rules per role are enforced on every query and mutation, configured in [Role-Based Access](/docs/features/backend/app-services/role-based-access). Operations that violate those rules return an authorization error. ## Where to go next > **[Queries](/docs/features/backend/graphql-api-explorer/queries)** > > Read records — single, list, filtered, paginated, sorted, aggregated, grouped. > **[Mutations](/docs/features/backend/graphql-api-explorer/mutations)** > > Create, update, and delete records, including bulk operations. > **[Subscriptions](/docs/features/backend/graphql-api-explorer/subscriptions)** > > Real-time event streams over WebSockets. > **[Standard operations](/docs/features/backend/graphql-api-explorer/standard-operations)** > > Filters, relationships, nested mutations, and upserts. > **[Variables](/docs/features/backend/graphql-api-explorer/variables)** > > Parameterize operations with typed variables. ## FAQ > **Does the Explorer hit production data?** > > It hits whichever environment is selected in the workspace. By default that's the active environment for your project — typically `master`. To work against a non-production environment, switch environments before running operations. > **How do I see all available queries and mutations?** > > Open the **Documentation** panel from the top-right toolbar, or click any type name in the editor to jump to its definition. Both list every query, mutation, subscription, and type generated from your Data Model. > **Why doesn't autocomplete show a field I just added?** > > The schema regenerates as soon as the Data Model changes, but the editor caches the schema. Reload the Explorer if a brand-new field doesn't appear. > **Can I share a query with a teammate?** > > Copy the full operation and paste it into your teammate's Explorer, or save it as part of your client code. The Explorer doesn't currently persist named queries across users. > **What's the difference between the GraphQL and REST APIs?** > > Both expose the same data, the same permissions, and the same auth model. GraphQL is best for nested reads, real-time subscriptions, and clients that want to fetch exactly what they need. REST is best when you want a familiar HTTP surface or are integrating with tools that don't speak GraphQL. See the [REST API Explorer](/docs/features/backend/rest-api-explorer/overview) for the REST equivalent. # Mutations Source: https://archie.com/docs/backend/graphql-api-explorer/mutations Create, update, and delete records through the auto-generated GraphQL mutations. Includes bulk operations and atomic upserts. Mutations write data through the GraphQL API. Every table in the [Data Model](/docs/features/backend/data-model/overview) generates a fixed set of mutations automatically — you don't write resolvers or input types by hand. The examples assume a `students` table with fields like `firstName`, `lastName`, `email`, `age`, and a `city` relationship. ## Auto-generated mutations per table For each table, Archie generates: | Mutation | What it does | | - | - | | `create` | Insert a single record. | | `createMany` | Insert multiple records in one request. | | `update` | Update a single record by id. | | `updateMany` | Update multiple records by filter, ids, or all — one atomic transaction. | | `delete` | Delete a single record by id. | | `deleteMany` | Delete multiple records by id. | | `upsert` | Insert or update by a unique field — atomic. Generated for tables with at least one unique column. | ![GraphQL mutations overview in API Explorer](https://mintcdn.com/archie-e998dbf6/GdaYz5W-YpQoJXsQ/features/backend/graphql-api-explorer/api-explorer-ide-overview.png?fit=max\&auto=format\&n=GdaYz5W-YpQoJXsQ\&q=85\&s=11a26f002a033e61af42aea755856dfd) ## Single record mutations ### Create ```graphql theme={null} mutation { createStudents( input: { firstName: "John" lastName: "Doe" email: "john.doe@example.com" age: 24 city: "2900562f-d036-486d-be98-9ebf064c27fe" } ) { id firstName email city { id nameCity } } } ``` The `input` object's shape comes directly from the table's schema. Mandatory fields without a default must be present; optional fields can be omitted. ### Update ```graphql theme={null} mutation { updateStudents( id: "2685ec12-a4c7-491d-a155-d0b09190993b" input: { age: 23 } ) { id firstName age } } ``` `update` is a partial update — only the fields in `input` change. Everything else stays as it was. ### Delete ```graphql theme={null} mutation { deleteStudents(id: "2685ec12-a4c7-491d-a155-d0b09190993b") } ``` Returns `true` on success. ## Multiple record mutations ### Create many ```graphql theme={null} mutation { createStudentsMany( inputs: [ { firstName: "Michael", lastName: "Jones", email: "michael@example.com", age: 24 } { firstName: "William", lastName: "Miller", email: "william@example.com", age: 23 } ] ) { success } } ``` `createStudentsMany` runs as a single transaction. If any record fails validation, none are inserted. For larger or dynamic batches, pass the inputs as a variable so you can reuse the operation: ```graphql theme={null} mutation CreateStudents($inputs: [StudentsCreateInput!]!) { createStudentsMany(inputs: $inputs) { success } } ``` ```json theme={null} { "inputs": [ { "firstName": "Michael", "email": "michael@example.com", "age": 24 }, { "firstName": "William", "email": "william@example.com", "age": 23 } ] } ``` ### Update many `updateMany` applies the same `data` to every matched row in a single atomic transaction. You choose the rows with **exactly one** selector: | Selector | Selects | | - | - | | `filter` | Rows matching an equality filter (all fields ANDed together). Must be non-empty. | | `ids` | An explicit list of ids. Ids that don't exist are skipped (not an error). | | `all: true` | Every row in the table. Must be passed explicitly. | ```graphql theme={null} mutation { updateStudentsMany( filter: { age: 24 } data: { lastName: "Updated" } ) { success totalRequested totalSucceeded updatedIds rolledBack rollbackReason } } ``` Update an explicit set of ids (the shift into a variable works the same as create many): ```graphql theme={null} mutation { updateStudentsMany( ids: ["2685ec12-a4c7-491d-a155-d0b09190993b", "f47ac10b-58cc-4372-a567-0e02b2c3d479"] data: { age: 25 } ) { success totalSucceeded updatedIds } } ``` Update every row (note the explicit `all: true` — there is no implicit "update everything"): ```graphql theme={null} mutation { updateStudentsMany(all: true, data: { active: true }) { success totalSucceeded } } ``` Key rules: * **Exactly one selector.** Passing none, or more than one of `filter` / `ids` / `all`, is an error. * **`filter` must be non-empty.** An empty filter is rejected — it is never treated as "update everything". Use `all: true` for that. * **Always atomic (`ALL_OR_NOTHING`).** There is no best-effort mode and no `transactionMode` argument. If the update fails partway, nothing is changed. * **`ids` is tolerant.** Ids that no longer exist are silently skipped: `totalRequested` counts the ids you sent, `totalSucceeded` counts the rows actually updated. * **`updatedAt` is set automatically** on every updated row. * **Business errors come back in the payload**, not as a top-level GraphQL error. On a rollback (e.g. a constraint violation), the mutation still returns a payload with `rolledBack: true` and `rollbackReason` — always check `success` / `rolledBack`. * **`updatedIds`** lists the ids that were updated. (It is part of the shared batch result, so it is `null` on `create…Many` / `delete…Many` responses.) ### Delete many ```graphql theme={null} mutation { deleteStudentsMany( ids: [ "2685ec12-a4c7-491d-a155-d0b09190993b", "f47ac10b-58cc-4372-a567-0e02b2c3d479" ] ) } ``` ## Nested mutations Instead of pre-creating related records and passing their foreign keys, you can express the entire graph in one operation. Every relationship field accepts one of: * `create` — insert a new related record inline. * `connect` — link to an existing record by a unique field. * `connectOrCreate` — find by a unique field; create if missing. ```graphql theme={null} mutation { createDeal( input: { title: "Enterprise License" amount: 50000 contact: { create: { firstName: "Juan", lastName: "Pérez", status: "LEAD" } } user: { connect: { id: "user-uuid-123" } } organization: { connectOrCreate: { where: { domain: "acme.com" } create: { name: "Acme Corp", domain: "acme.com" } } } } ) { id contact { id } organization { id } } } ``` The whole nested mutation runs as one transaction. If any step fails, the entire operation rolls back — you won't end up with half-created records. ## Atomic upserts Tables with at least one unique column get an `upsert` mutation. Pass a `where` clause that targets the unique column, plus `create` and `update` payloads. Archie atomically checks if the record exists and runs the matching branch. ```graphql theme={null} mutation { upsertContact( where: { email: "juan@example.com" } create: { firstName: "Juan" lastName: "Pérez" email: "juan@example.com" status: "LEAD" } update: { lastName: "Pérez Updated" status: "ACTIVE" } ) { id firstName lastName status } } ``` Use `upsert` instead of "select then insert or update" — it eliminates the race condition between the two queries. ## Permissions Every mutation is checked against the per-role permissions in [Role-Based Access](/docs/features/backend/app-services/role-based-access). A user without write access to a table will receive an authorization error before the mutation runs. Field-level write rules apply too — fields a role isn't allowed to write are rejected even if the rest of the input is valid. ## FAQ > **What's the difference between `update` and `upsert`?** > > `update` requires the record to exist and is keyed by `id`. `upsert` is keyed by any unique column and creates the record if it doesn't exist. Use upsert when you have an external identifier (an email, a Stripe customer ID) and you don't know whether the record already exists. > **Can I update related records in the same mutation?** > > Yes. Nested mutations work on update too — you can pass `create`, `connect`, or `connectOrCreate` to a relationship field on `update`. The whole graph is updated atomically. > **What happens if the input is missing a mandatory field?** > > The mutation fails before any data is written, with an error indicating which field is missing. The error includes the field name and the constraint that was violated. > **Are bulk operations faster than looping individual mutations?** > > Yes. `createStudentsMany` runs in one round-trip and one transaction; a client-side loop runs one round-trip per record and serializes them. Use bulk for any batch over a few records. > **Can I get the inserted ids back from a bulk create?** > > The default `createXxxMany` mutation returns `success`. Project a richer payload by selecting more fields in the response if your bulk mutation supports it, or run a follow-up query keyed on the unique values you inserted. # Queries Source: https://archie.com/docs/backend/graphql-api-explorer/queries Read records from the auto-generated GraphQL schema. Single record, list, filtered, paginated, sorted, aggregated, grouped. Every table in the [Data Model](/docs/features/backend/data-model/overview) generates a set of queries on the GraphQL schema. This page covers the patterns you'll reach for most often. The examples assume a `students` table with fields like `firstName`, `email`, `age`, `isActive`, `createdAt`, and a `city` relationship. ## Single record Fetch one record by its primary key. ```graphql theme={null} query { studentsById(id: "e25c4955-7ea7-4a83-a3de-d4c7427cc9fa") { id firstName email } } ``` ```json theme={null} { "data": { "studentsById": { "id": "e25c4955-7ea7-4a83-a3de-d4c7427cc9fa", "firstName": "Robert", "email": "robert.williams@example.com" } } } ``` Any field marked **Unique** in the Data Model can also be used as a query argument. ## Record list The list query wraps results in a Connection type with two top-level fields: `items` (the array of records) and `count` (the number of records returned). ```graphql theme={null} query { students { count items { id firstName email } } } ``` ## Filtered list The `filter` argument takes a structured input that mirrors the field types on the table. Each field exposes a set of operators (`equals`, `gt`, `contains`, `starts_with`, etc.). ```graphql theme={null} query { students( filter: { createdAt: { gt: "2025-12-01T00:00:00.000Z" } email: { contains: "williams" } } ) { items { id firstName email } } } ``` For the full operator reference and the relational filter quantifiers (`some`, `every`, `none`), see [Standard operations](/docs/features/backend/graphql-api-explorer/standard-operations). ### Combining conditions with AND / OR Use the `AND` and `OR` keys to compose multiple conditions on the same query. ```graphql theme={null} query { students( filter: { AND: { email: { contains: "johnson" } isActive: { equals: true } } } ) { items { id firstName email isActive } } } ``` ```graphql theme={null} query { students( filter: { OR: { firstName: { equals: "Mary" } lastName: { equals: "Williams" } } } ) { items { id firstName lastName } } } ``` ## Paginated list `first` controls the page size; `skip` controls the offset. ```graphql theme={null} query { students(skip: 0, first: 3) { items { id firstName email } } } ``` For cursor-stable pagination, use `pageInfo.hasNextPage` and `pageInfo.hasPreviousPage` on the Connection wrapper: ```graphql theme={null} query { students(first: 20) { pageInfo { hasNextPage hasPreviousPage } items { id firstName } } } ``` ## Sorted list Pass an array of field enums to `sort`. Append `_DESC` for descending; the bare enum sorts ascending. ```graphql theme={null} query { students(sort: [CREATEDAT]) { items { id createdAt firstName } } } ``` The object-style alternative is `orderBy`, which accepts `ASC` or `DESC` per field: ```graphql theme={null} query { students(orderBy: { firstName: ASC }) { items { id firstName } } } ``` For sorting by a computed aggregate value, use `aggregateSort` — see [Grouping and aggregation](#grouping-and-aggregation). ## Combining arguments `filter`, `first`, `skip`, `sort`, and `orderBy` all stack on the same query. ```graphql theme={null} query { students( filter: { isActive: { equals: true } } first: 3 orderBy: { age: DESC } ) { items { id firstName age } } } ``` ## Combining queries A single GraphQL request can run multiple top-level queries. They execute in parallel and return as one response. ```graphql theme={null} query { students(filter: { firstName: { equals: "James" } }) { count items { id firstName } } citiesById(id: "e14638cb-6d72-4a36-b30f-9b763136a7bb") { id nameCity } } ``` If you need the same query run twice with different arguments in one request, use **aliases**: ```graphql theme={null} query { cityOne: citiesById(id: "e14638cb-6d72-4a36-b30f-9b763136a7bb") { nameCity } cityTwo: citiesById(id: "0174dc55-d494-4ebc-a0e9-13575461cad4") { nameCity } } ``` ## Aggregation Every list query exposes a built-in `count` field on the Connection wrapper. No extra arguments needed. ```graphql theme={null} query { students { count items { id firstName } } } ``` For sums, averages, and other aggregate functions, see [Grouping and aggregation](#grouping-and-aggregation) below. ## Grouping and aggregation Four arguments work together to compute analytics in a single query: | Argument | Purpose | | - | - | | `groupBy` | Fields to group by (one row per distinct combination of values). | | `aggregateBy` | Aggregate functions to compute on each group. | | `having` | Filter groups by aggregate results (SQL `HAVING` equivalent). | | `aggregateSort` | Sort groups by aggregate values. | ### Group by a field ```graphql theme={null} query { students(groupBy: [ISACTIVE]) { items { isActive } count } } ``` The enum value is the camelCase field name converted to UPPERCASE: `isActive` → `ISACTIVE`, `paymentMethod` → `PAYMENTMETHOD`. ### Compute aggregates per group `aggregateBy` takes a list of `{ function, field, alias }` entries. The result lands in the `aggregates` array, parallel to `items`. ```graphql theme={null} query { students( groupBy: [ISACTIVE] aggregateBy: [ { function: COUNT, alias: "totalStudents" } { function: AVG, field: AGE, alias: "avgAge" } ] ) { items { isActive } count aggregates } } ``` ```json theme={null} { "data": { "students": { "items": [{ "isActive": true }, { "isActive": false }], "count": 2, "aggregates": [ { "totalStudents": 5, "avgAge": 23.4 }, { "totalStudents": 2, "avgAge": 21.0 } ] } } } ``` | Function | Field required? | | - | - | | `COUNT` | No (counts all rows when omitted) | | `SUM` | Yes | | `AVG` | Yes | | `MIN` | Yes | | `MAX` | Yes | | `COUNT_DISTINCT` | Yes | ### Filter groups with `having` `having` references the alias defined in `aggregateBy`. Operators are `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN`, `LESS_THAN_OR_EQUAL`. ```graphql theme={null} query { students( groupBy: [CITYID] aggregateBy: [{ function: COUNT, alias: "studentCount" }] having: [{ alias: "studentCount", operator: GREATER_THAN, value: 3 }] ) { items { cityId } aggregates } } ``` ### Sort by aggregate ```graphql theme={null} query { students( groupBy: [CITYID] aggregateBy: [{ function: COUNT, alias: "studentCount" }] aggregateSort: [{ alias: "studentCount", direction: DESC }] first: 3 ) { items { cityId } aggregates } } ``` ### All four together ```graphql theme={null} query { students( filter: { isActive: { equals: true } } groupBy: [CITYID] aggregateBy: [ { function: COUNT, alias: "studentCount" } { function: AVG, field: AGE, alias: "avgAge" } ] having: [{ alias: "avgAge", operator: GREATER_THAN, value: 20 }] aggregateSort: [{ alias: "avgAge", direction: DESC }] first: 5 ) { items { cityId } aggregates } } ``` `filter` runs **before** grouping (SQL `WHERE`); `having` runs **after** (SQL `HAVING`). ## Permissions Every query is filtered by the per-role permissions in [Role-Based Access](/docs/features/backend/app-services/role-based-access). Records and fields a role isn't allowed to read are silently omitted from the response — they don't trigger errors. ## FAQ > **Why is `count` separate from the array length?** > > `count` reflects the total number of matching records (subject to permissions), while `items` reflects only the current page. With `first: 10`, `items.length` is at most 10 but `count` may be much larger. > **What's the difference between `sort` and `orderBy`?** > > They do the same thing in different syntaxes. `sort` takes an array of enum values (`[CREATEDAT, FIRSTNAME_DESC]`); `orderBy` takes an object (`{ createdAt: ASC, firstName: DESC }`). Pick whichever reads better in your query. > **How do I filter by a related table's field?** > > Use the relational quantifiers — `some`, `every`, `none`. See [Standard operations](/docs/features/backend/graphql-api-explorer/standard-operations#relational-quantifiers). > **What's the largest page I can request?** > > The Explorer accepts large `first` values, but very large pages are slow. Use pagination with reasonable page sizes (20–100) and combine with sorting to keep results stable across requests. > **Can I run a query against a non-default environment?** > > Yes — switch environments in the Backend Console, or call the environment-specific endpoint from outside the Explorer. See [Environments](/docs/features/backend/environments/overview). # Standard operations Source: https://archie.com/docs/backend/graphql-api-explorer/standard-operations Filters, relationship traversal, nested mutations, and the standard patterns shared across every query and mutation in the schema. Most queries and mutations on the GraphQL API share a common shape — the same filter operators, the same Connection wrapper around lists, the same nested-mutation conventions. This page is the reference for those shared patterns. ## The Connection wrapper Every list field in the schema (root-level lists and nested relationship lists) returns a Connection wrapper: | Field | Type | Description | | - | - | - | | `items` | `[T!]!` | The records on the current page. | | `count` | `Int!` | Total matching records — useful for pagination UIs. | | `pageInfo.hasNextPage` | `Boolean!` | Whether more records exist after this page. | | `pageInfo.hasPreviousPage` | `Boolean!` | Whether records exist before this page. | | `aggregates` | `JSON` | Computed aggregate values when `aggregateBy` is used. | Because the wrapper is consistent at every depth, you can pass `filter`, `first`, `skip`, and `orderBy` to a nested list the same way you pass them at the root. ```graphql theme={null} query { categoriesList { items { name productList( filter: { isFeatured: { equals: true } } first: 3 orderBy: { stock: DESC } ) { count pageInfo { hasNextPage } items { id name stock } } } } } ``` ## Filter operators ### Comparison and text Most fields support a standard set of operators. The exact set depends on the field type — text fields get string operators, numeric fields get range operators, and so on. | Operator | Description | Example | | - | - | - | | `equals` | Exact match | `{ status: { equals: "active" } }` | | `not_equals` | Negated match | `{ status: { not_equals: "archived" } }` | | `gt` | Greater than | `{ age: { gt: 18 } }` | | `gte` | Greater than or equal | `{ age: { gte: 18 } }` | | `lt` | Less than | `{ price: { lt: 100 } }` | | `lte` | Less than or equal | `{ price: { lte: 100 } }` | | `contains` | Substring match | `{ name: { contains: "phone" } }` | | `starts_with` | Prefix match | `{ sku: { starts_with: "PROD-" } }` | | `is_empty` | NULL or empty | `{ phoneNumber: { is_empty: true } }` | | `is_not_empty` | Has any value | `{ bio: { is_not_empty: true } }` | ### Logical composition `AND`, `OR`, and `NOT` compose any number of clauses. They can nest. ```graphql theme={null} query { usersList(filter: { NOT: { status: { equals: "BANNED" } } }) { items { id email } } } ``` ```graphql theme={null} query { ordersList(filter: { OR: [ { status: { equals: "completed" } } { AND: [ { status: { equals: "pending" } } { createdAt: { last_days: 7 } } ] } ] }) { items { id status } } } ``` ### Date helpers Relative date filters save you from computing exact timestamps. | Operator | Example | | - | - | | `today` | `{ createdAt: { today: true } }` | | `this_week` | `{ createdAt: { this_week: true } }` | | `this_month` | `{ createdAt: { this_month: true } }` | | `last_days` | `{ createdAt: { last_days: 30 } }` | ```graphql theme={null} query { logsList(filter: { timestamp: { last_days: 7 } }) { items { message } } } ``` ### Relational quantifiers To filter parent records by the state of their related children, use `some`, `every`, and `none`: | Quantifier | Matches when... | | - | - | | `some` | At least one child matches the inner filter. | | `every` | All children match the inner filter (also matches parents with no children). | | `none` | No children match the inner filter. | ```graphql theme={null} query UsersWithHighRatedPosts { usersList(filter: { posts: { some: { rating: { gt: 5 } } } }) { items { id email } } } ``` ```graphql theme={null} query UsersWithOnlyPublishedPosts { usersList(filter: { posts: { every: { published: { equals: true } } } }) { items { id } } } ``` ```graphql theme={null} query UsersWithNoDrafts { usersList(filter: { posts: { none: { status: { equals: "DRAFT" } } } }) { items { id } } } ``` ## Nested mutations Every relationship field on a mutation input accepts exactly one of three operations: | Operation | What it does | | - | - | | `create` | Insert a new related record inline. | | `connect` | Link to an existing record by a unique field. | | `connectOrCreate` | Find by a unique field; create if missing. | The whole nested mutation runs as a single transaction. If any step fails, the entire operation rolls back. ```graphql theme={null} mutation { createDeal(input: { title: "Enterprise License" amount: 50000 contact: { create: { firstName: "Juan", email: "juan@acme.com" } } user: { connect: { id: "user-uuid-123" } } organization: { connectOrCreate: { where: { domain: "acme.com" } create: { name: "Acme Corp", domain: "acme.com" } } } }) { id contact { id } organization { id } } } ``` For the full mutations reference, see [Mutations](/docs/features/backend/graphql-api-explorer/mutations). ## Atomic upserts Tables with at least one unique column expose an `upsert` mutation. It atomically checks the unique field and runs `create` or `update`: ```graphql theme={null} mutation { upsertContact( where: { email: "juan@example.com" } create: { firstName: "Juan" email: "juan@example.com" status: "LEAD" } update: { status: "ACTIVE" } ) { id status } } ``` Use upsert in place of "select then create or update" to eliminate the race condition. ## Modifying relationships The `updateRelationship` mutation changes how a foreign key behaves — cascade behavior, nullability, descriptive metadata. Names map to the underlying database constraint name. ```graphql theme={null} mutation EnableCascade { updateRelationship( tableName: "organization_user" relationship: { name: "organization_user_users_id_fk" onDelete: "CASCADE" description: "Reference to the user belonging to the organization" } ) { name relationships { name onDelete fromColumn } } } ``` | Property | Accepts | What it does | | - | - | - | | `onDelete` | `CASCADE`, `SET NULL`, `RESTRICT`, `NO ACTION` | What happens to child rows when the parent is deleted. | | `onUpdate` | Same set | What happens to child rows when the parent's key changes. | | `isNullable` | `true` / `false` | Whether the relationship column can be null. Required `true` for `SET NULL`. | | `description` | String | Documentation for the relationship. | For modeling cardinality and many-to-many patterns at the schema level, see [Relationships](/docs/features/backend/data-model/relationships). ## Permissions and authentication Filters, nested mutations, and upserts all run through the same authorization checks. Read access is required for filter clauses that touch a related table; write access is required for every step of a nested mutation. See [Role-Based Access](/docs/features/backend/app-services/role-based-access). Authentication tokens come from [App Services → Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). ## FAQ > **When should I use `every` vs `some`?** > > `some` is "at least one child matches" — finds parents that have any matching child. `every` is stricter — all children must match. Note that `every` also matches parents with **zero** children, which is sometimes surprising. > **What's the difference between `connect` and passing a foreign key id directly?** > > `connect` is the schema-level way to link by a unique field — including non-id columns like an email. Passing a raw foreign key id is shorter, but only works when you already have the id and the column is the primary key. > **Are nested mutations slower than separate operations?** > > Faster, in practice. They run in one round-trip and one database transaction, where separate operations require a round-trip per step and don't share a transaction. They also avoid orphaned records when a later step fails. > **Can I combine `having` with `filter`?** > > Yes. `filter` runs before grouping (SQL `WHERE`); `having` runs after (SQL `HAVING`). Use `filter` to scope the rows that participate in the aggregation, and `having` to drop groups whose aggregate doesn't meet a threshold. See [Queries → Grouping and aggregation](/docs/features/backend/graphql-api-explorer/queries#grouping-and-aggregation). > **What's the maximum nesting depth on filters?** > > Deep nesting works for typical use cases. For very deep filter trees, consider the `_query` POST endpoint on the [REST API](/docs/features/backend/rest-api-explorer/queries) — it accepts a JSON body that's easier to compose in code than a deeply nested GraphQL filter literal. # Subscriptions Source: https://archie.com/docs/backend/graphql-api-explorer/subscriptions Real-time event streams over WebSockets. Subscribe to inserts, updates, and deletes on any table. GraphQL subscriptions push events to a connected client whenever data changes. Archie publishes an event when a row is inserted, updated, or deleted on a watched [table](/docs/features/backend/data-model/overview); a subscribed client receives the event over a WebSocket. You configure subscriptions in two stages: 1. **Define the subscription** — what to watch — by calling a mutation under the `system` namespace. 2. **Connect to the WebSocket endpoint** with the subscription's id; events flow over that connection until you disconnect. Subscriptions use the `wss://` protocol. The endpoint is per-environment — see [Environments](/docs/features/backend/environments/overview). ```text theme={null} wss:///subscriptions?project_id= ``` ## Defining a subscription Subscriptions are first-class records. Create them with `system { createSubscription }`, update them with `system { updateSubscription }`, and remove them with `system { deleteSubscription }`. ### Create ```graphql theme={null} mutation CreateSubscription($input: SubscriptionInput!) { system { createSubscription(input: $input) { id name active } } } ``` ```json theme={null} { "input": { "name": "students_create", "description": "Subscribe to new student records", "active": true, "tables": [ { "table": "students", "operations": ["CREATE"], "fields": ["id", "first_name", "email"] } ] } } ``` The response contains the subscription `id`. Hold on to it — that's how you reference the subscription on a WebSocket connection and how you update or delete it later. ### Update ```graphql theme={null} mutation UpdateSubscription($input: SubscriptionInput!) { system { updateSubscription(input: $input) { id name } } } ``` ```json theme={null} { "input": { "id": "id_subscription", "name": "students_all", "active": true, "tables": [ { "table": "students", "operations": ["CREATE", "UPDATE", "DELETE"], "fields": ["id", "first_name"] } ] } } ``` ### Delete ```graphql theme={null} mutation DeleteSubscription($id: String!) { system { deleteSubscription(id: $id) } } ``` ## Watching multiple tables A single subscription can watch any number of tables, each with its own operations and field set: ```json theme={null} { "input": { "name": "orders_and_payments", "active": true, "tables": [ { "table": "orders", "operations": ["CREATE", "UPDATE"], "fields": ["id", "status", "total"] }, { "table": "payments", "operations": ["CREATE"], "fields": ["id", "amount", "order_id"] } ] } } ``` ## Filtered subscriptions To receive events only for records matching a condition, attach `conditions` to a table block. Conditions stack with logical AND. | Operator | Example | | - | - | | `EQ` | `status = "active"` | | `NEQ` | `type != "admin"` | | `GT` | `age > 18` | | `LT` | `price < 100` | | `GTE` | `score >= 80` | | `LTE` | `attempts <= 3` | | `CONTAINS` | `name contains "john"` | | `STARTSWITH` | `email startsWith "admin"` | | `ENDSWITH` | `domain endsWith ".com"` | ```json theme={null} { "input": { "name": "students_25_to_50", "active": true, "tables": [ { "table": "students", "operations": ["CREATE"], "fields": ["id", "first_name", "email", "age"], "conditions": [ { "field": "age", "operator": "GTE", "value": "25" }, { "field": "age", "operator": "LTE", "value": "50" } ] } ] } } ``` You can mix multiple tables with their own conditions in the same subscription: ```json theme={null} { "input": { "name": "active_business_events", "active": true, "tables": [ { "table": "students", "operations": ["CREATE"], "fields": ["id", "first_name", "age"], "conditions": [ { "field": "age", "operator": "GTE", "value": "30" } ] }, { "table": "courses", "operations": ["UPDATE"], "fields": ["id", "code", "price"], "conditions": [ { "field": "price", "operator": "LTE", "value": "500" } ] } ] } } ``` ## On the WebSocket side Once the subscription exists, connect to the WebSocket endpoint and reference the subscription id. Most GraphQL client libraries (Apollo, urql, graphql-ws) handle the connection lifecycle for you — you provide the WebSocket URL and an authentication token, and they deliver events to a callback. Events arrive as JSON payloads matching the `fields` declared on each table block, plus the operation type (`CREATE`, `UPDATE`, `DELETE`). Toggle `active: false` to pause delivery without deleting the subscription. ## Permissions Subscriptions honor the read permissions defined in [Role-Based Access](/docs/features/backend/app-services/role-based-access). A connected client only receives events for records and fields it's allowed to read; everything else is filtered out before delivery. ## Authentication WebSocket connections require the same authentication tokens as queries and mutations — issued by an authentication provider configured in [App Services → Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). Pass the token through the connection params your GraphQL client supports. ## FAQ > **When should I use a subscription instead of polling?** > > Use a subscription when you need event-driven UI updates — collaborative editing, live dashboards, chat — and the upstream change rate is low to moderate. For high-volume or batched changes, polling at a controlled interval is often simpler and cheaper. > **Do subscriptions work in browsers without WebSocket support?** > > Modern browsers all support WebSockets. If you're targeting an environment without WebSocket support, fall back to polling the [REST API](/docs/features/backend/rest-api-explorer/overview) or use [webhooks](/docs/features/backend/rest-api-explorer/webhooks) for server-to-server delivery. > **What's the difference between a subscription and a webhook?** > > A subscription pushes events over a WebSocket to a long-lived client connection. A [webhook](/docs/features/backend/rest-api-explorer/webhooks) pushes events over HTTP to a server URL you control. Subscriptions are best for client UIs; webhooks are best for backend systems integrating with your data. > **Can I subscribe to changes on a related table?** > > Add a separate table block to the same subscription with its own operations, fields, and conditions. Subscriptions across tables run independently within the same subscription record. > **What happens if I miss events while disconnected?** > > Subscriptions are not durable queues — events fire while the WebSocket is connected. For at-least-once delivery to a backend, use [webhooks](/docs/features/backend/rest-api-explorer/webhooks), which retry on failure. # Variables Source: https://archie.com/docs/backend/graphql-api-explorer/variables Parameterize GraphQL operations with typed variables. The right move for reusable, dynamic, and safe queries. Variables let you parameterize a GraphQL operation. The operation document stays static; the values come in through a separate variables object. That's the right pattern for any query you'll run more than once with different inputs — and for any query that takes user input. ## Why use variables | Reason | Why it matters | | - | - | | **Reusability** | The same operation runs with different inputs. No string concatenation. | | **Type safety** | Variables are declared with types. The server validates them against the schema. | | **Safety** | Values are passed separately from the operation, eliminating an entire class of injection bugs that come from interpolating user input into a query string. | | **Caching** | Clients and servers can cache the parsed operation document and key it by variables. | In the API Explorer, the **Variables** panel sits below the editor. It accepts a JSON object whose keys match the variable names declared in your operation. ## Declaring variables Replace the static value with `$variableName`, then declare the variable's type at the top of the operation. Variable types must match the schema — for example, a filter argument expects a `StudentsFilter` input type. ```graphql theme={null} query MyQuery($domain: String) { students(filter: { email: { contains: $domain } }) { items { id firstName email } } } ``` ```json theme={null} { "domain": "example.com" } ``` The `$` prefix is part of the syntax — it tells GraphQL "this is a variable reference, look it up in the variables object." ## Multiple variables Declare each one with its type. Pass them all in the same variables object. ```graphql theme={null} query MyQuery($name_start: String, $status: Boolean) { students( filter: { firstName: { starts_with: $name_start } isActive: { equals: $status } } ) { items { id firstName email } } } ``` ```json theme={null} { "name_start": "M", "status": true } ``` ## Complex variable types Variables aren't limited to scalars. Pass entire filter objects, sort orders, and other input types. ```graphql theme={null} query MyQuery( $filter: StudentsFilter $first: Int $orderBy: StudentsOrderBy ) { students( filter: $filter first: $first orderBy: $orderBy ) { count items { id firstName lastName age } } } ``` ```json theme={null} { "filter": { "email": { "contains": "example" }, "firstName": { "starts_with": "M" } }, "orderBy": { "lastName": "ASC" }, "first": 2 } ``` The exact type names — `StudentsFilter`, `StudentsOrderBy`, etc. — come from your schema. Open the **Documentation** panel in the Explorer or click a type name in the editor to find the right type for any argument. ## Required vs optional Append `!` to a variable type to make it required. Without `!`, the variable is optional — leaving it out of the variables object produces a `null` value at runtime. ```graphql theme={null} mutation Create($input: StudentsCreateInput!) { createStudents(input: $input) { id } } ``` `StudentsCreateInput!` is required — the operation will not run without it. `StudentsCreateInput` (no `!`) is optional, and you'd typically only use that for arguments that have a server-side default. ## Default values You can give a variable a default that applies when the variables object omits it. ```graphql theme={null} query MyQuery($first: Int = 10) { students(first: $first) { items { id firstName } } } ``` The default is applied by the GraphQL parser before validation, so the server treats the operation as if `$first: 10` was supplied. ## Outside the Explorer Every GraphQL client library — Apollo, urql, graphql-request, etc. — accepts an operation string and a variables object as separate arguments to its query function. The shape on the wire is the same: ```json theme={null} { "query": "query MyQuery($domain: String) { students(filter: { email: { contains: $domain } }) { items { id email } } }", "variables": { "domain": "example.com" } } ``` That separation is what gives you the security and caching benefits — the operation is fixed, only the variables vary per call. ## FAQ > **Should I use variables even for one-off queries?** > > For anything that takes user input — search terms, filters, ids from URL params — yes. It's the simplest way to avoid both injection bugs and quoting bugs. For static queries that never change, inlining the values is fine. > **What's the right variable type for a filter argument?** > > The schema has a generated input type for every table's filter — typically `Filter` (for example, `StudentsFilter`). Open the Documentation panel and look at the argument's type — that's the type to use for the variable. > **Can a variable be used as a field in the response?** > > No. Variables only appear in argument positions. To return different fields per call, build the operation document on the client or use [GraphQL fragments](#) for reusable selection sets. > **What happens if I pass a variable of the wrong type?** > > The server returns a validation error before running the operation. The error includes the variable name, the expected type, and the value that was passed. > **Can I use variables in subscriptions and mutations too?** > > Yes — variables work the same way in queries, mutations, and subscriptions. The variable types differ (input types for mutations, the same filter inputs for subscriptions and queries) but the syntax is identical. # Overview Source: https://archie.com/docs/backend/integrations Connect Archie apps to third-party services through built-in integrations or custom ones — credentials live in your project, calls go through the GraphQL API. Integrations connect your Archie app to external services. Archie ships built-in integrations for the most common ones — payments, email, SMS, messaging, and e-commerce — and lets you build custom integrations for anything else. All integrations are configured per project and per environment from **Backend → Integrations**. ![Integrations catalog, showing Connected and Not Connected built-in integrations](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/integrations/overview/integrations-overview.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=2f734f2de466c46ec80a007a175c6734) > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## How integrations work Every built-in integration follows the same model: 1. **Pick a provider** from the catalog (Stripe, SendGrid, Twilio, Slack, Shopify) and click **Connect**. A form asks for that provider's credentials — for Stripe, a secret key, a publishable key, and the environment (Test or Live) they belong to. Credentials are encrypted at rest with AES-256-GCM and never leave the backend. ![Connect modal for Stripe, with Secret Key, Publishable Key, and Environment fields](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/integrations/overview/integrations-connect-modal.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=74f3385e5dfcdca227fd9aaef6084e90) 2. **Call the integration** through the auto-generated GraphQL API. Each integration adds a namespaced set of queries and mutations (e.g. `stripe_createCustomer`, `sendgrid_sendEmail`, `twilio_sendSms`) that you can call from your frontend, custom functions, or external clients. 3. **Receive webhooks** at a generated webhook URL. Archie verifies signatures using the secret you provide and stores events you can query and react to. Per-environment credentials let you keep test mode on development branches and live mode in production. The runtime selects the right credentials automatically based on which environment your app is running in. See [Environments](/docs/features/backend/environments/overview) for how environment scoping works across the rest of the backend. ## Built-in integrations > **[Stripe](/docs/features/backend/integrations/stripe)** > > Payment processing. Customers, payment intents, subscriptions, invoices, refunds, checkout, and the billing portal. > **[SendGrid](/docs/features/backend/integrations/sendgrid)** > > Transactional and marketing email. Single sends, bulk sends, dynamic templates, contacts, and stats. > **[Twilio](/docs/features/backend/integrations/twilio)** > > SMS, voice, and verification flows. > **[Slack](/docs/features/backend/integrations/slack)** > > Send notifications and interactive messages to Slack channels from your app. > **[Shopify](/docs/features/backend/integrations/shopify)** > > Connect your Shopify store — products, orders, and customer data. ## Custom integrations If the service you need isn't in the catalog, build a custom integration. Archie generates the scaffolding (config storage, credential encryption, webhook receiver, and a typed client wrapper) and you fill in the request and response logic. See [Building custom integrations](/docs/features/backend/integrations/building-custom). ## Webhooks Most integrations send webhooks back to your app for asynchronous events — `payment_intent.succeeded`, `email.delivered`, `inbound_sms.received`. Archie generates the receiver route and verifies the signature. You implement the handler. See [Webhooks](/docs/features/backend/integrations/webhooks) for the full pattern. ## FAQ > **Do integrations cost AI credits?** > > No. Integrations consume their own provider quotas (Stripe transaction fees, SendGrid email volume, Twilio per-message fees) — Archie doesn't charge AI credits to call them. > **Can I use a specific version of a provider's SDK?** > > Yes. Add the provider package to your project and call it directly using credentials available at `ctx.integrations..config`. The Archie-generated GraphQL operations cover the common cases; the SDK is there when you need something the GraphQL surface doesn't expose. > **How are credentials kept safe?** > > Secrets are encrypted at rest with AES-256-GCM and decrypted only inside the backend at request time. They are never sent to the frontend or surfaced through the GraphQL API. > **Can I share integration credentials across projects?** > > Not by default. Each project has its own credentials. Enterprise plans support workspace-level secrets — see [Environment variables](/docs/backend/settings/environment-variables). > **What happens if an integration's credentials are missing?** > > Calls to that integration return a clear error pointing at the missing configuration. Existing data and other integrations keep working. # Building Custom Integrations Source: https://archie.com/docs/backend/integrations/building-custom Author custom integrations Archie does not ship. When you need to talk to a service Archie does not provide a built-in for, build a custom integration. This page covers the developer workflow. ## Workflow 1. **Add to the plan** — open the [Integrations card](/docs/features/plan/editing-integrations) and add a custom integration with a clear description of its purpose 2. **Run the build** — the generator scaffolds an integration module 3. **Implement the client** — fill in HTTP requests in the generated client wrapper 4. **Configure credentials** — store API keys or OAuth tokens in the integration settings 5. **Add webhook handlers** — if the service pushes events to you ## Generated scaffolding A custom integration produces: * `integrations//client.ts` — typed wrapper with placeholder methods * `integrations//config.ts` — credential schema (what fields you store) * `api/webhooks//route.ts` — webhook receiver (if your description mentioned receiving events) * A registration entry so the integration appears in the **App services → Integrations** UI ## Implementing the client Replace placeholder methods with real HTTP calls. Use the integration's auth pattern — Bearer token, OAuth, signed request, mTLS. ```typescript theme={null} export class MyServiceClient { constructor(private config: MyServiceConfig) {} async sendEvent(event: Event) { const res = await fetch(`${this.config.baseUrl}/events`, { method: "POST", headers: { "Authorization": `Bearer ${this.config.apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify(event), }) if (!res.ok) throw new Error(`MyService error: ${res.status}`) return res.json() } } ``` ## OAuth-based integrations For OAuth, the workflow includes: * An install flow that redirects users to the third-party authorization page * A callback route that exchanges the authorization code for tokens * Token storage and refresh Archie's OAuth helpers handle the flow scaffolding. ## Webhook handlers The generated webhook route verifies signatures. You implement the event handling — typically by dispatching to a [Logic service](/docs/backend/logic) so the receiver can acknowledge the delivery quickly while the work runs in the background: ```typescript theme={null} export async function handler(req: Request, ctx: Context) { const event = await req.json() switch (event.type) { case "payment.succeeded": // your logic break } return new Response("ok") } ``` ## Publishing and reuse Custom integrations are project-scoped. To reuse across projects in your workspace, package the client as a workspace shared module. ## FAQ > **How do I test webhook handlers locally?** > > Use a tunnel (ngrok, cloudflared) to expose your local dev server, or test against the preview environment. # SendGrid Source: https://archie.com/docs/backend/integrations/sendgrid Send transactional and marketing email — single sends, bulk sends, dynamic templates, contact management, and delivery analytics. The SendGrid integration sends email from your Archie app. Once connected, Archie exposes namespaced GraphQL operations (`sendgrid_*`) for sending, managing contacts, and reading delivery stats. > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## Enable the integration Open **Integrations** in the left sidebar and pick SendGrid. You'll need from your [SendGrid dashboard](https://app.sendgrid.com/settings/api_keys): * **API Key** — generated in **Settings → API Keys**. Give it Mail Send permissions at minimum; add Marketing for contact and template features. * **Environment** — pick the environment context (`Production`, `Staging`, or a custom tag) so credentials route correctly. The API key is encrypted at rest and only decrypted at request time inside the backend. ## What you can do The integration adds operations across SendGrid's core surfaces: * **Send** — `sendgrid_sendEmail`, `sendgrid_sendBulkEmail`, `sendgrid_sendTemplate` * **Contacts** — `sendgrid_addContact`, `sendgrid_updateContact`, `sendgrid_deleteContact` * **Analytics** — `sendgrid_getEmailStats`, `sendgrid_validateEmail` ## Sending email ### A simple transactional email ```graphql theme={null} mutation { sendgrid_sendEmail(email: { from: { email: "hello@yourapp.com", name: "Yourapp" } to: { email: "ada@example.com", name: "Ada" } subject: "Welcome to Yourapp" content: { type: "text/html", value: "

Welcome

Glad you're here.

" } }) { messageId success } } ``` ### CC, BCC, and attachments `to`, `cc`, and `bcc` accept arrays of recipients. Attachments are passed as base64-encoded content: ```graphql theme={null} mutation { sendgrid_sendEmail(email: { from: { email: "hello@yourapp.com" } to: [{ email: "ada@example.com" }] cc: [{ email: "team@yourapp.com" }] subject: "Receipt" content: [{ type: "text/html", value: "

Receipt attached.

" }] attachments: [{ filename: "receipt.pdf" type: "application/pdf" content: "JVBERi0..." disposition: "attachment" }] }) { messageId success } } ``` ### Scheduled email Pass `sendAt` with an ISO 8601 timestamp to defer delivery: ```graphql theme={null} mutation { sendgrid_sendEmail(email: { from: { email: "hello@yourapp.com" } to: [{ email: "ada@example.com" }] subject: "Reminder: invoice due" content: [{ type: "text/html", value: "

Heads up.

" }] sendAt: "2026-06-01T10:00:00Z" }) { messageId success } } ``` ### Dynamic templates For richer designs, use a SendGrid Dynamic Template (the template ID starts with `d-`): ```graphql theme={null} mutation { sendgrid_sendTemplate(email: { from: { email: "hello@yourapp.com" } to: [{ email: "ada@example.com" }] templateId: "d-1234567890abcdef" substitutions: { name: "Ada" orderNumber: "12345" totalAmount: "$99.99" } }) { messageId success } } ``` ### Bulk sends `sendgrid_sendBulkEmail` accepts an array of email payloads in a single call. Useful for onboarding sequences and one-off campaigns under SendGrid's limits. ## Contacts Add, update, and delete recipients in your SendGrid contact lists: ```graphql theme={null} mutation { sendgrid_addContact(contact: { email: "ada@example.com" firstName: "Ada" lastName: "Lovelace" customFields: { plan: "founder" } listIds: ["a1b2c3..."] }) } ``` ## Analytics Pull aggregate delivery metrics for a date range: ```graphql theme={null} query { sendgrid_getEmailStats(startDate: "2026-04-01", endDate: "2026-04-30") { delivered opens clicks bounces spamReports } } ``` `sendgrid_validateEmail` checks an address's deliverability score before you send. ## Webhooks In SendGrid, go to **Settings → Mail Settings → Event Webhook** and point it at the Archie-generated webhook URL for your project. Configure the webhook signing key so Archie can verify events. Supported events include `processed`, `delivered`, `opened`, `clicked`, `bounce`, `dropped`, `spamreport`, and `unsubscribe`. See [Webhooks](/docs/features/backend/integrations/webhooks). ## FAQ > **What sender domains can I use?** > > Any domain you've authenticated in SendGrid. Domain authentication (SPF, DKIM) is required for reliable deliverability — single-sender verification works for testing but limits volume. > **Why is my email going to spam?** > > Almost always a sender authentication issue. Make sure you've completed domain authentication in SendGrid, set up DMARC, and that your `from` address matches the authenticated domain. Warm new domains by sending gradually-increasing volume. > **Can I send marketing emails through this integration?** > > Yes, including dynamic templates and contact list segmentation. For high-volume marketing campaigns specifically, SendGrid's Marketing Campaigns UI gives you scheduling and analytics that the API doesn't expose. > **My API key returned 403 on email validation. What happened?** > > SendGrid's email validation endpoint requires a paid Email Validation add-on. If your key doesn't have it, `sendgrid_validateEmail` falls back to a basic regex check and returns a slightly lower score with a note explaining why. > **How do I track which user clicked a link?** > > Pass a `customArgs` object on the send. SendGrid attaches it to every event for that message — Archie surfaces those custom args in the webhook events you receive. # Shopify Source: https://archie.com/docs/backend/integrations/shopify Read and write Shopify store data — products, orders, customers, and inventory — through Archie's GraphQL API. The Shopify integration connects your Archie app to a Shopify store. Once configured, you can read and update products, orders, customers, and inventory through namespaced GraphQL operations and react to store events via webhooks. > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## Enable the integration Open **Integrations** in the left sidebar and pick Shopify. You'll need a Shopify Partner app or a custom app installed on your store (Shopify Admin → **Apps and sales channels → Develop apps**). From the app's settings: * **API Key** — under **API credentials**. * **API Secret** — same place. Used to authenticate API calls and verify webhook signatures. * **Store URL** — your shop's `.myshopify.com` domain (e.g. `acme.myshopify.com`). * **Environment** — `Development` or `Production`, so credentials route to the right environment. Optional: * **Access Token** — set this if you want to skip the OAuth handshake and use a direct admin token from a private app. * **Webhook Secret** — separate from the API secret; used specifically to verify Shopify webhook payloads. Secrets are encrypted with AES-256-GCM at rest. ## What you can do The integration covers the most common Shopify Admin API surfaces: * **Products** — list, get, create, update, delete; manage variants and metafields * **Orders** — list, get, create, update, fulfill, refund, cancel * **Customers** — list, get, create, update, search * **Inventory** — read and adjust stock levels per location * **Webhooks** — subscribe to store events from your Archie app ## Reading and writing data ### List products ```graphql theme={null} query { shopify_products(first: 25) { edges { node { id title status totalInventory priceRange { minVariantPrice { amount currency } maxVariantPrice { amount currency } } } } pageInfo { hasNextPage endCursor } } } ``` ### Create an order draft ```graphql theme={null} mutation { shopify_createDraftOrder(input: { customerId: "gid://shopify/Customer/12345" lineItems: [ { variantId: "gid://shopify/ProductVariant/67890", quantity: 2 } ] note: "Manual order from internal CRM" }) { id invoiceUrl } } ``` ### Update inventory ```graphql theme={null} mutation { shopify_adjustInventory(input: { inventoryItemId: "gid://shopify/InventoryItem/...." locationId: "gid://shopify/Location/...." delta: -1 }) { available } } ``` ### Search customers ```graphql theme={null} query { shopify_searchCustomers(query: "email:ada@example.com") { edges { node { id firstName lastName email ordersCount totalSpent } } } } ``` ## Webhooks In Shopify Admin, go to **Settings → Notifications → Webhooks** and add the events you want, pointing each at the Archie-generated Shopify webhook URL for your project. Archie verifies the HMAC signature using your webhook secret before storing or dispatching the event. Common events to subscribe to: * `orders/create`, `orders/paid`, `orders/cancelled`, `orders/fulfilled` * `products/create`, `products/update`, `products/delete` * `customers/create`, `customers/update` * `inventory_levels/update` * `app/uninstalled` (so you can clean up if your app is removed) See [Webhooks](/docs/features/backend/integrations/webhooks) for receiving events in custom functions. ## FAQ > **Custom app vs. Partner app — which should I use?** > > For a single store, a custom app inside Shopify Admin is simpler. For multi-store distribution or a public app on the Shopify App Store, you need a Partner app with OAuth. Both work with this integration; the credentials are the same shape. > **Why are IDs in the format `gid://shopify/...`?** > > Shopify's GraphQL Admin API uses Global IDs. The legacy REST IDs (numeric) and GraphQL IDs are interchangeable — Archie accepts either format, but the GraphQL form is what comes back in responses. > **How do I sync product changes from Shopify into my Archie data model?** > > Subscribe to `products/update` and `products/create` webhooks. In the handler, upsert into your Archie data model. For initial sync, paginate `shopify_products` until `hasNextPage` is false. > **What about rate limits?** > > Shopify rate-limits the Admin API on a leaky-bucket model — roughly 2 requests per second on standard plans, more on Shopify Plus. If you need to do bulk reads, use Shopify's Bulk Operation API rather than paginating manually; the integration exposes `shopify_bulkOperationRunQuery` for it. > **Can I run the integration against a development store?** > > Yes. Development stores work the same as live stores. Use the development store's `.myshopify.com` URL and a custom app installed there, with your integration environment set to `Development`. # Slack Source: https://archie.com/docs/backend/integrations/slack Send notifications and interactive messages to Slack channels and DMs from your Archie app. The Slack integration lets your Archie app post messages, alerts, and interactive Block Kit cards into your team's Slack workspace. Once connected, you call Slack through namespaced GraphQL operations. > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## Enable the integration Open **Integrations** in the left sidebar and pick Slack. You'll need a [Slack App](https://api.slack.com/apps) with the right scopes installed in your workspace. From the app's settings: * **Bot Token** — `xoxb-...`. Found under **OAuth & Permissions**. Needs `chat:write` for posting; add `chat:write.public` if you want to post to channels the bot isn't a member of. * **App Token** — `xapp-...`. Found under **Basic Information → App-Level Tokens**. Needed if you use Socket Mode or subscribe to events. * **Signing Secret** — under **Basic Information → App Credentials**. Used to verify requests Slack sends to your app. * **Environment** — pick which environment this configuration applies to (e.g. `Production`, `Staging`). Tokens are encrypted at rest and only decrypted at request time. ## What you can do * **Send messages** — to channels, threads, or DMs * **Send rich messages** — Block Kit blocks, attachments, buttons * **Schedule messages** — deliver later at a chosen time * **Update and delete** — edit or remove a previously-sent message * **Receive events** — slash commands, button clicks, and interactivity callbacks via webhooks ## Sending a message ### A simple notification ```graphql theme={null} mutation { slack_sendMessage(input: { channel: "#general" text: "Build #482 deployed to production" }) { ts channel } } ``` The returned `ts` is the message timestamp — keep it if you want to update or thread on it later. ### Block Kit For richer messages with buttons or sections, pass a Block Kit `blocks` array: ```graphql theme={null} mutation { slack_sendMessage(input: { channel: "C0123456789" text: "New signup" blocks: """ [ { "type": "section", "text": { "type": "mrkdwn", "text": "*New signup:* ada@example.com" }}, { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "View" }, "url": "https://yourapp.com/admin/users/123" } ]} ] """ }) { ts } } ``` ### Reply in a thread Pass `threadTs` to thread under an existing message: ```graphql theme={null} mutation { slack_sendMessage(input: { channel: "C0123456789" threadTs: "1714060800.000100" text: "Resolution: rolled back release v1.2.3" }) { ts } } ``` ### Direct messages Pass a user ID (`U...`) as the channel to DM that user. The bot must have the `chat:write` scope and the user must be in a workspace where the app is installed. ### Scheduled messages ```graphql theme={null} mutation { slack_scheduleMessage(input: { channel: "#announcements" text: "Office reopens in 1 hour" postAt: "2026-06-01T14:00:00Z" }) { scheduledMessageId } } ``` ## Webhooks If you want to receive events from Slack — slash commands, button clicks, mention events — point your Slack App's **Event Subscriptions** request URL and **Interactivity** request URL at the Archie-generated Slack webhook URL for your project. Archie verifies the signing secret on every event. See [Webhooks](/docs/features/backend/integrations/webhooks) for handling them in custom functions. ## FAQ > **Channel name vs. channel ID — which should I pass?** > > Both work for public channels. Channel IDs (`C0123456789`) are more reliable because they don't change if the channel is renamed. For private channels and DMs, the ID is required and the bot must be a member. > **My bot can't post to a channel. What's wrong?** > > The bot has to be invited to the channel — `/invite @yourbot` from inside Slack. Public channels can be posted to without joining if you've granted the `chat:write.public` scope, but private channels always require membership. > **How do I respond to a button click?** > > Slack POSTs an interactivity payload to the URL configured under **Interactivity & Shortcuts**. Point that at Archie's webhook URL and handle the action in a custom function. Reply within 3 seconds with a 200 OK to acknowledge, then post a follow-up message asynchronously if needed. > **Can I send DMs to users who aren't in the workspace?** > > No. Slack only allows messaging users who are members of a workspace your app is installed in. For broader notifications, use email (SendGrid) or SMS (Twilio). > **What's the rate limit?** > > Slack's Web API allows roughly 1 message per second per channel, with short bursts allowed. For high-volume notifications, batch into a single message with multiple Block Kit sections or use a thread to group related updates. # Stripe Source: https://archie.com/docs/backend/integrations/stripe Process payments, manage subscriptions, and run a Stripe-hosted billing portal — all through Archie's auto-generated GraphQL API. The Stripe integration handles payments, subscriptions, invoicing, and refunds in your Archie app. Once you connect it, Archie exposes a namespaced set of GraphQL queries and mutations (`stripe_*`) that your frontend, custom functions, and backend routes can call. > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## Enable the integration Open the **Integrations** module in the left sidebar and pick Stripe from the catalog. You'll need from your [Stripe dashboard](https://dashboard.stripe.com/apikeys): * **Secret Key** — starts with `sk_test_` (test mode) or `sk_live_` (production). Used by the backend to authenticate API calls. * **Publishable Key** — starts with `pk_test_` or `pk_live_`. Safe to use in the frontend. * **Environment** — pick `TEST` while building, `LIVE` when you're ready to take real payments. * **Webhook Secret** *(optional)* — `whsec_...`. Required if you want Archie to verify Stripe webhook signatures. Archie encrypts the secret key with AES-256-GCM before storing it. Keys are scoped per project per environment, so test credentials on a development branch never touch production. ## What you can do The integration adds GraphQL operations for everything most apps need from Stripe: * **Customers** — `stripe_customer`, `stripe_customers`, `stripe_createCustomer`, `stripe_updateCustomer`, `stripe_deleteCustomer` * **Payment intents** — `stripe_paymentIntent`, `stripe_paymentIntents`, `stripe_createPaymentIntent`, `stripe_updatePaymentIntent`, `stripe_confirmPaymentIntent`, `stripe_cancelPaymentIntent` * **Subscriptions** — `stripe_subscription`, `stripe_subscriptions`, `stripe_createSubscription`, `stripe_updateSubscription`, `stripe_cancelSubscription` * **Products and prices** — `stripe_products`, `stripe_createProduct`, `stripe_prices`, `stripe_createPrice` * **Invoices** — `stripe_invoice`, `stripe_invoices`, `stripe_payInvoice` * **Refunds** — `stripe_refunds`, `stripe_createRefund` * **Checkout sessions** — `stripe_createCheckoutSession` for Stripe-hosted checkout * **Billing portal** — `stripe_createBillingPortalSession` for self-serve subscription management * **Payment methods** — `stripe_paymentMethods`, `stripe_attachPaymentMethod`, `stripe_detachPaymentMethod` * **Webhook events** — `stripe_webhookEvents` for inspecting signed events Archie has received All list queries support cursor-based pagination via `first` and `after`. ## Calling Stripe from your app ### Create a customer ```graphql theme={null} mutation { stripe_createCustomer(input: { name: "Ada Lovelace" email: "ada@example.com" phone: "+15551234567" metadata: { plan: "founder" } }) { id email createdAt } } ``` ### Create a Checkout session For Stripe-hosted checkout (the easiest path for most builders): ```graphql theme={null} mutation { stripe_createCheckoutSession(input: { mode: "subscription" customerEmail: "ada@example.com" successUrl: "https://yourapp.com/welcome" cancelUrl: "https://yourapp.com/pricing" lineItems: [{ priceId: "price_1ABC...", quantity: 1 }] }) { id url } } ``` Redirect the user to the returned `url`. Stripe handles the payment and redirects to your `successUrl` when complete. ### Open the billing portal Let customers manage their own subscription: ```graphql theme={null} mutation { stripe_createBillingPortalSession(input: { customerId: "cus_..." returnUrl: "https://yourapp.com/account" }) { url } } ``` ### Create a payment intent (manual flow) If you want to render a custom checkout with Stripe.js or Stripe Elements: ```graphql theme={null} mutation { stripe_createPaymentIntent(input: { amount: 49.99 currency: "usd" customerId: "cus_..." automaticPaymentMethods: true }) { id clientSecret status } } ``` Pass the returned `clientSecret` to your frontend Stripe.js confirm step. ## Webhooks When you save the integration, Archie generates a webhook URL specific to your project and environment. Add it in the Stripe dashboard under **Developers → Webhooks**, and provide the resulting signing secret in the integration config. Archie verifies every incoming event before storing it. Query received events with `stripe_webhookEvents`, or react to them inside a custom function — see [Webhooks](/docs/features/backend/integrations/webhooks). Common events you'll likely care about: * `payment_intent.succeeded` * `payment_intent.payment_failed` * `customer.subscription.created` / `updated` / `deleted` * `invoice.paid` / `invoice.payment_failed` * `checkout.session.completed` ## FAQ > **Test mode vs. live mode — when does each apply?** > > Archie selects credentials based on the environment your app is running in. Development environments use the keys saved with `TEST`; production uses the ones saved with `LIVE`. Setting up both up front means you can swap stages without touching code. > **Can I use Stripe.js or Stripe Elements on the frontend?** > > Yes. Use `stripe_createPaymentIntent` to get a `clientSecret`, then confirm it on the frontend with the publishable key. The publishable key is returned from the integration config and is safe to expose in client code. > **How do I refund a payment?** > > Call `stripe_createRefund` with the `paymentIntentId`. Omit `amount` for a full refund or pass an amount for partial. Refunds start as `pending` and update to `succeeded` or `failed` once Stripe processes them. > **What happens to webhook events if my app is down?** > > Stripe retries failed webhook deliveries with exponential backoff for up to three days. Once your app is reachable, Archie verifies and stores the event normally. You can also replay events from the Stripe dashboard. > **Can I use a specific Stripe SDK version directly?** > > Yes. Add the `stripe` package to your custom function and call it with `ctx.integrations.stripe.config.secretKey`. The GraphQL operations cover most cases; the SDK is for anything they don't expose. # Twilio Source: https://archie.com/docs/backend/integrations/twilio Send SMS, place voice calls, and run verification flows from your Archie app through Twilio's programmable communications API. The Twilio integration handles SMS, voice, and verification in your Archie app. Once connected, you can send messages, kick off verification codes, and receive inbound events — all through the auto-generated GraphQL API. > [!NOTE] > > A typed TypeScript SDK for integrations is coming with the next archie-app release. This page documents the GraphQL operation surface available today. ## Enable the integration Open **Integrations** in the left sidebar and pick Twilio. You'll need from your [Twilio Console](https://console.twilio.com/): * **Account SID** — starts with `AC...`. Acts as the username for API calls. * **Auth Token** — your account's secret. Treat it like a password. Optional, depending on what you're building: * **Messaging Service SID** — `MG...`, if you use a Messaging Service for SMS. * **Verify Service SID** — `VA...`, if you use Twilio Verify for OTP and 2FA flows. The auth token is encrypted with AES-256-GCM at rest and only decrypted in the backend at request time. ## What you can do * **SMS** — send single and bulk messages, with Messaging Service support * **Voice** — place calls, send TwiML responses, manage call status * **Verify** — start verification challenges, check codes, run 2FA flows * **Webhook events** — receive inbound SMS, status callbacks, and Verify events ## Sending an SMS ```graphql theme={null} mutation { twilio_sendSms(input: { to: "+15551234567" from: "+15557654321" body: "Your code is 123456" }) { sid status } } ``` For high-volume SMS, send through a Messaging Service instead of a single from-number — it handles rotation, carrier registration, and opt-out: ```graphql theme={null} mutation { twilio_sendSms(input: { to: "+15551234567" messagingServiceSid: "MG..." body: "Welcome to Yourapp" }) { sid status } } ``` ## Verification flows (Twilio Verify) Start a verification: ```graphql theme={null} mutation { twilio_startVerification(input: { serviceSid: "VA..." to: "+15551234567" channel: "sms" }) { sid status } } ``` Check a code: ```graphql theme={null} mutation { twilio_checkVerification(input: { serviceSid: "VA..." to: "+15551234567" code: "123456" }) { valid status } } ``` Verify supports SMS, voice, email, and TOTP channels — pick the one that matches your flow. ## Placing a voice call ```graphql theme={null} mutation { twilio_makeCall(input: { to: "+15551234567" from: "+15557654321" twimlUrl: "https://yourapp.com/voice/welcome" }) { sid status } } ``` `twimlUrl` should return TwiML that tells Twilio what to say or play. ## Webhooks Configure inbound webhooks in the Twilio Console: * **Phone Numbers → Active numbers → \[number] → Messaging** — point at Archie's generated SMS webhook URL for inbound messages. * **Phone Numbers → Active numbers → \[number] → Voice** — point at Archie's generated voice webhook URL. * **Verify Service → Webhooks** — point at Archie's Verify webhook URL for status changes. Archie verifies Twilio's `X-Twilio-Signature` header before accepting events. See [Webhooks](/docs/features/backend/integrations/webhooks) for handling them in custom functions. ## FAQ > **Do I need a Messaging Service or can I just use a single from-number?** > > A single from-number works fine for low volume and dev testing. Use a Messaging Service when you start sending more than a few hundred SMS per day, want number rotation across multiple numbers, or need carrier-mandated registration (A2P 10DLC in the US). > **What's the difference between sending an OTP via SMS directly and using Twilio Verify?** > > Verify handles code generation, expiry, attempt limits, and channel fallback (SMS, voice, email, TOTP) for you. If you send OTPs via raw SMS, you implement all of that yourself. Use Verify unless you have a reason not to. > **How do I receive inbound SMS replies?** > > Set the messaging webhook on your Twilio number to Archie's generated SMS webhook URL. Inbound messages arrive as webhook events you can react to in a custom function — see [Webhooks](/docs/features/backend/integrations/webhooks). > **Can I send international SMS?** > > Yes, but you need to enable geo permissions in the Twilio Console (**Messaging → Settings → Geo Permissions**) and may need country-specific sender registration. Twilio also charges per-country rates. > **What happens to my flow if Twilio is down?** > > Your call to `twilio_sendSms` returns an error and your code can decide what to do — retry, fall back to email through SendGrid, or queue for later. Webhook events are retried by Twilio with backoff if your endpoint isn't reachable. # Webhooks Source: https://archie.com/docs/backend/integrations/webhooks Receive webhooks from your app or send them to external systems. Webhooks allow you to receive real-time HTTP notifications when data changes in your Archie Core project. When a record is created, updated, or deleted, Archie Core sends a `POST` request to your configured URL with the event details. ## How webhooks work 1. **Register** a webhook subscription specifying the table, events, and target URL 2. **Data changes** — when a matching mutation occurs (via REST API or GraphQL), an event is published 3. **Delivery** — the webhook service sends an HTTP `POST` to your URL with the event payload 4. **Verification** — validate the HMAC signature to ensure the request is authentic ## Webhook management API All webhook management endpoints are under `/api/rest/_webhooks` and require a management API token. ### Required headers | Header | Required | Description | | - | - | - | | `Authorization` | Yes | `Bearer ` | | `X-Project-ID` | Yes | Your project identifier | | `environment` | Yes | Target environment name | > **Important:** The `environment` header is **required** for all webhook management endpoints. The header value must match the `environment` field in the request body when creating or updating webhooks. ### Endpoints | Method | Endpoint | Description | | - | - | - | | `POST` | `/api/rest/_webhooks` | Create a webhook subscription | | `GET` | `/api/rest/_webhooks` | List all webhook subscriptions | | `GET` | `/api/rest/_webhooks/:id` | Get subscription + deliveries | | `PATCH` | `/api/rest/_webhooks/:id` | Update a subscription | | `DELETE` | `/api/rest/_webhooks/:id` | Delete a subscription | ## Create a webhook **Request** ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/_webhooks" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "projectId": "your-project-id", "environment": "master", "table": "orders", "events": ["INSERT", "UPDATE", "DELETE"], "url": "https://your-app.example.com/webhooks/orders", "secret": "whsec_your_signing_secret", "active": true }' ``` ### Request body | Field | Type | Required | Description | | - | - | - | - | | `projectId` | String | Yes | Project identifier | | `environment` | String | Yes | Target environment | | `table` | String | Yes | Table name to watch | | `events` | Array | Yes | Event types: `INSERT`, `UPDATE`, `DELETE` | | `url` | String | Yes | HTTPS endpoint to receive events | | `secret` | String | Yes | Secret for HMAC signature verification | | `active` | Boolean | No | Enable/disable the webhook (default: `true`) | | `filter` | Object | No | Only trigger for records matching this filter | | `select` | Array | No | Fields to include in the payload | ### Optional filtering Only fire the webhook when specific conditions are met: ```json theme={null} { "table": "orders", "events": ["UPDATE"], "url": "https://your-app.example.com/webhooks/completed-orders", "secret": "whsec_secret", "filter": { "status": "completed" } } ``` ### Field selection Limit which fields are included in the event payload: ```json theme={null} { "table": "orders", "events": ["INSERT"], "url": "https://your-app.example.com/webhooks/new-orders", "secret": "whsec_secret", "select": ["id", "total", "status", "customerId"] } ``` **Response (201 Created)** ```json theme={null} { "id": "wh_a1b2c3d4-e5f6-7890", "projectId": "your-project-id", "environment": "master", "table": "orders", "events": ["INSERT", "UPDATE", "DELETE"], "url": "https://your-app.example.com/webhooks/orders", "active": true, "createdAt": "2025-12-15T14:30:00Z" } ``` ## List webhooks ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/_webhooks" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "environment: master" ``` ## Get webhook details Returns the webhook subscription along with its recent delivery history. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "environment: master" ``` ## Update a webhook ```bash theme={null} curl -X PATCH "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "environment: master" \ -H "Content-Type: application/json" \ -d '{ "events": ["INSERT", "UPDATE"], "active": false }' ``` ## Delete a webhook ```bash theme={null} curl -X DELETE "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "environment: master" ``` ## Event payload When a data mutation occurs, the webhook service delivers an HTTP `POST` to your URL: ```json theme={null} { "id": "evt_abc123def456", "timestamp": "2025-12-15T15:00:00Z", "project_id": "your-project-id", "environment": "master", "table": "orders", "event": "INSERT", "record": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "total": 99.50, "status": "pending", "customerId": "cust-001" }, "data": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "total": 99.50, "status": "pending", "customerId": "cust-001" }, "changed_fields": ["total", "status"] } ``` | Field | Description | | - | - | | `id` | Unique event identifier | | `timestamp` | ISO 8601 timestamp of the event | | `project_id` | Project that generated the event | | `environment` | Environment where the mutation occurred | | `table` | Table that was modified | | `event` | Event type: `INSERT`, `UPDATE`, or `DELETE` | | `record` | The full record (or selected fields) after mutation | | `data` | Same as `record` (alias for compatibility) | | `changed_fields` | List of fields that changed (for `UPDATE` events) | > **Note:** For `DELETE` events, `record` and `data` contain the record as it was before deletion. The `changed_fields` array is empty for `INSERT` and `DELETE` events. ## Delivery headers Each webhook delivery includes these headers: | Header | Value | Description | | - | - | - | | `Content-Type` | `application/json` | Payload format | | `X-Archie-Event` | `INSERT` / `UPDATE` / `DELETE` | Event type | | `X-Archie-Delivery` | `evt_abc123def456` | Event ID for tracking | | `X-Archie-Signature` | `sha256=` | HMAC-SHA256 signature with prefix | | `X-Webhook-Signature` | `` | Raw HMAC-SHA256 hex digest | ## Verifying signatures Every delivery is signed using HMAC-SHA256 with the webhook's `secret`. Always verify the signature before processing the payload. ### Node.js example ```javascript theme={null} const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); const sig = signature.replace('sha256=', ''); return crypto.timingSafeEqual( Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex') ); } // In your webhook handler: app.post('/webhooks/orders', (req, res) => { const signature = req.headers['x-archie-signature']; const isValid = verifyWebhookSignature( JSON.stringify(req.body), signature, 'whsec_your_signing_secret' ); if (!isValid) { return res.status(401).send('Invalid signature'); } // Process the event const { event, table, record } = req.body; console.log(`${event} on ${table}:`, record); res.status(200).send('OK'); }); ``` ### Python example ```python theme={null} import hmac import hashlib def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode('utf-8'), payload, hashlib.sha256 ).hexdigest() sig = signature.replace('sha256=', '') return hmac.compare_digest(sig, expected) ``` ## Retry policy If your endpoint returns a non-2xx response or is unreachable, the webhook service retries delivery with exponential backoff: | Attempt | Delay | | - | - | | 1 | Immediate | | 2 | \~1 second | | 3 | \~5 seconds | | 4 | \~30 seconds | | 5 | \~5 minutes | After the maximum retry attempts, the delivery is moved to a **dead letter** state and can be inspected via the webhook details endpoint. ### Circuit breaker If an endpoint fails consecutively, the webhook service activates a circuit breaker to protect your endpoint: * **Opens** after 5 consecutive failures per endpoint * **Cooldown** period: 2 minutes * During cooldown, deliveries are queued and retried after the cooldown expires ## Best practices * **Always verify signatures** — never trust a webhook payload without HMAC verification * **Respond quickly** — return a `200` status within 5 seconds; process events asynchronously if needed. Long-running handlers are the natural use case for [Logic services](/docs/backend/logic) — the webhook receiver dispatches to a function, which acknowledges immediately and processes in the background. * **Handle duplicates** — use the event `id` to implement idempotent processing * **Use HTTPS** — webhook URLs must use HTTPS for secure delivery * **Monitor deliveries** — check the webhook details endpoint regularly to catch persistent failures ## FAQ > **Where do I find webhook URLs to register with the third-party service?** > > The integration's settings page in your project shows the URL to register. > **What is the timeout for handlers?** > > Return a `200` status within 5 seconds. Process longer work asynchronously and acknowledge the delivery first. > **Can I see a log of every webhook received?** > > Yes — the webhook details endpoint (`GET /api/rest/_webhooks/:id`) returns recent deliveries with status, HTTP code, attempts, and timestamp. # Logic Source: https://archie.com/docs/backend/logic Logic is where you add backend code that runs next to your project's API. Generate it with AI, scaffold it in a language you choose, or connect a git repository you already have. **Logic** is the part of the Backend Console where you add code that runs alongside your project's API. In the console, the page is titled **Custom Services**: backend services you own, deployed next to your API on a path you choose. Use Logic for behavior that the auto-generated [GraphQL](/docs/backend/graphql-api-explorer) and [REST](/docs/backend/rest-api-explorer) APIs don't cover on their own, such as "when an order ships, notify the buyer and record the shipping date." ## Open Logic Open your project, switch to **Backend**, and select **Logic** in the Backend Console navigation. Select the [environment](/docs/backend/environments) you want to work in from the environment menu on the right. When the environment has no services yet, the page shows **Create your first custom service** and three ways to start. ![The Custom Services empty state with Start from scratch, Import from Git, and Generate with AI cards under the heading Create your first custom service.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/17-logic-empty-state.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=3984cbbb96190751311a2ee86f3606ad) ## One service per project For now, each project can have one service. When a service already exists, **New service** is unavailable and its tooltip reads "For now each project can have one service." Once a service exists, the page lists it in a table with its **Service** name, **Origin** (for example, "From scratch"), **Language**, and **Status**. The **Status** column shows values such as **Not deployed yet**, **Succeeded**, and **Failed**. Use **Search services…** to filter the list and **Refresh** to reload it. Each row also has an **Actions** menu with **Open in new tab** and **Delete**. Click a service name to open it. See [Working with a service](/docs/backend/logic/working-with-a-service). ![The Custom Services list with one service and its origin, language, and status.](https://mintcdn.com/archie-e998dbf6/JW67AKv9r3EIl9M1/images/42-logic-services-list.png?fit=max\&auto=format\&n=JW67AKv9r3EIl9M1\&q=85\&s=022433da3bd2130f72c09fec8de75b5c) ## Ways to add logic Click **New service** in the page header, or pick one of the cards in the empty state. The **Add Logic** dialog asks how you want to start. ![The Add Logic dialog with Add With AI, From Scratch, and From a Code Repository options.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/18-logic-add-logic-modes.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=91cbedadbe7c1274a39be669fe8f6563) > **[Add With AI](/docs/backend/logic/creating-with-ai)** > > Describe what should happen. Archie maps out the functions it needs and you choose which to generate. Archie marks this option as recommended. > **[From Scratch](/docs/backend/logic/from-scratch)** > > Archie creates and hosts the code. You edit it in Archie and deploy with one click. > **[From a Code Repository](/docs/backend/logic/from-a-repository)** > > Connect your own git repository. It deploys on every push. ## After you create a service Open the service to edit its code, deploy it, and set its variables: > **[Working with a service](/docs/backend/logic/working-with-a-service)** > > The service page, the code editor, the deploy file, and deleting a service. > **[Deployments](/docs/backend/logic/deployments)** > > Deploy, compare Head with the live version, read the pipeline, and fix a failed deploy. > **[Environment variables](/docs/backend/logic/environment-variables)** > > Configuration and secrets shared by the services in an environment. ## Services and functions A **service** is the deployable unit. It has one repository, one language, and one deploy history. A **function** is one operation inside a service, such as a GraphQL query, a GraphQL mutation, or a REST route. When a deploy finishes, Archie registers the service's operations with your project's API. ## Supported languages Services can use **Node.js**, **Python**, **Go**, **.NET**, or **Java**. The AI option also offers **Let Archie Decide**. ## FAQ > **Why is this page called Custom Services?** > > **Logic** is the name in the Backend Console navigation. The page header reads **Custom Services**. Both names refer to the same page. > **Which option should I pick?** > > Pick **Add With AI** when you can describe the behavior in plain words. Pick **From Scratch** when you want to write the code yourself in Archie. Pick **From a Code Repository** when the code already lives in GitHub, GitLab, or another git host. > **Is Logic the same as scheduled tasks?** > > No. A [scheduled task](/docs/backend/scheduled-tasks) is a schedule that calls a target. Logic is code you add and deploy. # Creating logic with AI Source: https://archie.com/docs/backend/logic/creating-with-ai Describe what your backend should do in plain words. Archie works out which functions you need, asks about anything unclear, and lets you choose what to generate. **Add With AI** turns a plain-language description into a set of functions. It takes two steps: describe what should happen, then choose what to generate. ## Step 1: describe it In the **Add Logic** dialog, select **Add With AI** and click **Continue**. The first step is titled **Add Logic With AI** and reads **Step 1 of 2 · Describe It**. ![The Describe It step with an empty What Should Happen box, the Improve button, the language options, and four suggestions from your data.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/19-logic-describe-with-ai.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=afc5c462b4af2ccc6e0111a812f27d54) 1. Type what should happen in **What Should Happen?**. For example: "When an inspection fails, notify the owner, open a work order and update the risk score." 2. Optionally click **Improve** to refine your description. 3. Choose a **Language**: **Node.js**, **Python**, **Go**, **.NET**, **Java**, or **Let Archie Decide**. 4. Click **Continue**, or press ⌘ + Enter. ### Suggestions from your data Under **Suggested from your data**, Archie lists ready-made descriptions based on your data model. Each one shows how many functions it expects and how they relate, such as "3 functions · side by side" or "4 functions · one feeds 3". Select one to fill in the description. ## Step 2: choose what to generate The second step is titled **Step 2 of 2 · Choose What to Generate**. Archie summarizes how it understood your request and shows what it plans to build. ![The Choose What to Generate step. The left column shows the description, Archie's summary, and open questions. The right column shows the service name, language, and one function with a checkbox.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/20-logic-function-map.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=0c4dbadd38503e315b57fc4a86842a7b) * **Questions.** When something is unclear, Archie asks. A question offers options, or a text box with an **Answer** button, and Archie marks its recommendation. Answer it, or click **Skip**. * **Service.** The service name Archie proposes, such as `bug-resolution`. Names are 3–40 characters: lowercase letters, numbers, and single hyphens, starting with a letter. You can edit it. * **Language.** The language Archie chose, or the one you picked. You can change it. * **Functions.** Each function has a checkbox and a short description. Clear the checkbox for any function you don't want. Click **Add Function** to add one of your own. The footer shows how many functions are selected. Click **Edit Description** to go back to step 1, or **Cancel** to close the dialog without creating anything. When you're ready, click **Generate**, or press ⌘ + Enter. > [!NOTE] > > Archie generates only the functions you leave selected. Nothing is created until you click **Generate**. ## After you create the service After Archie generates the functions, open the service. Review the code in the [Code tab](/docs/backend/logic/working-with-a-service), then [deploy it](/docs/backend/logic/deployments) to make the functions callable. Set any [environment variables](/docs/backend/logic/environment-variables) the code needs before you deploy. ## FAQ > **Why do the suggestions mention my own tables?** > > Suggestions are built from your data model, so they reference the tables and fields in your project. > **Can I create more than one service?** > > Not yet. Each project can have one service. See [Logic](/docs/backend/logic#one-service-per-project). > **What if I want to start from code instead?** > > Use [From Scratch](/docs/backend/logic/from-scratch) or [From a Code Repository](/docs/backend/logic/from-a-repository). # Deployments Source: https://archie.com/docs/backend/logic/deployments Deploy a service, compare Head with the live version, read the deploy pipeline step by step, and find out why a deploy failed. Redeploy an earlier commit when you need to go back. A deploy builds the service's latest commit and runs it through a pipeline. When every step passes, that commit becomes the **Live version**. ## Deploy a service Click **Deploy** in the service header. The header shows **Deploying** and the progress, such as **Deploying · 5/17**, while the pipeline runs. You can keep working. Only one deploy runs at a time, so starting another while one is running shows the existing one. Each deploy builds **Head**, the latest commit. If you edited files, [save them](/docs/backend/logic/working-with-a-service#save-and-commit) first. Changes you haven't saved aren't deployed. A failed deploy never replaces the live version. The service keeps running the last good commit. ## Deployment history Open the **Deployments** tab. The number next to the tab is the count of deploys for this service. ![The Deployments tab with the deploy list on the left and a failed deploy open on the right, showing the failed step and the pipeline.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/35-logic-deployments.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=e1357d5c5edf4d4989a014441a811411) The list on the left groups deploys by day and shows, for each one: * The commit message and the short commit hash. * Who started it and the time. * The trigger, such as **Manual**. * The duration. * A **Live** label on the deploy that's running now. At the top of the list, a summary reads like "14 deploys · median 2m 15s". Use **All** and **Failed** to filter the list. Select a deploy to see its details on the right. ### Deploy states | State | What it means | | - | - | | **Queued** | The deploy is waiting to start. | | **Deploying** | The pipeline is running. The details show the current step. | | **Succeeded** with **Live** | This commit is running now. | | **Succeeded** with **Superseded** | This deploy succeeded, and a newer deploy has since replaced it. | | **Deployed with N warnings** | The deploy finished and the service is live, but one or more steps reported a warning. | | **Failed** | A step failed. The details name it, such as **Failed at Build image**. | A successful deploy shows how long it was live and how it compares to the median, for example "16.3s faster than the median". ## The deploy pipeline Every deploy runs the same steps, grouped into four stages. A successful run shows **17 passed**. ![A successful deploy showing Succeeded and Live, the commit, the trigger, the environment, and the first stages of the pipeline.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/36-logic-deployment-succeeded.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=ba25350980329c19cfb16acfe639e79e) | Stage | Steps | | - | - | | **Source** | Fetch source, Detect language and download, Load environment variables, Runnability check. | | **Build** | Build image, Upload build context, Push image. | | **Release** | Deploy workload, Health check, Update footprint, Register API, Reconcile GraphQL schema. | | **Finalize** | Record job details, Refresh cache, Record status, Notify completion, Clean up. | Some points about specific steps: * **Load environment variables** reads the environment's [variables and secrets](/docs/backend/logic/environment-variables) for this deploy. * **Register API** and **Reconcile GraphQL schema** add the service's operations to your project's API. After a deploy, they appear in the [GraphQL API Explorer](/docs/backend/graphql-api-explorer). * If a step finishes with a warning, the deploy continues. Open the logs for that step to read the details. If a step after a failure doesn't run, it shows **Skipped**. ## When a deploy fails A failed deploy shows the stage and step that failed, for example **Failed at Build image · step 5 of 17**, and a red progress bar. Below it, Archie reminds you: "Nothing changed for your users: a2afeb1 is still live." To find the cause: 1. Select the failed deploy in the list. 2. Find the red step in the **Pipeline** and read its message. For a build failure, it reads "This step failed. Open the environment logs for details." 3. Click **View logs** to read the logs for the deploy. See also [Logs](/docs/backend/logs). 4. Fix the code in the **Code** tab and click **Save**. 5. Click **Deploy**. If the failure looks temporary, click **Retry deploy** on the failed deploy to run it again without changing anything. ### The GraphQL schema warning If the **Reconcile GraphQL schema** step can't finish, Archie still deploys the service. The service answers requests at its endpoint, but new operations may be missing from the API and from the API Explorer. Redeploying usually fixes it. ## Redeploy an earlier commit To go back to a version that worked, open an earlier successful deploy and click **Redeploy this commit**. Archie asks you to confirm. ![The Redeploy confirmation dialog reading "This builds and deploys commit 89fbd20 again. When it is healthy it replaces the live version a2afeb1."](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/39-logic-redeploy-dialog.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=661ecbf20a2bd8127f587cacd74587a8) Archie builds and deploys that commit again. When it's healthy, it replaces the live version. The commit itself isn't rewritten: Head still points at your latest commit and shows **Undeployed changes** until you deploy it. ## Deploys and environments Each deploy lists the **Environment** it ran in, such as `master`. The service list and its deploy history are scoped to the environment selected in the Backend Console. If a service doesn't appear after you switch environments, it belongs to another one. See [Environments](/docs/backend/environments). ## FAQ > **Do I need to deploy after every save?** > > No. Saving commits your change. Deploy when you're ready for the change to go live. You can save several times and deploy once. > **What happens to users while a deploy runs?** > > The current live version keeps running. It's replaced only when the new deploy is healthy. > **What if I click Deploy while a deploy is running?** > > Only one deploy runs at a time. Archie tells you "A deploy was already running" and shows its progress instead of starting a second one. > **How long does a deploy take?** > > It depends on the service. The deploy list shows a median for your own deploys. In the example above, the median is 2m 15s, and most of the time goes to the **Build image** step. # Environment variables Source: https://archie.com/docs/backend/logic/environment-variables Give a service its configuration and secrets. Variables are shared by every service in the environment and reach your code the next time you deploy. Your service's code reads configuration, such as API URLs and sender addresses, from environment variables. Archie injects them into the service's container when it deploys. ## Open the variables Open a service and select the **Environment variables** tab. The **Shared** badge on the tab tells you these variables aren't specific to this service. ![The Environment variables tab with six variables whose values are masked, an empty row to add a variable, and a Secrets section.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/37-logic-environment-variables.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=a6ad8dffbccd76244b5dca3d0e80f45b) The tab explains it in one line: "These variables are shared by every function in this environment. Per-function variables are coming later." ## Shared with Settings The tab shows the same variables as **Backend → Settings → Environment variables**. Both editors read and write the environment's variables, so a change in one appears in the other. See [Environment variables](/docs/backend/settings/environment-variables) in Settings. Variables belong to an [environment](/docs/backend/environments). A service in `master` receives the variables of `master`. ## Add and edit variables The **Key** and **Value** table lists your variables. Values are masked. Use the eye icon at the end of a row to show a value, and the X icon to remove the row. 1. Type a name in the empty **Add Key** field at the bottom of the table, and its value in **Add Value**. Or click **Add Variable**. 2. Click **Save variables**. Click **Cancel** to discard changes you haven't saved. > [!NOTE] > > Saving doesn't change a running service. The new values reach the service the next time you [deploy](/docs/backend/logic/deployments). The deploy's **Load environment variables** step picks them up. ## Secrets Scroll down below the variables table to the **Secrets** section. It holds values that must stay private. Archie describes them this way: "Encrypted and write-only: values are never shown again. Injected into the function container as secret env vars at deploy time." The section has its own **Key** and **Value** table. Existing secrets show their keys with masked values. Click **Add Secret** to add a row, then click **Save variables**, the same button that saves the variables above. Use secrets for API keys, tokens, and passwords. > [!WARNING] > > Don't put sensitive values in plain variables or in your code. Plain values can be shown with the eye icon, and files are visible in the repository. Secrets are never shown again. ## Read variables in your code Your code reads variables the usual way for its language. In a Go service, for example, `main.go` reads the port with `os.Getenv("PORT")`. Use the exact key you entered, such as `SENDER_EMAIL`. ## FAQ > **Can one service have its own variables?** > > Not yet. The tab states that per-function variables are coming later. For now, every service in the environment receives the same set. > **Do variables carry over to other environments?** > > Variables belong to one environment, and the tab shows the ones for the environment you selected. Check that each environment you deploy to has the variables your service needs. See [Environments](/docs/backend/environments). > **Why does my service still use the old value?** > > Variables are read when you deploy. Save the change, then click **Deploy**. # From a code repository Source: https://archie.com/docs/backend/logic/from-a-repository Connect a git repository to run its code as a service. Archie builds and deploys the service on every push to your repository. **From a Code Repository** connects a git repository you already own. It deploys on every push. ## Connect a repository Go to **Backend → Logic**, click **New service**, select **From a Code Repository**, and click **Continue**. In the empty state, you can click **Import from Git** instead. The dialog is titled **From a Code Repository**. ![The From a Code Repository dialog with Name, Repository URL, Provider, Default branch, API token, and Webhook secret fields.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/22-logic-from-repository.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=5aba2a185fbf1c276c810c57f0e9673b) | Field | What to enter | | - | - | | **Name** (optional) | A name for the service. It defaults to the repository name. | | **Repository URL** | The URL of the repository, for example `https://github.com/org/repo.git`. | | **Provider** | **GitHub**, **GitLab**, or **Other Git**. **GitHub** is selected by default. | | **Default branch** | The branch to deploy. It defaults to `main`. | | **API token** | A personal access token for the repository. Archie stores it encrypted and never shows it again. | | **Webhook secret** | A signing secret that Archie uses to verify incoming repository webhooks. Archie stores it encrypted. | Click **Create function** to connect the repository. The button stays disabled until the required fields are filled in. > [!WARNING] > > Treat the API token and webhook secret as credentials. Use a token that only has access to this repository. ## After you create the service Open the service to follow its [deployments](/docs/backend/logic/deployments). Each push to the connected branch builds a new deploy. You can also read and edit the code in the [Code tab](/docs/backend/logic/working-with-a-service) and set [environment variables](/docs/backend/logic/environment-variables). ## FAQ > **Which git hosts can I use?** > > **GitHub** and **GitLab** have their own options. Choose **Other Git** for any other git host. > **When does the service deploy?** > > On every push, as described in the dialog: "Connect your own git repository. It deploys on every push." # Starting from scratch Source: https://archie.com/docs/backend/logic/from-scratch Create a service with a starter handler in the language you choose. Archie creates and hosts the code, you edit it in Archie, and you deploy with one click. **From Scratch** creates a service that Archie hosts for you. You name it, pick a language, and write the logic. ## Create a service **1. Open the dialog** Go to **Backend → Logic**, click **New service**, select **From Scratch**, and click **Continue**. In the empty state, you can click **Start from scratch** instead. **2. Name the service** Enter a **Service Name**. Use 3–40 characters: lowercase letters, numbers, and single hyphens. The name must start with a letter. **3. Pick a language** Choose **Node.js**, **Python**, **Go**, **.NET**, or **Java**. Node.js is selected by default. **4. Create it** Click **Create Service**. The button stays disabled until the name is valid. ![The Start From Scratch dialog with a Service Name field and Node.js, Python, Go, .NET, and Java options.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/21-logic-from-scratch.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=869f4a54c745d97e5a4e8afb41005c06) Use **Back** to return to the list of options, or **Cancel** to close the dialog. > [!NOTE] > > Archie describes this option as "Archie creates and hosts the code. Edit it here and deploy with one click." The empty-state card adds that the service starts with a starter handler. ## After you create the service Open the new service to edit its starter handler in the [Code tab](/docs/backend/logic/working-with-a-service). Add the [environment variables](/docs/backend/logic/environment-variables) your code needs, then [deploy it](/docs/backend/logic/deployments). ## FAQ > **Can I change the language later?** > > The dialog doesn't say. Pick the language you want before you click **Create Service**. > **Can I add a second service?** > > Not yet. For now each project can have one service. See [Logic](/docs/backend/logic#one-service-per-project). > **Can I import code I already have?** > > Yes. Use [From a Code Repository](/docs/backend/logic/from-a-repository). # Working with a service Source: https://archie.com/docs/backend/logic/working-with-a-service Open a service to see its live version, its code, its deployments, and its environment variables. Edit files in the browser, commit your changes, and decide when to deploy. Click a service in the **Logic** list to open it. This page walks through what you see and how the code editor works. For the deploy side, see [Deployments](/docs/backend/logic/deployments). For configuration, see [Environment variables](/docs/backend/logic/environment-variables). The service page is scoped to the [environment](/docs/backend/environments) you picked in the Backend Console. In the screenshots, the environment is `master`. ## The service page ![The self-signup service page with the header, the version bar, and the Code, Deployments, Environment variables, and Logs tabs.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/33-logic-service-code.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=9397fd29b138808edf3787407c704c0b) ### Header The header shows: * The service name and its language, such as **Go**. * Status badges. **Live** means a version is running. **Last deploy failed** means the most recent deploy didn't succeed. Other states you may see are **Never deployed**, **Deploying**, **Disconnected**, **Unavailable**, and **Deleting…**. * **Generate with AI**, **Delete**, and **Deploy**. Click **Back to Services** to return to the list. ### Version bar The bar under the header compares three things: | Item | Meaning | | - | - | | **Live version** | The commit that is running now, with the date it went live. | | **Head** | The latest commit in the service's repository. Archie flags **Undeployed changes** when Head is newer than the live version. | | **Last deploy** | The outcome of the most recent deploy, such as **Failed · Oct 21**. | **Undeployed changes** doesn't mean you have unsaved edits. It means the repository has a commit that isn't live yet. Click **Deploy** to build the latest commit. ## The Code tab The **Code** tab is an in-browser editor for the service's repository. ![The Code tab with the file tree on the left and archie.deploy.json open in the editor.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/34-logic-code-deploy-config.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=79b8cccf675a5cd5ae47f2a04f526247) The panel on the left has four views: * The file tree, with a **Search files** box. This is the view that opens first. * **Changes**, which lists the files you edited but haven't saved. It shows a count and reads **No changes** when everything is saved. * A diagnostics view for problems in your code. * A search view for searching across files. The four views are icons at the top of the panel. Select a file to open it. Until you do, the editor reads **Select a file to view**. ### What's in a service A service created from scratch is a small, working server. The Go template includes: | File or folder | What it holds | | - | - | | `main.go` | The program entry point. It reads the `PORT` environment variable and starts the server. | | `graph`, `rest`, `server`, `internal`, `archie` | The server code, including the GraphQL and REST handlers. | | `gqlgen.yml`, `go.mod`, `go.sum` | Go and GraphQL generator configuration. | | `Dockerfile` | How Archie builds the image. | | `archie.deploy.json` | The deploy settings. See below. | | `README.md` | How to run the service locally. | Other languages use their own layout. Open `README.md` in your service to see the commands for running it on your machine. ### The deploy file Every service has an `archie.deploy.json` file. Archie needs it to deploy, and you can't delete it. The Go template contains: ```json theme={null} {"build":"docker","port":8080,"healthPath":"/health","start":"/app/server","startupSeconds":30} ``` | Property | What it sets | | - | - | | `build` | How Archie builds the service. The template uses `docker`. | | `port` | The port the service listens on. | | `healthPath` | The path Archie uses to check that the service is healthy. | | `start` | The command that starts the service. | | `startupSeconds` | How long Archie waits for the service to start before it considers it down. | The editor validates this file as you type. It reports invalid JSON, a file that isn't a JSON object, unknown properties, missing properties, and invalid values. ### Save and commit Edit a file, then click **Save** or press ⌘ + S. Archie asks for a **Commit message**. Each save creates a commit in the service's repository, and the editor then shows **Saved · not deployed**. Saving doesn't change what's live. To run your changes, click **Deploy**. See [Deployments](/docs/backend/logic/deployments). A few limits apply: * Files larger than 1 MiB open as read-only. * The repository has no conflict protection, so the last save wins. If someone saved a newer version while you had unsaved changes, the editor offers to **Review** or **Discard** your draft. * If a service shows **Disconnected**, its code is safe but the deploy service no longer knows it. Click **Re-register** to reconnect it before you edit or deploy. ## Logs The **Logs** tab doesn't show logs itself. It reads "Logs live in your environment" and points to the environment logs. This service's output appears there together with the other services in the environment. Per-service logs are marked as coming later. ![The Logs tab reading "Logs live in your environment" with an Open environment logs button.](https://mintcdn.com/archie-e998dbf6/FcInvfd50seksJIy/images/38-logic-logs-tab.png?fit=max\&auto=format\&n=FcInvfd50seksJIy\&q=85\&s=429c8c12c508371fe498b89d877606fe) Click **Open environment logs** to go to **Backend → Logs**. See [Logs](/docs/backend/logs) for filtering and searching. ## Generate with AI Click **Generate with AI** in the header to add a function to an existing service. A panel opens on the right. ![The Generate with AI panel with Function name and Description fields, an Improve with AI button, and a Continue button.](https://mintcdn.com/archie-e998dbf6/JW67AKv9r3EIl9M1/images/41-logic-generate-with-ai.png?fit=max\&auto=format\&n=JW67AKv9r3EIl9M1\&q=85\&s=c297ccbfbd73e7d09d0c1789db4b5c0f) 1. Enter a **Function name**. Use 3–40 characters: lowercase letters, numbers, and hyphens. 2. Describe what the function should do in **Description**. Write at least 20 characters. Click **Improve with AI** to refine your text. 3. Click **Continue**. To plan a whole new service from a description, use [Add With AI](/docs/backend/logic/creating-with-ai) instead. ## Delete a service Click **Delete** in the header, or open the **Actions** menu on the [Logic list](/docs/backend/logic) and choose **Delete**. Archie asks you to type the service name to confirm. ![The delete confirmation dialog asking you to type self-signup to confirm.](https://mintcdn.com/archie-e998dbf6/JW67AKv9r3EIl9M1/images/40-logic-delete-dialog.png?fit=max\&auto=format\&n=JW67AKv9r3EIl9M1\&q=85\&s=555561d4986be82a519446240bae1404) Deleting removes the service, its code repository, and its deployment. You can't undo it. If you imported the service from your own repository, Archie doesn't delete that repository. > [!WARNING] > > Deleting a service is permanent. Download or copy any code you want to keep before you confirm. ## FAQ > **What's the difference between a service and a function?** > > A service is the deployable unit: one repository, one language, one deploy history. A function is one operation inside it, such as a GraphQL query, a GraphQL mutation, or a REST route. A service can hold several functions, and each deploy ships them all together. > **Why does Head differ from the live version?** > > Head is the latest commit. The live version is the last commit that deployed successfully. They differ after you save and before you deploy, or after a failed deploy. > **Can I open a service in a new tab?** > > Yes. In the **Logic** list, open the **Actions** menu on a row and choose **Open in new tab**. # Logs Source: https://archie.com/docs/backend/logs Inspect Custom Function and core GraphQL logs for your project from the Backend Console. Search and filter entries, read the full request detail, tail new entries live, and export what you see. **Logs** is where you check what your backend did and when. Each entry records one event, such as a GraphQL request that reached your project. Open it from **Backend → Logs**. The page shows logs for the current project and the environment selected in the Backend Console, for example `master`. It never shows logs from other projects. ## Sources Logs cover two kinds of activity: * **Core GraphQL** — requests that reach your project's GraphQL API. * **Custom Function** — output from your [custom functions](/docs/backend/logic). The **Source** filter also lists **Workloads**, **Integration**, **Custom API**, and **File**. See [Filter by level, source, and time](#filter-by-level-source-and-time). ## The Logs screen ![The Logs page showing Requests, Error rate, and p95 latency cards above a table of log entries](https://mintcdn.com/archie-e998dbf6/_8MXf9wS7pzwm-r0/images/01-logs-overview.png?fit=max\&auto=format\&n=_8MXf9wS7pzwm-r0\&q=85\&s=5fd87528bc5e982defd7b8aaea597187) The page has four areas: * **Actions** — **Live tail**, a **Comfortable / Compact** density toggle, **Refresh**, **Export**, and **Log settings**. * **Metrics** — three cards that summarize the entries on screen. * **Search and filters** — a search box, a **Filters** panel, category tabs, and **Reset**. * **Table** — one row per entry, with **Time**, **Level**, **Source**, **Function**, and **Message** columns. Each row's **Time** shows the date and how long ago the event happened. The **Message** for a GraphQL request shows the status code, the operation type (`query` or `mutation`), the operation name, the latency, and a short text such as `gateway ingress`. At the bottom of the table, **Rows per page** sets how many entries you see at once (10, 25, 50, or 100). A counter such as "Showing 1 to 25 of 69 entries" shows where you are in the results. ## Metrics Three cards sit above the table: | Card | What it shows | | - | - | | **Requests** | Requests per minute, shown as `/min`. | | **Error rate** | The share of requests that failed, shown as a percentage. | | **p95 latency** | The latency that 95% of requests stay under, in `ms`. | > [!NOTE] > > The metrics use only the entries loaded for the selected range. When you change the time range or apply a filter, the numbers change with it. ## Search and filter ### Search Select the magnifying glass to open the search bar. Pick what to search with the selector on the left: * **Message** searches the message text. * **GraphQL** searches GraphQL requests, for example by operation name. Type your term, or pick one of the suggestions Archie offers from your own logs. Suggestions include quick filters such as **Failed operations**, **Mutations**, **Queries**, **Server errors (5xx)**, and **Client errors (4xx)**, followed by operation names found in your logs. Select **Apply** to run the search. Select **Reset** to clear the search and filters. ### Category tabs The tabs above the table narrow the list to one category of entry: **All**, **Request**, **Logic**, **Integrations**, **Files**, and **Errors**. A tab with no matching entries shows **No logs found**. ### Filter by level, source, and time Select **Filters** to open the filter panel. ![The Filters panel with Level, Source, and Time selectors and the System operations toggle](https://mintcdn.com/archie-e998dbf6/_8MXf9wS7pzwm-r0/images/02-logs-filters.png?fit=max\&auto=format\&n=_8MXf9wS7pzwm-r0\&q=85\&s=6a56562eaabc5a884eb253dc0db43207) | Filter | Options | | - | - | | **Level** | **Debug**, **Info**, **Warn**, **Error**. You can select more than one. The default is **All levels**. | | **Source** | **Core GraphQL**, **Custom Function**, **Workloads**, **Integration**, **Custom API**, **File**. The default is **All sources**. | | **Time** | **Last 5 minutes**, **Last 15 minutes**, **Last hour**, **Last 24 hours**, **Last 7 days**, **All time**, **Custom range**. The default is **Last hour**. | | **System operations** | A toggle that is off by default. | Turn on **System operations** to include system-generated operations, such as `GetDatabaseTables` and `QueryTable`. These entries are hidden until you turn the toggle on, so the entry count goes up when you enable it. ## Read an entry Select a row to expand it. The detail shows: * **Status** — the HTTP status code. * **Operation** — the operation type and name. * **Fields accessed** — the top-level GraphQL fields the request touched. * **Method** — the HTTP method. * **Latency** — how long the request took. * **Path** — the request path, for example `/graphql`. * **User** — the user the request ran as. * **Raw event** — the full entry as JSON. Select **Copy** next to **Raw event** to copy the JSON, for example to paste it into a bug report. > [!WARNING] > > Raw events and the **User** field can contain user identifiers. Remove them before you share a log entry outside your team. ## Live tail Select **Live tail** to watch new entries arrive without refreshing. A **LIVE** badge appears next to the page title and the button changes to **Live**. Select **Live** again to stop. Select **Refresh** to load the latest entries on demand. ## Change the row density Use the density toggle to switch between **Comfortable** and **Compact**. **Compact** fits more rows on screen by shortening each row. ![The Logs page in Compact density with Live tail turned on](https://mintcdn.com/archie-e998dbf6/_8MXf9wS7pzwm-r0/images/04-logs-live-tail-compact.png?fit=max\&auto=format\&n=_8MXf9wS7pzwm-r0\&q=85\&s=4bc0d0cb7e978bc80f5ced1c2b9bd43d) ## Export Select **Export** to download the entries in the current view. The menu offers **Download JSON** and **Download CSV**, and each option shows how many rows it will include. ## Log settings Select **Log settings** to open the settings drawer. ![The Log settings drawer showing Capture level, Retention options, and Alerts](https://mintcdn.com/archie-e998dbf6/_8MXf9wS7pzwm-r0/images/03-logs-settings.png?fit=max\&auto=format\&n=_8MXf9wS7pzwm-r0\&q=85\&s=331d35594da981c978e592f0aad73571) ### Capture level **Capture level** sets the lowest level Archie stores: **Debug**, **Info**, **Warn**, or **Error**. Entries below the level are not stored. You set it per environment. > [!NOTE] > > **Capture level** is marked **Coming soon**. You can see the control, but you can't change it yet. ### Retention **Retention** sets how long logs are kept before they age out. The option you can use depends on your plan: | Retention | Plan | | - | - | | 3 days | Free | | 14 days | Pro | | 30 days | Pro | | 90 days | Enterprise | Your current retention is selected. Options your plan doesn't include show an **Upgrade** link. ### Alerts **Alerts** will notify you when the error rate or latency crosses a threshold. It's marked **Coming later**. ## FAQ and troubleshooting > **Why don't I see my logs?** > > Check these in order: > > 1. Confirm you're in the right project and environment. Logs are scoped to both. > 2. Widen **Time**. The default is **Last hour**, so older entries stay hidden until you choose a longer range. > 3. Select **Reset** to clear any search, category tab, or filter. > 4. Check **Source** and **Level**. A narrow selection can hide the entries you want. > 5. Turn on **System operations** if you expect entries from system-generated requests. > 6. Select **Refresh** to load the latest entries. > > Logs older than your plan's retention period are no longer available. See [Retention](#retention). > **Why are the metrics different after I filter?** > > The metrics describe the entries loaded for the selected range. A different time range, search, or filter changes which entries are loaded, so the numbers change too. > **Can I see logs from other projects or environments?** > > No. Logs shows only the current project, and the environment selected in the Backend Console. > **Where do I look when a scheduled task fails?** > > Start with the task's [run history](/docs/backend/scheduled-tasks/run-history). It links to the full request and response in Logs. # Overview Source: https://archie.com/docs/backend/overview See your backend at a glance from the Backend Console. Check what Archie generated on the Structure tab, and track database, API, and storage consumption on the Usage tab. **Overview** is the first page you see in the Backend Console. It answers two questions: what does my backend contain, and how much of it am I using? Open it from **Backend → Overview**. The page shows the environment selected in the console, for example `master`. Switch environments and both tabs update with it. ## The page header The top bar shows: * **Environment** — the environment the page describes. * **Structure** and **Usage** tabs — the two views described below. * **Live** — a status indicator that confirms the backend is running. If it isn't, the indicator reads **Not live** followed by the current status. * **Region** — where the backend runs. ## Structure tab **Structure** shows what Archie generated for this environment. ![The Structure tab showing the live banner, key number cards, and the Your Backend cards for Data, API, Core Services, and Logic](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/05-backend-overview-structure.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=86c50b2b343925a303e078bc2fa98e5b) ### Live banner and compliance level When the backend is running, a banner reads **Your backend is live**. Select the close button to dismiss it. The banner stays dismissed for that project and environment in your browser. Below the banner, the page lists the provider, the region, and the compliance level of the environment. A standard environment shows "Standard environment. Need SOC 2, HIPAA or GDPR?" with a **Talk to Sales** link. ### Key numbers Five cards summarize the backend: | Card | What it counts | | - | - | | **Tables** | Tables in your data model. | | **API Endpoints** | Endpoints your project exposes. | | **Core Services** | Services such as authentication, roles, and file storage. | | **Logic Functions** | Your custom functions. | | **Integrations** | Connected third-party services. | ### Your Backend Under **Your Backend**, one card per area explains what is set up and links to where you manage it. | Card | What it shows | Shortcut | | - | - | - | | **Data** | The database engine, plus your tables and relationships. | **Open Data** | | **API** | The protocols your API supports, its endpoint, and the number of REST endpoints. | **Open API Explorer** | | **Core Services** | Sign-in providers, email, roles, and file storage. | **Open Core Services** | | **Logic** | Your custom functions and custom APIs. | **Open Logic** or **Open Custom APIs** | | **Integrations** | How many integrations are connected. | **Open Integrations** | | **Backups** | Your most recent backup. | — | If an area has nothing yet, its card says so. For example, **Data** shows "No tables yet." and **Integrations** points you to the integrations catalog. > [!NOTE] > > Logic is available on paid plans. On the Free plan, the **Logic** card shows an **Upgrade** button. ## Usage tab **Usage** shows how much of your plan this environment consumes. Figures cover this environment only. Plan limits and billing are shared across all environments, and **View billing** takes you to the billing page. ![The Usage tab showing the 30 days range and usage cards for Database Rows, Database Size, Bandwidth, Logic Compute, API Requests, Users, File Storage, and Backups](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/06-backend-overview-usage.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=bde53767ffdccce1072c37ae2ea3849e) ### Choose a range Pick **24 hours**, **7 days**, **30 days**, or **All time** to change the period the cards cover. ### Usage cards | Card | What it measures | | - | - | | **Database Rows** | Rows across all your tables. | | **Database Size** | Data and indexes. | | **Bandwidth** | Data served by the API and by file storage, with a split between the two. | | **Logic Compute** | Function invocations and their average duration. | | **API Requests** | GraphQL and REST calls. | | **Users** | Total users. | | **File Storage** | Number of files and their size. | | **Backups** | Your backup schedule and the time of the last backup. | ### Keep numbers fresh The page shows when the data was last updated. A badge such as **As of today** tells you the snapshot date. If live stats are unavailable, the badge reads **Stale** and the page shows the last snapshot instead. Select **Refresh** to request a new snapshot. If you refreshed recently, Archie tells you when you can refresh again. A card that reads **Not aggregated yet** or **Not available yet** has no data to show for now. ## Empty, error, and not-generated states * **Backend not generated** — if the backend hasn't been generated, the page shows **No overview data yet**. Check back after Archie generates the backend and your app starts to use it. * **Error** — if the overview can't load, the page shows **Could not load the overview** with the reason, for example that the service is unavailable, that you don't have access to the project, or that the project or environment wasn't found. Select **Retry** to try again. ## Go deeper > **[Data](/docs/backend/data-model)** > > Tables, fields, and relationships. > **[API explorers](/docs/backend/graphql-api-explorer)** > > Try your GraphQL API, or use the [REST API explorer](/docs/backend/rest-api-explorer). > **[Logs](/docs/backend/logs)** > > See what your backend did and when. > **[Integrations](/docs/backend/integrations)** > > Connect third-party services. > **[Environments](/docs/backend/environments)** > > Branch, compare, and back up your backend. > **[App Services](/docs/backend/app-services)** > > Authentication, roles, and file storage. ## FAQ > **Why does the Overview show different numbers in another environment?** > > Each environment has its own database, services, and usage. The page always describes the environment selected in the console. > **Can I change my backend from the Overview?** > > No. The Overview is read-only. Use the shortcuts on the **Your Backend** cards to open the page where you make changes. > **Why is my usage data marked Stale?** > > Live stats were unavailable, so the page is showing the last snapshot. Select **Refresh** to request a new one. > **Why does the Logic card ask me to upgrade?** > > Custom functions are available on paid plans. See [Plans and credits](/docs/plans-and-credits). # Overview Source: https://archie.com/docs/backend/rest-api-explorer The REST tab of the API Explorer lists your app's auto-generated REST endpoints, grouped by table, with schemas and a Try it out button. The **API Explorer** is a single surface for both APIs, with a **GraphQL** / **REST** toggle at the top. Open **Backend → API Explorer** and switch to **REST**: every endpoint your [Data Model](/docs/features/backend/data-model/overview) generates is listed with its parameters, request schema, response schema, and a **Try it out** button that issues real requests with your current authentication. The REST API runs alongside the [GraphQL API](/docs/features/backend/graphql-api-explorer/overview), shares the same authentication and permission model, and follows industry standards: [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110) (HTTP semantics), [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (Problem Details errors), and [RFC 7396](https://www.rfc-editor.org/rfc/rfc7396) (JSON Merge Patch). ![REST tab of the API Explorer, showing the ticket table's 10 generated endpoints](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/rest-api-explorer/rest-api-explorer.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=4f7a7d8358e6ba72e6fef7fa6b2fe2b1) ## What's in the Explorer * **Module sidebar** — every table and module in the project, searchable. * **Endpoint list** — every generated endpoint for the selected table, grouped and filterable by HTTP method. * **Schema panels** — request parameters and a response schema with an example value, including the constraints from your Data Model. * **Try it out** — fire a real request from the browser and inspect the live response, or copy the equivalent `cURL` command. * **OpenAPI link** — fetch the auto-generated [OpenAPI 3.1 spec](/docs/features/backend/rest-api-explorer/openapi-specification) for code generation and external tooling. ![An endpoint expanded, showing Parameters, Try it out, and the Response Schema with an example value](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/rest-api-explorer/rest-endpoint-try-it.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=989a48cf785ff376068b896b1f59eef0) ## Base URL and resources ```text theme={null} https://your-gateway.example.com/gw/api/rest ``` Every table is exposed as a resource at `/`. For example: ```text theme={null} GET /api/rest/students GET /api/rest/students/{id} POST /api/rest/students PATCH /api/rest/students/{id} DELETE /api/rest/students/{id} ``` Table names use the exact name from the Data Model. The base URL is per-environment — see [Environments](/docs/features/backend/environments/overview). ## Required headers | Header | Required | Default | Description | | - | - | - | - | | `Authorization` | Yes | — | `Bearer ` (JWT or API key) | | `X-Project-ID` | Yes | — | Your project identifier | | `Content-Type` | On write | — | `application/json` for POST/PATCH bodies | | `environment` | No | `master` | Target environment name | `X-Project-ID` can also be sent as a query parameter (`?project_id=...`). The `environment` header also accepts the alias `x-environment`. ## Authentication The REST API accepts two token formats — both go in the `Authorization: Bearer ...` header: * **JWT access token** — issued by an authentication provider configured in [App Services → Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview). Use this for end-user sessions in your client app. * **API key** — issued from **Settings → API Keys**, scoped per environment. Use these for server-to-server integrations and CI tooling. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` API keys are environment-scoped — a key generated for `dev` cannot read `master`. When using an environment-scoped key, set the `environment` header to match. ## Per-environment endpoints The base URL and the API key set are per-environment. To target a non-default environment, send the `environment` header: ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "environment: dev" ``` If the header is omitted, requests default to `master`. See [Environments](/docs/features/backend/environments/overview). ## Content types | Operation | Content-Type | | - | - | | Create | `application/json` | | Update | `application/merge-patch+json` (preferred) or `application/json` | | Delete | — | ## The Prefer header `Prefer` controls response shape and counting behavior: | Value | Effect | | - | - | | `count=exact` | Include exact `totalCount` in pagination metadata. Slower for large tables. | | `count=estimated` | Approximate `totalCount` from database statistics. Fast. | | `count=none` | Omit `totalCount` (default). Fastest. | | `return=representation` | Return the full record in the response body (default). | | `return=minimal` | Suppress the response body, return status only. | ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Prefer: count=exact" ``` ## Rate limiting REST requests are rate-limited per project. When the limit is exceeded, the API responds `429 Too Many Requests` with rate-limit headers — see [Error handling](/docs/features/backend/rest-api-explorer/error-handling#rate-limit-exceeded-429). ## CORS CORS policy is per-project, configured under **Settings → Network**. The REST API and GraphQL API share the same allowed origins. ## Permissions Every endpoint enforces the per-role permissions defined in [Role-Based Access](/docs/features/backend/app-services/role-based-access). A user without read access to a field doesn't see it in responses; a user without write access to a table receives `403 Forbidden` on writes. ## Where to go next > **[Queries](/docs/features/backend/rest-api-explorer/queries)** > > Read records — single, list, filtered, paginated, sorted, aggregated. > **[Mutations](/docs/features/backend/rest-api-explorer/mutations)** > > Create, update, and delete — including bulk and update-by-filter. > **[Error handling](/docs/features/backend/rest-api-explorer/error-handling)** > > The Problem Details error format and what each status means. > **[File management](/docs/features/backend/rest-api-explorer/file-management)** > > Upload, list, download, and delete files. > **[Idempotency](/docs/features/backend/rest-api-explorer/idempotency)** > > Retry POSTs safely with `Idempotency-Key`. > **[OpenAPI specification](/docs/features/backend/rest-api-explorer/openapi-specification)** > > The auto-generated spec and code-gen workflow. > **[Webhooks](/docs/features/backend/rest-api-explorer/webhooks)** > > Subscribe to data changes and deliver them to your services. ## FAQ > **REST or GraphQL — which should I use?** > > Both expose the same data, the same permissions, and the same auth model. REST is best for tools that don't speak GraphQL, server-to-server integrations, and idempotent writes. GraphQL is best for nested reads, real-time subscriptions, and clients that want to fetch exactly what they need. See [GraphQL API Explorer](/docs/features/backend/graphql-api-explorer/overview). > **Why am I getting `400 Missing X-Project-ID`?** > > Either include the `X-Project-ID` header on every request, or send it as a query parameter (`?project_id=...`). The Explorer fills it in automatically — outside the Explorer you have to provide it. > **Are the REST endpoints versioned?** > > The endpoint shapes are stable per project. Schema changes (new tables, new fields) update the endpoints in real time and are reflected in the OpenAPI spec. Breaking changes to existing fields require explicit Data Model edits. > **Can I generate a typed client from the API?** > > Yes. The [OpenAPI specification](/docs/features/backend/rest-api-explorer/openapi-specification) is auto-generated and can be fed into tools like `openapi-generator` or `openapi-typescript` to produce a typed SDK in any language. > **Where do I configure CORS for my frontend?** > > Under **Settings → Network**. The same allowed-origins list applies to the REST and GraphQL APIs. # Error handling Source: https://archie.com/docs/backend/rest-api-explorer/error-handling The REST API uses RFC 9457 Problem Details for every error response. Predictable shape, machine-readable types, field-level details. The REST API uses [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) for every error. Every error response has `Content-Type: application/problem+json` and a consistent JSON shape. ## Error response format ```json theme={null} { "type": "/errors/unique-constraint", "title": "Conflict", "status": 409, "detail": "Unique constraint \"students_email_key\" violated. Key (email)=(alice@example.com) already exists.", "instance": "/api/rest/students", "errors": [ { "field": "email", "rule": "unique", "message": "The value for email already exists" } ] } ``` | Field | Type | What it tells you | | - | - | - | | `type` | String | A stable, machine-readable URI identifying the error category. Switch on this in your code. | | `title` | String | A short human-readable summary, paired with the HTTP status. | | `status` | Number | The HTTP status code, duplicated in the body for convenience. | | `detail` | String | A human-readable explanation specific to this occurrence. | | `instance` | String | The request path that produced the error. | | `errors` | Array | Field-level details when applicable (validation, constraints). | ## Status code reference ### 4xx — client errors | Status | Type | Meaning | | - | - | - | | 400 | `/errors/bad-request` | Malformed request. | | 400 | `/errors/missing-project-id` | Missing `X-Project-ID` header. | | 400 | `/errors/complexity-limit` | Query exceeded depth or relation limits. | | 401 | `/errors/unauthorized` | Missing or invalid auth token. | | 403 | `/errors/forbidden` | Authenticated, but not allowed. | | 404 | `/errors/item-not-found` | Record does not exist. | | 404 | `/errors/not-found` | Resource not found (composite PK, etc.). | | 404 | `/errors/table-not-found` | Table doesn't exist in the schema. | | 405 | `/errors/view-is-readonly` | Tried to write to a view. | | 409 | `/errors/unique-constraint` | Duplicate value on a unique field. | | 422 | `/errors/validation-failed` | Body or parameters fail validation. | | 422 | `/errors/foreign-key-constraint` | Referenced record doesn't exist. | | 422 | `/errors/not-null-constraint` | Required field is missing or null. | | 422 | `/errors/check-constraint` | Field value violates a check constraint. | | 429 | `/errors/rate-limit-exceeded` | Too many requests. | ### 5xx — server errors | Status | Type | Meaning | | - | - | - | | 500 | `/errors/internal-error` | Unexpected server error. | | 504 | `/errors/query-timeout` | Database query exceeded time limit. | ## Common errors ### Validation (422) Required fields missing or wrong types. ```json theme={null} { "type": "/errors/validation-failed", "title": "Unprocessable Entity", "status": 422, "detail": "One or more fields failed validation", "instance": "/api/rest/students", "errors": [ { "field": "email", "rule": "required", "message": "The field 'email' is required" }, { "field": "age", "rule": "type", "message": "Expected number, got string" } ] } ``` ### Unique constraint (409) Duplicate value on a unique column. The `errors[].field` tells you which column. ```json theme={null} { "type": "/errors/unique-constraint", "title": "Conflict", "status": 409, "detail": "Unique constraint \"students_email_key\" violated. Key (email)=(alice@example.com) already exists.", "instance": "/api/rest/students", "errors": [ { "field": "email", "rule": "unique", "message": "The value for email already exists" } ] } ``` For handling this without the read-then-write race condition, use a GraphQL [upsert](/docs/features/backend/graphql-api-explorer/standard-operations#atomic-upserts). ### Foreign key (422) Referencing a related record that doesn't exist. ```json theme={null} { "type": "/errors/foreign-key-constraint", "title": "Unprocessable Entity", "status": 422, "detail": "Foreign key constraint violated. Referenced record does not exist.", "instance": "/api/rest/students", "errors": [ { "field": "cityId", "rule": "foreign_key", "message": "Referenced record in 'cities' does not exist" } ] } ``` ### Not found (404) ```json theme={null} { "type": "/errors/item-not-found", "title": "Not Found", "status": 404, "detail": "Record not found", "instance": "/api/rest/students/00000000-0000-0000-0000-000000000000" } ``` ### Rate limit exceeded (429) ```json theme={null} { "type": "/errors/rate-limit-exceeded", "title": "Too Many Requests", "status": 429, "detail": "Rate limit exceeded. Try again in 30 seconds.", "instance": "/api/rest/students" } ``` The response includes rate-limit headers — back off using `Retry-After`: | Header | What it tells you | | - | - | | `Retry-After` | Seconds to wait before retrying. | | `X-RateLimit-Limit` | Requests allowed per window. | | `X-RateLimit-Remaining` | Requests remaining in the current window. | | `X-RateLimit-Reset` | Seconds until the window resets. | ### Query timeout (504) ```json theme={null} { "type": "/errors/query-timeout", "title": "Gateway Timeout", "status": 504, "detail": "Database query exceeded the maximum execution time", "instance": "/api/rest/students" } ``` Tighten the filter, paginate, add an [index](/docs/features/backend/data-model/indexes), or move to a [view](/docs/features/backend/data-model/views) that pre-computes the result. ## Field names in errors Database column names are converted to camelCase in error messages — a constraint on `created_at` reports the field as `createdAt`. This matches what you send and receive in JSON bodies. ## Handling errors in code Switch on `status` first, then on `type` for ambiguous statuses: | Status | What to do | | - | - | | 400 | Fix the request. Don't retry. | | 401 | Refresh the auth token. Retry once. | | 403 | Surface a "no permission" message. Don't retry — same user, same outcome. | | 404 | Treat as the resource not existing. | | 409 | Show the user a "duplicate" message. For automatable cases, switch to upsert. | | 422 | Surface field-level errors back to the user. Don't retry the same payload. | | 429 | Honor `Retry-After` with exponential backoff. | | 5xx | Retry with backoff. If persistent, alert. | ```javascript theme={null} async function call(url, init) { const res = await fetch(url, init); if (!res.ok) { const problem = await res.json(); switch (problem.type) { case "/errors/unique-constraint": throw new DuplicateError(problem.errors?.[0]?.field); case "/errors/rate-limit-exceeded": const wait = parseInt(res.headers.get("Retry-After") ?? "1", 10); await new Promise((r) => setTimeout(r, wait * 1000)); return call(url, init); default: throw new ApiError(problem); } } return res.json(); } ``` ## FAQ > **Why are validation errors 422 and not 400?** > > `400` means the request itself was malformed (bad JSON, wrong content type). `422` means the request was syntactically fine but semantically invalid — required fields missing, types wrong, constraints violated. The distinction tells your code whether to retry the same payload or fix the request shape first. > **What's the difference between 401 and 403?** > > `401` means "we don't know who you are" — the auth token is missing, expired, or invalid. `403` means "we know who you are, but you can't do this" — the role doesn't have the necessary permission. Refresh tokens for `401`; surface a permission error for `403`. > **Should I retry on 5xx?** > > Yes, with exponential backoff. Most 5xx errors are transient. Cap retries at 3–5 and alert if they keep failing. > **Can I get more detail than `detail`?** > > The `errors` array gives field-level reasons. For complex cases, the `instance` URL points to the request that failed — useful for support tickets. > **What happens to a `Idempotency-Key` request when it errors?** > > 2xx and 4xx responses are cached and returned on retry with the same key. 5xx responses are not cached, so retries actually re-run the operation. See [Idempotency](/docs/features/backend/rest-api-explorer/idempotency). # File management Source: https://archie.com/docs/backend/rest-api-explorer/file-management Upload, list, download, and delete files via the REST API. Works with any storage provider configured in App Services. The REST API exposes a small set of endpoints for moving files in and out of your project. They work against whichever storage backend you've configured in [App Services → File Manager](/docs/features/backend/app-services/file-manager) — Google Cloud Storage, Amazon S3, Azure Blob Storage, Filestack, or the built-in Archie Storage. | Method | Endpoint | Purpose | | - | - | - | | `POST` | `/api/rest/files` | Upload a file | | `GET` | `/api/rest/files` | List uploaded files | | `GET` | `/api/rest/files/{fileId}` | Download a file | | `DELETE` | `/api/rest/files/{fileId}` | Delete a file | ## Upload a file Send a `multipart/form-data` request. Don't set `Content-Type` manually — let curl (or your HTTP client) generate the boundary header. ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/files" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -F "file=@/path/to/document.pdf" \ -F "providerType=ARCHIE_STORAGE" \ -F "filename=document.pdf" \ -F "contentType=application/pdf" ``` | Form field | Required | Description | | - | - | - | | `file` | Yes | The file binary. | | `providerType` | Yes | The storage provider to use. | | `filename` | Yes | Display name for the file. | | `contentType` | Yes | MIME type — validated against an allowlist. | ### Provider types | Value | Backend | | - | - | | `ARCHIE_STORAGE` | Built-in Archie storage | | `GCS` | Google Cloud Storage | | `S3` | Amazon S3 | | `AZURE` | Azure Blob Storage | | `FILESTACK` | Filestack | The provider must be configured in [App Services → File Manager](/docs/features/backend/app-services/file-manager) before files can be uploaded to it. ### Response — 201 ```json theme={null} { "data": { "fileId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "url": "https://your-gateway.example.com/files?id=encrypted-token" } } ``` The `url` is a signed, time-bounded URL. Persist the `fileId` if you need a stable reference; regenerate fresh URLs at read time when needed. ### Supported MIME types The API accepts a broad allowlist by prefix: | Category | Examples | | - | - | | Images | All `image/*` (jpeg, png, gif, webp, svg+xml, bmp, tiff, avif) | | Video | All `video/*` (mp4, webm, quicktime) | | Audio | All `audio/*` (mpeg, wav, ogg) | | Text | All `text/*` (plain, csv, html, xml) | | Documents | `application/pdf`, `application/rtf`, MS Office, OpenXML formats | | Archives | `application/zip`, `application/gzip`, `application/x-tar` | | Data | `application/json`, `application/xml` | | Generic | `application/octet-stream` | Unsupported types return `422 Unprocessable Entity`. ## List files ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/files?page=1&pageSize=20" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` | Parameter | Default | Max | | - | - | - | | `page` | 1 | — | | `pageSize` | 20 | 100 | ```json theme={null} { "data": { "items": [ { "fileId": "a1b2c3d4-...", "filename": "document.pdf", "mimetype": "application/pdf", "url": "https://your-gateway.example.com/files?id=encrypted-token" } ], "pagination": { "page": 1, "pageSize": 20, "total": 42 } } } ``` ## Download a file ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/files/a1b2c3d4-..." \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -o downloaded-document.pdf ``` The response body is the raw file binary. Headers include the original `Content-Type`, a `Content-Disposition` with the filename, and a 1-year `Cache-Control` header. ## Delete a file ```bash theme={null} curl -X DELETE "https://your-gateway.example.com/gw/api/rest/files/a1b2c3d4-..." \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` Returns `204 No Content` on success. The file is removed from the storage provider. ## Errors | Status | Scenario | | - | - | | 400 | Missing required form fields. | | 404 | File not found. | | 413 | File exceeds the maximum upload size. | | 422 | Unsupported MIME type. | For the full error format, see [Error handling](/docs/features/backend/rest-api-explorer/error-handling). ## Permissions File-management endpoints obey the same [Role-Based Access](/docs/features/backend/app-services/role-based-access) rules as other endpoints. Configure who can upload, download, and delete in App Services. For finer-grained access (per-record file permissions), reference the file from a Data Model record and govern access via that table's permissions. ## FAQ > **How do I store a file alongside a record?** > > Upload the file to get a `fileId`, then store the `fileId` (and any metadata you care about) on a JSONB or text field on your record. At read time, fetch the record and re-issue the file's signed URL through the download endpoint. > **Are upload URLs cached?** > > The upload `url` is a signed token with an expiry. Don't persist it — persist the `fileId` and generate fresh URLs at read time. Downloads of the binary itself are cached for one year by default. > **What's the maximum file size?** > > The default upload limit is enforced at the gateway. Exceeding it returns `413 Payload Too Large`. For very large files, use a direct-to-storage upload pattern through your provider's SDK and store only the resulting object key in Archie. > **Can I switch storage providers later?** > > Yes — configure a new provider in [File Manager](/docs/features/backend/app-services/file-manager) and point new uploads at it. Existing files stay where they were uploaded; migrate them with a script if you want to consolidate. > **What happens if I delete a file that's referenced by a record?** > > The reference becomes a broken link. Archie doesn't enforce referential integrity between records and files — clean up references when you delete files, or add a server-side check before deletion. # Idempotency Source: https://archie.com/docs/backend/rest-api-explorer/idempotency Make POST requests safely retryable with the Idempotency-Key header. The same key returns the cached response instead of creating a duplicate. The REST API supports idempotent `POST` requests via the `Idempotency-Key` header. Send a unique key with your `POST`; if the request is retried with the same key (within 24 hours), the API returns the **cached response** from the first call instead of executing the operation a second time. This is the safe way to retry creates after a network failure or timeout — you'll never accidentally double-create the same record. ## When you need it Any time the cost of accidentally running an operation twice is non-trivial. The classic case is payments — you don't want to charge a customer twice because a network blip caused the client to retry. Other examples: * Creating an order * Creating an invoice or receipt * Sending a notification * Submitting a form that triggers downstream side effects For idempotent reads (`GET`) you don't need the header — re-running the same `GET` is already safe. For `PATCH` and `DELETE`, idempotency is built into the verb (the second call has the same effect as the first). ## How it works 1. Generate a unique key on the client. Persist it for the duration of the operation. 2. Send the `POST` with `Idempotency-Key: `. 3. The server processes the request normally on the first call. 4. If the same key arrives again within 24 hours, the server returns the cached response from the first call without re-executing the work. ## Example First request — creates the order: ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/orders" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-abc-123-attempt-1" \ -d '{ "customerId": "cust-001", "total": 99.50, "status": "pending" }' ``` ```json theme={null} { "data": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "customerId": "cust-001", "total": 99.50, "status": "pending" } } ``` Same request retried — returns the cached response, no duplicate record: ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/orders" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-abc-123-attempt-1" \ -d '{ "customerId": "cust-001", "total": 99.50, "status": "pending" }' ``` The body is identical to the first response. The order was created once. ## Behavior at a glance | Property | Value | | - | - | | Applies to | `POST` requests only (excluding `POST /_query`) | | Key lifetime | 24 hours | | Key scope | Per project (isolated by `X-Project-ID`) | | Cached responses | 2xx and 4xx status codes | | Not cached | 5xx responses (server errors retry the operation) | ## Choosing keys The key needs three properties: **unique**, **stable across retries**, and **bound to the operation**. * **Unique** — across all operations of this kind. UUIDs are the safest default. * **Stable across retries** — the client must store the key once and reuse it on every retry. A new key on every retry defeats the purpose. * **Bound to the operation** — different operations need different keys. Don't reuse `payment-2025-04-08` for two different payments on the same day. A reliable pattern: combine the entity, a user/session id, and a UUID per attempt. ```text theme={null} order-{userId}-{uuid} payment-{customerId}-{uuid} notification-{recipientId}-{uuid} ``` Generate the key on the client **before** the first attempt and persist it (in memory, in local storage, in your job queue) until the operation succeeds. ## Example: safe payment processing ```bash theme={null} # Generate once, before the first attempt IDEMPOTENCY_KEY="payment-$(uuidgen)" curl -X POST "https://your-gateway.example.com/gw/api/rest/payments" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -d '{ "orderId": "order-001", "amount": 150.00, "method": "credit_card" }' ``` If the network drops before you receive the response, retry with the same key: ```bash theme={null} # Same key = safe retry, no duplicate payment curl -X POST "https://your-gateway.example.com/gw/api/rest/payments" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -d '{ "orderId": "order-001", "amount": 150.00, "method": "credit_card" }' ``` ## Best practices * **Use unique, descriptive keys.** A UUID is fine; an entity-typed UUID like `payment-` is easier to debug in logs. * **One key per operation.** Don't reuse the same key for distinct operations — even similar ones. * **Persist the key before the request leaves the client.** If the client crashes mid-request, the persisted key lets a recovery job resume the operation safely. * **Combine with retries.** On transient errors (5xx, network failures), retry with the same key and exponential backoff. ## FAQ > **What if I send the same key with a different body?** > > The cached response from the first call is returned. Idempotency keys assume the client is retrying the same operation, not changing it. To submit a different operation, generate a new key. > **Are 4xx responses cached?** > > Yes. If the first call returned `422 Validation Failed`, retrying with the same key returns the same `422`. This is safe — the client should fix the request and use a new key for the corrected attempt. > **Why aren't 5xx responses cached?** > > A 5xx is a server-side failure — the operation may not have completed. Retries with the same key actually re-run the operation, so the eventual success is captured. > **What's the right key length?** > > No strict limit, but keep it under 256 characters. UUIDs (36 characters) are well within bounds and collision-resistant by construction. > **Does this work on `POST /_query`?** > > No. `_query` is a read-only operation; idempotency doesn't apply. `Idempotency-Key` is ignored on that endpoint. # Mutations Source: https://archie.com/docs/backend/rest-api-explorer/mutations Create, update, and delete records via standard HTTP verbs. Includes bulk operations and update-by-filter. The REST API uses standard HTTP verbs for writes: `POST` to create, `PATCH` to update, `DELETE` to delete. Updates follow [RFC 7396 JSON Merge Patch](https://www.rfc-editor.org/rfc/rfc7396) — only the fields you send are changed. The endpoints are auto-generated from your [Data Model](/docs/features/backend/data-model/overview), so adding a field to a table extends the request and response schema automatically. The examples assume a `students` table with fields like `firstName`, `email`, `age`, `isActive`, and a `city` relationship. ## Single record ### Create `POST /api/rest/` with a JSON body. Returns `201 Created` with the new record and a `Location` header. ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/students" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Alice", "email": "alice.jones@example.com", "age": 21, "isActive": true }' ``` ```json theme={null} { "data": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "firstName": "Alice", "email": "alice.jones@example.com", "age": 21, "isActive": true, "createdAt": "2025-12-15T14:30:00.000Z", "updatedAt": "2025-12-15T14:30:00.000Z" } } ``` To skip the response body, send `Prefer: return=minimal` — you'll get `201 Created` with no body. To link to a related record, include the foreign key in the input: ```json theme={null} { "firstName": "Alice", "email": "alice.jones@example.com", "cityId": "e14638cb-6d72-4a36-b30f-9b763136a7bb" } ``` For safe retries (network failures, timeouts), include an `Idempotency-Key` header — see [Idempotency](/docs/features/backend/rest-api-explorer/idempotency). ### Update `PATCH /api/rest/
/` with the fields you want to change. Fields you don't include are left alone. ```bash theme={null} curl -X PATCH "https://your-gateway.example.com/gw/api/rest/students/287cff0a-345b-4cca-9e9a-75a2161238fd" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/merge-patch+json" \ -d '{ "age": 23, "isActive": false }' ``` ```json theme={null} { "data": { "id": "287cff0a-345b-4cca-9e9a-75a2161238fd", "firstName": "James", "age": 23, "isActive": false, "updatedAt": "2025-12-15T14:45:00.000Z" } } ``` To explicitly null a field, include it with a `null` value: ```json theme={null} { "bio": null, "middleName": null } ``` Both `application/merge-patch+json` and `application/json` are accepted as `Content-Type`. ### Delete `DELETE /api/rest/
/`. Returns `204 No Content` on success. ```bash theme={null} curl -X DELETE "https://your-gateway.example.com/gw/api/rest/students/287cff0a-345b-4cca-9e9a-75a2161238fd" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ## Bulk create `POST /api/rest/
/_bulk` creates up to 100 records in one request. ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/students/_bulk" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "firstName": "Alice", "email": "alice@example.com", "age": 21 }, { "firstName": "Bob", "email": "bob@example.com", "age": 25 } ], "transactionMode": "ALL_OR_NOTHING" }' ``` ### Transaction modes | Mode | Behavior | | - | - | | `ALL_OR_NOTHING` (default) | Single transaction. Any failure rolls back the whole batch. | | `BEST_EFFORT` | Independent inserts. Failures are reported but don't block other rows. | Pass `transactionMode` in the body, or as a query parameter (`?mode=BEST_EFFORT`). ### Success — 201 ```json theme={null} { "data": { "success": true, "transactionMode": "ALL_OR_NOTHING", "totalRequested": 2, "totalSucceeded": 2, "totalFailed": 0, "items": [ { "id": "f47ac10b-..." }, { "id": "a1b2c3d4-..." } ], "errors": [] } } ``` ### Partial success in BEST\_EFFORT — 207 ```json theme={null} { "data": { "success": false, "transactionMode": "BEST_EFFORT", "totalRequested": 3, "totalSucceeded": 2, "totalFailed": 1, "items": [ { "id": "f47ac10b-..." }, { "id": "c3d4e5f6-..." } ], "errors": [ { "index": 1, "message": "Unique constraint violated: email already exists" } ] } } ``` ### Rollback in ALL\_OR\_NOTHING — 422 When any row fails in `ALL_OR_NOTHING` mode, the API returns `422` and `totalSucceeded` is `0` — none of the rows were inserted. ## Bulk delete `DELETE /api/rest/
/_bulk` removes multiple records in one request. ### By ids (single primary key) ```bash theme={null} curl -X DELETE "https://your-gateway.example.com/gw/api/rest/students/_bulk" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "f47ac10b-58cc-4372-a567-0e02b2c3d479", "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ] }' ``` ### By filters (composite primary keys) ```bash theme={null} curl -X DELETE "https://your-gateway.example.com/gw/api/rest/orderProducts/_bulk" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "orderId": "order-1", "productId": "prod-1" }, { "orderId": "order-2", "productId": "prod-2" } ], "transactionMode": "ALL_OR_NOTHING" }' ``` You can't mix `ids` and `filters` in the same request. Composite primary keys require `filters`. ## Update by filter `PATCH /api/rest/
` (no id in the path) updates **every record** matching the filter parameters. Useful for batch operations like "mark all expired". ```bash theme={null} curl -X PATCH "https://your-gateway.example.com/gw/api/rest/students?isActive=equals.false&age=lt.18" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/merge-patch+json" \ -d '{ "status": "inactive", "notes": "Auto-deactivated: underage" }' ``` > [!WARNING] > > At least one filter parameter is **required**. A `PATCH` to `/
` with no filters returns `422 Unprocessable Entity` — this is intentional, to prevent accidentally updating every row in the table. The filter syntax matches the [filtered list query](/docs/features/backend/rest-api-explorer/queries#filtered-list). ## Limits | Setting | Default | | - | - | | Max items in a bulk request | 100 | | Max field selection depth | 3 | | Max relations per query | 10 | Exceeding any limit returns `422 Unprocessable Entity` with details. ## Error responses Errors follow the [Problem Details](https://www.rfc-editor.org/rfc/rfc9457) format with `Content-Type: application/problem+json`. ```json theme={null} { "type": "/errors/validation-failed", "title": "Unprocessable Entity", "status": 422, "detail": "One or more fields failed validation", "instance": "/api/rest/students", "errors": [ { "field": "email", "rule": "required", "message": "The field 'email' is required" } ] } ``` For the full status-code reference and how to handle each one, see [Error handling](/docs/features/backend/rest-api-explorer/error-handling). ## Permissions Every mutation is checked against [Role-Based Access](/docs/features/backend/app-services/role-based-access). Field-level write rules apply too — fields a role isn't allowed to write are rejected. ## FAQ > **PATCH or PUT — which should I use?** > > Use `PATCH`. Archie's update endpoints implement RFC 7396 Merge Patch semantics — only the fields you send are changed. There's no full-record `PUT` replacement variant. > **How do I retry a failed POST safely?** > > Send the same `Idempotency-Key` header on the retry. The API will return the cached response from the first call instead of creating a duplicate. See [Idempotency](/docs/features/backend/rest-api-explorer/idempotency). > **Why is bulk delete returning 207 Multi-Status?** > > `BEST_EFFORT` mode reports per-row outcomes — some succeeded, some didn't. The body's `errors` array tells you which rows failed and why. For all-or-nothing semantics, switch to `ALL_OR_NOTHING`. > **Can I create a record with related rows in one call?** > > For nested writes — creating a parent and its children atomically — use the [GraphQL nested mutations](/docs/features/backend/graphql-api-explorer/standard-operations#nested-mutations). REST creates flat records and links them via foreign-key columns. > **What happens if I PATCH a field that doesn't exist?** > > The API returns `422 Unprocessable Entity` with the offending field name. Unknown fields are not silently ignored. # OpenAPI specification Source: https://archie.com/docs/backend/rest-api-explorer/openapi-specification Every project gets an auto-generated OpenAPI 3.1 spec that documents every endpoint, schema, and auth requirement — always in sync with your data model. Every Archie project automatically generates an [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) specification covering every REST endpoint, request/response schema, and authentication scheme. The spec stays in sync with your [Data Model](/docs/features/backend/data-model/overview) — add a table or change a field and the spec regenerates. The spec drives: * The **REST** tab of the **API Explorer**, which is generated from this same document. * External tooling — Postman, Insomnia, code generators — that imports the spec to provide typed clients. ## Where to get the spec ### From the dashboard Open **Backend → API Explorer** and switch to **REST**. What you see is built from the live spec for the selected environment. Browse endpoints, expand each operation, click **Try it out** to fire a request with your current authentication. ### From the API Fetch the raw JSON with a `GET`: ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/_openapi.json" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```json theme={null} { "openapi": "3.1.0", "info": { "title": "Archie Core REST API", "version": "1.0.0" }, "servers": [ { "url": "/api/rest" } ], "paths": { "/students": { "get": { "..." }, "post": { "..." } }, "/students/{id}": { "get": { "..." }, "patch": { "..." }, "delete": { "..." } } }, "components": { "schemas": { "Students": { "..." }, "StudentsInput": { "..." } } } } ``` Paths in the spec are relative to `servers[0].url`. The gateway prefix (`/gw`) is handled at the infrastructure level. ## What's inside The auto-generated spec includes: * **Endpoints per table** — CRUD, bulk, aggregate, and the structured `_query` endpoint. * **Per-table schemas** — `` for responses, `Input` for write payloads, generated from your column definitions. * **Filter parameters** — every column with the operators it supports and example values. * **Security schemes** — Bearer token (JWT) and API key. * **Error schemas** — RFC 9457 Problem Details response shape. ## Per-environment specs Each environment has its own spec — useful when a branch environment has Data Model changes that aren't yet in `master`. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/_openapi.json" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "environment: dev" ``` See [Environments](/docs/features/backend/environments/overview) for how environments work. ## Caching The spec is cached for performance: | Behavior | Value | | - | - | | `Cache-Control` | `public, max-age=300, stale-while-revalidate=30` | | Storage | Persistent (S3) | | Regeneration | On-demand when the cache expires or the schema changes | You don't need to manually invalidate the cache — schema edits flow through automatically. ## Generating typed clients The spec is the right input to any OpenAPI 3.1 code generator. A few examples: ### TypeScript ```bash theme={null} npx openapi-generator-cli generate \ -i "https://your-gateway.example.com/gw/api/rest/_openapi.json" \ -g typescript-fetch \ -o ./generated-client \ --additional-properties=supportsES6=true ``` Other JS-friendly options: * [`openapi-typescript`](https://github.com/drwpow/openapi-typescript) — generates pure type definitions, pairs well with `openapi-fetch`. * [`orval`](https://orval.dev/) — generates React Query hooks alongside types. ### Other languages `openapi-generator-cli` supports 50+ targets — Python, Go, Ruby, Java, C#, Swift, Kotlin, Rust. Pick the generator (`-g`) that matches your stack. ## Importing into REST clients | Tool | How to import | | - | - | | **Postman** | New → Import → Link → paste the spec URL. Postman generates a full collection with example requests. | | **Insomnia** | Application menu → Import → URL → paste. Endpoints appear ready to fire. | | **Bruno**, **Hoppscotch**, **Thunder Client** | All support OpenAPI import via URL or file. | When importing, point the auth at your API key and `X-Project-ID` header so requests work out of the box. ## Permissions The spec itself is not access-controlled per role — it describes the surface area available. Authentication is required to fetch it. Per-record and per-field permissions are enforced at request time by [Role-Based Access](/docs/features/backend/app-services/role-based-access). ## FAQ > **Does the spec include my custom functions or App Services endpoints?** > > The auto-generated spec covers Data Model endpoints — CRUD, bulk, aggregate, query, file management, webhooks. Custom functions and custom APIs that you write live alongside but are documented separately. > **How fresh is the spec after a schema change?** > > Cached for 5 minutes with `stale-while-revalidate`. Schema changes regenerate the spec on the next fetch after the cache expires. To force-fresh, fetch from a different environment or wait the cache window out. > **Is the spec OpenAPI 3.0 or 3.1?** > > 3.1, which uses the latest JSON Schema draft for component schemas. Most modern tools support it; if you hit a generator that's still on 3.0, downgrade tools rather than the spec. > **Can I publish the spec to my own developer portal?** > > Yes. Cache the JSON and serve it from your portal. For self-updating docs, fetch the spec on a schedule and re-deploy when it changes. > **What's a good code-gen workflow for CI?** > > Pin the generator version, fetch the spec from your `master` environment, generate the client into a build artifact, and check that artifact into version control or publish as an internal package. Run the generator on every Data Model change. # Queries Source: https://archie.com/docs/backend/rest-api-explorer/queries Read records over HTTP — single, list, filtered, paginated, sorted, aggregated, with field selection and an advanced JSON query endpoint. Every table in the [Data Model](/docs/features/backend/data-model/overview) generates a REST query surface. This page covers the read patterns you'll use most. The examples assume a `students` table with fields like `firstName`, `email`, `age`, `isActive`, `createdAt`, and a `city` relationship. ## Single record `GET /api/rest/
/` returns one record by primary key. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students/287cff0a-345b-4cca-9e9a-75a2161238fd" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```json theme={null} { "data": { "id": "287cff0a-345b-4cca-9e9a-75a2161238fd", "firstName": "James", "email": "james.smith@example.com", "age": 22, "isActive": true, "createdAt": "2025-12-01T10:30:00.000Z" } } ``` A missing record returns `404` with a Problem Details body — see [Error handling](/docs/features/backend/rest-api-explorer/error-handling). Tables with composite primary keys don't expose `/` routes. Use the [filtered list](#filtered-list) or the [advanced query endpoint](#advanced-query-endpoint) instead. ## Record list `GET /api/rest/
` returns up to 100 records by default, wrapped in a `data` array with pagination metadata. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```json theme={null} { "data": [ { "id": "287cff0a-...", "firstName": "James", "email": "james.smith@example.com" } ], "meta": { "pagination": { "limit": 100, "offset": 0, "hasNextPage": false, "hasPreviousPage": false } } } ``` To get the total record count, send `Prefer: count=exact` (slower) or `count=estimated` (fast, approximate). ## Filtered list Filters are query parameters in the form `?fieldName=operator.value`. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?isActive=equals.true&age=gte.21" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` Multiple parameters AND together by default. ### Comparison operators | Operator | Example | | - | - | | `equals` | `?status=equals.active` | | `not_equals` | `?status=not_equals.archived` | | `gt` / `gte` | `?age=gte.18` | | `lt` / `lte` | `?price=lt.100` | | `contains` | `?name=contains.john` | | `starts_with` / `ends_with` | `?email=starts_with.admin` | | `in` | `?status=in.(active,pending)` | | `not_in` | `?status=not_in.(archived,deleted)` | | `between` | `?age=between.18,30` | | `is_empty` / `is_not_empty` | `?bio=is_empty.true` | ### Date helpers | Operator | Example | | - | - | | `today` | `?createdAt=today.true` | | `yesterday` | `?createdAt=yesterday.true` | | `this_week` | `?createdAt=this_week.true` | | `this_month` | `?createdAt=this_month.true` | | `last_days` | `?createdAt=last_days.7` | | `last_weeks` | `?createdAt=last_weeks.2` | | `last_months` | `?createdAt=last_months.3` | ### Logical groups `or=(...)`, `and=(...)`, and `not=(...)` compose conditions. They can nest. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?or=(status.equals.active,role.equals.admin)" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?or=(status.equals.active,and(role.equals.admin,age.gte.21))" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ### Filtering across relations Use dot notation to filter a parent by a related table's field. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?city.state=equals.Illinois" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ### Field name conflicts If a field name collides with a reserved query parameter (`select`, `sort`, `limit`), prefix with `col.`: ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/products?col.select=equals.premium" ``` ## Sorted list `?sort=field` sorts ascending. Prefix with `-` for descending. Comma-separate for multi-column sort. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?sort=-age,firstName" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` The dot-style alternative (`sort=createdAt.desc,firstName.asc`) is also supported. ## Paginated list Two pagination styles, both work on the same endpoint. ### Offset-based | Parameter | Alias | Default | Max | | - | - | - | - | | `limit` | `first` | 100 | 1000 | | `offset` | `skip` | 0 | — | ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?limit=20&offset=40" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Prefer: count=exact" ``` `hasNextPage` and `hasPreviousPage` in the response tell you when to stop. ### Cursor-based For stable pagination on frequently-changing tables, use cursors. Cursor values are opaque, base64-encoded strings — read them from the response, don't construct them yourself. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?first=20" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?first=20&after=eyJpZCI6Ijk3..." \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ## Field selection `?select=field1,field2` returns only the listed fields. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?select=id,firstName,email" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ### Embedding related records Parentheses embed fields from related tables — equivalent to a SQL JOIN or a nested GraphQL query. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?select=id,firstName,city(nameCity,state)" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` You can nest up to 3 levels deep, with a maximum of 10 relations per query. Exceeding either limit returns `400 Bad Request`. ### Field aliases Use `originalName:alias` to rename fields in the response. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/students?select=firstName:name,email:contactEmail,city:location(nameCity:cityName)" ``` ## Aggregation `GET /api/rest/
/_aggregate` computes counts, sums, averages, and other aggregates. ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/orders/_aggregate?fn=count,sum:total,avg:total" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ```json theme={null} { "data": { "aggregates": [ { "count": 156, "sum_total": 24780.50, "avg_total": 158.85 } ] } } ``` | Function | Example | | - | - | | `count` | `fn=count` | | `sum` | `fn=sum:total` | | `avg` | `fn=avg:total` | | `min` | `fn=min:total` | | `max` | `fn=max:total` | | `count_distinct` | `fn=count_distinct:status` | ### Group by ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/orders/_aggregate?fn=count,avg:total&groupBy=status,country" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ### Filter groups with `having` ```bash theme={null} curl -X GET "https://your-gateway.example.com/gw/api/rest/orders/_aggregate?fn=count&groupBy=status&having=count.gt.10" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" ``` ### Sort and filter aggregates `sort=-count` sorts groups by an aggregate alias. Filters from a regular list query (`createdAt=this_month.true`) apply **before** aggregation. ## Advanced query endpoint For deeply nested filter logic that's awkward to express as URL params, `POST /api/rest/
/_query` accepts a structured JSON body. It's a `POST` for ergonomics — but the operation is **read-only**, no data is modified. ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/students/_query" \ -H "Authorization: Bearer archie_YOUR_API_KEY" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "filter": [ { "field": "isActive", "operator": "equals", "value": true }, { "field": "age", "operator": "between", "value": [18, 30] } ], "sort": [ { "field": "age", "direction": "DESC" } ], "pagination": { "first": 10, "skip": 0 }, "select": [ { "field": "id" }, { "field": "firstName" }, { "field": "city", "children": [{ "field": "nameCity" }] } ] }' ``` Use the advanced endpoint when: | Use case | Why | | - | - | | Deeply nested AND/OR/NOT logic | Easier to express in JSON than URL params. | | Dynamic queries built in code | Constructing JSON is cleaner than URL string concatenation. | | Filters exceed URL length limits | Long URL filters can hit gateway/proxy limits; bodies don't. | For simple lookups, the `GET` form is still better — it's cacheable and bookmarkable. ## Permissions All query endpoints enforce the per-role permissions in [Role-Based Access](/docs/features/backend/app-services/role-based-access). Records and fields a role can't read are silently filtered out of responses. ## FAQ > **What's the maximum number of records per request?** > > 1000 with `limit=1000`. The default is 100. For larger result sets, paginate. > **Why doesn't `totalCount` appear in my response?** > > By default it's omitted for performance. Send `Prefer: count=exact` for the precise total or `count=estimated` for a fast approximation. > **Should I use offset or cursor pagination?** > > Offset is simpler — works for jump-to-page UIs. Cursor is more stable when records are being inserted or deleted between page fetches. Use cursor for infinite-scroll feeds; use offset for paginated tables. > **When should I use `_query` vs URL params?** > > URL params for simple, browser-friendly, cacheable queries. `_query` for complex nested logic, dynamic query construction, or filters that exceed URL length limits. > **How do I select fields from a relation that's many-to-many?** > > Embed the join table — for example, `?select=id,name,enrollments(course(name))` — and the parentheses traverse the relationship. Up to 3 levels deep. # Webhooks Source: https://archie.com/docs/backend/rest-api-explorer/webhooks Subscribe to data changes and have them delivered to your services as signed HTTP POSTs. Retry, filter, and verify with HMAC signatures. Webhooks deliver real-time HTTP notifications to your own services whenever data changes in a project. When a record is inserted, updated, or deleted on any [Data Model](/docs/features/backend/data-model/overview) table, Archie sends a signed `POST` to a URL you control, with the event payload in the body. This page covers webhooks **Archie sends from your project to external systems**. For incoming webhook handling on the integration side (Stripe events, GitHub events, etc.), see [Integrations → Webhooks](/docs/features/backend/integrations/webhooks). ## How it works 1. **Register** a webhook subscription: which table, which events, which URL. 2. **Data changes** — every matching mutation (REST or GraphQL) publishes an event. 3. **Delivery** — the webhook service `POST`s to your URL with the payload. 4. **Verify** — your endpoint validates the HMAC signature, then processes the event. 5. **Retry** — non-2xx responses retry with exponential backoff up to 5 attempts. ## Webhook management API All management endpoints live under `/api/rest/_webhooks` and require a management API token. | Method | Endpoint | Purpose | | - | - | - | | `POST` | `/api/rest/_webhooks` | Create a subscription | | `GET` | `/api/rest/_webhooks` | List subscriptions | | `GET` | `/api/rest/_webhooks/{id}` | Get subscription + recent deliveries | | `PATCH` | `/api/rest/_webhooks/{id}` | Update a subscription | | `DELETE` | `/api/rest/_webhooks/{id}` | Delete a subscription | ### Required headers | Header | Required | Description | | - | - | - | | `Authorization` | Yes | `Bearer ` | | `X-Project-ID` | Yes | Project identifier | | `environment` | Yes | Target environment — must match the body for create/update | The `environment` header is **required** on every webhook management request, including `GET` and `DELETE`. ## Create a webhook ```bash theme={null} curl -X POST "https://your-gateway.example.com/gw/api/rest/_webhooks" \ -H "Authorization: Bearer your-management-token" \ -H "X-Project-ID: your-project-id" \ -H "Content-Type: application/json" \ -d '{ "projectId": "your-project-id", "environment": "master", "table": "orders", "events": ["INSERT", "UPDATE", "DELETE"], "url": "https://your-app.example.com/webhooks/orders", "secret": "whsec_your_signing_secret", "active": true }' ``` | Field | Type | Required | Description | | - | - | - | - | | `projectId` | String | Yes | Project identifier. | | `environment` | String | Yes | Target environment. | | `table` | String | Yes | Table name to watch. | | `events` | Array | Yes | Any of `INSERT`, `UPDATE`, `DELETE`. | | `url` | String | Yes | HTTPS endpoint to receive events. | | `secret` | String | Yes | HMAC signing secret — keep this safe. | | `active` | Boolean | No | Default `true`. Set `false` to pause delivery. | | `filter` | Object | No | Only deliver for records matching this filter. | | `select` | Array | No | Limit which fields are included in the payload. | ### Filtering events Only deliver when the record matches a condition: ```json theme={null} { "table": "orders", "events": ["UPDATE"], "url": "https://your-app.example.com/webhooks/completed-orders", "secret": "whsec_secret", "filter": { "status": "completed" } } ``` ### Selecting fields Limit the payload to specific fields — useful for keeping payloads small or avoiding sensitive data in webhook bodies: ```json theme={null} { "table": "orders", "events": ["INSERT"], "url": "https://your-app.example.com/webhooks/new-orders", "secret": "whsec_secret", "select": ["id", "total", "status", "customerId"] } ``` ## Event payload When a matching change happens, Archie delivers an HTTP `POST` to your URL with this body: ```json theme={null} { "id": "evt_abc123def456", "timestamp": "2025-12-15T15:00:00Z", "project_id": "your-project-id", "environment": "master", "table": "orders", "event": "INSERT", "record": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "total": 99.50, "status": "pending", "customerId": "cust-001" }, "data": { "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "total": 99.50, "status": "pending", "customerId": "cust-001" }, "changed_fields": ["total", "status"] } ``` | Field | Description | | - | - | | `id` | Unique event identifier — use this to deduplicate. | | `timestamp` | ISO 8601 timestamp of the event. | | `project_id` | Project that generated the event. | | `environment` | Environment in which the change happened. | | `table` | Table that was modified. | | `event` | `INSERT`, `UPDATE`, or `DELETE`. | | `record` | Full record (or selected fields) after the mutation. | | `data` | Same as `record`, kept for compatibility. | | `changed_fields` | Fields that changed (for `UPDATE`; empty for `INSERT` and `DELETE`). | For `DELETE`, `record` reflects the row's state immediately before deletion. ## Delivery headers Each delivery includes: | Header | Value | | - | - | | `Content-Type` | `application/json` | | `X-Archie-Event` | `INSERT` / `UPDATE` / `DELETE` | | `X-Archie-Delivery` | The event id (for tracking and dedup). | | `X-Archie-Signature` | `sha256=` HMAC signature with prefix. | | `X-Webhook-Signature` | Raw hex digest, no prefix. | ## Verifying signatures Always verify the HMAC signature before processing the payload — never trust an unsigned webhook body. ### Node.js ```javascript theme={null} const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); const sig = signature.replace('sha256=', ''); return crypto.timingSafeEqual( Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex') ); } app.post('/webhooks/orders', (req, res) => { const signature = req.headers['x-archie-signature']; const isValid = verifyWebhookSignature( JSON.stringify(req.body), signature, process.env.WEBHOOK_SECRET ); if (!isValid) { return res.status(401).send('Invalid signature'); } const { event, table, record } = req.body; // process the event... res.status(200).send('OK'); }); ``` ### Python ```python theme={null} import hmac import hashlib def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode('utf-8'), payload, hashlib.sha256 ).hexdigest() sig = signature.replace('sha256=', '') return hmac.compare_digest(sig, expected) ``` Always use a constant-time comparison (`timingSafeEqual`, `hmac.compare_digest`) to avoid leaking the signature one byte at a time. ## Retries and circuit breakers If your endpoint returns a non-2xx status or doesn't respond, the webhook service retries with exponential backoff: | Attempt | Delay | | - | - | | 1 | Immediate | | 2 | \~1 second | | 3 | \~5 seconds | | 4 | \~30 seconds | | 5 | \~5 minutes | After the maximum attempts, the delivery moves to a **dead-letter** state and shows up in `GET /_webhooks/{id}` for inspection. A **circuit breaker** protects your endpoint from sustained failures: after 5 consecutive failures, deliveries pause for a 2-minute cooldown, then retry. This avoids hammering an endpoint that's clearly down. ## Inspecting deliveries `GET /api/rest/_webhooks/{id}` returns the subscription and recent delivery history: ```json theme={null} { "webhook": { "id": "wh_a1b2c3d4-e5f6-7890", "table": "orders", "events": ["INSERT", "UPDATE", "DELETE"], "url": "https://your-app.example.com/webhooks/orders", "active": true, "createdAt": "2025-12-15T14:30:00Z" }, "deliveries": [ { "id": "del_x1y2z3", "eventId": "evt_abc123", "status": "delivered", "httpStatus": 200, "attempts": 1, "createdAt": "2025-12-15T15:00:00Z" } ] } ``` Use this endpoint as part of your monitoring — alert if a webhook accumulates failed deliveries. ## Best practices * **Always verify signatures.** Never trust an unsigned payload. * **Respond fast.** Return `200` within 5 seconds. Process the event asynchronously if needed. * **Deduplicate by event id.** The same event may be redelivered if your endpoint returns a non-2xx — process the work once. * **Use HTTPS.** Webhook URLs must be HTTPS. * **Monitor for dead-letter deliveries.** Check `/_webhooks/{id}` regularly or wire it to alerting. ## FAQ > **Webhooks vs subscriptions — which should I use?** > > Use webhooks for **server-to-server** delivery — your backend reacts to data changes from another machine. Use [GraphQL subscriptions](/docs/features/backend/graphql-api-explorer/subscriptions) for **client UIs** — a browser stays connected over a WebSocket and receives events while it's open. Webhooks retry; subscriptions don't. > **Why might the same event be delivered twice?** > > Network failures or 5xx responses trigger a retry. The retry uses the same event id (`X-Archie-Delivery`), so you can deduplicate by tracking processed event ids on your side. At-least-once delivery is the trade-off for guaranteed delivery on transient failures. > **Can I rotate the signing secret?** > > Yes — `PATCH` the subscription with a new `secret`. To rotate without downtime, configure your endpoint to accept either the old or new secret during the rotation window, then drop the old one once the new is in place. > **What happens if my webhook endpoint is down for hours?** > > Deliveries retry up to 5 times over a few minutes, then move to dead-letter. They aren't auto-redelivered after that — you'd need to read the dead-letter list and replay them yourself. For at-least-once-with-long-windows guarantees, plan endpoint reliability accordingly. > **Can I have multiple webhooks for the same table?** > > Yes. Multiple subscriptions to the same table-and-event combination each receive a copy of the event. Useful for sending the same event to different downstream systems. # Overview Source: https://archie.com/docs/backend/scheduled-tasks Scheduled Tasks run a target — a GraphQL operation, a gateway route, or an external API — on a cron schedule or once at a set time, with retries, overlap control, and a full run history. Scheduled Tasks let a project run work automatically — on a repeating cron schedule or once at a future time. Each task is a **trigger**: a schedule plus a target to call and the credential to call it with. Archie fires the target on schedule, retries on failure, and records every run. Use them for nightly rollups, reminder emails, data syncs, cache warming, or any job that should run without someone clicking a button. Open a project, switch to the Backend Console, and select **Tasks**. ![Tasks list with no scheduled tasks yet](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/backend/scheduled-tasks/tasks-empty-state.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=e183bce0e3daea438e6e30d1b7701f98) ## Core concepts > **Trigger** > > One scheduled task: a name, a schedule, a target, and an execution credential. Triggers have a lifecycle — active, suspended, completed, or archived. > **Schedule** > > When the trigger fires. Either a recurring cron expression in a timezone, or a one-off run at a specific date and time. > **Target** > > What the trigger calls: a GraphQL operation, a REST or Custom API gateway route, or an external HTTPS API. > **Execution** > > One run of a trigger. Every fire — scheduled or manual — produces an execution record with its status, timing, and response. ## How it works Scheduled Tasks span three parts of the platform. Knowing the path helps when you're debugging a task that isn't firing. **1. You configure the task in the Backend Console** The **Scheduled Tasks** UI in the frontend collects the schedule, target, and options, and sends a GraphQL mutation to your project's backend API. **2. The backend validates and forwards it** The backend API resolves your **project and environment from the auth token** — never from the request body — and forwards the call to the scheduler service. Because the tenant comes from the token, one project can't create or read another's tasks. **3. The scheduler service stores and runs it** The scheduler service persists the trigger, computes the next fire time, and enqueues the run. When the time comes it dispatches the target with your stored credential, applies retries and the overlap rule, and records the execution. Everything you do in the UI — create, edit, suspend, run now, browse history — is a GraphQL operation against the backend API, so the same actions are available programmatically. See [the API reference](/docs/features/backend/scheduled-tasks/run-history#graphql-api) for the operation names. ## The trigger lifecycle A trigger is always in one of four **stored** states: | Status | Meaning | | - | - | | **Active** | Scheduled and firing on time. | | **Suspended** | Kept but not firing. Resume any time with **Activate**. | | **Completed** | A one-off task that has fired its single run. Terminal. | | **Archived** | Retired. Kept for history but never fires again. | > [!NOTE] > > The Backend Console labels these **Suspend** / **Activate** on the task itself, and **Delete** for archiving — the confirmation dialog spells out that deleting archives the task and stops it running rather than erasing it. See [Manage the lifecycle](/docs/features/backend/scheduled-tasks/run-history#manage-the-lifecycle). **Failing** is not a stored state — it's a derived health signal. An active trigger whose recent runs all failed is surfaced as failing, with its consecutive-failure count and last success and failure times, so a broken task stands out without changing its status. ## What a target can be | Target | What it calls | | - | - | | **GraphQL** | A GraphQL operation you write inline — query or mutation, with variables — against your project's API. | | **REST** | A [REST API](/docs/features/backend/rest-api-explorer/overview) route on your project. | | **Custom API** | A [Custom API](/docs/features/backend/app-services/custom-apis) gateway route. | | **External REST** | An arbitrary public `https://` API. Requests are screened by an anti-SSRF guard — only public hosts are allowed; private, loopback, and cloud-metadata addresses are blocked. | The first three call **internal platform artifacts** by reference, so they stay inside your project's auth and gateway model. External REST is the only target that leaves the platform, which is why it's restricted to public HTTPS hosts. ## Execution credential Every trigger carries an **execution credential** — the `Authorization` value (for example `Bearer `) used when it dispatches. Create a role-bound [API key](/docs/features/backend/settings/api-keys) with exactly the permissions the task needs, and give it to the trigger. The credential is **write-only**: it's stored encrypted and never returned by the API or shown again in the UI. When you edit a task, leaving the credential blank keeps the stored one. > [!WARNING] > > If the API key behind a task is revoked or expires, its runs fail — an execution is recorded with an identity-revoked status. Rotate the key on the task before revoking the old one. ## Per-environment Scheduled Tasks are scoped to an [environment](/docs/features/backend/environments/overview). A task defined in `master` is separate from one in another environment, each with its own schedule, credential, and run history. Create the tasks an environment needs in that environment. ## Where to go next > **[Create a task](/docs/features/backend/scheduled-tasks/creating-a-task)** > > Walk through the schedule, target, and execution options step by step. > **[Run history & monitoring](/docs/features/backend/scheduled-tasks/run-history)** > > Read execution statuses, run a task on demand, and manage its lifecycle. ## FAQ > **What's the difference between a Scheduled Task and a Custom Function?** > > A [Logic service](/docs/backend/logic) is code you write and deploy. A Scheduled Task is a schedule that calls a target — which can be a GraphQL operation, a gateway route, or an external API. A task can call an endpoint backed by a custom function, but the task itself is scheduling and dispatch, not code. > **Can a task run every minute / on a complex schedule?** > > Yes — any cron expression is accepted, in the timezone you choose. You can also write the schedule in plain language and have Archie resolve it to a cron expression for you. See [Creating a task](/docs/features/backend/scheduled-tasks/creating-a-task). > **Are tasks copied when I branch an environment?** > > Tasks live per-environment. Create the tasks each environment needs in that environment; credentials in particular are never shared across environments. > **Can two runs of the same task overlap?** > > That's up to the task's overlap rule — skip the new fire, queue it behind the running one, or allow concurrent runs. See [Creating a task](/docs/features/backend/scheduled-tasks/creating-a-task#execution-options). # Creating a task Source: https://archie.com/docs/backend/scheduled-tasks/creating-a-task Define a scheduled task in the Backend Console: name it, choose a target, set a schedule, and tune retries, timeout, and overlap behavior. Create a task from **Backend → Tasks → New Task**. The form is grouped into the target, the schedule, and the execution options — this page walks each one. ## Name the task Give the task a clear **name** and an optional **description**. Both are for you and your team — the name is how the task shows up in the list and the run history. You can also add **tags** to group and filter related tasks. ## Choose a target The target is what the task calls each time it fires. The **Service type** dropdown offers four options. ![New Scheduled Task panel showing the Service type dropdown with GraphQL, REST, Custom API, and External REST](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-service-type.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=2d8f3a8a2648f17736bf2fdcdd3c9e4f) > **GraphQL** > > Write a GraphQL **operation** — a query or mutation — directly, with optional **variables** as a JSON object. The operation runs against your project's GraphQL API. There's no saved-operation catalog; you author the operation on the task. > > ![New Scheduled Task panel with a GraphQL target: query and payload variables](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-target.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=d67514d402e21751ebe62ad86e9f6a1e) > **REST / Custom API** > > Reference a gateway route on your project by its **route ID** and **path**, with an HTTP **method**, an optional JSON **body**, and optional **headers**. This calls an internal [REST](/docs/features/backend/rest-api-explorer/overview) or [Custom API](/docs/features/backend/app-services/custom-apis) route by reference — never an arbitrary URL. > **External REST** > > Call an arbitrary public API. Enter an absolute `https://` URL, an HTTP method, and an optional body and headers. Requests are screened by an anti-SSRF guard: only public hosts are allowed — private, loopback, and cloud-metadata addresses are blocked. > > ![External REST target with Method, Endpoint URL, request body, and headers](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-external-rest.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=01114a8155d648c776c293b03fcdb2db) ## Set the schedule Pick how the task fires. There are three modes. **Natural language** Describe the schedule in plain words — for example, *"every weekday at 8am"* — and click **Resolve schedule**. Archie fills in the cron expression and previews the next few fire times; review both before saving. ![Schedule resolved from natural language, showing the cron expression and the next three fire times](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-schedule-resolved.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=a902fc5da961aea0960ce24f7c1f97e2) **Cron** Enter a cron expression directly, plus the **timezone** it runs in. The task fires on that recurring schedule. **Once** Pick a single date and time. The task fires exactly once, then moves to **Completed**. ![New Scheduled Task panel showing Schedule type, When should this run, and Timezone](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-schedule.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=9bf6aa7fafdaa78227081c1a641ab9a1) The **timezone** is an IANA zone (for example `America/Bogota`). Cron schedules are evaluated in that zone, so daylight-saving shifts are handled correctly. If you don't set one, the schedule runs in UTC. > [!NOTE] > > Natural-language resolution just fills in the cron expression — it's a convenience on top of the cron mode, not a separate kind of schedule. Once resolved, the task stores and runs the plain cron expression. ## Execution options Tune how each run behaves. | Option | What it controls | | - | - | | **Overlap policy** | What happens when a run is still going and the next fire arrives: **Skip if still running** drops the new fire, **Queue** runs it after the current one finishes, **Allow** lets them run concurrently. | | **Retries on failure** | How many times to retry a failed run. | | **Timeout (seconds)** | How long a single run may take before it's marked timed out. Defaults to 30. | > [!NOTE] > > Choose **Skip if still running** or **Queue** for jobs that must not run twice at once — a nightly rollup, a job that writes to the same rows. Use **Allow** only when concurrent runs are genuinely safe. ## Provide the execution credential Add an **execution credential** — the `Authorization` value sent when the task dispatches (for example `Bearer `). For a GraphQL, REST, or Custom API target, create a role-bound key under [Settings → API Keys](/docs/features/backend/settings/api-keys) scoped to exactly what the task needs. For an External REST target the field is optional — only fill it in if the third-party API requires that header. ![New Scheduled Task panel showing Execution policy, the write-only Execution credential field, and Tags](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/new-task-execution-credential.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=089f329a491d1f2bec77635aec9dbe03) * On **create**, fill in a credential whenever the target needs one to authenticate. * On **edit**, it's optional: leave it blank to keep the stored one. The credential is write-only — it's stored encrypted and never shown again. > [!WARNING] > > Scope the API key to the minimum the task needs. A scheduled task runs unattended with whatever the key can do, so an over-scoped key is a standing risk. ## Save Saving creates the trigger in **Active** state and schedules its next fire. From the task list you can then [run it on demand, suspend it, or review its history](/docs/features/backend/scheduled-tasks/run-history). ![Tasks list showing a newly created Active task with its type, schedule, and next run](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/backend/scheduled-tasks/tasks-list-active.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=953644019ba8502b02476e0ee8c628c5) ## Editing a task Open a task and edit any field. Two things to know: * Leaving the **API key** blank keeps the existing credential — you don't re-enter it to change the schedule. * Edits use optimistic concurrency: if someone else changed the task since you opened it, your save is rejected so you don't overwrite their change. Reload and reapply. ## FAQ > **Do I have to know cron syntax?** > > No. Use the natural-language mode — describe the schedule in words and click **Resolve schedule** to get a cron expression and a preview of the next fire times. Cron mode is there when you want exact control. > **Can a task call an endpoint that needs authentication?** > > Yes — that's what the execution credential is for. The task sends the value you give it as the `Authorization` header on every dispatch. Scope that key to the task's needs. > **Why is my External REST target rejected?** > > External REST only allows public `https://` hosts. Private, loopback, and cloud-metadata addresses are blocked by the anti-SSRF guard. To call something inside your project, use a REST or Custom API target instead. > **What happens to a one-off task after it runs?** > > It moves to **Completed** and never fires again. It stays in the list with its run history until you delete (archive) it. # Run history & monitoring Source: https://archie.com/docs/backend/scheduled-tasks/run-history Every run of a scheduled task is recorded with its status, timing, and response. Review the history, run a task on demand, and manage its lifecycle from the Backend Console. Every time a task fires — on schedule or on demand — it produces an **execution** record. The run history is where you confirm a task is working, and where you look first when it isn't. Open a task from **Backend → Tasks**. ![Task detail view showing Target, Schedule, Next Run, and Runs As, with Suspend and Run Now in the header](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/backend/scheduled-tasks/task-detail-view.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=6f48ba72fec5ad945620b91c4a0f670f) ## What a run records Each execution captures: * **Status** and **type** (scheduled or manual). * **Timing** — when it was due to fire, when it started and finished, and how long it took. * **Attempts** — how many tries it took, including retries. * **Result** — the HTTP status returned, an error code and message on failure, and a **response excerpt** (the called service's response body, truncated to the task's max-response size). * A **trace correlation ID** that links to the [Logs](/docs/features/backend/logs) view for the full request and response. ## Execution statuses | Status | Meaning | | - | - | | **Pending** | Enqueued, not started yet. | | **Running** | In flight. | | **Success** | Completed successfully. | | **Failed** | The target returned an error or the call failed. | | **Timeout** | Exceeded the task's timeout. | | **Retrying** | Failed and waiting for the next retry. | | **Cancelled** | Stopped before completing. | | **Skipped by lock** | A previous run was still going and the task's overlap mode is **Skip**, so this fire was dropped. | | **Identity revoked** | The task's execution credential was revoked or expired, so the run couldn't authenticate. | > [!NOTE] > > **Skipped by lock** is expected behavior, not an error — it's how the **Skip** overlap mode prevents two runs at once. If you see it often, the task is firing faster than a run completes; widen the schedule, shorten the work, or switch the overlap mode. ## Task health A task's list entry shows a **health** signal derived from its recent runs. An active task whose last runs all failed is flagged **failing**, along with how many consecutive failures it's had and when it last succeeded. This is a display signal only — a failing task stays **Active** and keeps firing; the flag just makes a broken task easy to spot. ## Run a task on demand Use **Run Now**, in the header of the task detail view, to fire a task immediately without waiting for its schedule — handy for testing a new task or re-running after a fix. A manual run produces an execution marked as **manual** in the history, alongside the scheduled ones. The schedule is unaffected; the next scheduled fire still happens as planned. ## Manage the lifecycle The task detail header has **Suspend** (or **Activate** on a suspended task) next to **Run Now**. Its **•••** menu has **Edit** and **Delete**. | Action | Effect | | - | - | | **Suspend** | Stop firing but keep the task. Its next-fire time clears. Status becomes **Suspended**. | | **Activate** | Resume a suspended task. It picks its schedule back up. | | **Delete** | Retire the task. The confirmation dialog is explicit that this **archives** the task and stops it running — it does not erase the task or its run history — but it cannot be undone from the UI. | ![The ••• menu on a task, with Edit and Delete](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/task-detail-more-menu.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=4f03f47ea28e3c163346eea91b19c180) ![A task after clicking Suspend: status is Suspended and the header action becomes Activate](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/backend/scheduled-tasks/task-suspended-state.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=a67e8104a6bd817bf59cb352f9ff83ab) Suspending is reversible; deleting is the retire path, confirmed by typing the task's name. ![Delete scheduled task confirmation: typing the task name to confirm](https://mintcdn.com/archie-e998dbf6/MYkq_yUeVpCqwHiI/features/backend/scheduled-tasks/task-delete-confirm.png?fit=max\&auto=format\&n=MYkq_yUeVpCqwHiI\&q=85\&s=84f84151f042896a6e1313fa8a75c832) ## GraphQL API Everything the UI does is a GraphQL operation against your project's backend API, so you can manage tasks programmatically with the same calls. Your project and environment are taken from the auth token, not the request — so a token only ever sees its own project's tasks. **Mutations** | Operation | Does | | - | - | | `createScheduledTrigger` | Create a task. | | `updateScheduledTrigger` | Edit a task (supports optimistic concurrency via an expected version). | | `pauseScheduledTrigger` | Suspend a task (the **Suspend** button in the UI). | | `enableScheduledTrigger` | Resume a suspended task (the **Activate** button in the UI). | | `archiveScheduledTrigger` | Retire a task (the **Delete** action in the UI). | | `runScheduledTriggerNow` | Fire a task immediately (**Run Now**). | **Queries** | Operation | Returns | | - | - | | `schedulerTrigger` | One task by ID. | | `schedulerTriggers` | The task list, with filters. | | `schedulerExecution` | One execution by ID. | | `schedulerExecutions` | A task's run history, with filters. | | `schedulerResolveSchedule` | The cron expression for a natural-language schedule (no task is created). | > [!NOTE] > > `schedulerResolveSchedule` is pure — it just turns a plain-language schedule into a cron expression and returns it. It's what the natural-language mode in the [create form](/docs/features/backend/scheduled-tasks/creating-a-task#set-the-schedule) calls before you save. ## FAQ > **A task shows failing — where do I look?** > > Open its run history and read the failed execution: the HTTP status, error message, and response excerpt usually say why. For the full request and response, follow the run's trace correlation ID into the [Logs](/docs/features/backend/logs) view. > **My run history shows 'Identity revoked' — what happened?** > > The API key the task dispatches with was revoked or expired. Edit the task, paste a fresh key, and save. Rotate the key on the task before revoking the old one to avoid the gap. > **Does 'Run Now' change the schedule?** > > No. It fires one manual run immediately and records it in the history. The recurring schedule is untouched — the next scheduled fire still happens on time. > **What's the difference between Suspend and Delete?** > > Suspend is a reversible off switch — click **Activate** later and it resumes. **Delete** retires the task for good; the confirmation dialog says this archives it rather than erasing it, but there's no UI path back. Both keep the run history. # Overview Source: https://archie.com/docs/backend/settings Project-level backend settings — API keys, environment variables, and network rules for CORS and rate limiting. Settings holds the project-level configuration for your backend: who can call your APIs, which values your app reads at runtime, and how incoming traffic is handled. Open it from **Settings** in the Backend Console sidebar. ## What's in Settings > **[API Keys](/docs/backend/settings/api-keys)** > > Create, scope, and rotate keys that let external apps, scripts, and tools call your API. > **[Environment variables](/docs/backend/settings/environment-variables)** > > Store configuration values your app reads at runtime instead of hard-coding them. > **[Network](/docs/features/backend/settings/network)** > > Control which origins can call your API (CORS) and how traffic is rate-limited. ## Settings and environments Settings are tied to your [environments](/docs/backend/environments). Before you change anything, check which environment is active in the environment switcher. * **API keys** belong to one environment. A key created in staging does not work in production. * **Environment variables** can be scoped per environment, so production-only secrets stay out of development. * **Network** settings are project-wide defaults, scoped per environment. ## Related pages * [Role-Based Access](/docs/backend/app-services/role-based-access) — an API key gets its permissions from the roles you assign to it. * [Custom APIs](/docs/backend/app-services/custom-apis) — override CORS and rate limits for a single route. * [Integrations](/docs/backend/integrations) — the better place for third-party credentials like Stripe or SendGrid keys. ## FAQ > **Can I use the same API key in every environment?** > > No. Each environment has its own set of keys. Create a separate key in each environment that needs one. > **Should I put integration credentials in environment variables?** > > Use [Integrations](/docs/backend/integrations) for credentials like Stripe or SendGrid keys. Integrations handle rotation and isolation better than raw environment variables. > **Can I set different CORS rules for one endpoint?** > > Yes. Network settings are the defaults for the whole project. To change the rules for one route, configure it in [Custom APIs](/docs/backend/app-services/custom-apis). # API Keys Source: https://archie.com/docs/backend/settings/api-keys Manage API keys at the developer level. API keys grant external clients access to your project's APIs. This page covers the developer-side workflow — creating, scoping, and rotating keys. The **API Keys** section allows you to generate and manage secure tokens for your project. These keys are essential for granting external applications, scripts, or third-party tools access to your project's API without exposing user credentials. To access this section, navigate to **Settings > API Keys** in the left-hand sidebar menu. > **Note:** API keys are scoped per environment. Each environment has its own set of API keys, and keys generated in one environment are not valid in another. See [Environments](/docs/features/backend/environments/overview) for the per-environment scoping model. ![API Keys management panel](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/settings/api-keys/settings-api-keys.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=44662bd6c92a623e6643e068e5c20a04) ## Creating a new API key To generate a new credential: 1. Click the black **+ Add new API key** button located in the top-right corner of the dashboard. 2. A configuration modal titled **Create API Key** will appear. 3. Fill in the required fields to define the key's purpose and structure. ### Configuration fields * **Key Name:** Give your token a descriptive name to identify its purpose. * **Key Prefix:** Give your key a prefix to identify its purpose. This will be used to identify the key in the API requests. * **Roles:** Select one or more roles to assign to this API key. This determines the level of access the key will have. Roles are defined in [Role-Based Access](/docs/features/backend/app-services/role-based-access); the key inherits whatever Read/Write/Update/Delete permissions and row-level filters those roles carry. * **Context:** Add custom key-value pairs that will be sent to the permissions service for filter validation. This allows for fine-grained access control based on specific attributes. Once configured, click the **Create Key** button to finalize the process and generate your token. ![API Key details and permissions](https://mintcdn.com/archie-e998dbf6/LxC2D3x9dqFVd4Vn/features/backend/settings/api-keys/settings-api-key-details.png?fit=max\&auto=format\&n=LxC2D3x9dqFVd4Vn\&q=85\&s=90cd2e89100a47769a99a60dbc482706) > The key value is shown only once at creation. Persist it in your secrets manager immediately. ## Using a key The scheme word in the `Authorization` header isn't fixed to `Bearer` — it's whatever you set in the **Key Prefix** field when you created the key (`Bearer`, `Token`, `ApiKey`, or any custom value). ```bash theme={null} curl -H "Authorization: $KEY_PREFIX $ARCHIE_API_KEY" \ https://api.yourapp.com/api/projects ``` For example, if you set the prefix to `Bearer`: ```bash theme={null} curl -H "Authorization: Bearer $ARCHIE_API_KEY" \ https://api.yourapp.com/api/projects ``` Or if you set it to `ApiKey`: ```bash theme={null} curl -H "Authorization: ApiKey $ARCHIE_API_KEY" \ https://api.yourapp.com/api/projects ``` Or in a typed client, set the auth header on construction. ## Rotation Plan for key rotation: 1. Create a new key 2. Deploy consumers reading from the new key 3. Verify traffic on the old key has stopped 4. Delete the old key A short overlap window where both keys work simplifies cutover. ## Audit Each authenticated request logs the key it used. Filter audit by key when investigating. ## FAQ > **Is there a key prefix that identifies it as Archie's?** > > Yes — keys are prefixed at creation (configurable via the **Key Prefix** field). The platform-default prefix appears in REST examples as `archie_YOUR_API_KEY`. > **Can I create a key from a CI/CD pipeline?** > > Yes via the API mutation. Store the resulting key in your CI's secret store. > **Are keys workspace-wide or project-scoped?** > > Project-scoped, and further scoped per environment. For workspace-level credentials, see [workspace secrets](/docs/backend/settings/environment-variables). # Environment variables Source: https://archie.com/docs/backend/settings/environment-variables Store the configuration and secrets your backend code needs, such as API URLs, sender addresses, and keys. Variables belong to an environment, secrets are encrypted and write-only, and changes reach your services the next time you deploy. Environment variables hold values your code reads at runtime instead of hard-coding them: an API URL, a sender address, a template ID. Archie also lets you store **secrets**, which are values like keys and tokens that must stay private. The variables you set here are the same ones the [Logic](/docs/backend/logic) services in the environment receive when they deploy. ## Open the page Go to **Backend → Settings → Environment Variables**. The **Settings** section is at the end of the Backend Console navigation. Its sidebar has a **Project** group with **API Keys**, **Environment Variables**, **Network**, and **Danger Zone**. ![The Environment Variables page with a table of variables whose values are masked, an empty row for a new variable, and a Secrets section.](https://mintcdn.com/archie-e998dbf6/JW67AKv9r3EIl9M1/images/43-env-vars-page.png?fit=max\&auto=format\&n=JW67AKv9r3EIl9M1\&q=85\&s=7bbae0a9cb30ce2fb4519d4c9d34cd01) The page shows the variables of the [environment](/docs/backend/environments) selected in the environment menu on the right of the Backend Console, such as `master`. Switch environments to see that environment's variables. ## Variables The first table lists your variables by **Key** and **Value**. Archie masks every value. Each row also has an eye icon and an X icon. ### Add a variable 1. Type a name in the empty **Add Key** field at the bottom of the table. 2. Type its value in **Add Value**. You can also click **Add Variable** to add a row. 3. Save your changes. Use a key that starts with a letter and contains only letters, digits, dots, dashes, and underscores, up to 253 characters. Archie rejects keys that don't fit. ### Edit or remove a variable * To change a value, edit it in its row and save. * To remove a variable, click the X icon at the end of its row and save. Archie asks you to confirm before it permanently deletes the values. ## Secrets Scroll down to the **Secrets** section. Archie describes secrets as "Encrypted and write-only: values are never shown again." Archie injects them into the service container as secret environment variables at deploy time. Use secrets for API keys, tokens, and passwords. The table lists the names of the secrets you stored, with masked values. ### Add a secret 1. Click **Add Secret**. 2. Enter a name and a value. Both are required. 3. Save your changes. A secret name can contain letters, numbers, hyphens, underscores, and dots, and each name can be used only once. > [!WARNING] > > Saving secrets replaces the whole set. Archie can't read the values it stored, so when you change secrets, re-enter the value of every secret you want to keep, or remove the ones you don't. Check the on-screen hint before you save. > [!NOTE] > > If you delete every secret, services that read them fail after their next deploy. ## When changes take effect Saving updates the stored values. It doesn't restart a running service. Services pick up the new values the next time you deploy them. After you save, open the service and click **Deploy**. See [Deployments](/docs/backend/logic/deployments). If you leave the page with unsaved changes, Archie warns you and lets you save or leave without saving. ## Where the variables are used * **Logic services.** Every service in the environment receives these variables when it deploys. The **Environment variables** tab of a service shows the same list. See [Logic environment variables](/docs/backend/logic/environment-variables). * **New environments.** When you create an environment, you can choose to copy its variables, keys and values included. See [Creating an environment](/docs/backend/environments/creating). ## FAQ > **Are my values encrypted?** > > Archie describes **secrets** as encrypted and write-only. It doesn't make that claim for plain variables, which it masks in the editor. Put anything sensitive in **Secrets**. > **Can I see a secret again after I save it?** > > No. Secrets are write-only. To change one, enter a new value. > **Can one service have its own values?** > > Not yet. The service's own **Environment variables** tab says per-function variables are coming later. > **Where do I put credentials for Stripe, SendGrid, and other integrations?** > > Use the integration's own settings. See [Integrations](/docs/backend/integrations). # Best practices Source: https://archie.com/docs/best-practices How to think about what you're building so Archie's plan lands close to what you want, and where to spend your iteration time. You don't need to write a perfect prompt. Archie is built so most of the refinement happens on the plan, not on the prompt — your prompt is a starting point, and the plan is where you shape what gets built. This page covers how to think about the description you give Archie and where iteration actually pays off. ## Be specific about the problem Tell Archie what the product does and who it's for. The two together give Archie enough to choose a sensible set of modules, a user model, and a starting tech stack. "An app for managing freelance invoices" is enough. So is "an internal tool for logistics dispatchers to track driver shifts." If you don't know yet what your product is exactly, that's okay — Archie can generate a plan from a rough idea and you sharpen it from there. ## Give context about your users Mention the people who'll use the product. A scheduling app for hairstylists has different user types than a scheduling app for surgeons; a B2B marketplace looks different from a consumer one. The more Archie knows about who the app serves, the more accurate its first pass at user types, permissions, and feature areas. You don't need to enumerate every persona. One or two is enough — Archie will draft the rest in the plan. ## Iterate on the plan, not the prompt Once Archie generates the plan, that's where to spend your time. Add or remove modules, rename user types, swap an integration, change a service. The plan is the source of truth for the build, and edits there shape what the code looks like before any code is generated. Re-prompting from scratch is rarely the right move. If your plan drifts from what you wanted, edit it in place — that's faster and keeps the work you've already done. See [Editing modules](/docs/features/plan/editing-modules), [Editing user types](/docs/features/plan/editing-user-types), and [Iterating](/docs/features/plan/iterating) for how to make plan changes. ## Ask Archie clarifying questions If you're unsure whether something is feasible — "can I add a webhook here", "should auth happen at the org level or the user level" — ask Archie inside the plan chat. Archie answers in plain language and offers to make the change for you. This is faster than guessing and rebuilding. ## After the build Once the app is generated, you switch from shaping the plan to editing the running app: * **Chat** — describe a change ("make the sidebar collapsible") and Archie applies it across the right files. This is the primary editing modality. * **Visual editor** — point and click for layout, copy, and styling. See [Visual editor](/docs/features/frontend/visual-editor). * **IDE** — direct code editing for logic-heavy changes. Go back to the plan when a change is structural — a new user type, a new module, swapping a payment provider. Smaller changes belong in chat or the visual editor. ## FAQ > **Do I need to learn prompt engineering?** > > No. Archie's flow is designed so you don't have to. Describe your product and your users in plain language and let the plan do the heavy lifting. Tricks like "you are a senior architect" don't help here. > **My plan isn't quite what I wanted. Should I re-prompt?** > > Edit the plan first. Adding, removing, or renaming a module is faster than starting over and you keep the work that's already correct. Re-prompt only when the plan is fundamentally off from what you intended. > **How specific should I be about features in my first prompt?** > > A few sentences is usually enough. Name the product, the users, and a couple of core actions. Archie drafts the feature areas and you refine them in the plan — adding modules by clicking is faster than listing them in a prompt. > **Should I specify the tech stack in my prompt?** > > Only if you have a hard requirement. Archie picks a stack tailored to your app, and it's managed automatically from there — not shown or editable in the plan. If you need something specific, say so up front; otherwise naming a stack mostly just constrains Archie's recommendations. > **Can I attach images or mockups?** > > Yes. The prompt bar accepts screenshots, mockups, and design references. Archie uses them to inform visual direction and overall design. # Changelog Source: https://archie.com/docs/changelog Release notes and product updates for Archie. **October 2026** Backend Overview, Logs, ERD, and billing ## Backend Overview **Backend → Overview** is now the entry page of the Backend Console. A top bar shows the environment, the **Structure** and **Usage** tabs, a **Live** indicator, and the region. ![The Backend Overview page on the Structure tab with the live banner and Your Backend cards](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/05-backend-overview-structure.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=86c50b2b343925a303e078bc2fa98e5b) **Structure** confirms "Your backend is live" and shows the provider, region, and compliance level. It counts your tables, API endpoints, core services, logic functions, and integrations. The **Your Backend** cards cover Data, API, Core Services, Logic, Integrations, and Backups, each with a shortcut such as **Open Data** or **Open API Explorer**. **Usage** shows Database Rows, Database Size, Bandwidth, Logic Compute, API Requests, Users, File Storage, and Backups for 24 hours, 7 days, 30 days, or all time. Select **Refresh** to update the numbers. If the backend isn't generated yet, you see an empty state. [Learn more](/docs/backend/overview) ## Logs The new **Logs** viewer shows your project's Custom Function and Core GraphQL logs. Turn on **Live tail** to follow new entries as they arrive. Three KPIs track requests per minute, error rate, and p95 latency. ![The Logs page showing Requests, Error rate, and p95 latency cards above a table of log entries](https://mintcdn.com/archie-e998dbf6/_8MXf9wS7pzwm-r0/images/01-logs-overview.png?fit=max\&auto=format\&n=_8MXf9wS7pzwm-r0\&q=85\&s=5fd87528bc5e982defd7b8aaea597187) Jump to a category with the quick tabs: All, Request, Logic, Integrations, Files, and Errors. Narrow results by level, source, service, and time range. Search suggests terms as you type and offers quick filters for failed operations, mutations, queries, 5xx, and 4xx. Export what you see as JSON or CSV. [Learn more](/docs/backend/logs) ## Improved ERD The ERD toolbar is redesigned. Use **Detail** to show names, keys, or everything. Filter tables, search tables and columns with Cmd/Ctrl+K, switch orientation, fit the diagram to the screen, go full screen, or export an image. ![The redesigned ERD toolbar and the Domains panel next to the diagram](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/07-erd-toolbar-domains.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=2577e8dd31eee50b7870989762aa9ee7) The new **Domains** panel shows or hides whole domains. Hidden domains are left out of the diagram and the export. Select a table to open the **Inspector**, which lists its columns with primary and foreign key markers, jumps to a referenced table, and shows which tables reference it. The diagram now opens in a vertical layout. On the **Data** page, if your data model can't load, you now see an error with **Retry** instead of a misleading "not generated" message. [Learn more](/docs/backend/data-model/erd) ## Preview a referenced record In the Data Viewer, a foreign key cell now has a **View referenced record** button. It opens a card with the full record from the related table, so you never leave the table you're working in. Use **Copy ID** to copy the record's ID, or **View in table** to open it in its own table. ![The View referenced record card opened from a foreign key cell, with Copy ID and View in table buttons](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/08-data-viewer-referenced-record.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=94c7e6fcf480cfc9c694a4b0fdd737b8) The preview respects your access. If you don't have access to the related record, the card tells you. [Learn more](/docs/backend/data-model/data-viewer) ## Project cards show each project's stage Every project card now has a label for the current step: **Needs your answers**, **Plan ready**, **Backend ready**, **UX architecture ready**, **Prototype ready**, **Frontend ready**, **Full-stack ready**, **Generating…**, or a failed state. Hover over the label for a short explanation. ![A project card with the Prototype ready stage label](https://mintcdn.com/archie-e998dbf6/tb41NP3PE78iEyct/images/09-home-project-card-stage.png?fit=max\&auto=format\&n=tb41NP3PE78iEyct\&q=85\&s=516868f535ae4a3dba2b82aa1e6b3649) The card image shows your real design once a prototype or frontend exists. Before that, it shows a sketch that matches the stage. Published projects also carry a **Published**, **Publishing…**, or **Publish failed** label. ## More free credits The Free plan now includes 100 extra gift credits from Archie. You see them in the **Credits from Archie** row of the AI credits wallet, which opens from the credits button in the header. [Learn more](/docs/plans-and-credits) ## Clearer billing across organizations **Account settings → Billing** now lists one row per organization. Each row shows your role (Personal, Owner, Admin, or Member), marks your current organization, and shows the plan and today's usage. Select **Open billing** to manage an organization you administer. For organizations you don't administer, you still see the balance you consume and who owns it. [Learn more](/docs/plans-and-credits) ## Coming next: Custom Functions Custom Functions are coming next. You'll write your own logic in Node.js, Python, Go, Java, or .NET, starting from a template or by importing your git repo and deploying on every push. The plan includes a built-in code editor, live deployments with history and redeploy of an earlier version, and encrypted environment variables. You'll expose functions through Custom APIs, set permissions per role for each operation, schedule them with Scheduled Tasks, and read their output in Logs. Custom Functions will be available on paid plans. [Learn more](/docs/backend/logic) Follow [Archie on LinkedIn](https://linkedin.com/company/archie/) or join the [Discord community](https://discord.gg/EwKqQH5TQU) for more updates. # Enterprise Source: https://archie.com/docs/enterprise Enterprise-grade Archie with SSO, SOC 2, HIPAA, GDPR, dedicated infrastructure, custom pricing, and dedicated support. Archie's Enterprise plan is for organizations with compliance, governance, scale, or agency-specific requirements that exceed what Team can offer. ## What is included Enterprise extends every Team feature and adds: * **Unlimited team members** with custom role definitions and per-member governance * **Multi-factor authentication** required across the org * **Enterprise SSO (SAML 2.0)** with your identity provider — Okta, Azure AD, custom * **Dedicated and isolated infrastructure** — your own compute, your own database, your choice of cloud (AWS, GCP, Azure) and region * **Multi-region deployment** with read replicas * **Compliance add-ons** — SOC 2 Type II, HIPAA, GDPR * **Data residency controls** — pin where data lives * **Custom uptime SLAs** up to 99.95% with service credits and RCA within 48 hours * **Dedicated CSM**, priority support, custom onboarding, training * **Invoice billing**, custom payment terms, and custom SLA ## Compliance Compliance add-ons run alongside the Enterprise plan with a 12-month minimum commitment: * **SOC 2 Type II** — \$500/mo. Annual audit, continuous controls monitoring, audit report access for your customers. * **HIPAA** — \$750/mo. Business Associate Agreement, PHI encryption, access controls, breach notification, annual risk assessment. * **GDPR** — \$350/mo. Data Processing Agreement, data residency, right-to-erasure tooling, 72-hour breach notification, DPIA support. * **SOC 2 + HIPAA bundle** — \$1,000/mo. Combined program with shared controls, save \$250/mo. * **Full compliance bundle** — \$1,250/mo. SOC 2 + HIPAA + GDPR. See [plans and credits](/docs/introduction/getting-started/plans-and-credits) for the full pricing surface. ## Agency capabilities Enterprise includes capabilities for agencies that build and operate apps on behalf of clients: * White labeling * Per-customer project isolation * Customer-specific RAG (retrieval-augmented generation) * Agency-level RAG * Preferred tech stack presets * Jira integration * Client handoff workflows ## How do I get started with Enterprise? Enterprise is custom-priced. Reach out via [contacting support](/docs/introduction/community-support/contacting-support) and select Enterprise sales. A typical engagement involves a scoping call, a security review, contract negotiation (DPA, BAA if applicable), and a 2–4 week onboarding. ## Pilots and proof of concept If your organization needs to validate Archie before a full Enterprise commitment, we run paid pilots with scoped projects. Pilots include access to most Enterprise features and a defined exit ramp into a long-term contract. ## Migration off other tools If you are moving from a Vercel + Supabase stack, AWS Amplify, Firebase, or a self-hosted setup, Archie Labs offers migration services as part of Enterprise onboarding. Database export, API mapping, frontend regeneration, and cutover are scoped per engagement. ## FAQ > **How is Enterprise priced?** > > Enterprise is custom-priced based on seat count, dedicated infrastructure, region, and compliance add-ons. Reach out via [contacting support](/docs/introduction/community-support/contacting-support) for a scoped quote. > **What identity providers does SSO support?** > > SAML 2.0 with Okta, Azure AD / Entra ID, Google Workspace, and any other SAML-compliant IdP. SCIM provisioning for user lifecycle is also available on Enterprise. > **Can we run Archie in our own cloud account?** > > Self-hosted Enterprise deployments are available. The team scopes a deployment in your AWS, GCP, or Azure account during onboarding. Contact sales for the security review. > **Do compliance add-ons require an annual commitment?** > > Yes. SOC 2, HIPAA, and GDPR add-ons run alongside the Enterprise plan with a 12-month minimum. Bundles save versus per-add-on pricing — see the prices above. > **Can we pilot Archie before signing a full Enterprise contract?** > > Yes. Paid pilots cover a scoped project with most Enterprise features and a defined exit ramp into a long-term contract. Useful when security review, integration validation, or executive buy-in needs to happen before full rollout. # Archie FAQ: Account, support, and company Source: https://archie.com/docs/faq/account Creating and managing your Archie account, setting up your team, getting support, and who is behind Archie. Your account, getting help, and the people behind Archie. ## How do I create an Archie account? Two ways, both free and with no credit card: sign up directly at [app.archie.com](https://app.archie.com), or type your idea into the prompt bar at [archie.com](https://archie.com) and Archie creates your account as part of the flow. You can sign in with Google, Microsoft, GitHub, or email. See [Create an account](/docs/introduction/getting-started/create-an-account). ## How do I manage my profile, notifications, and security? Everything lives in [My Account](/docs/introduction/account/managing-your-account): your [profile and preferences](/docs/introduction/account/profile-and-preferences), [notifications](/docs/introduction/account/notifications), your [activity](/docs/introduction/account/activity) timeline, and [security](/docs/introduction/account/security) settings like two-factor authentication. ## How do I set up Archie for my team? Create an [organization](/docs/introduction/organizations/overview). Organizations give your team shared projects, pooled credits, role-based access, and centralized billing. See [Setting up an organization](/docs/introduction/organizations/setting-up-an-organization) and [Managing team members](/docs/introduction/organizations/managing-team-members). ## How do I delete my account? You can delete your account and all associated data from your account settings at any time. If you need help, email [support@archie.com](mailto:support@archie.com). ## How do I get support? Every user gets help from the [Discord community](/docs/introduction/community-support/community) and by email. Team users get priority email support with a 24-hour response time. Enterprise users get a dedicated customer success manager and priority support channels. See [Contacting support](/docs/introduction/community-support/contacting-support), or check [Troubleshooting](/docs/introduction/community-support/troubleshooting) for fixes to common issues. ## Where is Archie based? Archie is built and headquartered in Miami, Florida. Archie.com, Inc. is the legal entity behind the platform. ## Who is behind Archie? Who founded it? Archie was founded by Albert Santalo, a computer engineer, management consultant, and three-time venture-backed entrepreneur based in the United States. Archie's domain, [www.archie.com](https://www.archie.com), was acquired from the beloved Archie Comics brand in 2025. # Archie vs. Lovable, Bolt, Base44, v0, Replit, and Cursor Source: https://archie.com/docs/faq/archie-vs-other-tools An honest comparison of Archie with Lovable, Bolt, Base44, v0, Replit, Cursor, Supabase, and Vercel — and what vibe coding really means. Common questions about how Archie compares to other AI app builders and developer tools. For a full side-by-side comparison with each tool, see [Alternatives](/docs/alternatives). ## How is Archie different from other AI app builders? Most AI app builders start from a prompt and generate code. Many now add a planning step before a change and a built-in backend, and they get you to a working demo quickly. Archie starts from a definition. Before any code exists, Archie generates a [plan](/docs/features/plan/overview) for the whole app — modules, user types, services, integrations — and you shape it. From that plan, Archie lays out every screen in a [UI architecture](/docs/features/frontend/generating) with its own spec and access rules, then builds a prototype, then the full-stack app. The backend is [Archie Core](/docs/features/backend/overview): a real relational database with GraphQL and REST APIs, role-based access with row-level filters, field-level encryption, and Git-like [environments](/docs/features/backend/environments/overview) with diff, merge, and backups. The result is an app with structure underneath it — one you can keep building on after the demo. ## What is "vibe coding," and how is Archie different? "Vibe coding" means building software by prompting an AI with no defined architecture underneath. It's fast to start, and it's great for prototypes. The trouble comes later: once the app grows, nothing holds it together, and progress stalls. Archie keeps the speed and adds the structure. The [plan](/docs/features/plan/overview) defines the app before code is written, the backend is built on a real [data model](/docs/features/backend/data-model/overview), and you can work however suits the task — [chat](/docs/features/frontend/talking-to-archie), [visual editing](/docs/features/frontend/visual-editor), or [code](/docs/features/frontend/code-editor). Vibe coding is good for prototypes. Archie is built for production. ## Other tools have a "plan mode." Isn't that the same as Archie's plan? No. A plan mode writes a task list before one change, and it's gone once the change is made. Archie's plan is the lasting definition of the whole app, and every build is generated from it: the UI architecture, each screen's spec, and the backend's data model and permissions. See [The plan and how Archie works](/docs/faq/the-plan). ## How does Archie compare to Lovable? Lovable is a conversational full-stack builder with a built-in backend (Lovable Cloud) and a Plan mode for reviewing larger changes. Archie generates a lasting plan of the whole app first and runs every app on Archie Core, with PostgreSQL or MySQL, GraphQL and REST APIs, and branchable environments. See [Archie vs. Lovable](/docs/faq/archie-vs-lovable). ## How does Archie compare to Bolt? Bolt runs a full development environment in your browser and is built for speed, with Bolt Cloud for databases and hosting and usage metered in tokens. Archie trades a few minutes up front for a plan of the whole app, a relational backend, and credits whose cost you see before heavier tasks run. See [Archie vs. Bolt](/docs/faq/archie-vs-bolt). ## How does Archie compare to Base44? Base44, owned by Wix, generates full-stack apps on its managed platform and uses two credit pools. Archie starts with a plan of the whole app and runs your data on a standard PostgreSQL or MySQL database you can query with SQL, branch, and export. See [Archie vs. Base44](/docs/faq/archie-vs-base44). ## How does Archie compare to v0? v0, by Vercel, builds Next.js apps with strong default UI, connects to external databases, and deploys to Vercel. Archie includes the backend — database, APIs, auth, roles, and environments — and defines the whole app in a plan before it builds. See [Archie vs. v0](/docs/faq/archie-vs-v0). ## How does Archie compare to Replit? Replit is a general-purpose cloud development platform with an AI agent, for web apps, mobile apps, scripts, and more. Archie is focused on production web apps, with the architecture defined by the plan and the infrastructure provided by Archie Core. See [Archie vs. Replit](/docs/faq/archie-vs-replit). ## How does Archie compare to Cursor or Windsurf? Cursor and Windsurf are AI-powered code editors for developers working in an existing codebase. Archie builds and deploys the whole application from an idea — and because it syncs to GitHub, developers can open the same project in Cursor or Windsurf. See [Archie vs. Cursor](/docs/faq/archie-vs-cursor). ## Does Archie work with Cursor, Claude Code, or other AI coding agents? Yes. When you connect GitHub, Archie keeps your code in a standard repository and syncs changes both ways — Archie pushes generated code, and pulls back commits made outside Archie. That means you can work on the same project in Cursor, Claude Code, or any other tool. See [Organization integrations](/docs/introduction/organizations/organization-integrations). ## How does Archie compare to Supabase or Vercel? Supabase and Vercel each solve one part of the stack: Supabase is a backend platform, and Vercel hosts and deploys frontends. Archie covers both — [Archie Core](/docs/features/backend/overview) for the backend and a global CDN for the frontend — and adds the part neither one does: going from an idea to a defined, generated, deployed application in one platform. ## Can I move an existing project to Archie? There's no direct import from other builders today. Most people describe the app again in Archie and let the plan capture its structure. If you're moving a production app with existing data, [Archie Labs](https://archielabs.com) offers migration services as part of Enterprise onboarding. # Archie FAQ: Building with Archie Source: https://archie.com/docs/faq/building What you can build with Archie — SaaS, marketplaces, internal tools — and the four ways to edit your app once it's built. What you can build with Archie, and how you work with it once it's built. ## What can I build with Archie? Full-stack web applications: SaaS products, marketplaces, internal tools, dashboards, CRMs, client portals, and more. Every app runs on real infrastructure — a relational database, [auto-generated APIs](/docs/features/backend/graphql-api-explorer/overview), [authentication](/docs/features/backend/app-services/authentication-providers/overview), and [production hosting](/docs/features/frontend/deployment). Archie is built for software you ship, not demos you throw away. ## Can I build a SaaS product with Archie? Yes — it's one of the most common things people build. A SaaS needs user accounts, roles, billing, email, and a backend that grows with you, and Archie covers each of them: [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview) for sign-in, [role-based access](/docs/features/backend/app-services/role-based-access) for permissions, [Stripe](/docs/features/backend/integrations/stripe) for payments and subscriptions, [SendGrid](/docs/features/backend/integrations/sendgrid) for email, and [environments](/docs/features/backend/environments/overview) so you can test changes before they reach your customers. ## Can I build a marketplace with Archie? Yes. Marketplaces are hard because they have more than one kind of user, and each sees and does different things. Archie defines those [user types](/docs/features/plan/editing-user-types) in the plan before any code exists, so buyers, sellers, and admins are part of the design from the start. The backend handles the rest: per-role permissions with [row-level filters](/docs/features/backend/app-services/row-level-filters), payments, [notifications](/docs/features/backend/app-services/notifications/overview), and file storage for listings. ## Can I build internal tools and dashboards with Archie? Yes, and they're a strong fit. Internal tools need a real database, sign-in, and strict permissions — exactly what Archie Core provides by default. Admin panels, CRMs, inventory systems, and reporting tools all benefit from [role-based access](/docs/features/backend/app-services/role-based-access), SQL [views](/docs/features/backend/data-model/views) for reporting, and bulk [data import](/docs/features/backend/data-model/importing-data) from CSV. ## Can I build a mobile app with Archie? Archie generates responsive web applications that work on phones, tablets, and desktops. You can check every screen at different device sizes in the [prototype](/docs/features/frontend/prototype/screens/navigation). Native iOS and Android apps are not available yet. ## Can I use images or design references when prompting? Yes. You can attach screenshots, mockups, and other reference material when you generate your plan, and Archie uses them to inform the structure and visual direction. See [Generating a plan](/docs/features/plan/generating). You can also upload your own logo and brand assets in the prototype's [branding](/docs/features/frontend/prototype/design/branding) settings. ## How do I edit my app after it's built? Four ways, and you can mix them: * [**Talk to Archie**](/docs/features/frontend/talking-to-archie) — describe a change in plain language, like "add a search field to the orders page," and Archie applies it across the right files. * [**Visual editor**](/docs/features/frontend/visual-editor) — click any element in the live preview and change its text, color, spacing, or position directly. * [**Draw mode**](/docs/features/frontend/draw-mode) — mark up the live preview and send your marks to chat for precise visual changes. * [**Code editor**](/docs/features/frontend/code-editor) — edit the code in the built-in IDE, or sync with GitHub and work locally. For structural changes — a new module, a new user type — go back to the [plan](/docs/features/plan/overview) and generate again. ## Can I revert to an earlier version of my app? Yes. The Team plan includes app versioning, so you can roll back to an earlier version of your project. On any paid plan, you can connect GitHub, which keeps a full commit history of your code. On the backend, [environment backups](/docs/features/backend/environments/backups) are taken automatically before every merge and can be restored. See [Plans and credits](/docs/introduction/getting-started/plans-and-credits) for what each plan includes. ## Can I collaborate with my team on Archie? Yes. Every plan includes unlimited viewers. On Pro, you can invite builders who have their own Pro subscription. On Team, you get full collaboration in a shared [organization](/docs/introduction/organizations/overview): pooled credits, [role assignment](/docs/introduction/organizations/managing-team-members), per-member credit limits, real-time collaboration, and an activity feed. Teammates can also leave [comments](/docs/features/frontend/prototype/comments) pinned to exact spots on a screen and track them to resolved. ## What happens when a vibe-coded app can't scale (the "70% problem")? Many AI app builders get you to a working prototype fast, then stall around 70% of the way to a real product. The reason is usually the same: nothing underneath defines how the app should work, so every new feature fights the last one. Archie starts from the definition. The [plan](/docs/features/plan/overview) sets the modules, user types, and services before any code exists, and the backend is built on a real relational database with a proper [data model](/docs/features/backend/data-model/overview), permissions, and [environments](/docs/features/backend/environments/overview). Your app has a foundation to grow on, so the last 30% is building features — not rebuilding the app. # Archie FAQ: Getting started Source: https://archie.com/docs/faq/getting-started What Archie is, who it's for, what happens after your first prompt, and how the prototype becomes a full-stack app. The questions people ask first when they meet Archie. Can't find yours? Ask in [Discord](https://discord.gg/EwKqQH5TQU) or email [support@archie.com](mailto:support@archie.com). ## What is Archie? Archie is an agentic AI platform that turns an idea into a production-ready, full-stack application. Before it writes any code, Archie generates a [plan](/docs/features/plan/overview) — a structured definition of your app's modules, user types, services, and integrations — and you shape it until it matches what you mean. Then Archie builds from it: a [prototype](/docs/features/frontend/prototype/overview) of your screens first, then a real application with a database, APIs, authentication, and hosting. We call this **clarity before code**. See [How Archie works](/docs/introduction/getting-started/welcome#how-archie-works). ## Who is Archie for? Founders, product teams, designers, and developers who want to move fast without ending up with code they have to throw away. Whether you're validating an idea, building a first MVP, or shipping a production app, Archie gives you structure from the start. You don't need to know how to code. If you do, you get full access to the [code](/docs/features/frontend/code-editor), the [data model](/docs/features/backend/data-model/overview), and the infrastructure. ## Do I need to know how to code to use Archie? No. You describe what you want in plain language, shape the plan, and Archie builds the app. After the build, you keep changing it by [talking to Archie](/docs/features/frontend/talking-to-archie) or with the [visual editor](/docs/features/frontend/visual-editor). Technical users can open the [code editor](/docs/features/frontend/code-editor) or sync to GitHub and work in their own tools — but nobody has to. ## How do I get started with Archie? Type your idea into the prompt bar at [archie.com](https://archie.com), or sign in at [app.archie.com](https://app.archie.com). If you don't have an account yet, Archie creates one as part of the flow. The Free plan needs no credit card and lets you generate your plan and prototype before you pay anything. The [Quick start](/docs/introduction/getting-started/quick-start) walks through the whole flow. ## What happens after I submit my first prompt? Archie generates your plan. It may ask a few clarifying questions first, and you can attach reference material to give it more context. You then review and edit the plan — modules, user types, services, integrations — with sample screens that show how it translates into UI. When the plan is right, you generate the frontend: Archie lays out every screen in a sitemap, and you pick which screens to build into a click-through prototype. When the prototype holds together, you generate the full-stack app. See [Generating a plan](/docs/features/plan/generating) and [Generating the frontend](/docs/features/frontend/generating). ## How long does it take to build an app with Archie? The plan arrives in minutes, not days. From there, most of your time goes into shaping it — which is the point, because the plan is cheap to change and a build is not. Screen generation, the prototype, and the full-stack build each take minutes, and the build view shows live progress. How long the whole thing takes depends mostly on how long you spend getting the plan right. ## What's the difference between the prototype and the full-stack app? The prototype is an interactive, click-through version of your screens, with your design system applied and real navigation — but no real data and no backend. It's where you check that the screens and flows make sense. The full-stack app is the real thing: a deployed frontend connected to a backend on [Archie Core](/docs/features/backend/overview), with a database, APIs, authentication, and role-based access. You go from one to the other with **Generate Full-Stack**. See [From prototype to real app](/docs/features/frontend/generating#from-prototype-to-real-app). ## What is Archie Core, and how is it different from Archie? Archie is the platform that takes your idea to a finished application. [Archie Core](/docs/features/backend/overview) is the backend underneath every app Archie builds: the database, auto-generated GraphQL and REST APIs, authentication, roles and permissions, file storage, notifications, queues, and environments. Developers can also use Archie Core on its own, as a full-featured backend for apps they build themselves. ## What is Archie Labs? Archie Labs is Archie's professional services arm, at [archielabs.com](https://archielabs.com). It offers custom software design and development, AI strategy and transformation, data, machine learning and business intelligence, agentic AI, and staff augmentation. Archie is self-serve; Archie Labs pairs Archie's technology with a hands-on team when a project needs people, not just software. # Archie FAQ: Infrastructure and Archie Core Source: https://archie.com/docs/faq/infrastructure Archie Core, hosting, databases, auto-generated APIs, environments, integrations, auth providers, custom domains, and scaling. What runs your app, where it lives, and how it grows. ## What is Archie Core? Archie Core is the backend that powers every app built on Archie. It gives you: * A relational database, PostgreSQL or MySQL, designed visually in the [Data Model](/docs/features/backend/data-model/overview) * Auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs, with an always-in-sync [OpenAPI spec](/docs/features/backend/rest-api-explorer/openapi-specification) * [Authentication](/docs/features/backend/app-services/authentication-providers/overview), [role-based access](/docs/features/backend/app-services/role-based-access), and [row-level filters](/docs/features/backend/app-services/row-level-filters) * [File storage](/docs/features/backend/app-services/file-manager), [notifications](/docs/features/backend/app-services/notifications/overview), [queues and an event bus](/docs/features/backend/app-services/queues-and-event-bus/overview), and [scheduled tasks](/docs/features/backend/scheduled-tasks/overview) * Git-like [environments](/docs/features/backend/environments/overview) with branching, schema diff, merge, and backups Pro includes one Starter backend, and Team includes three. Developers can also use Archie Core on its own, as a full-featured backend for apps they build themselves. See the [Backend overview](/docs/features/backend/overview). ## Does Archie handle hosting and deployment? Yes. Your frontend is hosted on a global CDN, and your backend runs on Archie Core's managed infrastructure. Publishing is one click, with [custom domains, environments, and rollbacks](/docs/features/frontend/deployment). You don't need to set up Vercel, Netlify, AWS, or any other hosting provider. ## What database does Archie use? A real relational database — PostgreSQL or MySQL — not a proprietary data store. Your data is portable, and you can query it with standard SQL in the [SQL Playground](/docs/features/backend/data-model/sql-playground), see its structure in a live [ERD](/docs/features/backend/data-model/erd), and [export](/docs/features/backend/data-model/exporting-data) it to CSV at any time. ## Do I get APIs for my app automatically? Yes. Every table in your [data model](/docs/features/backend/data-model/tables) automatically gets GraphQL and REST APIs — queries, mutations, and real-time [subscriptions](/docs/features/backend/graphql-api-explorer/subscriptions). You can test them in the built-in [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) explorers, connect external clients with [API keys](/docs/features/backend/settings/api-keys), and send data changes to other systems with [webhooks](/docs/features/backend/rest-api-explorer/webhooks). ## Can I generate only the backend? Yes. **Generate Backend** runs independently of the frontend, straight from your plan, and builds the schema, database, and APIs. You can run it before, after, or alongside the frontend. See [When to run the build](/docs/features/plan/when-to-build). ## Do I get separate development, staging, and production environments? Yes, on backends that support multiple environments. [Environments](/docs/features/backend/environments/overview) are isolated copies of your backend, each with its own database, roles, auth providers, and configuration. You can branch a new one from any environment, compare schemas with [diff](/docs/features/backend/environments/diff), and [merge](/docs/features/backend/environments/merge) changes forward — with an automatic [backup](/docs/features/backend/environments/backups) before every merge. The Starter backend includes production only; the Professional and Enterprise backends support multiple environments. See [Plans and credits](/docs/introduction/getting-started/plans-and-credits#archie-core-backends). ## Which integrations are built in? Archie Core includes built-in integrations for [Stripe](/docs/features/backend/integrations/stripe) (payments and subscriptions), [SendGrid](/docs/features/backend/integrations/sendgrid) (email), [Twilio](/docs/features/backend/integrations/twilio) (SMS and voice), [Slack](/docs/features/backend/integrations/slack) (notifications), and [Shopify](/docs/features/backend/integrations/shopify) (store data). You can add them to your plan before you build, or [build your own](/docs/features/backend/integrations/building-custom) for services Archie doesn't ship. See [Integrations](/docs/features/backend/integrations/overview). ## Can I use my own authentication provider? Yes. Archie includes [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/overview), a built-in email and password system with user management, password reset, and email verification. You can also connect external providers like Auth0, Google, or AWS Cognito, or use both. See [Authentication providers](/docs/features/backend/app-services/authentication-providers/overview). ## Can I connect a custom domain? Yes. You can connect a custom domain to any app you build with Archie when it's ready to go live. See [Deployment](/docs/features/frontend/deployment). ## What happens if I need to scale my app? Archie Core is built to grow from first launch to production traffic without a rewrite. When you need more, upgrade a backend from Starter to Professional for dedicated compute, multiple environments, and higher quotas. Enterprise backends add dedicated, isolated infrastructure, region selection across AWS, GCP, and Azure, and SLAs up to 99.95%. See [Archie Core backends](/docs/introduction/getting-started/plans-and-credits#archie-core-backends). ## Does Archie support MCP (Model Context Protocol)? Yes. Archie supports MCP for connecting directly to ChatGPT, Claude, and other AI assistants, and apps built on Archie can support MCP too. ## Is Archie open source? No, Archie is a commercial platform. But what you build is yours: the code your app generates belongs to you, and so does your data. Archie builds on standard, open technologies — a React frontend and a PostgreSQL or MySQL database — so you can export your code to GitHub and your data to CSV whenever you want. ## Does Archie support multiple languages? Yes. You can work with Archie in the most common business languages, and you can build apps in other languages — or in several at once — for your own users. # Archie FAQ: Pricing, plans, and AI credits Source: https://archie.com/docs/faq/pricing Archie's Free, Pro, Team, and Enterprise plans, how AI credits work, top-ups, rollover, annual billing, and cancellation. How Archie's plans and AI credits work. For full plan details and every price, see [Plans and credits](/docs/introduction/getting-started/plans-and-credits). ## How much does Archie cost? Archie has four plans: * **Free** — \$0, no credit card required. * **Pro** — starts at \$25/month for a single builder and scales through a credit selector up to \$1,500/month. * **Team** — \$30 per seat per month, for 5 to 25 seats, with pooled credits. * **Enterprise** — custom pricing for organizations that need compliance, SSO, and dedicated support. Every app also runs on an [Archie Core backend](/docs/introduction/getting-started/plans-and-credits#archie-core-backends). Pro includes one Starter backend, Team includes three, and Enterprise includes unlimited backends. ## Does Archie have a free plan? Yes. Free lets you generate a full plan and an interactive prototype, with 50 AI credits per month, unlimited projects, and unlimited viewers. You can export your plan as a PDF. To turn your prototype into a real, deployed application, upgrade to Pro. ## Is there a free trial of Pro? The Free plan is the trial. You get the full plan and prototype experience without paying anything, and you upgrade only when you're ready to turn your prototype into a real application. ## What are AI credits, and how do they work? AI credits are what you spend on AI-assisted work: generating a plan, a prototype, or an app, AI-driven edits, and generating a data model or seed data. Each task uses credits based on how complex it is, and heavier tasks show their estimated cost before they run — no surprises. Manual edits in the visual editor or IDE, deploying, exporting code to GitHub, and inviting collaborators are always free. See [AI credits](/docs/introduction/getting-started/plans-and-credits#ai-credits). ## Where can I see how many credits I have left? Your remaining credits are shown in the top right of the Archie app and on your account page. Your project's activity feed records every credit spend. ## What happens when I run out of AI credits? Your completed work is preserved, and manual editing still works. Only new AI tasks pause. To keep going, buy a top-up pack, move to a higher credit level on Pro, or wait for your credits to renew. ## Can I buy more credits without changing plans? Yes. Top-up packs start at \$15. They're one-time purchases that stack on top of your monthly credits and never expire while your subscription is active. If you're buying top-ups regularly, a higher credit level on Pro is cheaper per credit. ## Do unused credits roll over? Yes, within a window. On monthly plans, unused credits roll over for one billing cycle. On annual plans, they roll over for three. Top-up credits never expire while your subscription is active. ## Can I change plans or credit levels later? Yes. Moving up within Pro takes effect immediately with pro-rated billing, so you get the extra credits right away. Moving down takes effect at the start of your next billing cycle. Upgrades from Pro to Team or Enterprise happen in your account settings. ## What is the prototype-to-app conversion fee? When you upgrade to Pro and convert your prototype into a real, deployed application, there is a one-time conversion fee based on the scope of your app — the number of screens, data models, and integrations. This fee is separate from your monthly subscription, so your monthly credits stay available for ongoing development after conversion. Enterprise plans include unlimited conversions. ## Does Archie offer annual plans? Yes. Annual plans are 20% cheaper than monthly. Annual subscribers also get a longer credit rollover window — three billing cycles instead of one — which helps if you build in bursts. ## What happens to my app if I cancel my subscription? You can cancel any time from your account settings. Your subscription stays active through the end of the current billing cycle. Your plans and code stay yours: after you move to Free, your deployed apps stay live until the billing cycle ends, and after that you need Pro or higher to keep them deployed. You can export your code to GitHub at any time. # Archie FAQ: Security, compliance, and data Source: https://archie.com/docs/faq/security How Archie secures your app, SOC 2, HIPAA, and GDPR, who owns your code and data, exporting, and lock-in. How Archie protects your app and your data, and what you own. ## Is Archie secure? Yes. Security is part of the platform, not something you add later. Archie Core ships with: * [Role-based access](/docs/features/backend/app-services/role-based-access) with read, write, update, and delete permissions on every table, plus [row-level filters](/docs/features/backend/app-services/row-level-filters) * [Archie Auth](/docs/features/backend/app-services/authentication-providers/archie-auth/security) with password hashing, signed and encrypted tokens, refresh-token rotation, rate limiting, and account lockout * [Field-level encryption](/docs/features/backend/app-services/encryption/overview) for sensitive columns * Credentials encrypted at rest with AES-256-GCM * Configurable [CORS and rate limiting](/docs/features/backend/settings/network) Your prototype becomes your production app directly — there's no separate "demo" version to harden later. ## How do I secure my Archie account? Turn on two-factor authentication, and review your active sessions and connected devices, from your account's [Security](/docs/introduction/account/security) settings. ## Is Archie SOC 2 compliant? SOC 2 is available as an add-on for Enterprise plans. The infrastructure is built to be compliance-ready, and the formal certification is layered on as part of a dedicated Enterprise engagement. See [Enterprise](/docs/introduction/enterprise/overview). ## Is Archie HIPAA compliant? HIPAA compliance is available as an add-on for Enterprise plans. If you're building healthcare applications that handle protected health information, we provide the compliance framework, Business Associate Agreement, and required controls as part of an Enterprise engagement. See [Enterprise](/docs/introduction/enterprise/overview). ## Is Archie GDPR compliant? GDPR compliance is available as an add-on for Enterprise plans. It includes Data Processing Agreements, data residency controls, right-to-erasure workflows, and breach notification procedures as part of the engagement. See [Enterprise](/docs/introduction/enterprise/overview). ## Who owns the code and data I create with Archie? You do. Every application you build with Archie belongs to you — the code, the architecture, and the data. You can export your code to GitHub at any time, and your data lives in a standard PostgreSQL or MySQL database that is fully portable. Full ownership and IP terms are in our Terms of Service. ## Can I export my code? Yes. Paid plans include GitHub sync and full code export. Push your codebase to your own GitHub repository and work on it in your own environment whenever you want. Exporting is always free — it never uses AI credits. ## Can I export my data? Yes. You can [export any table to CSV](/docs/features/backend/data-model/exporting-data), query your data directly with SQL in the [SQL Playground](/docs/features/backend/data-model/sql-playground), or read it through the auto-generated [GraphQL](/docs/features/backend/graphql-api-explorer/overview) and [REST](/docs/features/backend/rest-api-explorer/overview) APIs. ## Does Archie lock me into its platform? No. Archie uses standard languages, standard databases, and major cloud providers. Your code lives in your GitHub repository and your data lives in PostgreSQL or MySQL. Archie adds a lot on top — the plan, the generation, the managed backend — but if you ever want to leave, you can export everything and replace the parts Archie runs with your own code or open-source tools. ## Can Archie be self-hosted or run on-premise? Yes. A self-hosted option is available with Enterprise backends. Schedule a call with our sales team to learn more — see [Enterprise](/docs/introduction/enterprise/overview). # Archie FAQ: The plan and how Archie works Source: https://archie.com/docs/faq/the-plan What the Archie plan is, why Archie plans before it writes code, how it differs from a plan mode, and how the plan becomes an app. The plan is what makes Archie different. Here's what it is, why it comes first, and how it drives the build. ## What is the Archie plan? The plan is Archie's structured definition of your app. Archie generates it from your idea, and you edit it until it says what you mean. It has five sections, each editable on its own: * **Overview** — what the app is for * [**Modules**](/docs/features/plan/editing-modules) — the features and capabilities the app offers * [**User types**](/docs/features/plan/editing-user-types) — the people who use the app, and what each can do * [**System services**](/docs/features/plan/editing-services) — built-in services like authentication, file storage, and roles * [**Integrations**](/docs/features/plan/editing-integrations) — the third-party services the app talks to A preview section shows sample screens so you can see how the plan becomes UI. When you build, Archie reads the plan as its source of truth. See [Plan overview](/docs/features/plan/overview). ## Why does Archie create a plan instead of jumping straight to code? Because code generated straight from a prompt has nothing underneath it. It can look right on the first try, but there's no defined user model, no clear structure, and no shared idea of what the app is supposed to be — so every change after that is a guess. That's where most AI-built apps start to fall apart. The plan fixes the order. You decide what the app is while decisions are cheap — the plan is structured text, and changing it costs almost nothing. A build is expensive: it takes time and credits, and a build from the wrong idea gets thrown away. Steer the plan, then build once. See [Why the plan comes first](/docs/introduction/getting-started/welcome#why-the-plan-comes-first). ## Other tools have a "plan mode" now. How is Archie's plan different? Several AI builders now offer a planning step: before a change, the agent writes a task list for you to approve. That's useful, but the plan is about one change, and it's gone once the change is made. Archie's plan is the lasting definition of the whole app. It stays with the project, and everything is generated from it: the [UI architecture](/docs/features/frontend/generating) that lays out every screen and who can access it, the spec for each screen, and the [backend](/docs/features/backend/overview) with its data model and permissions. When you want a structural change, you change the plan — and the next build follows it. ## How does the plan turn into an app? Generation splits into two tracks, both started from the plan. **Generate Frontend** lays out every screen in a sitemap, lets you review each screen's spec, and builds a click-through prototype from the screens you choose. **Generate Backend** builds the database schema and APIs on Archie Core. The tracks don't depend on each other. Once the prototype is ready, **Generate Full-Stack** turns it into a real, deployed application. See [When to run the build](/docs/features/plan/when-to-build). ## How do I know my plan is ready to build? You're ready when you've read every section, the modules list everything you need and nothing you don't, your user types have real names and concrete descriptions (not placeholders like "User" and "Admin"), and you can describe what the built app should do without looking back at the plan. If a section feels off, fix it first — a few minutes of editing saves a wasted build. The full checklist is in [When to run the build](/docs/features/plan/when-to-build). ## Can I edit the plan after I've built the app? Yes. Edit the plan and generate again; the next build picks up your changes. Each project has one plan — to explore a different direction, create a new project. ## Can I export the plan? Yes. You can export your plan as a PDF at any time, including on the Free plan. It's a clean way to share what you're building with cofounders, investors, developers, or anyone else who needs the full picture. ## What tech stack does Archie use? Archie chooses and manages the tech stack for you, so it isn't shown or edited in the plan. The frontend is a generated React application, hosted on a global CDN. The backend runs on [Archie Core](/docs/features/backend/overview): a PostgreSQL or MySQL database with auto-generated GraphQL and REST APIs, authentication, role-based access, file storage, and real-time subscriptions. If you have a hard requirement, mention it in your prompt before you generate. ## What are Specifications? Specifications are coming soon. They add a detailed layer between the plan and the code: functional, visual, and technical design documents — features with user stories and acceptance criteria, styling and layout, the data model and technical strategy — that the build agents read before writing anything. When Specifications ships, the flow becomes prompt → plan → specifications → code. See the [Specifications preview](/docs/features/specifications/overview). ## Is Archie's plan the same as "spec-driven development"? It's the same idea. Spec-driven development means defining what software should do before generating it, and Archie's plan does exactly that for your whole app. [Specifications](/docs/features/specifications/overview), coming soon, take it further with detailed functional, visual, and technical documents for every feature. # Archie MCP Source: https://archie.com/docs/features/backend/app-services/mcp-server/archie-mcp The Archie MCP is a platform-wide assistant for managing Archie itself. You sign in with your Archie account and work across your projects, organizations, and environments — including administrative actions — from an AI assistant. The **Archie MCP** is a platform-wide connector. You sign in with your **Archie account** and an assistant like Claude, Cursor, or ChatGPT can help you **manage Archie itself** — browse your projects, organizations, and environments, explore any project's API, and run administrative actions. It is distinct from the per-project [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp): the Archie MCP spans everything your account can reach and includes management tools; the MCP Server is scoped to one project and is data-only. ## Connect it The Archie MCP is added as a **custom connector by URL** and authenticates with your Archie account. **Connection URL** ``` https://mcp.services.archie.com/mcp ``` **1. Add the connector** In your assistant, open its connectors settings (for example, **Settings → Connectors → Add custom connector** in Claude) and paste the connection URL above. **2. Sign in** A window opens — sign in with your **Archie account** and approve access. **3. Ask** The assistant can now help you navigate and manage your projects and organizations. For **Claude Code** (the CLI), add it from your terminal: ```bash theme={null} claude mcp add --transport http archie https://mcp.services.archie.com/mcp ``` ## Authentication The Archie MCP authenticates with **OAuth 2.1**. When you add the connector, your assistant opens a browser window where you **sign in with your Archie account**; the assistant then holds a token and calls the server on your behalf. There is no key to paste. > [!NOTE] > > The Archie MCP acts with **your** platform access — the same projects, organizations, and permissions you have when you sign in to Archie. It doesn't grant new access. Sharing the connector doesn't share your session: each person signs in as themselves. ## Tools Because the Archie MCP is platform-wide, it exposes the **full** tool set — account navigation, API exploration, data, and management — not just the data-only floor the [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp#tools) exposes. Clients group them as **read-only** or **write**. **Account & platform** — get your bearings across Archie: | Tool | Type | What it does | | - | - | - | | `whoami` | Read | Returns your identity and what you can access. | | `list_projects` | Read | Lists your projects. | | `list_organizations` | Read | Lists your organizations. | | `list_environments` | Read | Lists a project's environments. | **API & data** — explore a project's schema and work with its data: | Tool | Type | What it does | | - | - | - | | `list_operations` | Read | Lists the queries and mutations in a project's GraphQL API. | | `get_field_details` | Read | Returns the fields and arguments of a type. | | `list_database_objects` | Read | Lists tables, views, and enums in a project. | | `get_database_object` | Read | Returns the definition of a specific database object. | | `semantic_search` | Read | Searches data by meaning, when [vector search](/docs/features/backend/data-model/data-viewer) is enabled. | | `read_archie_docs` | Read | Looks up Archie product documentation. | | `execute_graphql_operation` | Write | Runs a GraphQL query or mutation against a project. | | `get_context` / `set_context` | Read / Write | Read and set the current project and environment scope. | **Management** — administrative actions on a project: | Tool | Type | What it does | | - | - | - | | `manage_api_keys` | Write | Create and delete project API keys. | | `manage_roles` | Write | Create and delete roles. | | `manage_authentication` | Write | Enable, update, and disable authentication providers. | | `manage_file_storage` | Write | Configure file storage. | | `manage_notifications` | Write | Enable and disable notifications. | > [!WARNING] > > The management tools make real changes to your project (keys, roles, authentication, storage). Keep your assistant's **write approvals** on so a write is confirmed before it runs. Every action is still bounded by your own platform access — the Archie MCP can never do more than you can. ## Archie MCP vs. MCP Server | | Archie MCP | [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp) | | - | - | - | | **Scope** | Your whole account — all projects and organizations | One project | | **Sign-in** | Your Archie account (OAuth) | The project's sign-in or a project API key | | **Tools** | Full set — account, API, data, **management** | Data-only floor | | **Use it to** | Manage the platform | Work with a project's **data** | To connect an assistant to a single project's data — for your project's own users and agents — enable and use the [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp) instead. # MCP Server Source: https://archie.com/docs/features/backend/app-services/mcp-server/client-mcp Enable a per-project MCP Server so your project's users and agents can query its data from an AI assistant. It is data-only, inherits each user's project roles, and you configure its identity and tools. The **MCP Server** is a per-project connector for your project's own data. Once enabled, your users and their AI agents connect from a tool like Claude, Cursor, or ChatGPT and work with that project's data — governed by each user's project role, and limited to a **data-only** set of tools. It is separate from the [Archie MCP](/docs/features/backend/app-services/mcp-server/archie-mcp): the Archie MCP manages the platform; this one is scoped to a single project. ## Enable it **1. Open the panel** Go to **Backend → App Services → MCP Server**. **2. Turn it on** Toggle the MCP Server **on**. The connection details and configuration appear below the toggle. > [!NOTE] > > Enabling the MCP Server does **not** open your data to everyone. Every request still authenticates as a specific user and runs with that user's [project roles](/docs/features/backend/app-services/role-based-access). Enabling it only makes the connector *available*; access is still governed by your permissions. ## Server identity Give the server a **name, icon, and description**. These are optional — leave them blank to use the defaults. | Field | What it does | Notes | | - | - | - | | **Name** | The name assistants show for this connector. | Shown by most clients (for example, as the connector's title). | | **Icon** | An emoji or an `https` URL to a logo image. | Used in Archie's UI and, when you publish to an assistant's directory, as the listing logo. Assistants that add the server by URL usually show a letter avatar regardless. | | **Description** | A short description of what this server gives access to. | Sent to the assistant as context (it shapes how the model uses the server); it is not shown as a label in every client's UI. | Edit the fields and click **Save**. ## Authentication How users connect depends on the [authentication providers](/docs/features/backend/app-services/authentication-providers/overview) your project has enabled. The panel adapts the connection instructions to match: **Sign-in (identity)** If your project uses an identity provider (Archie Auth, Google, Microsoft, and so on), connecting **opens a login window**. The user signs in with their project account — there is no token to paste. After sign-in, the assistant acts with that user's project role. **API key** If your project uses API keys, the user authenticates with a **project API key** sent as an `Authorization: Bearer` header. Create keys under **Backend → App Services → Authentication Providers**. The connection config the panel shows already includes the header — the user just replaces the placeholder with their key. > [!WARNING] > > If no authentication provider is active on the project, users can't connect. Enable at least one under [Authentication Providers](/docs/features/backend/app-services/authentication-providers/overview) first. ## Tools The MCP Server exposes a **data-only** tool set. Assistants use these to understand your API and work with your data — never to administer the platform. Clients group them as **read-only** or **write**: | Tool | Type | What it does | | - | - | - | | `list_operations` | Read | Lists the queries and mutations in your project's GraphQL API (schema introspection). | | `get_field_details` | Read | Returns the fields and arguments of a type, so the assistant can build a valid query. | | `semantic_search` | Read | Searches your data by meaning, when [vector search](/docs/features/backend/data-model/data-viewer) is enabled. | | `read_archie_docs` | Read | Looks up Archie product documentation. | | `get_context` | Read | Returns the current project and environment scope for the session. | | `execute_graphql_operation` | Write | Runs a GraphQL query or mutation against your project — governed by the user's role and `ALLOW_MUTATIONS`. | | `set_context` | Write | Session-local scope helper. | > [!NOTE] > > The exposed set is the intersection of this built-in floor and your own selection: you can **turn a tool off** to withhold it, but you can't add tools beyond the data-only floor. Administrative and platform tools are never available here. To narrow the set, toggle individual tools off in the panel. An empty selection uses the full data-only floor. ## Permissions The MCP Server never widens access. A connected user acts with their **own** project role: * [Role-Based Access](/docs/features/backend/app-services/role-based-access) decides which operations and tables the user can touch. * [Row-level filters](/docs/features/backend/app-services/row-level-filters) apply exactly as they do on the GraphQL API. * Mutations run only when the user's role allows the write **and** the environment permits mutations. This is the same authorization the GraphQL API enforces — the MCP is just another authenticated caller. ## Next step Ready to connect a tool? See [Connecting an assistant](/docs/features/backend/app-services/mcp-server/connecting). ## FAQ > **Does enabling the MCP Server expose all my data?** > > No. Every request authenticates as a specific user and runs with that user's project role and row-level filters. The connector is data-only and never exposes platform or admin operations. > **Is this the same as the Archie MCP?** > > No. The [Archie MCP](/docs/features/backend/app-services/mcp-server/archie-mcp) manages the platform across all your projects. The MCP Server is scoped to one project and works with that project's data. > **Can users write data, or only read it?** > > Both, within their permissions. Writes go through `execute_graphql_operation` and are allowed only when the user's role permits the mutation and the environment allows mutations. Read-only and write tools are grouped separately so clients can require approval for writes. > **Why doesn't my icon show up in the assistant?** > > Most assistants show a letter avatar for a connector added by URL — the per-server icon mainly matters when you publish the connector to that assistant's directory. The name is what assistants display; the description is sent to the model as context rather than shown as a label. > **Is the configuration per environment?** > > Yes. Enablement, identity, and the tool selection are stored per project **and** environment. # Connecting an assistant Source: https://archie.com/docs/features/backend/app-services/mcp-server/connecting Connect Claude, Cursor, ChatGPT, or any MCP-capable assistant to your project's MCP Server. The panel gives you ready-to-paste connection details for each tool. Once the [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp) is enabled, the panel shows a **Connect an assistant** section. Pick your tool and copy the connection details — they already carry your project's address, so there's nothing to type by hand. > [!NOTE] > > Every tool below connects to the **same** MCP Server. What differs is where you paste the connection details and how the tool authenticates. The exact URL and config are shown in the panel — copy them from there rather than typing them. **Claude** Claude connects as a **custom connector**. **1. Copy the Claude URL** In the panel's **Claude** tab, copy the connection URL. Claude needs the per-project address the panel shows for it. **2. Add the connector** In Claude, open **Settings → Connectors → Add custom connector** and paste the URL. **3. Authenticate** A window opens — **sign in** with your project account (or, on API-key projects, the config includes the `Authorization` header to fill in). Approve the tools. For **Claude Code** (the CLI), the panel also gives you a ready `claude mcp add ...` command — run it in your terminal. **Cursor** Cursor reads an `mcp.json` entry. **1. Copy the configuration** In the panel's **Cursor** tab, copy the JSON block. **2. Add the server** In Cursor, open **Settings → Tools & MCP → New MCP server** and paste it, or save it as `.cursor/mcp.json` in your project. **3. Authenticate** Sign in with your project account in the browser window that opens, or — on API-key projects — the JSON already includes the `Authorization` header; replace the placeholder with your key. **ChatGPT** ChatGPT connects as a **custom connector** (requires a plan that supports connectors). **1. Copy the URL** In the panel's **ChatGPT** tab, copy the connection URL. **2. Add the connector** In ChatGPT, open **Settings → Connectors** and add a custom connector by URL. **3. Authenticate** Sign in with your project account and approve access. **Other assistants** Most MCP-capable assistants connect the same way: add an **"MCP server"** or **"connector"** by URL, using the connection URL from the panel's **Other** tab. Sign in when prompted, or send a project API key as the `Authorization: Bearer ` header. ## Sign-in vs. API key The connection details adapt to your project's [authentication providers](/docs/features/backend/app-services/authentication-providers/overview): * **Sign-in (identity):** connecting opens a login window — no token to paste. Best for human users. * **API key:** the config includes an `Authorization: Bearer` header. Create a key under **Backend → App Services → Authentication Providers** and replace the placeholder. Best for automated agents and CI. > [!WARNING] > > Treat a project API key like a password. It carries the permissions of the role it belongs to. Store it in a secret manager, never commit it, and rotate it if it leaks. ## Refreshing after a change Assistants cache the server's details from when you connected. If you change the server's **name, icon, or description** — or switch authentication — **disconnect and reconnect** the connector so the assistant reads the new details. ## FAQ > **Why does Claude need a different URL than Cursor?** > > Claude derives the sign-in server from the connection URL, so its address carries the project in the host. The panel shows the correct URL for each tool — always copy Claude's URL from the Claude tab. > **Can I connect more than one assistant?** > > Yes. The MCP Server accepts connections from any number of tools and users at once. Each connection authenticates independently and runs with that user's permissions. > **Do I need to redeploy anything to connect?** > > No. Enabling the MCP Server and copying the connection details is all that's required on Archie's side. # Overview Source: https://archie.com/docs/features/backend/app-services/mcp-server/overview The Model Context Protocol (MCP) lets AI assistants like Claude, Cursor, and ChatGPT connect to Archie and work with your data. Archie exposes two MCP servers: a platform assistant for managing Archie, and a per-project MCP Server for your project's own data. The **Model Context Protocol (MCP)** is an open standard that lets an AI assistant connect to an external system and use its tools. Archie speaks MCP, so assistants like **Claude**, **Cursor**, and **ChatGPT** can read and act on your data through a secure connection — no glue code to write. Archie exposes **two** MCP servers, for two different jobs: > **[Archie MCP](/docs/features/backend/app-services/mcp-server/archie-mcp)** > > A platform-wide assistant for **managing Archie itself** — browse your projects and organizations and work across them. You sign in with your Archie account. > **[MCP Server (per project)](/docs/features/backend/app-services/mcp-server/client-mcp)** > > A **per-project** connector you enable for a single project. Your project's users and agents query that project's data — governed by your project roles, data-only by design. ## Which one do I want? | | Archie MCP | MCP Server (per project) | | - | - | - | | **Scope** | All your projects and organizations | One project | | **Who connects** | You, the Archie account holder | Your project's own users / agents | | **What it does** | Manage the platform (list projects, environments, organizations) | Read and write **that project's data** | | **How it authenticates** | Your Archie identity | The project's sign-in **or** a project API key | | **Permissions** | Your platform access | The connecting user's **project roles** ([RBAC](/docs/features/backend/app-services/role-based-access)) | > [!NOTE] > > The per-project **MCP Server** is **data-only** and **separate** from the Archie MCP. It never exposes platform or admin operations — a connected user can only do what their project role allows, over that project's data. Turning it on for one project has no effect on any other project. ## Key concepts * **Data-only surface.** The MCP Server exposes tools to explore your API, run GraphQL operations, and search your data — not to administer the platform. See [Tools](/docs/features/backend/app-services/mcp-server/client-mcp#tools). * **Permissions are inherited.** A user who connects the MCP Server acts with **their own project role**. The MCP does not grant new access; it uses [Role-Based Access](/docs/features/backend/app-services/role-based-access) and any [row-level filters](/docs/features/backend/app-services/row-level-filters) exactly as the GraphQL API does. * **Two ways to authenticate.** Depending on the [authentication providers](/docs/features/backend/app-services/authentication-providers/overview) your project has enabled, users either **sign in** with their project identity (a browser login, no token to paste) or authenticate with a **project [API key](/docs/features/backend/app-services/authentication-providers/archie-auth/getting-started)**. * **You control the identity and the tools.** From the panel you set the server's **name, icon, and description**, and curate which tools it exposes. ## Get started **1. Enable the MCP Server for your project** Go to **Backend → App Services → MCP Server** and turn it on. See [MCP Server](/docs/features/backend/app-services/mcp-server/client-mcp). **2. Connect your assistant** Pick your tool (Claude, Cursor, ChatGPT, or another MCP client) and copy the connection details the panel shows you. See [Connecting an assistant](/docs/features/backend/app-services/mcp-server/connecting). **3. Ask about your data** Your assistant can now explore the API and query your project's data, within the connecting user's permissions. # Overview Source: https://archie.com/docs/features/backend/data-model/fields A field is a column on a table. Archie supports nine field types, each with its own validation, defaults, and configuration options. A field is a column on a table. Each field has a type that controls what data it can store, what validation runs on insert and update, and how it appears in the auto-generated GraphQL and REST APIs. ## Adding a field Open the table on the **Schema** tab and click **+ Add Field**. Pick a name, choose a type from the dropdown, and configure its options in the right-hand panel. Changes save in real time. ## Field types > **[Boolean](/docs/features/backend/data-model/fields/boolean)** > > True / false flags. > **[Date](/docs/features/backend/data-model/fields/date)** > > Calendar dates and timestamps. > **[Number](/docs/features/backend/data-model/fields/number)** > > Integers, decimals, and floats. > **[Text](/docs/features/backend/data-model/fields/text)** > > Strings of any length. > **[JSONB](/docs/features/backend/data-model/fields/jsonb)** > > Structured JSON objects and arrays. > **[UUID](/docs/features/backend/data-model/fields/uuid)** > > 128-bit unique identifiers. > **[Relationship](/docs/features/backend/data-model/fields/relationship)** > > Foreign keys between tables. > **[Inline enum](/docs/features/backend/data-model/fields/inline-enum)** > > A one-off list of allowed values defined on the field. > **[User-defined](/docs/features/backend/data-model/fields/user-defined)** > > A reusable Data Type (enum) shared across tables. ## Configuration options at a glance Most field types share a common set of options. The exact set varies by type — see each field type's page for the full list. | Option | Available on | What it does | | - | - | - | | **Name** | All types | The system identifier used in the GraphQL and REST APIs. | | **Description** | All types | An internal note describing the field's purpose. | | **Default Value** | Most types | The value assigned when a record is created without an explicit value. | | **Mandatory** | All types | Enforces a `NOT NULL` constraint — the field cannot be saved empty. | | **Unique** | Most types | Enforces a uniqueness constraint across all rows in the table. | | **Encrypted** | Text types | Stores the value as ciphertext at rest; it's returned as plaintext only to callers that sign the request. See [Encryption](/docs/features/backend/app-services/encryption/overview). | | **Type-specific** | Varies | Number precision, timestamp granularity, JSONB vs. JSON, varchar length, and similar choices. | ## Inline enum vs. user-defined Both let you constrain a field to a fixed list of values. Choose between them based on reuse: | Choose this | When | | - | - | | [Inline enum](/docs/features/backend/data-model/fields/inline-enum) | The list of values is specific to one field on one table and won't be reused. | | [User-defined](/docs/features/backend/data-model/fields/user-defined) | The list of values needs to stay consistent across multiple fields or tables. Defined once as a [Data Type](/docs/features/backend/data-model/data-types). | ## Permissions Field-level visibility and write access is controlled per role in [Role-Based Access](/docs/features/backend/app-services/role-based-access), not in the Data Model. ## FAQ > **Can I change a field's type after I've added it?** > > Yes, with caveats. Date, Number, and Text values are auto-converted where possible. Switching a field to **Unique** runs a check on the existing data and refuses the change if duplicates exist. Switching a field to **Mandatory** prompts you for a default to backfill existing rows. > **What's the difference between a relationship field and a UUID field?** > > A UUID field stores a free-form 128-bit identifier with no enforcement. A [relationship field](/docs/features/backend/data-model/fields/relationship) stores a foreign key to another table, enforces referential integrity, and creates the inverse query in the GraphQL API automatically. > **Should I use JSON or JSONB?** > > JSONB. It's stored in a binary format that's significantly faster to query and supports indexing. Plain JSON is faster to write but slower to read. See [JSONB](/docs/features/backend/data-model/fields/jsonb). > **How do I make a field optional?** > > Leave **Mandatory** off. Optional fields can be `null` in the database and will be nullable in the generated GraphQL types. # Network Source: https://archie.com/docs/features/backend/settings/network Configure CORS and rate limiting settings for your project. The Network settings page controls how your app handles incoming requests — which origins can call its API, and how aggressively to rate-limit traffic. The settings here are project-wide defaults, scoped per [environment](/docs/features/backend/environments/overview). For per-route overrides on a specific endpoint, configure CORS and rate limits on the route itself in [Custom APIs](/docs/features/backend/app-services/custom-apis). ## CORS configuration Cross-Origin Resource Sharing (CORS) controls which web origins are allowed to call your API from a browser. Add the origins you trust: * `https://yourapp.com` * `https://*.yourapp.com` (subdomain wildcards) * `http://localhost:3000` (local development) ## Rate limiting Set per-route or global limits to protect your API from abuse and runaway clients: * **Global limit** — total requests per minute * **Per-IP limit** — requests per minute from a single IP * **Per-key limit** — requests per minute per API key * **Burst** — short-term spike allowance ## When to tighten * After launch, when traffic patterns are stable * If you see abuse from specific origins or IPs * When introducing public APIs * Before announcing a high-traffic event ## FAQ > **Does Archie rate-limit by default?** > > Yes — sensible defaults apply. Tighten or relax as your app's needs require. > **What HTTP status code is returned when rate-limited?** > > 429 Too Many Requests, with a `Retry-After` header. # Adding context Source: https://archie.com/docs/features/plan/context Provide reference materials and clarifying answers that inform your plan. The Context tab is where Archie gathers the information it needs to refine your plan. It contains your **initial prompt** and any **clarifying questions** Archie asked. ## The initial prompt Your initial prompt is the description you typed when you created the project. You can edit it any time. Click **Edit** on the initial prompt card, change the text, save. ![Initial prompt card with project description](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/context-prompt-card.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=890b2648cc6d9ad0d0d0dcb58ca96f9b) ## Clarifying questions After you submit your idea, Archie may ask one or two clarifying questions. Each question has multiple-choice options and a free-text field. You can: * Pick an option (A, B, C, and so on) * Type a free-text answer * Skip the question Skipping is fine — Archie will use defaults. Answering gives you tighter control. The questions cover things Archie cannot infer reliably from your prompt — for example, who uses the app, what role plays admin, or which side of a marketplace pays. ![Plan refinement with clarifying questions and multiple-choice options](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/context-clarifying-questions.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=2619280930c104e2380585d44222309c) ## When questions appear Questions stream in as Archie analyzes your prompt. You will see "Agent is working…" while a question is being prepared. Once a question is ready, you can answer or skip immediately. ## Editing context after the plan exists You can revisit the Context tab at any time. Editing the initial prompt or answering more questions will inform the next regeneration or [iteration](/docs/features/plan/iterating). Existing plan content does not change automatically — use the plan chat to apply the new context. ## FAQ > **Will my answers retroactively change the plan?** > > Not automatically. After updating context, ask the plan chat to apply the change, or regenerate the relevant section. > **How many questions will Archie ask?** > > Usually one or two. Archie only asks when it cannot infer something it needs. # Best practices for specifications Source: https://archie.com/docs/features/specifications/best-practices How to write specifications that produce good code — what to include, what to leave out, when to be precise vs. vague. > [!NOTE] > > Specifications is coming soon. This documentation previews what the section will look like at launch. Specifications are most useful when they are precise where it matters and vague where it does not. This page covers the heuristics that lead to good specs and good generated code. ## Be precise about behavior, not implementation A good spec describes what should happen, not how. "When a user submits an order, charge their card and send a confirmation email" is precise behavior. "Use Stripe's Charge API with idempotency keys, then call SendGrid's templates endpoint" is implementation — Archie picks the right implementation from the integrations in your plan. Implementation detail in a spec is fragile: if you change the integration in the plan, your spec is out of date. Behavior in a spec is durable. ## Write copy first, design later Copy in the spec is one of the highest-leverage edits you can make. Specifying button text, placeholder text, and error messages upfront removes a whole round of "the button says the wrong thing" edits after the build. Visual design (font, color, spacing) does not belong in the spec. It belongs in [Frontend theming](/docs/features/frontend/theming). ## Name edge cases explicitly The fastest way to ship a broken feature is to leave edge cases unspecified. Empty states, network failures, race conditions, large datasets, permission boundaries — name each one and describe what should happen. The spec format encourages this: each feature has an "Edge cases" section with structured prompts. Use them. ## Be vague about non-decisions Not everything needs precision. If you do not have a strong opinion on whether the order list shows 20 or 50 items per page, leave it unspecified. Archie picks a reasonable default based on the feature type and the data shape. Over-specification creates work without value. The spec is for things you care about; defaults handle the rest. ## Match the spec's granularity to the feature Some features are simple (a profile page) and need a short spec. Others are complex (a multi-step checkout) and need detailed flows, edge cases, and copy. Use the granularity that matches the feature, not a fixed template. If a spec is taking a long time to write and the feature is straightforward, you are over-specifying. If a spec is short and the feature is complex, you are likely missing detail that will surface as bugs later. ## Spec the unhappy paths The happy path is usually obvious. The unhappy paths are where products break: * What happens when the network drops mid-action? * What happens when two users edit the same record at the same time? * What happens when a payment fails after the user navigates away? * What happens when a permission boundary blocks an action mid-flow? Spec these explicitly. Generated code will handle them with concrete logic instead of guessing. ## Iterate the plan, not the spec If you find yourself writing the same edit across many specs ("on every list, allow filtering by status"), promote the change to the plan. Cross-cutting changes belong at the plan level — modules, user types, services, integrations. The plan cascades to specs automatically. ## Review specs as a team Specifications are designed to be readable by non-developers. Use that. Have a product manager or designer review the spec before you build. Catching a misunderstanding in the spec is much cheaper than catching it in deployed code. For team workflows, version each spec change with a comment ("aligned with PM on canceling logic") so future readers understand the why. See [Editing specifications](/docs/features/specifications/editing-specifications) for the version history. # Editing specifications Source: https://archie.com/docs/features/specifications/editing-specifications Refine specifications in plain language — add constraints, refine behavior, mark edge cases, and edit copy directly. > [!NOTE] > > Specifications is coming soon. This documentation previews what the section will look like at launch. Editing a specification is the same as editing the plan: in plain language, with structural awareness, in a surface designed for non-developers. You do not write code. You write the spec. ## Three ways to edit Each section of a spec offers three editing modes, the same as the plan: * **Direct edit** — click into a screen, flow, or rule and change it inline. Useful for small tweaks, copy changes, and adding rules. * **Spec chat** — describe the change in plain language. Useful for multi-step changes ("split the orders list into two views, active and archived, and move the cancel action to the detail page"). * **AI suggestions** — Archie surfaces gaps it noticed (missing edge cases, unspecified copy, conflicting rules). You accept, modify, or dismiss each suggestion. ## Adding constraints and rules Validation, business rules, and edge cases live in each spec's "Validation and edge cases" section. To add a rule: * Open the relevant feature's spec * Click **Add rule** in the Validation section * Describe the rule in plain language: "Users can only have one active subscription at a time." or "Orders cannot be canceled after they have shipped." Archie translates the rule into structured form (which field, which operator, which user type) and validates it against the rest of the spec for conflicts. ## Refining flows Flows are step-by-step. Each step is a user action or a system response. To refine a flow, click into it and: * Add a step (e.g., "show a confirmation modal before deleting") * Change a step's action ("auto-save" instead of "save when the user clicks Save") * Branch a step into success and error paths * Add a precondition or a postcondition If you make changes that affect the data model or user types, Archie surfaces the implications: "This requires a `confirmed_at` field on Orders. Add it?" You confirm and the data model updates. ## Editing copy Copy lives in each screen's element list. Click any text to edit. Archie tracks copy across the project so the same string is consistent everywhere — change the cancel button's text once and it updates in all the places it appears. For multi-locale projects, the copy view shows all locales side by side. Archie can also auto-translate when you mark a string ready for localization. ## Marking edge cases Edge cases are first-class in specifications. Click **Add edge case** in any feature's spec to describe one in plain language: "What happens when the user disconnects mid-checkout?" Archie suggests handling — show a recovery prompt, save progress, retry on reconnect — and adds the appropriate flow steps. For complex edge cases, mark them as **needs review** and a human teammate can sign off before the spec drives code generation. ## Versioning Specifications are versioned automatically. Every edit creates a checkpoint. The version history shows what changed, when, and by whom. Roll back to any previous version from the version panel. See [Specifications and code generation](/docs/features/specifications/specifications-and-code-generation) for how spec versions interact with code generation. # Archetype Source: https://archie.com/docs/features/specifications/functional-design/archetype The foundational shape of your app — solution type, commercialization strategy, and the details of how it generates revenue. Archetype defines the foundational structure of your application — what kind of app it is, how it generates revenue, and the rules that govern that revenue. It's the first specification the build agents read because everything downstream depends on it. ## What you configure The page has three sections: * **Solution Type** — the broad category of app. Examples: marketplace, SaaS, internal tool, e-commerce, social platform. Archie pre-selects one based on your prompt and plan; you can change it. * **Commercialization Strategy** — how the app makes money. Examples: subscription, transaction fees, freemium, one-time purchase, ads, marketplace commission. Each strategy carries different defaults for downstream specs (auth requirements, payment integration, billing flows). * **Commercialization Details** — a free-form description of your monetization plan. Free vs. paid tiers, feature gating, subscription levels, usage-based pricing — anything that shapes how pricing aligns with the value you're delivering. ## What this drives The archetype influences: * Which **system services** Archie suggests (payments, subscriptions, billing portals) * Which **integrations** appear in your plan by default (Stripe for transactions, SendGrid for receipts) * Which **user types** make sense (admin, customer, vendor, guest) * The default **technical strategy** — single-tenant vs. multi-tenant, B2B vs. B2C considerations Edit the archetype any time. Changes propagate when you re-run plan generation or code generation. ## FAQ > **What if my app doesn't fit a standard archetype?** > > Pick the closest one and override the details. Commercialization Details is a free-form field — use it to describe anything the standard categories miss. Archie's agents read the full description, not just the dropdown values. > **Can I change the archetype after I've generated code?** > > Yes, but expect downstream impact. Switching from a transactional model to a subscription model, for example, means Archie will want to add billing flows, recurring invoices, and a portal. Re-run generation and review the changes before merging. > **Does archetype affect tech stack choices?** > > Indirectly. The archetype affects which system services Archie recommends, and those services in turn shape the tech stack — for example, picking subscriptions usually surfaces a Stripe integration recommendation in your plan. # Features Source: https://archie.com/docs/features/specifications/functional-design/features Modules and the features inside them — each feature has user stories, business rules, and acceptance criteria that drive code generation. Features is the deepest specification surface in Archie and the one that most directly shapes the code that gets generated. Modules group related features; each feature has user stories, business rules, and acceptance criteria that the build agents read line by line. ## The hierarchy Four levels: 1. **Module** — a feature area in your app (e.g. Scheduling, Stylist Profiles, Reporting). Modules come from your plan. 2. **Feature** — a discrete capability within a module (e.g. inside Scheduling: "Book an appointment", "Cancel an appointment", "Reschedule"). 3. **User Stories** — one-line statements describing what a user does, in the form "As a \[user type], I want to \[action] so that \[outcome]". 4. **Business Rules** and **Acceptance Criteria** — the constraints and the testable conditions that define when the feature is correct. You edit each level inline. Adding a feature creates an empty user-stories table; adding a user story creates spaces for business rules and acceptance criteria. ## What this drives Features are the most directly load-bearing specifications: * **Routes and screens** — each feature usually maps to one or more screens * **Database changes** — new features often introduce new tables, columns, or relationships * **API endpoints** — GraphQL queries and mutations follow from the user stories * **Validation logic** — business rules become validation in code; acceptance criteria become tests * **Permissions** — features connect to user types through the use cases on each persona When you re-run code generation, the agents read the full features tree and decide what to add, change, or leave alone. ## Working efficiently A few patterns: * **Start with user stories**, not screens. The story drives the screen, not the other way around. * **Keep business rules sentence-length**. "An order can only be cancelled within 24 hours of placing it" is the right shape; long paragraphs get truncated by the agents. * **Acceptance criteria should be testable**. "User sees an error when trying to cancel after 24 hours" beats "Cancellation works correctly". ## FAQ > **Where do modules come from initially?** > > Archie generates modules from your plan. Each module in the plan becomes an editable module here, pre-populated with features and stories drawn from your prompt and the plan's description. > **What's the difference between user stories and use cases?** > > User stories live on features (a feature has many stories). Use cases live on user types (a persona has a list of use cases). The two cross — a use case on the Admin persona is satisfied by a story on a feature. Archie wires them together when generating code. > **Do I need to fill in business rules and acceptance criteria for every story?** > > No. Stories without rules or criteria still generate working code, just with Archie's defaults for validation and behavior. Add rules and criteria when you have specific constraints — financial calculations, regulatory requirements, edge cases that matter for your domain. > **Can I add features that aren't in the plan?** > > Yes. Click **Add Feature** in any module to create one from scratch. You can also add new modules. Both flow into code generation. > **What happens if I delete a feature after generating code?** > > The next code generation will remove the corresponding routes, components, and API operations. Existing data in your database isn't dropped — Archie surfaces destructive schema changes for explicit confirmation before applying them. # Overview Source: https://archie.com/docs/features/specifications/functional-design/overview Functional Design defines what the app does and who does it — archetype, user types, features, system services, and system requirements. Functional Design covers what your app does and who does it. It's the largest of the three specification groups and the one most builders spend the most time on. Five sub-areas: > **[Archetype](/docs/features/specifications/functional-design/archetype)** > > The foundational shape of your app — solution type, commercialization strategy, and pricing details. > **[User Types](/docs/features/specifications/functional-design/user-types)** > > The personas who use the app and the use cases each one performs. > **[Features](/docs/features/specifications/functional-design/features)** > > Modules and the features inside them, with user stories, business rules, and acceptance criteria. > **[System Services](/docs/features/specifications/functional-design/system-services)** > > Foundational capabilities — auth, payments, notifications, file storage, and more. > **[System Requirements](/docs/features/specifications/functional-design/system-requirements)** > > Regional, compliance, security, performance, and accessibility requirements. ## How Archie pre-fills these Functional Design specs are generated from your plan. Archetype reads your initial prompt and the plan's recommendations; user types come from the plan's persona section; features come from the plan's modules; system services and requirements are seeded from the relevant plan sections. Edit any of them in place. Archie stores edits without touching your generated code until you re-run code generation. ## FAQ > **Where do I start editing?** > > Most builders start with **User Types** (to confirm Archie has the right personas) and **Features** (to refine the modules and user stories). The rest can usually run on Archie's defaults until you're ready to ship. > **Can I add a brand-new module in Functional Design?** > > Yes. The Features page lets you add modules and features that weren't in the original plan. Adding here doesn't require a plan regeneration. > **Are functional specs separate from visual specs?** > > Yes — they're different surfaces. Functional Design defines behavior; Visual Design defines look and layout. Both feed into the same generation step. # System Requirements Source: https://archie.com/docs/features/specifications/functional-design/system-requirements Non-functional requirements your app needs to meet — regional, compliance, security, performance, and accessibility. System Requirements covers the non-functional requirements your app has to satisfy. Five categories sit alongside each other on tabs; each one informs different parts of the generated code, the infrastructure choice, and the deployment surface. ## Regional Where your app runs and the regions you serve. Configure target regions, data residency preferences, and any region-specific constraints. Drives the [Archie Core](/docs/features/backend/overview) region selection and influences which compliance frameworks apply. ## Compliance Compliance frameworks that apply to your app — SOC 2, HIPAA, GDPR, and others. Archie tracks which framework you've selected and surfaces the controls that flow from it. Compliance add-ons are an Enterprise plan feature; see [Enterprise overview](/docs/introduction/enterprise/overview). ## Security Security controls beyond defaults. The platform ships with secure defaults (AES-256-GCM encryption at rest, configurable CORS, API rate limiting, RBAC), but you can pin extra requirements here — required MFA, password complexity policies, session lifetime, audit log retention. ## Performance Performance targets. Expected concurrent users, response-time targets, peak-load assumptions. Archie uses these to inform the recommended Archie Core backend tier and compute size — see [Plans and credits](/docs/introduction/getting-started/plans-and-credits) for the tier breakdown. ## Accessibility Accessibility standards your app needs to meet — WCAG 2.1 AA is the typical target. Setting a target here informs the visual design defaults (color contrast, keyboard navigation, ARIA labels) and the generated component library configuration. ## How requirements flow downstream System requirements rarely change the structure of your app, but they shape: * **Backend tier and region** in [Archie Core](/docs/features/backend/overview) * **Default security posture** of the generated code (encryption, sessions, headers) * **Component library defaults** for accessibility-aware patterns * **Compliance flags** that surface controls in audit logs and admin UIs Most of these defaults are sensible for typical apps. Override them when you have explicit obligations. ## FAQ > **Do I need to set every category?** > > No. Sensible defaults cover most apps. Only set explicit values when you have a specific obligation — a compliance framework you're subject to, a region you must run in, an accessibility standard your customers require. > **How does setting GDPR or HIPAA actually affect my app?** > > GDPR and HIPAA are part of the Enterprise compliance add-ons. Selecting them here surfaces the relevant controls; the formal certification and Business Associate Agreement come with the Enterprise engagement. See [Enterprise overview](/docs/introduction/enterprise/overview). > **What happens if my performance targets exceed what my plan supports?** > > Archie surfaces a recommendation to upgrade your Archie Core backend tier (Starter → Professional → Enterprise) so the compute matches your target. You can ignore the recommendation, but expect performance to track your tier, not your stated target. > **Do these requirements affect generated tests?** > > Some do. Setting an accessibility standard prompts Archie to add accessibility-aware tests; setting compliance flags adds checks for required logging and consent flows. The depth varies by category. # System Services Source: https://archie.com/docs/features/specifications/functional-design/system-services Foundational capabilities your app needs — auth, payments, notifications, file storage, audit logs, and multi-language support. System Services covers the foundational capabilities every modern app needs but most teams shouldn't have to build from scratch. Archie pre-selects the services your app needs based on the plan; you tune the configuration here. ## Available services The catalog includes: * **User Management** — authentication methods, role-based access, onboarding flows. Configure email/password, Google, Microsoft, Facebook, Apple sign-in. Toggle a guided onboarding wizard for new users. * **Notifications** — in-app, email, and SMS channels. Pick which channels to enable per notification category. * **File Storage** — managed file storage for images, videos, and documents. Used by the [Assets](/docs/features/assets/overview) surface. * **Payment Processing** — payment types (one-time, subscriptions, usage-based, invoices) and methods (cards, ACH, digital wallets, manual). Drives the Stripe integration. * **Audit Logs** — record system events. Levels: Basic (security events), Standard (security + business actions), Advanced (full traceability — coming soon). * **Multi-Language Support** — internationalization for the generated frontend. ## What this drives Each service influences: * **Backend infrastructure** — auth providers and token handling, file-storage buckets, notification channels, audit-log tables * **Integrations** — turning on Payment Processing pulls Stripe into the integration set; turning on SMS pulls Twilio * **Frontend flows** — sign-in screens, onboarding wizards, notification preference panels * **Data model** — `users`, `roles`, `audit_logs`, and similar shared tables come from these services ## Editing Toggle services on or off, change configuration (e.g. enable Apple Sign-In, change audit-log level), and Archie re-generates the relevant parts of the code on the next generation pass. You don't need to re-run the plan. ## FAQ > **What's the difference between a system service and an integration?** > > A system service is a capability (Notifications, Payments). An integration is the third-party provider that powers it (Twilio, Stripe). Turning on a service usually adds the corresponding integration to the project — you can swap the integration but not skip having one if the service is enabled. > **Can I add my own custom service?** > > Not as a first-class system service today. Custom capabilities live in [Logic](/docs/backend/logic) and your data model. The system-services catalog is the curated list of cross-app capabilities Archie maintains. > **Why is Advanced audit logging marked as coming soon?** > > Advanced (compliance-ready) audit logging — full traceability of every data change — is in development. Basic and Standard levels work today and cover most use cases. > **Do enabled services cost extra?** > > The services themselves are part of the platform. Their underlying integrations have provider costs (Stripe transaction fees, SendGrid email volume, Twilio per-message charges) — those are billed by the provider, not by Archie. # User Types Source: https://archie.com/docs/features/specifications/functional-design/user-types The personas who use your app and the use cases each one performs — the source of truth for permissions and feature access. User Types defines who uses your app and what each persona does. It's the source of truth for permissions, feature access, and the role-based parts of the generated UI. ## What you configure Each user type has: * A **name** (e.g. Admin, Customer, Vendor, Guest) * A **description** explaining who this persona is and what they need from the app * A list of **use cases** — the actions or workflows this persona performs Use cases are the bridge between user types and features. When Archie generates code, every feature gets routed to the right user types based on the use cases attached to each persona. ## How user types feed code generation User types influence: * **Authentication and authorization** — which login flows, MFA requirements, and role assignments end up in the code * **Role-based access control** — which routes, features, and data each persona can reach * **Conditional UI** — what each persona sees on shared screens (e.g. an Admin sees "Delete user" on the user list; a Customer doesn't) * **Onboarding flows** — different welcome experiences per persona Add and remove user types as your understanding sharpens. Archie's agents read the full set when generating code. ## FAQ > **How granular should I make user types?** > > Group together personas who do roughly the same thing. "Admin" and "Super Admin" are usually one user type with a permission flag rather than two separate types. Treat them as separate types only when their workflows diverge enough to need different navigation, different default screens, or different feature sets. > **What's the difference between a user type and a use case?** > > A user type is a persona — Admin, Customer, Vendor. A use case is something that persona does — "Approve a refund", "Place an order", "List a product". Each use case attaches to one or more user types and informs which features that user type can access. > **Can guests or unauthenticated users be a user type?** > > Yes. Add a "Guest" user type for visitors who don't have an account. Use cases for Guest typically include browsing public content, signing up, and logging in. Archie generates the unauthenticated routes accordingly. > **What if my app has 20+ roles?** > > Archie supports as many user types as you need, but consider whether they really need separate types or whether they're variations of a smaller set with different permission flags. Maintenance is much simpler with fewer types. # Generating specifications Source: https://archie.com/docs/features/specifications/generating-specifications How Archie generates detailed specifications from your approved plan — the workflow from plan to per-feature spec. > [!NOTE] > > Specifications is coming soon. This documentation previews what the section will look like at launch. Specifications are generated from the plan, one feature at a time. Archie reads the plan context — modules, user types, services, integrations — and produces a draft specification for each feature. ## When generation happens Specifications generate at two points: 1. **After plan approval** — when you mark the plan as ready, Archie generates initial specs for every feature. 2. **On plan changes** — when you add a module or change a user type's permissions, the affected specs regenerate. Existing edits to other specs are preserved. You can also trigger generation manually from the specifications surface — useful when you want a fresh draft after extensive edits. ## What's in a generated spec The default spec includes: * All the screens needed to support the feature, derived from the user types and the actions in the plan * The flows that connect those screens * Default validation rules from the data model (required fields, type constraints, relationships) * Default copy in your project's primary locale * Edge cases Archie identifies from the feature description Generated specs are starting points. They are reasonable but not perfect. You edit them next. ## How Archie decides what to put in Archie reads: * The feature's description in the plan * The user types that interact with the feature * The data model fields tied to the feature * Integrations the feature depends on (Stripe for orders, SendGrid for email) * Industry conventions for the feature type (orders, profiles, listings — common patterns) The output reflects all of these inputs. If you change the data model, the spec regenerates with the new fields. If you add a user type, the relevant flows pick up the new actor. ## Generation cost Spec generation consumes AI credits, weighted by feature complexity. Archie shows the cost before generating. A typical full project specification — covering 8–12 features — generates in 2–5 minutes and costs comparable to a single full-stack build. ## What gets regenerated, what does not * **Adding a feature to the plan** — that feature's spec gets generated. Other specs stay. * **Changing a feature's description** — that feature's spec regenerates. Other specs stay. * **Adding a user type** — affected specs regenerate to include the new actor. * **Adding a field to the data model** — the spec for the feature using that field regenerates. * **Editing a spec directly** — the spec stays as you edited it; only plan changes can trigger regeneration. For specs you have heavily customized, the spec chat applies plan-driven changes incrementally rather than regenerating from scratch. See [Editing specifications](/docs/features/specifications/editing-specifications). # Overview Source: https://archie.com/docs/features/specifications/overview Specifications are the detailed documents Archie generates from your plan — three design groups that the build agents read before writing any code. > [!NOTE] > > Specifications is coming soon. This documentation previews what the section will look like at launch. Specifications are the detailed documents Archie generates from your plan and uses to drive code generation. Where the plan sets the direction (modules, user types, services, tech stack), specifications fill in the behavior, the visual decisions, and the technical strategy. ## Plan comes first You can't open Specifications until your project has a generated plan. The Specifications tab gates itself behind the plan state and shows a "Specifications not ready yet" empty state until generation completes — Archie uses your plan as the foundation for modules, features, and user stories. If you land on a "Specifications not ready" screen, click **Go to Plan**, run plan generation, and come back. ## Three design groups Specifications are organized by design group, not by feature. Every project has the same three groups: > **[Functional Design](/docs/features/specifications/functional-design/overview)** > > Archetype, user types, features, system services, and system requirements. What the app does and who does it. > **[Visual Design](/docs/features/specifications/visual-design/overview)** > > General brand and form factors, design principles, styling, and layout. How the app looks and behaves on screen. > **[Technical Design](/docs/features/specifications/technical-design/overview)** > > Technical strategy, data model, custom functions, and integrations. How the app is built and runs. ## What feeds what The flow is one direction: prompt → plan → specifications → code. * Your **plan** defines the modules, user types, services, integrations, and technical stack at the architecture level. * **Specifications** turn those into concrete editable plans: each archetype option, each user type's permissions, each feature's user stories and acceptance criteria, the visual styling, the technical strategy, and the data model. * **Code generation** reads the whole spec set and generates frontend, backend, or full-stack code in one pass. There is no per-spec-type generation — you don't generate "just the visual design" or "just the technical design". See [Specifications and code generation](/docs/features/specifications/specifications-and-code-generation) for the details. ## FAQ > **Why can't I see Specifications on a new project?** > > Specifications are gated until plan generation completes. Open the Plan tab, run generation, and the Specifications tab becomes available. The empty state literally tells you this with a "Go to Plan" button. > **Do I have to fill in every specification before generating code?** > > No. Archie pre-fills specs from the plan. You only need to edit the ones you want to steer — for most builders, that's user types, features, and visual styling. The rest can use the generated defaults. > **Can I generate code from just one design group?** > > No. Code generation reads the entire specification set. The split happens at a different axis — Frontend-Only, Backend-Only, or Full-Stack — see [Quick Start](/docs/introduction/getting-started/quick-start). > **Can I edit specs and regenerate without losing other work?** > > Yes. Specifications are stored separately from the generated code; editing a spec doesn't touch your code until you re-run generation. See [Editing specifications](/docs/features/specifications/editing-specifications). # Specifications and code generation Source: https://archie.com/docs/features/specifications/specifications-and-code-generation How specifications drive code generation — the whole spec set feeds Frontend-Only, Backend-Only, or Full-Stack generation in one pass. Specifications are what code generation reads. When you click **Generate**, Archie's build agents read the entire specification set and produce frontend, backend, or full-stack code based on the stack you picked. ## The whole spec set, one generation Code generation is not run per spec type. There is no "generate just visual design" or "regenerate just functional design" mode. The agents need the full picture to generate consistent code. The split happens on a different axis: the stack you pick at generation time. | Stack | What it generates | Reads from specs | | - | - | - | | Full-Stack | Frontend, backend, APIs, and database schema | All spec groups (Functional, Visual, Technical) | | Frontend-Only | UI components, pages, client-side routing | Functional (user types, features) + Visual + Technical (strategy) | | Backend-Only | APIs, services, database schema, server logic | Functional (features, requirements) + Technical (strategy, data model) | ## When edits affect code Spec edits don't touch existing code until you re-run generation. Editing the data model, adding a feature, swapping a UI framework — all of these queue up for the next generation pass. A few edits are exceptions and apply immediately: * Toggling a system service that has runtime configuration (e.g. enabling SMS) takes effect once the corresponding integration credentials are set * Changes in the [Data Model](/docs/features/backend/data-model/overview) editor commit to the database via tracked migrations rather than going through code generation ## What gets regenerated vs. preserved Archie tries hard to preserve hand edits in non-generated files. Edits inside generated files may be merged or overwritten depending on the change. The build view shows a diff before applying so you can review what's about to happen. For protected work — custom logic in custom functions, hand-written tests, your own utility files — keep them outside the generated paths and Archie won't touch them. ## Generation phases A typical full-stack generation moves through these phases: 1. **Queued** — generation request accepted 2. **Initializing** — agents load the spec set 3. **Configuring foundations** — base config, dependencies, scaffolding 4. **Generating** — agents write code module by module 5. **Building** — the generated code compiles and tests run 6. **Completed** (or **Failed** with a reason) You watch progress live in the build view. If a phase fails, your specs and previous code remain intact — credits are only consumed for completed work. ## FAQ > **Can I regenerate just one feature?** > > Not as a separate generation type, but you can edit one feature's spec and run a regular generation — the agents focus changes on the affected files. Most generations are partial in practice. > **What if I want to start over?** > > Re-running generation from scratch is supported, but most builders find it faster to edit specs and run incremental generations. The exception is if your plan drifts far from your intent — then start from a refreshed plan. > **Do spec edits cost AI credits?** > > No. Editing specs is free. Code generation is what consumes credits — the generation cost depends on the stack (Full-Stack, Frontend-Only, Backend-Only) and the size of your spec set. > **What happens to my generated code if a generation fails partway?** > > Existing code stays intact. The new generation either completes or rolls back — you don't end up with a half-applied diff. Open the activity feed to see why generation failed. # Data Model Source: https://archie.com/docs/features/specifications/technical-design/data-model The data-model spec — a synchronized view of the tables, relationships, and types defined in your Backend → Data Model. The Data Model spec page is a synchronized view of your project's data model. It shows the same tables, columns, relationships, and types as [Backend → Data Model](/docs/features/backend/data-model/overview) — the two surfaces edit the same underlying schema. ## Why it lives in two places The data model is both a specification (it constrains what code Archie generates) and an operational surface (you query it, view rows, run migrations). Specifications shows it as a spec; Backend shows it with the full editor. For most editing — adding tables, configuring fields, defining relationships, creating views, managing indexes — go to [Backend → Data Model](/docs/features/backend/data-model/overview). The spec view is read-mostly: a sidebar listing tables and types, and a panel showing the selected entity's structure. ## What's here * **Tables** — list of all tables with their columns and types * **Data Types** — enums and other custom types * **Relationships** — foreign keys and cardinality (1:1, 1:N, M:N) * **Indexes** — read-only summary; configure in Backend → Data Model If a table or data type is selected in the sidebar, the right panel shows its full structure including field types, defaults, nullability, and relationships to other tables. ## How the data model feeds code The data model is one of the most directly load-bearing specifications: * **Database schema** — every table becomes a Postgres or MySQL table * **GraphQL API** — every table generates queries (`tableName`, `tableNames`) and mutations (`createTableName`, `updateTableName`, `deleteTableName`) * **REST API** — same operations exposed as REST endpoints * **TypeScript types** — codegen produces typed clients from the schema Changes to the data model regenerate the matching parts of the code on the next pass. Destructive changes (dropping columns, dropping tables) require explicit confirmation before applying. ## FAQ > **Why can't I add tables here?** > > The full editor lives in [Backend → Data Model](/docs/features/backend/data-model/overview). Specifications is the design view; Backend is where you build, query, and migrate. Both edit the same schema. > **What's the difference between this and Strategy → Data Architecture?** > > Strategy → Data Architecture is prose and diagrams describing the data design at the architecture level. Data Model is the actual table definitions — the concrete schema that becomes the database. The two work together. > **If I add a table in Backend, will it show up here automatically?** > > Yes. The two views are synchronized. Edits in either place reflect in both. # Overview Source: https://archie.com/docs/features/specifications/technical-design/overview Technical Design covers the engineering plan — strategy, data model, custom functions, and integrations. Technical Design covers how the app is built and runs. Two pages live inside Specifications; two more are sidebar cross-links to dedicated areas of Backend. > **[Strategy](/docs/features/specifications/technical-design/strategy)** > > Technical strategy with Mermaid diagrams — system overview, architectural principles, data architecture, and more. > **[Data Model](/docs/features/specifications/technical-design/data-model)** > > The high-level data model summary; full editor lives in Backend → Data Model. > **[Logic](/docs/backend/logic)** > > Cross-link. Custom server logic lives under Backend, not Specifications. > **[Integrations](/docs/features/backend/integrations/overview)** > > Cross-link. Third-party connections live under Backend, not Specifications. ## What's actually a spec Strategy and Data Model are the technical specifications proper. Custom Functions and Integrations appear in the Technical Design sidebar group for navigation convenience, but their content is configured under Backend — that's where credentials, code, and deployment live. When you change a custom function or an integration, you don't re-run spec generation; you edit it in place and the change applies on the next deployment. ## How technical specs feed generation Strategy describes the architecture in prose and diagrams — the build agents read it when making structural decisions about the codebase (modular monolith vs. service split, REST vs. GraphQL emphasis, where caching should live). Data Model defines the tables, relationships, and types the backend exposes. Both feed code generation directly. The Strategy page also stores the Mermaid diagrams Archie's agents reference and humans can read when onboarding to the project. ## FAQ > **Why are Custom Functions and Integrations in the Specifications sidebar if they aren't specs?** > > For navigation. They're closely related to the technical strategy and data model, so the product surfaces them together. The actual configuration and code lives under Backend. > **Can I have a project without a Technical Strategy?** > > Archie always generates a strategy from your plan. You can edit or simplify it; you can't skip it. The strategy is what the build agents use to make architecture-level decisions. > **If I change the data model in Backend, does the spec update?** > > Yes. The Data Model spec page is a synchronized view of the actual data model — edits in either place reflect in both. # Strategy Source: https://archie.com/docs/features/specifications/technical-design/strategy The Technical Strategy spec — architecture sections plus Mermaid diagrams that document and drive the generated codebase. The Technical Strategy spec is the architecture reference for your project. Archie generates it from your plan and uses it when making structural decisions during code generation. It also doubles as the engineering README for anyone joining the project later. ## What it covers Each section is a short prose block; some sections also have a generated Mermaid diagram alongside. The full set: * **Executive Summary** — one-paragraph view of what the system does and why * **System Overview** — the major components and how they interact * **Architectural Principles** — guiding rules (e.g. "single source of truth", "fail loudly") * **Tenancy Architecture** — single-tenant vs. multi-tenant, isolation strategy * **Archie BaaS Integration** — how the app uses Archie Core (database, auth, storage, hosting) * **Custom Function Design** — patterns for server-side logic the agents generate or you write * **Core Components** — the major code modules and their responsibilities * **API Strategy** — REST, GraphQL, or both; auth, versioning, pagination defaults * **Data Architecture** — how data flows; database engine; caching strategy * **Frontend Architecture** — module structure, state management, routing * **Security Framework** — auth, encryption, input validation, audit logging posture * **Scalability Design** — vertical vs. horizontal scaling assumptions * **Custom Function Specifications** — per-function specifications referenced from custom-function code * **Database Strategy** — schema management, migrations, backup posture * **Risk Assessment** — known risks and mitigation patterns ## Diagrams Archie generates Mermaid diagrams for the major views: * **System Diagram** — the major components and their relationships * **Integration Diagram** — third-party services and data flow between them * **Components Diagram** — internal module breakdown * **Data Diagram** — entity relationships, foreign keys, denormalization choices * **Security Diagram** — auth flows, trust boundaries, encryption points You can regenerate any diagram individually if your strategy evolves. Archie writes the Mermaid source directly into the spec, so you can edit the diagrams by hand if you need precision. ## What this drives * The build agents read the strategy when making architecture-level decisions during code generation * The diagrams ship into the generated repo's `/docs` folder for engineering reference * The risk assessment surfaces in code-review prompts when you regenerate code ## FAQ > **Can I edit individual sections without regenerating the whole strategy?** > > Yes. Each section is independently editable. Archie does not regenerate sections you haven't touched, and edits don't cascade unless you ask. > **What if a generated diagram is wrong?** > > Edit the Mermaid source directly. Archie stores diagrams as Mermaid text in the spec, so they're as editable as the prose around them. You can also regenerate just that one diagram. > **Does the strategy affect existing generated code?** > > Strategy edits affect the next code generation, not the existing code. Archie surfaces the diff before applying so you can review what's about to change. > **Can I export the strategy as a PDF for stakeholders?** > > Yes — strategy export uses the same PDF flow as the plan export. Useful for handoffs, investor diligence, and onboarding documents. # Design Principles Source: https://archie.com/docs/features/specifications/visual-design/design-principles The visual and interaction principles your design and engineering teams should follow when extending or customizing the generated UI. Design Principles is a structured place to capture the rules of thumb that guide design decisions in your app. Archie reads these principles when generating components and surfaces them when teammates extend the generated UI. ## What goes here A list of principles, each with: * A short **name** (e.g. "Density", "Progressive Disclosure", "Forgiveness") * A **description** explaining the rule and when it applies * Optional examples Typical principles for Archie-generated apps include things like: * "Forms over modals for any input that takes longer than 5 seconds" * "Empty states should always provide a next action" * "Primary actions are bottom-right on mobile, top-right on desktop" * "Color is decorative, not informational — never rely on color alone" These aren't enforced automatically — they're guidance for both Archie's agents (when generating new components) and humans (when extending the app). ## Why bother Design principles are easy to skip on a small project. They become valuable when: * Multiple builders are editing the app — principles keep new work consistent with existing work * You're handing the app off to a team for ongoing development — principles document the design decisions that aren't visible in code * You're regenerating screens — Archie reads principles when applying new patterns to existing modules ## FAQ > **Does Archie auto-fill principles?** > > Archie pre-fills a starter set based on your archetype and the chosen UI framework. Edit, add, or delete to match your team's standards. > **Are principles enforced in generated code?** > > No. They're documentation that Archie's agents read when making decisions, not strict rules. If you need an actual enforced rule, encode it as a business rule on the relevant feature or as a lint check in your codebase. > **How are these different from styling settings?** > > Styling sets concrete values (colors, fonts, sizes). Principles are higher-level — "use generous whitespace" rather than "padding is 16px". The two work together. # General Source: https://archie.com/docs/features/specifications/visual-design/general Brand identity and form factors — logos, favicons, and which device sizes the app needs to support. The General page covers brand identity and form factors — the broad visual choices that apply across the entire app. Two sections. ## Form Factors Pick which device sizes your app needs to support. Typical options: * **Desktop** — primary surface for most B2B and admin apps * **Tablet** — important for in-the-field use cases * **Mobile** — required for any consumer-facing app The choices here drive responsive breakpoints in the generated code and influence which layout templates are useful (mobile-first vs. desktop-first). ## Brand Identity Upload the logo files Archie uses across the generated frontend, in emails, and in the favicon: * **Logomark (Icon)** — the icon-only version of your mark * **Horizontal Version** — full logo with wordmark * **Favicon** — small icon for browser tabs and bookmarks If your brand has separate dark-mode versions, toggle **Use different logos for Dark Mode** to upload Logomark/Horizontal/Favicon variants for the dark theme. ## What this drives * The favicon Archie places at `/favicon.svg` * Logos rendered in the navigation, footer, login screens, and email templates * Responsive design system breakpoints based on form factors * Default Open Graph and meta image references for SEO and link previews ## FAQ > **What format should logo files be in?** > > SVG is best — it scales without quality loss and works well in dark mode. PNG works as a fallback. Avoid JPEG (no transparency) for logos. > **Do I have to upload all three logo files?** > > Logomark and Horizontal are recommended; Favicon is required (Archie generates a fallback if you skip it). The horizontal version is used in headers; the icon is used in tight spaces (collapsed sidebars, mobile nav). > **What if I haven't designed a brand yet?** > > You can ship with the Archie defaults and replace logos later. Open the page anytime to swap in your brand assets — the generated code picks up the change on the next deployment. > **Can I serve different logos for different tenants in a multi-tenant app?** > > Yes — that's an Enterprise feature. White-labeling and per-tenant branding are part of the Enterprise plan; see [Enterprise overview](/docs/introduction/enterprise/overview) and [Theming](/docs/features/frontend/theming) for the multi-brand pattern. # Layout Source: https://archie.com/docs/features/specifications/visual-design/layout Reusable page templates for the major sections of your app — main navigation, authentication, public landing, onboarding, minimal, and custom. Layout defines the page templates Archie uses for the major sections of your app. Picking templates here keeps the generated frontend internally consistent and lets you change a layout once instead of editing every page. ## The six template slots * **Main Navigation** — system-wide pages for authenticated users (admin, dashboard, settings). Pick a layout family: Header with Sidebar, Sidebar Only, Top Nav, Floating Top Nav. * **Authentication** — login, registration, password reset, and any other unauthenticated flows. Common patterns: Center Aligned, Framed, Half Screen, Third Screen. * **Public Landing (Guest)** — public-facing pages or marketing content for unauthenticated visitors. Patterns: Website, Marketplace / Catalog. * **Onboarding / Wizard** — multi-step flows for new-user setup and other guided experiences. Patterns: Stepper, Progress Bar, Prompt-First. * **Minimal** — specialized flows that need a clean, near-full-screen environment (e.g. preview mode, focus mode, AI consoles). Patterns: Minimal Left, Minimal Top. * **Custom** — a custom layout you describe in plain language. Archie generates the layout from your description. Each pattern lives in the generated component library after code generation, so you can override it per page. ## How layouts feed code The chosen layout is the page chrome for that section — header, sidebar, footer, content area sizing. Archie generates the layout component once, then composes individual pages inside it. Switching a layout after generation regenerates the corresponding page chrome. Page-content components stay intact. ## FAQ > **Can I use a different layout for individual pages within a section?** > > Yes. The section-level layout is the default; individual pages can opt out and use a different layout in the generated code. Common case: the home page in Main Navigation often uses a different chrome than internal pages. > **What does Custom actually do?** > > Custom takes a plain-language description of the layout you want and generates the layout component from it. Use this when none of the standard patterns fit. The result is editable like any other generated layout. > **If I switch from Sidebar Only to Header with Sidebar, do I lose my navigation contents?** > > No. Navigation entries (links, sections, icons) are stored in your project config and re-applied to the new layout shape. The chrome changes; the contents migrate. > **Can I have a layout with no navigation at all?** > > Yes — the Minimal templates are exactly this. Use Minimal for full-screen flows, embedded experiences, or any page where chrome would be a distraction. # Overview Source: https://archie.com/docs/features/specifications/visual-design/overview Visual Design defines how your app looks and behaves on screen — brand, design principles, styling, and reusable layout templates. Visual Design specifies how your app looks and behaves on screen. Four sub-areas, each tackling a different layer of the visual system: > **[General](/docs/features/specifications/visual-design/general)** > > Brand identity (logos, favicons) and form factors (desktop, tablet, mobile). > **[Design Principles](/docs/features/specifications/visual-design/design-principles)** > > The principles your design and engineering teams should follow. > **[Styling](/docs/features/specifications/visual-design/styling)** > > Theme, typography, UI framework, and iconography. > **[Layout](/docs/features/specifications/visual-design/layout)** > > Reusable page templates for common app sections. ## How visual specs feed code Visual Design specs drive the generated [Frontend](/docs/features/frontend/overview): * The component library (Tailwind, Material, Chakra, Bootstrap) is set on the styling page * The icon set (Lucide, Heroicons, Material, Font Awesome) is also on styling * The theme tokens (color palette, typography, spacing) flow into the component theme * The layout templates set the page chrome for each section type When you re-run code generation, the visual specs are read in full and applied to the frontend. ## FAQ > **Can I customize the theme after generation?** > > Yes. Edit the styling specs and re-run frontend generation, or edit theme tokens directly in the generated code via the [Visual editor](/docs/features/frontend/visual-editor). Both paths work. > **What if I want to use a UI framework that isn't in the dropdown?** > > Pick the closest one (Tailwind is the most flexible) and override component-level styling via the visual editor and IDE. Custom UI frameworks aren't in the catalog yet. > **Do visual specs affect anything besides the frontend?** > > Mostly no. Email templates picked up by SendGrid use brand identity (logo, colors); other backend surfaces are visual-spec-agnostic. # Styling Source: https://archie.com/docs/features/specifications/visual-design/styling Theme, typography, UI framework, and iconography — the concrete style tokens Archie applies across the generated frontend. Styling is where you set the concrete visual choices Archie applies across your app — color palette, typography, the UI framework powering your components, and the icon set. Four sections. ## Theme Pick a **color palette** from Archie's curated themes or supply your own brand colors. The chosen palette generates the full set of design tokens — primary, secondary, accent, neutrals, success, warning, danger — used by every generated component. Themes flow into: * Component backgrounds, borders, and accents * Email template backgrounds * Charts and dashboards (a default chart palette is derived from the theme) ## Typography * **Font Family** — the typeface for the app. Archie picks from Google Fonts and a curated list of system stacks; uploading a custom font is supported on Enterprise. * **Font Size** — base size from which the type scale is generated. * **Font Weight** — default weight for body copy. The chosen typography generates a full type scale (display, headings, body, caption) used across the generated frontend. ## UI Framework The component library underlying the generated frontend. Options: * **Tailwind** — utility-first CSS framework with custom-styled primitives. Most flexible. * **Material** — Google's Material Design implementation in React. * **Chakra** — modular, accessible component library. * **Bootstrap** — responsive, mobile-first framework with prebuilt components. This is one of the most load-bearing visual choices — switching frameworks after generation requires Archie to regenerate the entire frontend. ## Iconography Pick the icon set used throughout the app: * **Lucide** — community-maintained icon set; default * **Heroicons** — by the makers of Tailwind CSS * **Material** — Google's Material icons * **Font Awesome** — large catalog covering most common needs The icon set is used in navigation, buttons, empty states, and anywhere a glyph is needed. Switching icon sets after generation re-keys icon references across the codebase. ## FAQ > **Can I override theme tokens for individual components?** > > Yes. The theme defines defaults; the visual editor and IDE let you override component-by-component. Component overrides survive code regeneration unless you regenerate that specific component. > **What if my brand uses a custom color not in the curated themes?** > > Provide custom hex values when picking the palette. Archie generates the rest of the token set (lighter/darker shades, hover states, focus rings) from the colors you supply. > **Can I mix UI frameworks (e.g. Tailwind for some screens, Material for others)?** > > No — pick one. Mixing frameworks creates conflicting style systems and inflates bundle size. Archie generates a single consistent UI framework across the whole frontend. > **How do I add a custom icon that isn't in any of the sets?** > > Drop the SVG into the generated `/icons` directory and import it like a component. The icon set choice doesn't restrict you from adding custom one-offs. > **What happens if I change the UI framework after generating code?** > > Archie regenerates the frontend on the next code generation pass. Custom edits in the visual editor that depend on framework-specific classes may not migrate cleanly — review the diff before merging. # What are specifications Source: https://archie.com/docs/features/specifications/what-are-specifications Specifications describe each feature, screen, and flow in detail before any code is written — the layer between plan and code. > [!NOTE] > > Specifications is coming soon. This documentation previews what the section will look like at launch. A specification is a structured description of a feature's behavior, written in plain language. It lives between the plan and the code, providing the detail that determines exactly what the build produces. ## Anatomy of a specification Each feature in your plan generates a specification with five sections: ### Screens The user-facing surfaces for this feature. Each screen has a purpose, a list of elements (text, inputs, buttons, lists), and the actions available on it. Specifications do not specify pixels — they specify what is on screen and what the user can do with it. Visual styling lives in the [Frontend theming](/docs/features/frontend/theming) layer. ### Flows A flow is a sequence: the user does X, then Y happens, then they see Z. Flows connect screens. They include success paths, alternate paths, and error paths. Each flow names the actor (which user type), the trigger, and the outcome. ### Behaviors The non-flow logic. What gets calculated, what runs on a schedule, what fires on an event, what auto-saves. Behaviors are usually one or two sentences each — "when an order's total exceeds \$500, flag it for manual review." ### Validation and edge cases Required fields, format rules, business rules ("a user cannot order more than 10 of an item"), and edge cases (empty states, network failures, concurrent edits, large datasets). The specification is the place where you say what should happen in each case. ### Copy Exact text for labels, buttons, placeholders, helper text, success messages, error messages, and empty states. Specifying copy upfront avoids two rounds of "the button text is wrong" after the build. ## How specs relate to plans The plan says *what* your app does. Specifications say *how each thing behaves*. A plan module called "Orders" might generate a specification covering the order list screen, the order detail screen, the create-order flow, the cancel-order flow, the validation rules for each, and the copy throughout. When you change the plan (add a module, drop a service), the affected specifications regenerate. When you edit a specification directly, the plan stays the same — you are refining the detail, not the structure. ## How specs relate to code Specifications are the contract for what the build produces. Each spec section maps to generated code: screens become routes and components, flows become page transitions and form handlers, behaviors become functions, validation becomes form rules and database constraints, copy becomes localization strings. See [Specifications and code generation](/docs/features/specifications/specifications-and-code-generation). ## Where specifications come from Archie generates an initial specification for every feature in your plan. You edit it the same way you edit plan sections — directly, or through the spec chat for multi-step changes. See [Editing specifications](/docs/features/specifications/editing-specifications). # Overview Source: https://archie.com/docs/frontend The Archie frontend — a generated React app with visual editing, chat-based code edits, and a full IDE for direct code work. The Archie frontend is the user-facing layer of every app you build. Archie generates a modern web application — typically React-based with a component library, routing, and state management — from your plan and specifications. Getting there happens in stages: Archie lays out a UI architecture, you generate an interactive prototype from it, then Archie turns that prototype into the real, deployed app. See [Generating the frontend](/docs/features/frontend/generating) for how that works, and [Prototype](/docs/features/frontend/prototype/overview) for shaping how it looks and browsing every generated screen. Once the app exists, you customize it with three modalities that work together. ## Prototype vs. the real frontend "Frontend" in Archie actually spans two distinct surfaces. They look similar — both render your screens — but they exist for different reasons, and confusing them leads to the wrong expectations about what's safe to click and what actually ships. | | Prototype | Real frontend | | - | - | - | | **What it's for** | Refining design and flows before you commit to a build | The application you deploy and give to users | | **Data** | Mock data, generated to look realistic | Real data, read and written through [Archie Core](/docs/features/backend/overview) | | **Persistence** | Nothing is saved as application state — you're editing the design, not records | Fully persistent — creates, edits, and deletes flow through the database | | **Backend connection** | None | Connected — auth, file storage, integrations, APIs | | **Deployable** | No — it's a design and refinement tool | Yes | | **What editing changes** | The screen's design spec — layout, copy, theme | Real source code | | **IDE access** | No — there's no source code to open | Yes — direct code editing | The prototype is a design tool, not a working app in miniature. Every screen renders from mock data, and there is no real functionality behind the buttons — its purpose is to let you validate layout, navigation, and how your theme reads on real content, while a screen still costs little to change. The real frontend is a different kind of thing entirely: it's the deployable application, wired to Archie Core for data, auth, and file storage, and integrated with whatever third-party services your plan calls for. Both surfaces are edited the same way — point at an element and use the inspector, or describe the change in chat. [Prototype editing](/docs/features/frontend/prototype/screens/editing) acts on that screen's design spec and re-renders it; the [visual editor](/docs/features/frontend/visual-editor) and [chat](/docs/features/frontend/talking-to-archie) on the real frontend act on the actual source code. The IDE is the one tool exclusive to the real frontend, since the prototype has no underlying code to open. > [!NOTE] > > The real frontend is generated **from the prototype**, not straight from the plan. That's the reason to spend real effort refining the prototype: every layout problem, awkward flow, or off-brand screen you catch and fix there is one the generated frontend will already get right — instead of something you have to track down and fix again in production code after the build. ## Three editing modalities > **[Talk to Archie](/docs/features/frontend/talking-to-archie)** > > Describe a change in plain language. The chat applies it across files. > **[Visual editor](/docs/features/frontend/visual-editor)** > > Point and click for layout, copy, and styling. > **IDE** > > Direct code editing in the browser, with syntax highlighting, IntelliSense, and search. Each modality fits a different kind of work. The chat is the primary path for most edits. The visual editor is for layout and copy tweaks. The IDE is for logic-heavy changes and custom code. ## What's in this section * **[Generating the frontend](/docs/features/frontend/generating)** — UI architecture, prototype, and the real app * **[Prototype](/docs/features/frontend/prototype/overview)** — theme, branding, and every generated screen * **[Visual editor](/docs/features/frontend/visual-editor)** — point-and-click element editing * **[Draw mode](/docs/features/frontend/draw-mode)** — annotate the preview and send your marks to chat * **[Talking to Archie](/docs/features/frontend/talking-to-archie)** — using the chat for code edits * **[Theming](/docs/features/frontend/theming)** — branding, colors, fonts, dark mode * **[Routing](/docs/features/frontend/routing)** — pages, dynamic routes, navigation * **[Connecting to the backend](/docs/features/frontend/connecting-to-the-backend)** — API client, auth tokens, real-time * **[Deployment](/docs/features/frontend/deployment)** — publishing to Archie's hosting or self-deploy ## What the frontend includes A generated Archie frontend ships with: * A page router with route definitions for every feature in your plan * A component library based on shadcn/ui with your branding applied * Authentication wired to your chosen [auth provider](/docs/features/backend/app-services/authentication-providers/overview) * A typed API client for your project's GraphQL and REST endpoints * Real-time subscriptions for live data * Form handling with validation matching your spec rules * Internationalization, with at least your primary locale populated The frontend talks to [Archie Core](/docs/features/backend/overview) for everything backend — data, auth, file storage, integrations. ## Stack The default stack is Next.js with TypeScript, Tailwind, and shadcn/ui. Stacks Archie supports out of the box include Next.js, Vite + React, and SvelteKit. The tech stack is chosen and managed by Archie automatically — it is not shown or editable in the plan. If you need a specific stack, mention it in your prompt before generating. The frontend connects to Archie Core regardless of the chosen stack. ## Customizing further For changes the chat and visual editor cannot express — custom hooks, complex state management, third-party libraries — open the IDE and edit the code directly. Hand edits are preserved across rebuilds; see [Specifications and code generation](/docs/features/specifications/specifications-and-code-generation) for the regeneration semantics. # Code editor Source: https://archie.com/docs/frontend/code-editor Edit your app's code directly with the built-in IDE — Monaco-powered file editing, file tree navigation, and inline build errors. The code editor is a built-in IDE for directly editing your app's source code — useful when you need precise control beyond what the visual editor offers. ## When to use the code editor The code editor is one of four modalities in the Frontend tab. Each is best for a different kind of change. | Modality | Best for | Code required | | - | - | - | | [Visual editor](/docs/features/frontend/visual-editor) | Layout, copy, styling, swapping images | No | | **Code editor** | Logic, custom hooks, refactors, anything beyond visual edits | Yes | | [Talking to Archie](/docs/features/frontend/talking-to-archie) | Multi-file changes you describe in words | No | | Preview | Running app — viewing only, no editing | No | Reach for the code editor when the change is logic-heavy, touches files the visual editor cannot inspect, or when you want to read the generated code to understand what Archie produced. ## Switching into code mode The Frontend tab has a segmented control at the top with two tabs: **Preview** and **Code**. Click **Code** to switch into the editor. ![Code editor open on a generated page file, with the file tree, tabs, and Monaco editor](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/code-editor-overview.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=09fdf671154c040dcc38817483317c49) The Code tab is hidden for users without code-view permission. Owners and editors see it; viewers do not. Switching back to **Preview** keeps your unsaved edits in memory — they stay in the Source Control panel until you save or discard. ## The file explorer The left sidebar in the code editor has four panels, switched with the icons at the top. > **Files** > > The project file tree. Click a file to open it in a tab. A search input at the top filters the tree by file name. The collapse/expand toggle next to it folds the whole tree at once. > > Right-click anywhere in the tree for **New File**, **New Folder**, and **Paste**. Right-clicking a file or folder also exposes rename, delete, and duplicate. > > Some files are read-only — config files Archie owns, generated artifacts, and lockfiles. The tree marks them; the editor opens them but blocks edits. > **Source Control** > > The list of files with unsaved changes. Each entry shows a status badge: **modified**, **new**, **deleted**, or **renamed**. The total count is shown on the icon when changes exist. > > From this panel you can save or discard changes per-file or for the whole working set. Click a file to open a diff against its last saved state. > **Problems** > > All TypeScript and lint diagnostics from your project, grouped by file. Filter by severity (errors, warnings, info) using the toggles at the top. Click any diagnostic to jump to the exact line and column. > **Search** > > Project-wide text search across all editable files. ## Editing files Files open in tabs at the top of the editor. The editor is Monaco — the same engine that powers VS Code — so most of what you expect from a modern IDE works out of the box. What's enabled: * Syntax highlighting for the languages in a Next.js + TypeScript + Tailwind project * IntelliSense, parameter hints, and hover tooltips * Auto-closing brackets and quotes, format on paste, format on type * Multi-cursor (default modifier: Alt; switchable to Ctrl/Cmd in settings) * Find and replace (Cmd/Ctrl+F, Cmd/Ctrl+H) * Toggle comment (Cmd/Ctrl+/) * Cmd/Ctrl-click on an import specifier to jump to that file * Emmet expansion in HTML, CSS, and JSX * Tabs are draggable — drop a tab into the chat to attach the file as context The toolbar above the tabs has back/forward navigation across recently visited files, a theme picker, **Format Document** (Cmd/Ctrl+Shift+F), zoom (Cmd/Ctrl+Alt++ / -), copy file contents, download the file locally, and an entry to open the settings dialog. ```typescript theme={null} // Cmd/Ctrl-click on the import path to jump to the file import { useUser } from "@/hooks/use-user" import type { User } from "@/domain/entities/user" ``` ### Settings Open **Settings** from the toolbar's overflow menu (Cmd/Ctrl+,). Notable preferences: | Setting | Range | Default | | - | - | - | | Theme | Multiple light/dark themes | Dark | | Font family | JetBrains Mono, Fira Code, Consolas, Monaco, system monospace | JetBrains Mono | | Font size | 8–28 px | 14 | | Line height | 1.0–2.0 | 1.5 | | Font ligatures | On/off | On | | Multi-cursor modifier | Alt or Ctrl/Cmd | Alt | You can export and import your settings as JSON from the **Advanced** section, and reset everything to defaults from the same place. ## Build errors and diagnostics The **Problems** panel in the file explorer is the central place for all in-editor diagnostics. It surfaces: * TypeScript errors and warnings * ESLint issues * Anything else Monaco's language services flag Each diagnostic shows the file, line:column, the message, and the source (for example, `ts (2304)`). Click to open the file at the exact location. The panel groups by file and lets you filter by severity — errors only, warnings only, or info only. Diagnostics also appear inline in the editor itself: red squiggles under errors, amber under warnings. Hover for the message. Build errors that come from the project's actual build pipeline (not just in-editor type checks) surface through the same panel once the build has run. If the Source Control icon shows a green count and the Problems icon shows a number, you have unsaved changes that haven't been validated against the build yet — save first, then check. ## Saving and discarding The code editor uses a manual save model, not autosave. When you edit a file, the changes are buffered in memory. They appear in the **Source Control** panel with a status badge and stay there until you commit or discard them. * **Save All** in the Source Control panel persists every pending change at once * **Save** on a single row persists only that file * **Discard All** rolls back every unsaved change * **Discard** on a single row rolls back just that file Switching between Code and Preview does not clear unsaved buffers. Closing the project does — make sure pending changes are saved before you leave. > [!WARNING] > > Cmd/Ctrl+S in the editor **downloads** the current file to your machine. It does not save. Use the Source Control panel to save. ## Cross-modality consistency Edits from [Talking to Archie](/docs/features/frontend/talking-to-archie) and the [visual editor](/docs/features/frontend/visual-editor) write to the same files. When the chat applies a change, the modified files appear in the Source Control panel just as if you had edited them by hand. The diff view, save, and discard controls work the same way. If you have unsaved hand edits and the chat or visual editor touches the same file, the latest write wins. Save your work before delegating to another modality if you don't want it overwritten. ## Limitations The code editor is for editing source files. It deliberately does not include: * **A terminal.** No shell access, no `pnpm install`, no running scripts. * **Package management.** You cannot add or remove npm packages from this editor. Dependency changes are managed elsewhere. * **Git operations.** The Source Control panel is a change tracker for your unsaved edits — it is not a Git client. There is no branching, committing, pushing, or pull-requesting from inside the editor. Git is handled by Archie's GitHub integration on the project level. * **Custom Monaco extensions.** You cannot install third-party Monaco plugins. If you need any of these, clone the project locally through the GitHub integration and use a desktop editor of your choice. Push your changes back, and Archie will pick them up on the next regeneration. ## Related * [Visual editor](/docs/features/frontend/visual-editor) — the alternative for layout and styling work * [Talking to Archie](/docs/features/frontend/talking-to-archie) — describe changes in natural language * [Connecting to the backend](/docs/features/frontend/connecting-to-the-backend) — how generated API code reaches your components * [Deployment](/docs/features/frontend/deployment) — shipping the changes you make here ## FAQ > **Can I install npm packages from the code editor?** > > No. The editor does not have a terminal or a package manager UI. Clone the project locally and install packages there. > **What happens if I break the build with my edits?** > > The Problems panel shows the errors. The preview build will fail until they are resolved — the Preview tab surfaces the failure, and the live preview stops updating. Fix the errors in the editor, save, and the preview rebuilds. If you want to roll back fast, use **Discard All** in the Source Control panel. > **Can I use Vim, Emacs, or custom keyboard shortcuts?** > > No. The editor is Monaco with its default keymap. Standard shortcuts work — undo, redo, find, replace, comment toggle, multi-cursor — but Vim, Emacs, and custom keymaps are not supported. The multi-cursor modifier can be switched between Alt and Ctrl/Cmd in settings. > **Are my changes synced if I'm also using Talking to Archie?** > > Yes. The chat and the code editor write to the same files. Edits from either show up in the Source Control panel. If both touch the same file in close succession, the most recent write wins, so save before handing off to the chat if you want your hand edits preserved. > **Can I edit backend files from this editor?** > > No. The code editor in the Frontend tab edits frontend source only. Backend logic — data model, custom functions, app services — lives in the [Backend](/docs/features/backend/overview) section and is edited there. # Connecting to the backend Source: https://archie.com/docs/frontend/connecting-to-the-backend How your Archie frontend talks to Archie Core — typed API client, authentication tokens, and real-time subscriptions. The frontend connects to [Archie Core](/docs/features/backend/overview) through a generated, typed API client. Your data, auth, file storage, and real-time subscriptions all flow through this client. ## The API client When you build, Archie generates a TypeScript client for your project's API surface. It includes: * One method per GraphQL query, mutation, and subscription * One method per REST endpoint * Type definitions matching your data model * Error types for each failure mode The client lives in `lib/api/` (or your stack's equivalent). Use it from any component: ```typescript theme={null} import { api } from '@/lib/api' const { data } = api.orders.list({ status: 'active' }) ``` The client handles authentication, request signing, retries, and error formatting. You write one line; the client handles the rest. ## Authentication tokens The API client picks up the active session token automatically. When a user signs in, their token is stored (httpOnly cookie by default) and attached to subsequent requests. Sign-out clears the token. Three flows are wired: * **Browser session** — the user's auth token, used for browser-side calls * **Server-side** — for SSR/RSC, the framework reads the cookie on each request * **External clients** — server-to-server callers use [API keys](/docs/features/backend/settings/api-keys) instead of session tokens ## Real-time subscriptions For data that changes live (an order list, a chat thread, a dashboard counter), use a subscription: ```typescript theme={null} const orders = api.orders.subscribe({ status: 'active' }) ``` Subscriptions use GraphQL over WebSocket. The hook returns the live data and re-renders your component when the data changes. Cleanup happens automatically when the component unmounts. ## File uploads Files upload through a dedicated endpoint: ```typescript theme={null} const url = await api.files.upload(file) ``` The client returns a URL you can store and reference. Behind the scenes, the upload streams to your project's [file storage provider](/docs/features/backend/app-services/file-manager). ## Error handling Each method's return type includes typed errors. You handle errors with normal try/catch or by checking the result: ```typescript theme={null} try { await api.orders.create(orderData) } catch (err) { if (err.code === 'INSUFFICIENT_INVENTORY') { // ... } } ``` Common errors (auth, validation, server) have shared types. Spec-level rules generate domain-specific error types automatically. ## Environments The client picks up the active environment automatically. In development, it points at your dev environment's API URL. In production, it points at production. See [Environments](/docs/features/backend/environments/overview) for the full setup. ## Custom requests For requests outside the auto-generated surface (calling a custom function with a non-standard signature, calling a third-party API), use the lower-level fetch utilities the client exposes. Or write a [Logic service](/docs/backend/logic) that wraps the call and exposes it through the typed API. ## Performance The client batches GraphQL queries by default — multiple component-level queries on a page resolve in one network request. Subscriptions multiplex over a single WebSocket. You write per-component data needs; the client makes them efficient. # Deployment Source: https://archie.com/docs/frontend/deployment Publish your Archie app to production with one click — Archie hosting, custom domains, environments, and rollbacks. Deployment publishes your built app to a hostable URL. You deploy to Archie's hosting (one-click, custom domain, automatic TLS) or hand off the generated code for self-deploy through GitHub. ## Deployment options * **Archie hosting** — one-click deploy, custom domain, automatic TLS, global CDN * **Self-deploy** — pull the code via the GitHub integration and host on your own infrastructure Most projects deploy to Archie. Self-deploy is for teams with specific compliance, region, or operational requirements. ## Deploying via Archie 1. Open **Deployment** in your project 2. Pick the target environment (development, staging, production) 3. Click **Deploy** Archie builds the production bundle, runs migrations if needed, and publishes. The first deploy takes a few minutes; subsequent deploys are faster (incremental builds). You can deploy to staging first, verify, then promote to production. See [Environments](/docs/features/backend/environments/overview) for the promotion flow. ## Custom domains Connect a custom domain in your project's network settings. Archie handles DNS verification and TLS certificate issuance via Let's Encrypt. Setup: 1. Add the domain in **Settings → Network** 2. Add the displayed CNAME or ANAME record at your DNS provider 3. Wait for verification (typically minutes, sometimes up to 48 hours for propagation) 4. Archie issues the TLS certificate automatically Pro plan includes 1 custom domain per project. Team and Enterprise include multiple. See [plans and credits](/docs/introduction/getting-started/plans-and-credits). ## Self-deploying If you prefer to host the app yourself: 1. Connect a GitHub repository 2. Pull the generated code 3. Deploy using your normal infrastructure (Vercel, Render, AWS, your own servers) The code is standard for its stack — there is no Archie runtime requirement on the frontend. You will need to provision a database, file storage, and environment variables yourself, or point your self-hosted frontend at your Archie Core backend (which can run anywhere AWS, GCP, or Azure runs). ## Rollbacks Every deployment is preserved. Roll back to any previous deployment in seconds: 1. Open **Deployment → History** 2. Find the version you want 3. Click **Roll back** Rollback is instant on Archie hosting — traffic switches immediately, no rebuild. For database migrations that ran with the rolled-back deploy, see the [Environments](/docs/features/backend/environments/overview) docs for the rollback semantics. ## Preview deployments Pro and higher plans get a preview URL per branch. Push to a branch on GitHub, Archie builds and deploys automatically to a unique URL. Useful for sharing in-progress work with reviewers. Preview URLs are public by default. Add password protection from project settings if needed. ## Build minutes and bandwidth Each plan includes a monthly allotment: * **Starter** — 100 build minutes, 100 GB CDN bandwidth * **Professional** — 500 build minutes, 1 TB CDN bandwidth * **Enterprise** — custom Overages are billed at \$0.03/minute and \$0.03/GB. See the pricing page for full numbers. ## FAQ > **Does deployment cost AI credits?** > > No. Deployments do not consume AI credits. Build minutes and bandwidth are tracked separately and counted against your plan allotment. > **What happens to data during a deployment?** > > Data is preserved. Schema migrations apply automatically; destructive changes require explicit confirmation. > **Can I have multiple production environments?** > > Yes via [environments](/docs/features/backend/environments/overview). Configure each with its own variables, domain, and database. # Draw mode Source: https://archie.com/docs/frontend/draw-mode Annotate the live preview directly, then send your marks to chat to request precise, visual changes. Draw mode turns the preview into an editable whiteboard. Instead of describing a change in words alone, you circle, box, or write directly on the screen — then hand those annotations to the chat so Archie knows exactly what you mean. ## Turning it on Click the pencil icon in the floating toolbar at the bottom of the canvas (alongside **Navigate**, **Select / Inspect**, and **Comment**). The toolbar switches to a drawing-specific set of tools: pen, rectangle, arrow, text, a color picker, undo, delete, and **Send to chat**. ## Marking up the screen Use whichever tool fits the note: * **Pen** — freehand marks * **Rectangle** — box around the element you mean * **Arrow** — point at something specific * **Text** — write a short instruction right on the canvas * **Color picker** — change the color of what you're about to draw Draw as many marks as you need. In this example, a box calls out the button and a text note says what to change about it: ![Draw mode active: a rectangle around a button and a text annotation reading "Make this bigger"](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/draw-mode-annotation.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=8afc848490dad8d5f491dd23355f316d) ## Sending it to chat Click **Send to chat**. Draw mode closes and your marks collapse into a single attachment in the chat input — labeled with the screen name and how many marks it contains (for example, "landing · 2 marks"). ![Chat input with the drawing attached as "landing · 2 marks", ready to send](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/draw-mode-sent-to-chat.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=3dfb77489354303a98bc57b35cbbd07c) Type what you want done and send the message as normal. Archie reads the screenshot together with your marks and instructions, so a note like "make this bigger" pointing at a specific button is unambiguous about what to change — the same way it would be if you were marking up a screenshot for a teammate. ## When to use Draw mode Reach for Draw mode when a change is easier to point at than to describe — resizing or repositioning something, calling out several small tweaks across the screen at once, or flagging a spot where "that one, right there" is clearer than a written description. For changes you can fully describe in words, [Talking to Archie](/docs/features/frontend/talking-to-archie) alone is faster. For a single element you want to edit directly rather than describe, use the [visual editor](/docs/features/frontend/visual-editor). ## FAQ > **Do my marks stay on the screen after I send them?** > > No. Sending to chat clears the canvas and returns you to normal preview. The marks live on in the chat as an attached image. > **Can I draw on more than one screen before sending?** > > Each attachment is scoped to the screen you were drawing on. Switch screens and use Draw mode again to annotate a different one — it attaches separately. > **What if I want to erase a mark before sending?** > > Use undo to remove the last mark, or the delete tool to clear one you've selected. # Generating the frontend Source: https://archie.com/docs/frontend/generating From UI architecture to prototype to a real, deployed frontend. Frontend generation happens in stages: Archie first lays out every screen your app needs, then builds an interactive prototype from the screens you pick, then turns that prototype into the real, deployed app. This page covers each stage. ## From plan to UI architecture Clicking **Generate Frontend** on the plan page starts by generating a **User Interface Architecture** — every screen your app needs and how someone moves between them, worked out from your plan. When it finishes, the Frontend tab opens on the **Sitemap** view: every planned screen as a card, grouped by module and color-coded by who can access it (Public, Auth, Onboarding, Private, Admin). ![Sitemap view showing every planned screen grouped by module, with a legend by access level](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ui-architecture-sitemap.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=a8f453a962fb41e034a8472064d4ffc8) ## Reviewing a screen before generating it Click **See details** on any pending screen to open its spec before any code exists: purpose, route, screen metadata (module, capabilities, navigation group), permissions (view-only vs. manage access by user type), and two tabs — **Key Jobs** (what the screen must let someone do) and **Layout Specs** (its structure). Edit any of it, then click **Generate screen** to build that screen from the spec. ![Screen detail panel showing purpose, permissions, and Key Jobs for a pending screen](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ui-architecture-screen-spec.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=fc6fa26de4e78de6c3bd1709c77cd498) Use the chat to ask Archie to adjust the architecture itself — add a screen, merge two, change who can access one — before you generate anything. Once a screen has been generated, its detail panel adds a rendered mockup and a **Re-imagine this screen** action that rewrites the spec and re-renders it. ## Generating the prototype The sitemap arrives with a sensible default selection already ticked — typically the bespoke screens your plan calls out specifically. You don't have to click **Select all**: untick anything you don't need yet, or tick additional screens, and the screen count and the **Generate** button update to match. ![Custom screen selection: 9 of 23 screens selected instead of the full default, with the Generate button updating to match](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ui-architecture-screen-selection.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f3be2f46a68428bed7bc98aa6461ac9b) How many you generate at once is up to you — it depends on what your plan needs and how many AI credits you want to spend now. Untick a screen to save it for later: you can always come back and generate it screen by screen once the rest of the app takes shape. Click **Generate** to build whatever is ticked. Only those screens are built; everything else stays exactly as it is. The result is an interactive, click-through **prototype** — wired navigation, your design system applied, no real data or backend yet. Switch to the **Grid** view to browse every generated screen at once. ![Generated prototype screens grouped by module in Grid view](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/prototype-generated-screens-grid.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=e7bbfd5a9b953e8edbe623ba650acfd0) Reviewing the architecture and each screen's spec catches design and navigation problems while they are still cheap to fix — moving a screen or rethinking a flow costs nothing close to redoing a generated prototype. Click through the finished prototype as a user would and confirm the flows hold together end to end before moving on. See [Prototype](/docs/features/frontend/prototype/overview) for shaping the theme and browsing every generated screen in depth. ## From prototype to real app Once a prototype exists, the plan page's bottom action becomes **Generate Full-Stack** — it turns your prototype and plan into a real, deployed application with a working frontend connected to [Archie Core](/docs/features/backend/overview). From there, use [Talking to Archie](/docs/features/frontend/talking-to-archie), the [visual editor](/docs/features/frontend/visual-editor), or the [code editor](/docs/features/frontend/code-editor) to keep iterating. ## FAQ > **Do I have to generate every screen at once?** > > No. Tick only the screens you want built and click Generate. Untouched screens keep whatever state they were in — pending or already generated. > **Can I change the UI architecture after generating some screens?** > > Yes. Ask the chat to add, remove, or restructure screens, or use Re-imagine on an individual screen. Regenerating a screen replaces its current design. > **How many times will I generate?** > > Most projects tick a handful of screens, review them, then generate the rest once the pattern looks right. If you are regenerating the same screen often, its spec probably needs another pass first. # Overview Source: https://archie.com/docs/frontend/prototype Prototype is where you shape how your generated screens look — before you build the real frontend. Once [Generating the frontend](/docs/features/frontend/generating) produces a prototype, Prototype is where you shape how it looks: pick a visual direction, adjust the underlying design values, and browse every screen it produced — before you move into the real, deployed frontend. Everything you set here applies globally. Change the primary color once, and it updates across all generated screens at the same time. ## What's in Prototype > **[Theme](/docs/features/frontend/prototype/design/theme)** > > Pick a complete, pre-built visual direction, or generate new alternatives. > **[Branding](/docs/features/frontend/prototype/design/branding)** > > Upload your logo, icon, and favicon for light and dark mode. > **[Feel](/docs/features/frontend/prototype/design/feel)** > > Set density and motion — how tightly things are packed and how they move. > **[Read](/docs/features/frontend/prototype/design/read)** > > Choose a typographic personality and let it map to font families and sizes. > **[Color](/docs/features/frontend/prototype/design/color)** > > Set your primary color and neutral tone; Archie generates the rest of the palette. > **[Polish](/docs/features/frontend/prototype/design/polish)** > > Corners, shadows, and icon stroke — the surface character of every element. > **[Components](/docs/features/frontend/prototype/design/components)** > > A live gallery that proves your design values hold together across real UI. > **[Screens](/docs/features/frontend/prototype/screens/overview)** > > Browse every generated screen as a single page, a grid, or a full sitemap. > **[Comments](/docs/features/frontend/prototype/comments)** > > Leave feedback pinned to an exact spot on a screen, and tag teammates. ## How Prototype fits together This section splits into three parts: * **Design** — Theme, Branding, Feel, Read, Color, and Polish define the design system: the colors, type, spacing, motion, and surface treatment your app uses everywhere. Components is a live proof that renders those values on real UI so you can see them hold together before committing. * **Screens** — the generated prototype itself. Every route in your plan gets a screen, and the Screens views let you browse them as a single page, a thumbnail grid, or a full sitemap. * **Comments** — feedback pinned to a specific screen, for you or a teammate to act on. Picking a **Theme** sets a starting point for every value at once. From there, the individual Branding, Feel, Read, Color, and Polish sections let you fine-tune anything the theme got close but not exact. ## Where Prototype sits in the workflow * [**Generating the frontend**](/docs/features/frontend/generating) produces the prototype — one screen for every route your plan describes. * **Prototype** (this section) defines what it looks like, and is where you review every generated screen. * The theme you set here becomes the starting values in the frontend's own [theming](/docs/features/frontend/theming) system once you build — you can keep refining it there with the visual editor, chat, or code. ## FAQ > **Do changes here affect the real app, or just the prototype?** > > Prototype works on the generated prototype — the screens Archie produces from your plan. When you build, the theme and screens you land on here become the starting point for the actual frontend. You can keep adjusting afterward from [Frontend → Theming](/docs/features/frontend/theming). > **Do I have to finish Prototype before I build?** > > No. The build uses whatever theme and screens are in place at the time. Many teams pick a theme, glance at a few key screens, and build — then fine-tune from the frontend once real data and interactions are in place. > **Can different user types see different screens?** > > Yes. The [Screens](/docs/features/frontend/prototype/screens/overview) view has a **User Type** filter that shows only the screens relevant to a given user type — useful for checking an admin dashboard separately from the end-user experience. # Comments Source: https://archie.com/docs/frontend/prototype/comments Leave feedback pinned to an exact spot on a screen, tag teammates, and track it through to resolved. Comments let you leave feedback directly on a generated screen instead of describing it in chat or fixing it yourself. Use them to flag something for a teammate, leave a note for later, or ask Archie to act on the feedback — all without leaving the screen you're looking at. ## Leaving a comment In [Screen view](/docs/features/frontend/prototype/screens/views#screen), switch to the **Comment** tool in the toolbar at the bottom of the canvas, then click anywhere on the screen to drop a pin. A comment card opens right there — write the note and submit it. Because the comment is pinned to the exact spot you clicked, a note like "change color to red" or "update the image" is unambiguous about which element it refers to. ![A comment pinned on a screen, with the full Comments panel on the right listing every thread](https://mintcdn.com/archie-e998dbf6/uKV7VZ3hAIAhG9GA/features/design/comments-panel.png?fit=max\&auto=format\&n=uKV7VZ3hAIAhG9GA\&q=85\&s=e5e157b1a08a5f4583bcc8fb6db14f07) ## Tagging teammates Type `@` in a comment to bring up a list of people on the project, with their name and email, and mention one directly. Mentioning someone is how you route feedback to a specific person rather than leaving it for whoever looks next — it's also what surfaces the comment under their **For me** tab. ![Typing @ in a comment brings up a list of project members to tag, showing name and email](https://mintcdn.com/archie-e998dbf6/uKV7VZ3hAIAhG9GA/features/design/comments-mention.png?fit=max\&auto=format\&n=uKV7VZ3hAIAhG9GA\&q=85\&s=b22a2f55306eccac0c8fcc428d597aa7) ## Resolving comments Once a comment has been addressed, mark it resolved from the checkmark icon on the comment card. Resolved comments do not disappear — they move out of the **Open** tab but stay visible under **Resolved** or **All**. ## Filtering and finding comments The **Comments** panel on the right lists every thread on the current screen. Four tabs narrow what you see: * **Open** — unresolved comments, the default view * **For me** — comments that mention you * **Resolved** — comments already addressed * **All** — everything, regardless of status Use the search bar at the top of the panel to find a specific comment on a screen with a lot of feedback. ## Comments are scoped to a screen Every comment belongs to the screen it was left on. Switching to a different screen shows that screen's own comments — not the ones you just left elsewhere. If feedback applies to more than one screen, leave it on each one. ## FAQ > **Can I use comments to describe a change I want made, not just feedback?** > > Yes. The sparkle icon on a comment card hands it to Archie directly, turning the note into an instruction without retyping it in chat. > **Do resolved comments disappear?** > > No. Resolving a thread moves it out of the **Open** tab, but it's still there under **Resolved** or **All** — comments aren't deleted unless you remove them. > **Why don't I see a comment I left on another screen?** > > Comments are saved per screen. Check the **Comments** panel while viewing the specific screen the comment was left on. # Overview Source: https://archie.com/docs/frontend/prototype/design Design is where you set the visual system your whole app uses — theme, branding, feel, type, color, and polish — and check it on real components. Design is the part of [Prototype](/docs/frontend/prototype) where you define your app's design system: the colors, typography, spacing, motion, and surface details that every generated screen uses. Everything here is global. Change a value once and it updates across all of your screens at the same time. ## What's in Design > **[Theme](/docs/frontend/prototype/design/theme)** > > Pick a complete, pre-built visual direction, or generate new alternatives. > **[Branding](/docs/frontend/prototype/design/branding)** > > Upload your logo, icon, and favicon for light and dark mode. > **[Feel](/docs/frontend/prototype/design/feel)** > > Set density and motion — how tightly things are packed and how they move. > **[Read](/docs/frontend/prototype/design/read)** > > Choose a typographic personality that maps to font families and sizes. > **[Color](/docs/frontend/prototype/design/color)** > > Set your primary color and neutral tone. Archie generates the rest of the palette. > **[Polish](/docs/frontend/prototype/design/polish)** > > Corners, shadows, and icon stroke — the surface character of every element. > **[Components](/docs/frontend/prototype/design/components)** > > A live gallery that shows your design values working together on real UI. ## How the sections work together **Theme sets everything at once.** A theme is a complete package — palette, typography, motion, and density — that Archie has already put together. Picking one sets the starting values for every other section. **Branding, Feel, Read, Color, and Polish fine-tune.** Each one adjusts a single part of the design system. Use them when the theme is close but not exact. Your changes adjust the values directly; they don't create a new theme. **Components shows the result.** There is nothing to configure on the Components page. It renders buttons, forms, cards, alerts, and badges with your current values so you can catch problems before you look at full screens. ## A good order to work in 1. Pick the [theme](/docs/frontend/prototype/design/theme) closest to what you want. 2. Upload your logo and favicon in [Branding](/docs/frontend/prototype/design/branding). 3. Adjust [Feel](/docs/frontend/prototype/design/feel), [Read](/docs/frontend/prototype/design/read), [Color](/docs/frontend/prototype/design/color), or [Polish](/docs/frontend/prototype/design/polish) only where the theme isn't right. 4. Open [Components](/docs/frontend/prototype/design/components) to check that everything holds together. 5. Review your full generated screens in [Screens](/docs/frontend/prototype/screens/overview). ## FAQ > **What happens if I switch themes after fine-tuning?** > > Switching themes resets Branding, Feel, Read, Color, and Polish to the new theme's defaults, and your manual adjustments are overwritten. Choose your theme first, then fine-tune. > **Do I have to set every section?** > > No. A theme gives you a complete set of values on its own. The other sections are there for when you want to change something specific. > **Does my design carry into the built app?** > > Yes. When you build, the values you set here become the starting point for the frontend's [theming](/docs/frontend/theming) system. You can keep refining them there with the visual editor, chat, or code. # Branding Source: https://archie.com/docs/frontend/prototype/design/branding Upload your logo, icon, and favicon for light and dark mode. Branding configures your application's visual identity — the assets that appear in the header, browser tab, and anywhere else your app needs to represent itself as a mark rather than a screen of content. ## What you can upload Each asset has a separate slot for light mode and dark mode, so your brand stays legible on either background: * **Logo** — Light Mode, Dark Mode * **Icon** — Light Mode, Dark Mode * **Favicon** — Light Mode, Dark Mode ![Branding panel with Logo, Icon, and Favicon upload slots for light and dark mode](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/branding-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=23483400a44136656772bc008c7ec57e) Click a slot to upload the corresponding file. Once uploaded, the generated screens in [Screens](/docs/features/frontend/prototype/screens/overview) reflect it immediately, in both preview modes. ## Light and dark variants If your logo is a single dark mark, it will disappear against a dark background — that is why each asset has its own light and dark slot rather than one shared file. Upload a variant suited to each mode; a common pattern is an inverted or lightened version of the same mark for dark mode. Use the **light/dark preview toggle** in [Screens](/docs/features/frontend/prototype/screens/overview) to check both variants against real screens before you build. ## FAQ > **Do I need to upload all six assets?** > > No — leave a slot empty and Archie falls back to a reasonable default. For the cleanest result, at minimum upload a logo for both modes. > **Can I change branding after I build?** > > Yes. Branding assets carry over as the starting point for the frontend's own [theming](/docs/features/frontend/theming) system, and you can replace them there through `public/` or the [Assets](/docs/features/assets/overview) surface. # Color Source: https://archie.com/docs/frontend/prototype/design/color Set your primary color and neutral tone — Archie generates the full set of semantic roles, contrast-checked, in light and dark. Color asks for a few high-level decisions and generates the rest. You do not hand-pick every shade in the palette — you set the primary color and the neutral temperature, and Archie derives a complete, contrast-checked set of semantic color roles for both light and dark mode. ## Primary The action color for your app — used for primary buttons, links, and other elements that mean "do this." Set separately for **Light** and **Dark**, each as a hex value. ## Neutrals Neutrals are the grays used for backgrounds, borders, and body text — the colors that show up everywhere but are never the star. Choose the temperature: * **Cool** — feels technical. * **Neutral** — balanced, no strong lean. * **Warm** — feels human. ![Color panel showing Primary color pickers for light and dark, and the Neutrals temperature control](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/color-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=798be18db48fe110c30e5ed39f291378) ## Advanced editing Click **Edit the full palette** to open every generated semantic color role directly — surface, content, status colors, and so on — instead of deriving them from Primary and Neutrals. Use this when the automatic derivation gets close but you need to nudge a specific role. ## FAQ > **Why can't I just pick every color myself?** > > You can, through **Edit the full palette**. The Primary + Neutrals flow exists because deriving a full, accessible palette from two decisions is faster than picking a dozen shades by hand, and Archie checks contrast for you along the way. > **Does the palette meet accessibility contrast requirements?** > > Yes — the generated roles are contrast-checked in both light and dark mode as part of generation. # Components Source: https://archie.com/docs/frontend/prototype/design/components Your design values rendered on real UI — a live gallery that proves your design system holds together. Components is a live gallery, not a form. Every family here compiles from the same values you set in [Theme](/docs/features/frontend/prototype/design/theme), [Branding](/docs/features/frontend/prototype/design/branding), [Feel](/docs/features/frontend/prototype/design/feel), [Read](/docs/features/frontend/prototype/design/read), [Color](/docs/features/frontend/prototype/design/color), and [Polish](/docs/features/frontend/prototype/design/polish) — there is nothing to configure directly on this page. It exists to show you those values working together on real UI before you commit to them. ## What's in the gallery * **Alerts** — semantic feedback colors, used sparingly and conservatively * **Buttons** * **Forms** * **Cards** * **Pills & badges** ![Components gallery showing the Alerts family — inline alerts and toasts rendered with the current design values](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/components-gallery.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=8eaa1e946d3e55e681aba09e749b223e) The preview renders in the canvas and reacts live to your colors, radius, density, type, and dark mode — change a value anywhere else in Prototype and come back here to see how it lands on real components. ## How to use it Treat Components as a checkpoint, not a starting point. Set your theme and design values first, then open Components to catch anything that looks off — a shadow that reads too heavy on a card, a corner radius that clashes with a pill badge — before you look at full generated screens in [Screens](/docs/features/frontend/prototype/screens/overview). ## FAQ > **Can I add or edit components here?** > > No. Components is a preview surface for the values set elsewhere in Prototype. To change how a button or card looks, adjust [Color](/docs/features/frontend/prototype/design/color) or [Polish](/docs/features/frontend/prototype/design/polish) and return here to check the result. # Feel Source: https://archie.com/docs/frontend/prototype/design/feel Two dials that change how everything spaces, paces, and reacts: density and motion. Feel controls the pacing of your app — how tightly elements are packed together, and how much they move when the user interacts with them. Both are set independently and apply across every generated screen. ![Feel panel showing Density and Motion controls, each with three presets](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/feel-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=581380e92405842f5b6265147a1de55d) ## Density How tightly things are packed. * **Compact** — the most information per screen, minimal padding. Good for dashboards and data-heavy admin tools. * **Comfortable** — a middle ground between information density and breathing room. * **Spacious** — generous padding, fewer elements per screen. Good for marketing pages and consumer-facing apps where the reader's attention matters more than density. ## Motion How elements move and respond. * **Still** — minimal to no animation. Changes appear instantly. * **Subtle** — light transitions on interaction (hover, open, close) without calling attention to themselves. * **Lively** — more pronounced motion, used to make an app feel energetic and responsive. ## Advanced editing Click **Edit spacing scale directly** to open the underlying spacing values instead of using the Density preset. Use this when you need a specific value the presets do not cover. ## FAQ > **Does density affect font size too?** > > Density primarily affects spacing and padding. For type scale, see [Read](/docs/features/frontend/prototype/design/read). > **Which density should I pick for an internal tool?** > > Compact or comfortable — internal and admin tools are usually read by people who use them daily and value information density over visual breathing room. # Polish Source: https://archie.com/docs/frontend/prototype/design/polish Corners, shadows, and icon stroke — the surface character of every element. Polish controls the small details that give your UI its surface character — sharp or rounded, flat or lifted, thin or bold icons. These are the values that make two apps with the same colors and type still feel different. ## Corners How rounded elements are. * **Soft** — larger radii, feels approachable. * **Balanced** — a moderate radius that works for most products. * **Sharp** — minimal or no radius, feels editorial and precise. ![Polish panel showing Corners and Shadows presets](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/polish-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=c9717b60082b54c4ad1dc1deb9b00205) ## Shadows How much lift surfaces have. * **None** — flat, no elevation cues. * **Soft** — subtle lift, barely-there depth. * **Regular** — standard elevation, the default for most card- and modal-based UIs. * **Strong** — pronounced elevation for a more dramatic, layered look. ## Icon stroke Line weight — changes every icon in the preview. * **Thin** * **Regular** * **Bold** ## Advanced editing Open the radius, shadow, and icon editor to set specific values directly instead of using the presets above. ## FAQ > **Do Corners and Shadows apply to every component?** > > Yes — they're global values, so buttons, cards, inputs, and modals all pick up the same radius and shadow values automatically. See [Components](/docs/features/frontend/prototype/design/components) to check them on real UI. # Read Source: https://archie.com/docs/frontend/prototype/design/read Pick a typographic personality — Archie maps it to font families and sizes that work together. Read controls typography: which fonts your app uses and how their sizes relate to each other. Instead of picking a font family and a type scale separately, you choose a personality and Archie maps it to a pairing that already works. ## Font feel Four presets, each previewed with a live "Aa" sample: * **Editorial** — serif or serif-adjacent headings, built for long-form reading and a considered, print-influenced tone. * **Modern** — clean geometric or humanist sans, the default for most SaaS products. * **Expressive** — more distinctive display type, for brands that want typography to carry personality. * **Technical** — precise, often monospace-influenced, suited to developer tools and data-dense products. ![Read panel showing the four Font feel presets, each previewed with a live Aa sample](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/read-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=4f9c6cdbdd8bfd316f71c29f919f50c2) Picking a font feel resets any custom font families you set below it — see [Advanced editing](#advanced-editing). ## Advanced editing Click **Edit font families & type scale** to set specific font families and the type scale directly, instead of using a Font feel preset. Use this when you have a specific typeface in mind, or need a size relationship the presets do not cover. ## FAQ > **Can I use a custom font that isn't in the presets?** > > Yes — use **Edit font families & type scale** to set any font family directly. > **Does Read affect font size in Feel's density settings too?** > > They're independent. Read sets font family and the type scale; [Feel](/docs/features/frontend/prototype/design/feel) sets spacing and padding around that type. # Theme Source: https://archie.com/docs/frontend/prototype/design/theme Pick a complete, pre-built visual direction for your app, or generate new alternatives — each one comes with a written rationale. Theme is the fastest way to set every design value at once. Instead of picking a color, then a font, then a corner radius one at a time, you choose a complete look that Archie has already put together and validated. ## Choosing a theme Each theme option is a full package — palette, typography, motion, and density — bundled into one card. Click a theme to apply it. The generated screens in [Screens](/docs/features/frontend/prototype/screens/overview) update immediately. ![Theme picker showing three complete theme options with palette swatches and written rationale](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/theme-picker.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=ced147adeaa79187c46dcd60127e5744) Every theme ships with a short written rationale explaining the reasoning behind it: why that palette, why that type pairing, and how it positions your app relative to competitors in the same space. Read it before picking — it tells you what the theme is optimizing for, not just what it looks like. ![A different theme selected — Launch Studio's violet palette and editorial serif heading replace the previous theme across the same screen](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/theme-picker-launch-studio.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=145df1686328c2d1913f212a39da1d06) ## Generating alternatives Click **New Theme** to generate additional options beyond the ones Archie starts you with. Each new theme explores a different point in the design space — for example, one alternative might keep the same brand positioning but swap a light, editorial look for a dark, high-contrast one. ## Archiving a theme Themes you are not considering can be archived to keep the list focused. Archiving does not delete anything you have already applied — it just removes the option from the picker. ## After you pick a theme Selecting a theme sets the starting values for every other section in this part of Prototype: * [Branding](/docs/features/frontend/prototype/design/branding) * [Feel](/docs/features/frontend/prototype/design/feel) * [Read](/docs/features/frontend/prototype/design/read) * [Color](/docs/features/frontend/prototype/design/color) * [Polish](/docs/features/frontend/prototype/design/polish) Fine-tune any of them afterward — picking a theme is a starting point, not a lock-in. Changes you make in those sections do not create a new theme entry; they adjust the underlying values directly. ## FAQ > **What happens to my customizations if I switch themes?** > > Switching to a different theme resets those values to the new theme's defaults, overwriting manual adjustments you made in Branding, Feel, Read, Color, or Polish. Pick a theme close to what you want before fine-tuning. > **How many theme options do I get to start?** > > Archie generates a small starting set tailored to your plan's positioning. Generate more with **New Theme** whenever you want to explore a different direction. # Editing a screen Source: https://archie.com/docs/frontend/prototype/screens/editing Click into any element to change it directly with a full property inspector, or describe the change in chat. In [Screen view](/docs/features/frontend/prototype/screens/views#screen), the toolbar at the bottom of the canvas switches how clicks on the preview behave: * **Navigate** — click through the prototype's links as a user would, to move between screens. * **Select / Inspect** — click an element to select it and edit it directly. * **Arrange** — reposition elements on the canvas. * **Comment** — leave feedback pinned to a specific spot on the screen; see [Comments](/docs/features/frontend/prototype/comments). ## Selecting an element Switch to **Select / Inspect**, then click any element in the preview — a heading, a button, a card. A small floating toolbar appears on the element itself, showing: * Its tag (for example, `h1`) * **Select parent element** — move selection up to the containing element * **Remove element** — delete it * **Open prompt mode** — describe a change for this specific element in natural language; see [Asking Archie for a change](#asking-archie-for-a-change) below At the same time, the left panel switches from **Components** (the [Theme and Customize](/docs/features/frontend/prototype/design/theme) panel) to an **Editor** tab, showing the full property inspector for whatever you selected. ## The Element Inspector The inspector is scoped to the selected element and typically includes: * **Text Content** — the element's text, editable directly, for elements that hold text * **Text** — Font, Size, Weight, and Line height * **Style** — bold, italic, underline, and text alignment * **Color** — the element's text or icon color * **Appearance** — Background and Border Color * **Corners** — border radius * **Layout** — horizontal and vertical alignment * **Position** Not every element shows every field — a text-only element won't offer background or border controls, for example. Changes apply immediately to the preview. ![Element Inspector open for a selected heading, showing text content, color, appearance, and layout controls](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/screens/element-inspector.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=c9aeb685c8e56c1fe0ea02cbd37013ca) ## Asking Archie for a change You do not have to use the inspector for every change. For anything easier to describe than to dial in by hand — swapping an image, rewording a headline, "make this card feel less crowded" — ask Archie in natural language instead. ### Prompt mode Click the sparkle icon on a selected element's floating toolbar to open **prompt mode**: a small box anchored right on the canvas, next to the element. It shows the element's tag (for example, `Img`) so there's no ambiguity about what you're pointing at, plus a text field to describe the change and a back arrow to return to the inspector. ![Prompt mode open on a selected image, asking Archie to swap it for the one already used on the Dashboard page](https://mintcdn.com/archie-e998dbf6/uKV7VZ3hAIAhG9GA/features/design/screens/prompt-mode.png?fit=max\&auto=format\&n=uKV7VZ3hAIAhG9GA\&q=85\&s=fe6bd0c1561c859bf33073bd991bd174) Submitting sends the request to chat as a message for Archie to process — it automatically includes which element you selected and which screen you're on, so the message carries everything Archie needs to update the component to your preference without you having to spell out the context yourself. ### The chat panel You can also work from the main chat panel on the left. With an element selected, it shows a reference chip for it (for example, `@h1`) — type an instruction there and it applies to that element specifically, the same way [Talking to Archie](/docs/features/frontend/talking-to-archie) works in the frontend. Without a selection, chat instructions apply to the screen or app more broadly. Use the inspector for precise, mechanical changes (an exact color, a specific font weight). Use prompt mode or the chat panel for changes that are easier to describe than to dial in by hand. For leaving feedback instead of making a change directly, see [Comments](/docs/features/frontend/prototype/comments). ## Screen-wide vs. global changes Edits made here — through the inspector or through chat with an element selected — apply to that one screen. For a change that should apply everywhere (a color, a font, a corner radius across the whole app), use [Theme and Customize](/docs/features/frontend/prototype/design/theme) instead: it updates the underlying design values, and every screen picks up the change at once. ## FAQ > **Should I set my theme and design values before or after editing individual screens?** > > Before. Theme, Color, Feel, and Polish apply everywhere at once — deciding those first means less rework than fixing one-off edits after a global change lands. > **What does 'Open prompt mode' do differently from typing in the chat panel?** > > Both send an instruction to Archie. Opening prompt mode from a selected element's toolbar guarantees the instruction is scoped to that exact element, which is useful when a screen has several similar-looking elements. # Navigation Source: https://archie.com/docs/frontend/prototype/screens/navigation Switch between screens, check devices and dark mode, track generation progress, and filter by user type. Alongside the [view modes](/docs/features/frontend/prototype/screens/views), the toolbar at the top of Screens gives you a set of controls that apply no matter which view you're in. ## Switching screens The page selector at the top of Prototype shows the current screen's name. Click it to search or browse the full list of **Available Routes** and jump to any screen. ![Page selector dropdown open, showing a searchable list of available routes](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/screens/page-selector.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=df1c278238566cc310ba9a90aa63ab7d) ## Checking devices and dark mode In [Screen view](/docs/features/frontend/prototype/screens/views#screen), use the device toggles — **Desktop**, **Tablet**, **Mobile** — to check the current screen at different viewport widths, and the light/dark toggle to preview it in either mode. ![Screen view of the User Dashboard with the dark mode preview toggled on](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/screens/user-dashboard-dark.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=0f52e97c16e4b5432eaf5903fe7d3aa6) ## Generation progress The **Generated: X/Y** readout near the top of the page tracks how many of your plan's screens have been generated so far. Screens still pending show up flagged in [Sitemap](/docs/features/frontend/prototype/screens/views#sitemap). ## Previewing by user type The **User Type** filter narrows every view to the screens relevant to a single user type from your plan — useful for reviewing an admin's experience separately from an end user's, without the rest of the app in the way. ## FAQ > **Does the User Type filter change what a user can actually access?** > > No — it only changes what you see while working in Prototype, so you can review one audience's screens at a time. Actual access control for your app is defined in the plan's user types and enforced by the backend, not by this filter. # Overview Source: https://archie.com/docs/frontend/prototype/screens/overview Every route your plan describes becomes a generated screen. Browse them, check how your theme lands on real content, and edit them directly. Every route your [plan](/docs/features/plan/overview) describes gets a generated screen. Screens is where you browse all of them, check how your [theme and design values](/docs/features/frontend/prototype/design/theme) land on real content, and make direct edits before you build. ## What's in this section > **[Views](/docs/features/frontend/prototype/screens/views)** > > Screen, Grid, Pages, and Sitemap — four ways to look at your generated screens. > **[Navigation](/docs/features/frontend/prototype/screens/navigation)** > > Switch screens, check devices and dark mode, track generation progress, filter by user type. > **[Editing a screen](/docs/features/frontend/prototype/screens/editing)** > > Click into any element to change it directly, or describe the change in chat. ## Where screens come from Screens are generated automatically from your plan — one per route. Beyond the modules you defined yourself, Archie also generates screens for the **core services** it builds in automatically, like User Management, Payment Processing, and Notifications. See [Views → Sitemap](/docs/features/frontend/prototype/screens/views#sitemap) to see the full structure at once. ## FAQ > **A screen shows 'Needs regeneration' — what does that mean?** > > The screen's content is out of sync with a change elsewhere — usually a plan edit made after that screen was generated. Use **Regenerate Prototype** to bring it back in sync. > **Do changes to a screen affect the real app, or just the prototype?** > > Screens work on the generated prototype. When you build, the state of your screens becomes the starting point for the actual [frontend](/docs/features/frontend/overview) — you can keep adjusting afterward with the visual editor, chat, or code. # Views Source: https://archie.com/docs/frontend/prototype/screens/views Screen, Grid, Pages, and Sitemap — four ways to look at your generated screens. Four view modes, switchable from the toolbar at the top of [Screens](/docs/features/frontend/prototype/screens/overview): > **Screen** > > A single screen at full size, live in an interactive preview. > **Grid** > > Every screen as a thumbnail, grouped by module. > **Pages** > > Every screen listed for quick scanning. > **Sitemap** > > The full site structure as a node graph, module by module. ## Screen The default view. Shows the current screen full size, in an interactive preview you can click into — see [Navigation](/docs/features/frontend/prototype/screens/navigation) for the device and dark-mode controls, and [Editing a screen](/docs/features/frontend/prototype/screens/editing) for how to change what's on it. ## Grid and Pages Both give you an overview of every generated screen at once instead of one at a time — useful for a quick visual pass across the whole app after a design change. Grid and Pages both support: * **Group by module** — cluster screens by the module they belong to, color-coded * **Search** — find a screen by name * **All modules** / **All surfaces** filters — narrow the set shown ![Grid view showing generated screens as thumbnails](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/screens/grid-view.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=8bb52fdcfb13356b4137b6079c2e41d9) Reach for whichever layout makes it easier to scan the specific thing you're checking — Grid's module grouping, or Pages' straight list. ## Sitemap Shows your app's structure as a node graph: a root node fanning out to each module, and each module fanning out to its screens. Beyond your plan's modules, Sitemap also surfaces the **core services** Archie generates screens for automatically — things like User Management, Payment Processing, and Notifications. ![Sitemap in Map mode, showing modules fanning out to their screens as a node graph](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/design/screens/sitemap-view.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=2b5d61969f7d0a7b1727df1c57002b53) Each screen node shows: * Its name and route * An access badge — **Public**, **Private**, **Admin**, or **Auth** * Generation status, when relevant (for example, **Pending** or **Needs regeneration**) * How many jobs it took to generate Toggle between **Map** (the node graph, with a minimap and zoom controls) and **Outline** (a flat list of the same structure). Sitemap supports the same search and module/surface filters as Grid and Pages. ### The screen drawer Click any screen node to open a drawer with everything Archie knows about that screen. It's the closest thing to a spec sheet for a single screen: * **Preview and route** — a thumbnail of the current screen, its route path (with a copy button), and a link to open it directly * **Regenerate screen** — rebuilds the screen but keeps its written spec * **Re-imagine layout** — rewrites the layout from instructions you add in the field above it, replacing the current design; only enabled once you've added instructions * **Screen Metadata** — screen title, surface, user capability, navigation group, and navigation depth * **Permissions** — view-only access and manage access, each showing which roles are granted ![Top of the screen drawer for Admin Dashboard, showing the preview, regenerate actions, and Screen Metadata](https://mintcdn.com/archie-e998dbf6/uKV7VZ3hAIAhG9GA/features/design/screens/sitemap-drawer-header.png?fit=max\&auto=format\&n=uKV7VZ3hAIAhG9GA\&q=85\&s=2cbb2db2cfa010731a75767a77e25b61) Scroll further and the drawer gets more technical: * **Purpose** — a short paragraph on why the screen exists * **Key Jobs** — the specific tasks the screen is built to support, each with a one-line summary and a longer description * **Data Requirements** — every data entity the screen depends on, listed with its fields (for example, `Subscription` — `id, status, planId, createdAt, canceledAt`) * **Layout Specification** — a detailed written description of the screen's composition — regions, hierarchy, and what each part is for ![Bottom of the screen drawer, showing Data Requirements per entity and the full Layout Specification](https://mintcdn.com/archie-e998dbf6/uKV7VZ3hAIAhG9GA/features/design/screens/sitemap-drawer-spec.png?fit=max\&auto=format\&n=uKV7VZ3hAIAhG9GA\&q=85\&s=e0846eacfe2a23ab6c6bed574ad0fc16) Metadata, Permissions, and the Layout Specification each have an **Edit** link — this is where you correct or refine the spec Archie generated, not just view it. ## FAQ > **Why does Sitemap show screens I didn't add to my plan?** > > Core services like User Management, Payment Processing, and Notifications generate their own screens (login, checkout, the notification inbox, and so on) automatically, alongside the modules you defined yourself. > **What's the difference between Regenerate screen and Re-imagine layout?** > > Regenerate screen rebuilds the screen from its existing spec — use it after a design value or content change elsewhere. Re-imagine layout replaces the layout itself, driven by instructions you write in the drawer, for when the structure needs to change, not just the values feeding it. > **Can I edit the Layout Specification directly instead of regenerating from instructions?** > > Yes. Click **Edit** next to Layout Specification to change the written spec directly, the same way you can edit Screen Metadata and Permissions. # Routing Source: https://archie.com/docs/frontend/routing How routing works in your Archie app — pages, dynamic routes, navigation, and route protection. Routing in your Archie app maps URLs to pages. The default stack uses Next.js App Router; other stacks use their respective conventions. The structure is generated from your plan's modules and your specifications' screens. ## Where routes live Routes are defined by file structure. In a Next.js project: ``` app/ page.tsx → / orders/ page.tsx → /orders [orderId]/ page.tsx → /orders/:orderId settings/ layout.tsx → wraps all /settings/* pages page.tsx → /settings profile/ page.tsx → /settings/profile ``` Each `page.tsx` is a route. Each `layout.tsx` wraps its sibling pages and any descendants. ## Generated routes When you build, Archie generates a route for every feature's screens. A plan module called "Orders" with screens "list", "detail", and "create" produces: * `/orders` — list * `/orders/[orderId]` — detail * `/orders/new` — create The route paths can be customized in the spec. ## Dynamic routes Routes with parameters use bracketed segments: `[orderId]`, `[userId]`. Inside the page component, the parameter is available via the route hook (Next.js: `useParams()` or the `params` prop). For nested resources (an order's items), nest the route file structure: `app/orders/[orderId]/items/[itemId]/page.tsx`. ## Layouts Layouts wrap a group of routes with shared structure: a sidebar, a header, an authentication guard. The default layout in `app/layout.tsx` wraps the whole app. Per-section layouts wrap subsections. To add a layout: create `layout.tsx` in the relevant directory. Anything you render inside it (the sidebar, the header) appears on every page within that directory. ## Navigation Internal navigation uses the framework's link component (Next.js: ``). The chat and visual editor handle wiring up navigation when you add a button that should go somewhere. For programmatic navigation (after a form submit, after a delete confirmation), use the framework's router hook (Next.js: `useRouter()`). ## Route protection Pages can require authentication, a specific role, or a specific permission. Protection is configured in two places: * **Spec-level** — the spec marks which user types can access each screen. Generated code enforces this. * **Code-level** — for fine-grained checks, edit the page or layout and call the auth utilities (`useAuth`, `requireRole`). Protected routes redirect unauthenticated users to the sign-in page. See [Connecting to the backend](/docs/features/frontend/connecting-to-the-backend) for the auth flow. ## Catch-all and 404 The app has a built-in 404 page in `app/not-found.tsx`. To customize it, edit that file or describe the change in the chat. For catch-all routes (rare), use `[[...slug]]` segments. Useful for documentation sites, marketing pages, and CMS-driven content. ## Sitemap and SEO For public-facing apps, generated routes include `` and `<meta>` tags from the spec's screen-level metadata. To add OG tags, structured data, or canonical URLs, edit the page's metadata export. The framework handles the rest. A sitemap.xml generates automatically, listing all public routes. Customize it by editing `app/sitemap.ts`. # Talking to Archie Source: https://archie.com/docs/frontend/talking-to-archie Use the chat to edit your Archie app's code in plain language — the primary editing modality after the build. The chat is the primary way to edit your app after the build. You describe a change. Archie reads the relevant files, plans the edit, applies it across components, and shows you the diff. ## When to use the chat The chat is the default. Reach for it first when you want to make a change. Cases where the chat shines: * **Cross-file changes** — "Add a search field to the orders page and wire it to filter the list" (touches the page, the form component, and the data hook) * **Refactors** — "Pull the order summary into a shared component and use it on both the cart and the checkout pages" * **Pattern matching** — "Match the styling of the dashboard cards on the settings page" * **Logic** — "When the cart is empty, show the empty state instead of the items grid" * **Data shape changes** — "Show only the user's own orders, not all orders" For small visual tweaks, use the [Visual editor](/docs/features/frontend/visual-editor). For structural changes (adding a module, changing a user type), edit the [Plan](/docs/features/plan/overview). ## How chat edits work When you describe a change, Archie: 1. Reads the project structure and finds files relevant to your change 2. Plans the edit — files to touch, code to add, code to modify 3. Generates the changes 4. Shows a diff for review 5. Applies the changes when you accept If the change is large, Archie may break it into steps and let you confirm each one. For ambiguous requests, the chat asks a clarifying question before generating code. ## Writing good chat prompts The chat works well with concrete, scoped requests. Compare: * "Make it look better" → vague, hard to interpret * "Add more spacing between the order rows in the orders table" → concrete, scoped Mention the surface (page, component, modal) when you can. Reference specific elements by what they show ("the cancel button on the order detail page"). If you have a precedent, point to it ("match the styling of the user profile page"). ## Multi-turn conversations The chat keeps context across turns. Once you have steered to the right area, follow-up turns can be brief: "Now do the same for the products list" or "Make the spacing tighter." When the context drifts (you switch features), start a new chat thread to keep things clean. Old threads remain in your project history. ## What the chat cannot do * **Change the plan** — restructuring (adding modules, changing user types) lives in the [Plan](/docs/features/plan/overview), not the chat. * **Run code or migrations** — the chat generates code; running it (deploys, migrations) happens through dedicated surfaces. * **Access third-party services not in your plan** — to add a new integration, edit the plan and rebuild. See [Editing integrations](/docs/features/plan/editing-integrations). ## Cost Chat edits consume AI credits weighted by the size and complexity of the change. The chat shows the estimated cost before generating. Manual edits in the IDE or visual editor are free — the chat is for AI-assisted edits specifically. ## Chat and other surfaces together The chat coexists with the [Visual editor](/docs/features/frontend/visual-editor), the IDE, and the [Backend](/docs/features/backend/overview) surfaces. You can be in the chat and click into the IDE to read the code Archie just generated. You can be in the visual editor and ask the chat to apply a change you cannot express through clicks. The modalities reinforce each other. # Theming Source: https://archie.com/docs/frontend/theming Configure branding, colors, fonts, and dark mode for your Archie app — applied across every component and page. Theming controls the visual identity of your app: colors, typography, spacing, border radii, and dark mode. Theme values are defined once and applied across every component automatically. ## Where theme lives Your theme is defined in `app/theme.ts` (or your stack's equivalent). It is a single source of truth: components, pages, and generated UI all reference theme tokens rather than hard-coded values. The default theme uses Tailwind's design tokens with values from your plan's brand section. ## What you can configure ### Colors Colors are defined as semantic tokens, not raw hex values: * **Brand** — primary, secondary, accent * **Surface** — background, surface, surface-elevated * **Content** — primary, secondary, tertiary, inverse * **Status** — success, warning, error, info Each token has a light-mode and dark-mode value. Update one token, the change cascades. ### Typography * Font families — primary, monospace * Font sizes — xs through 4xl, with consistent line heights * Font weights — normal, medium, semibold, bold ### Spacing and sizing * Spacing scale — 0 through 24, in consistent increments * Border radii — none, sm, md, lg, full * Shadows — none, sm, md, lg ## How to update the theme Three ways: * **[Visual editor](/docs/features/frontend/visual-editor)** — open the theme panel, change colors and typography with a UI. Useful for visual tuning. * **[Talking to Archie](/docs/features/frontend/talking-to-archie)** — describe a change ("brand color to #5C7CFA, primary font to Inter"). * **IDE** — edit `app/theme.ts` directly. Changes apply live to the preview. Saved theme values travel with the project to GitHub. ## Dark mode Dark mode is on by default. Each color token has a light and dark value. Users toggle their preference; the app responds. To customize dark mode: * Edit the dark values for each color token * Test by switching the preview's mode in the visual editor * For component-specific dark mode behavior, use Tailwind's `dark:` modifier in the component code ## Brand assets Logos, favicons, and OG images live in `public/`. The plan's brand section is where you upload them initially; you can swap them anytime by replacing the file in `public/` or through the [Assets](/docs/features/assets/overview) surface. ## When to put a change in theme vs. a component * **In theme** — used in many places, expected to apply consistently (brand color, body font, success state color) * **In a component** — specific to one element type and not reused (a special button variant, a unique card style) Putting one-off styles in theme creates noise. Putting reusable styles in components creates inconsistency. Use the right scope. ## Multi-brand setups If you build multiple white-labeled apps from a single project (Enterprise feature), the theme can be parameterized by tenant. Each tenant's theme overrides the base. See [Enterprise overview](/docs/introduction/enterprise/overview) for the agency capabilities. # Visual editor Source: https://archie.com/docs/frontend/visual-editor Edit your Archie app visually with element inspection, point-and-click property editing, and live preview updates. The visual editor is a point-and-click interface for refining your built app. Click an element in the preview, the inspector surfaces its properties, and you edit copy, styling, layout, and behavior directly. ## Inspecting elements A floating toolbar sits at the bottom of the canvas with four modes — **Navigate**, **Select / Inspect**, **Comment**, and **Draw** — plus a drag handle to reposition it and a chevron for more actions. Click **Select / Inspect**, then click any element in the preview to select it. Once an element is selected, the left panel switches from **Components** to **Editor** and shows: * The element's tag and component name (for example, `h1` · `Hero/title`), with a pinned link to its source file and line (`hero.jsx · Line 38`) * Editable **Text Content** * **Text** properties — size, weight * **Style** (bold, italic, underline) and **Alignment** * **Color** * **Appearance** — background and border color * **Layout** — display and positioning If the element belongs to a shared component, a banner above the canvas reminds you: *"Editing a shared component — changes apply everywhere it is used."* ![Visual editor with an element selected, showing the floating toolbar and the Editor panel](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/visual-editor-inspector.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f6362dfa5a629c0bf899758b936b9eb6) ## Editing an element With an element selected, a small floating toolbar appears on the element itself (its tag, **select parent**, **delete**, and a sparkle icon for prompt mode), and you can: * **Edit text** directly in the inspector * **Adjust styling** (color, padding, font size) via property controls * **Replace media** for image and video elements * **Open the source file** for the component that produced this element Changes apply live to the preview. You keep iterating without rebuilding. ## When to use which modality * **Visual editor** — small visual changes, copy edits, swapping images, tweaking spacing * **[Talking to Archie](/docs/features/frontend/talking-to-archie)** — multi-step or cross-file changes, logic adjustments * **[Plan](/docs/features/plan/overview)** — adding modules, changing user types, swapping integrations, restructuring the app If you find yourself making the same visual change in many places, capture it in [theming](/docs/features/frontend/theming) instead — it applies consistently across the app. ## Editing components vs. instances Some changes apply to a single instance of a component (the dashboard page's button), and some apply to the component itself (every button everywhere). The inspector shows which scope you are editing. Switch between **Instance** and **Component** in the inspector header. Editing the component cascades to every place it is used. Useful for design-system-level edits. Editing only the instance is useful when one location needs to differ from the rest. ## Persistence Edits made through the visual editor persist when they apply to component-level state and to instance-specific overrides on routed pages. The IDE shows the same files updated. When you push to GitHub, the edits travel with the code. ## What the visual editor cannot edit * Server-only or background elements (computed in middleware, hydrated late) — not directly inspectable. Edit those in the IDE. * Logic-heavy components where the visual element is generated dynamically — use the chat or IDE. * Backend-side surfaces (database schema, API routes) — those live in the [Backend](/docs/features/backend/overview) console. For these cases, switch to the [chat](/docs/features/frontend/talking-to-archie) or the IDE. # Overview Source: https://archie.com/docs/organizations Organizations let teams collaborate on shared Archie projects with pooled credits, role-based access, and centralized billing. An organization in Archie is a shared workspace where a team of builders collaborates on projects. Organizations have their own plan, billing, members, and integrations — separate from individual user accounts. ## When do I need an organization? You need an organization when more than one person needs to build, edit, or deploy. The Pro plan supports collaboration through invitations, but only Team and Enterprise plans let multiple builders share a workspace, pool AI credits, and assign per-project roles. If you are working solo, your personal account is enough. You can create an organization later and migrate projects in. ## What is in an organization? An organization holds: * **Projects** — applications built with Archie * **Members** — users granted access at the org level * **Roles** — Frontend Builder, Backend Builder, Admin, and custom roles on Enterprise * **Plan and billing** — Team or Enterprise subscription, pooled credits, shared invoices * **Integrations** — GitHub, Slack, and any other org-wide connections ## Organization vs. account vs. project Archie's three scopes: * **Account** — your personal user. See [Managing your account](/docs/introduction/account/managing-your-account). * **Organization** — a team workspace. * **Project** — an individual application. Settings live at the most granular level that makes sense. Personal preferences on your account. Plan and team on the organization. Data model and environments on the project. ## Multiple organizations You can belong to many organizations — for example, your own org for personal projects and your employer's org for work. The org switcher in the top nav changes which projects are visible. Your account preferences stay the same across orgs. ## Creating one Open the org switcher and click **Create organization**. Name the org, choose a plan, invite teammates. Full walkthrough in [Setting up an organization](/docs/introduction/organizations/setting-up-an-organization). ## Agency and white-label Enterprise plans add agency capabilities: per-customer project isolation, customer-specific RAG, white labeling, and custom client handoff workflows. See [Enterprise overview](/docs/introduction/enterprise/overview). ## FAQ > **When do I need an organization vs. an individual account?** > > Use a personal account if you're working solo. Move to an organization when more than one person needs to build, edit, or deploy — Team and Enterprise plans give you a shared workspace, pooled credits, and per-project roles. > **Can I move existing projects from my personal account into an organization?** > > Yes. Open the project, go to settings, and transfer ownership to the organization. Your collaborators keep access; the project's billing moves to the org plan. > **How do credits work in an organization?** > > Team and Enterprise organizations have a shared credit pool. Admins can set per-member credit limits so a single user can't burn the whole budget. Pooled credit usage and per-member breakdowns appear on [Plans and credits](/docs/introduction/getting-started/plans-and-credits). > **Can someone belong to multiple organizations?** > > Yes. Use the org switcher in the top nav to change which projects are visible. Account-level preferences stay the same across orgs. > **What if I'm the only admin and need to leave the org?** > > Promote another member to admin first. Archie blocks removing the only admin to prevent lockout — once a second admin exists, you can step down. # Managing team members Source: https://archie.com/docs/organizations/managing-team-members Invite, remove, and assign roles to members of your Archie organization with per-project permissions. The team members surface in your organization controls who has access, what they can do, and how their AI credit usage is tracked. ## How do I invite a new member? Open **Organization → Members** and click **Invite member**. Provide an email and a role. The invitation email contains a one-click link — when the recipient clicks it, they sign in or sign up and join your org immediately. You can resend or cancel pending invitations from the same surface. ## Roles Archie ships four roles, with custom roles available on Enterprise: * **Admin** — full access. Manage members, billing, integrations, and all projects. * **Frontend Builder** — build, edit, deploy frontend code. Cannot change billing, members, or integrations. * **Backend Builder** — same scope as Frontend Builder, focused on backend modules. * **Viewer** — read-only on assigned projects. Available on Pro plan as collaborator invites. Roles are assigned at the organization level and can be overridden per-project on Team and Enterprise. ## Per-project roles In a Team or Enterprise plan, assign someone Backend Builder org-wide and Frontend Builder on a specific project where they need broader access. Open the project settings and adjust their role for that project only. ## Per-member credit limits On Team and Enterprise, you can cap how many AI credits each member can consume per cycle. Useful when you want to give a junior builder access without risking the pool. Open the member's row and set a credit limit. Pooled credit usage shows on the [Plans and credits page](/docs/introduction/getting-started/plans-and-credits) with a per-member breakdown. ## Removing a member Click **Remove** on the member's row. They lose access to all projects in the organization immediately. Their previous activity (commits, plan edits, comments) remains attributed to them in the activity feed. If they were the last admin, designate another admin first — the system blocks removal of the only admin to prevent lockout. ## Activity and audit The org-level **Activity** feed shows what every member has been doing. Filter by user, project, or action type. Audit retention follows the plan: 90 days on Team, custom on Enterprise. ## Outside collaborators For one-off external collaborators (a contractor, a designer reviewing the prototype), invite them as **Viewer** instead of consuming a paid seat. Viewers do not count against your seat limit and have read-only access. # Organization integrations Source: https://archie.com/docs/organizations/organization-integrations Connect GitHub, Slack, and other organization-wide integrations to your Archie organization for source control and notifications. Organization integrations apply across every project in your org. Configuring them once means each new project inherits the connection automatically. ## Available integrations The most common org-level integrations are GitHub and Slack. Connect them from **Organization → Integrations**. ### GitHub GitHub integration enables two-way code sync between Archie projects and your GitHub repositories. After connecting, every project in the org can push code to a GitHub repo and pull changes back. The connection covers: * Pushing generated code to a repo * Pulling commits made outside Archie * Pull request creation from project changes * Branch-based deployment to environments ### Slack Slack integration sends project notifications (build success, deploy failures, mentions) to a channel of your choosing. Once connected, configure which categories trigger Slack messages from the [notifications page](/docs/introduction/account/notifications). ### Jira (Enterprise) Jira integration lets Archie create issues from build errors and link plan changes to tickets. Available on Enterprise plans only. ## Per-project vs. organization integrations Some integrations live at the org level (GitHub, Slack, Jira) — they configure once and apply org-wide. Others live per-project (Stripe, SendGrid, Twilio) because each project has its own API keys and secrets. Per-project integrations are managed in the project's [Backend Integrations](/docs/features/backend/integrations/overview) surface. ## How do I connect GitHub? Open **Organization → Integrations**, click **GitHub**, then **Connect**. Archie redirects you to GitHub's OAuth flow. Approve the requested scopes, choose which repositories Archie can access (all, or selected), and confirm. The connection persists for the org. After connecting, individual projects can link to specific repositories from their own settings. ## Disconnecting Click **Disconnect** on the integration row. This removes Archie's access to the third-party service for the entire org. In-flight syncs complete. Each project's link to a specific repo or channel breaks, but project history stays intact. ## Custom integrations To connect a service Archie does not ship with, build a custom integration with [Archie Core integrations](/docs/features/backend/integrations/building-custom). Custom integrations live at the project level, not the org level, since they typically use project-specific credentials. # Setting up an organization Source: https://archie.com/docs/organizations/setting-up-an-organization Create an Archie organization, configure billing and plan, and invite your team to collaborate on shared projects. Setting up an organization is a four-step flow: create, configure, invite, and connect integrations. ## 1. Create the organization Open the org switcher in the top-left of any Archie page and click **Create organization**. You provide: * **Name** — visible to members and on invoices. * **Slug** — used in URLs and identifies the org programmatically. Lowercase, hyphens, no spaces. * **Logo** — optional, displayed in the org switcher and member views. The first user to create the organization becomes its initial admin. ## 2. Choose a plan Organizations need at least the Team plan for multi-builder collaboration. From the new org, open **Settings → Billing**: * **Team** — \$30 per seat per month, minimum 5 seats, maximum 25. 400 AI credits per seat per month, pooled. * **Enterprise** — custom pricing, unlimited seats, advanced governance, compliance add-ons. Add a payment method or, on Enterprise, request invoice billing. See [plans and credits](/docs/introduction/getting-started/plans-and-credits) for full breakdowns. ## 3. Invite your team Open **Organization → Members** and click **Invite member**. For each invitee, provide: * **Email** — the invitation goes to this address. * **Role** — Frontend Builder, Backend Builder, Admin, or a custom Enterprise role. * **Optional credit limit** — cap on credits this member can consume per cycle. Members get an email with a one-click sign-in link. They are added to your organization on accept. See [managing team members](/docs/introduction/organizations/managing-team-members) for the full role and permission model. ## 4. Connect integrations Most teams connect at least GitHub (for source control) and Slack (for notifications). Open **Organization → Integrations** to add them. See [organization integrations](/docs/introduction/organizations/organization-integrations) for the full list. ## Migrating personal projects in If you started on a personal account and want to move projects into your new organization, open the project settings and click **Transfer ownership**. Choose the target organization. The project moves over with its history, environments, and integrations intact. Personal credits do not transfer — the project consumes org-pooled credits going forward. ## Removing the organization If you ever need to delete an organization, all members must be removed first, all projects archived or transferred out, and the subscription canceled. The action is irreversible — see [contacting support](/docs/introduction/community-support/contacting-support) for guided deletion. # Overview Source: https://archie.com/docs/plan What a plan is and why it is the unit of editing in Archie. A plan is Archie's structured description of your app. Archie generates one from your idea. You edit it until it matches what you want. Then the build uses it as the spec. Editing the plan is where you spend most of your time. That is by design — the plan is cheap to change (it is structured text), the build is expensive. Steer the plan, then build once. ## What is in a plan A plan has five sections. Each is independently editable: * **Overview** — a high-level summary of the app's purpose * **Modules** — the features and capabilities your app offers * **User types** — the people who use the app and what each can do * **System services** — built-in services like authentication, file storage, and roles * **Integrations** — third-party services your app talks to (Stripe, SendGrid, and so on) You will also see a **Preview section** with sample screens that visualize how the plan translates into UI. ![Plan page with all sections and the Generate menu](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/full-plan-six-sections.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=e85e94315f1b71994746491cbb7a1e08) > [!NOTE] > > The tech stack (frameworks, runtime, database) is chosen and managed by Archie automatically. It is not shown or editable in the plan — the plan stays agnostic to the underlying stack. ## How the plan relates to the rest of Archie The plan is the high-level description of your app. The [specifications](/docs/features/specifications/overview) hold finer-grained detail (system requirements, visual design principles, technical decisions). When you run the build, Archie reads the plan and the specifications together. After the build, the [visual editor](/docs/features/frontend/visual-editor) and [frontend](/docs/features/frontend/overview) surfaces let you make small, direct changes. For larger changes, come back to the plan and rebuild. ## What you will do here * [Generate](/docs/features/plan/generating) a plan from an idea, answering Archie's clarifying questions along the way * [Review](/docs/features/plan/reviewing) your generated plan * Edit each section: [modules](/docs/features/plan/editing-modules), [user types](/docs/features/plan/editing-user-types), [services](/docs/features/plan/editing-services), [integrations](/docs/features/plan/editing-integrations) * [Iterate](/docs/features/plan/iterating) using the plan chat * Decide [when to run the build](/docs/features/plan/when-to-build) ## FAQ > **Can I edit the plan after the build?** > > Yes. Edit and run again. The next build will incorporate your changes. > **Can I have more than one plan per project?** > > No. Each project has one plan. To explore alternatives, create a new project. > **What if I do not need a section?** > > Leave it minimal. For example, an internal tool may not need integrations. Archie will not force features into your build. # Generating a plan Source: https://archie.com/docs/plan/generating Submit an idea and let Archie produce your plan. Generating a plan is the first thing that happens when you create a project. This page covers how it works and what to expect. ## Submitting an idea From **New project**, type your idea in the input. There is no prompt template, no required structure, no character minimum. Archie infers what is missing. Examples that work: * "A booking system for a small dental practice." * "A course platform where teachers upload lessons and students pay per course. Stripe for payments. Email notifications when a new lesson is published." * A few paragraphs describing your app in detail. Click submit. Archie creates the project and starts generating. ![New project input with an example idea filled in](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/new-project-input.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=48614079a297ca9af7d343fe54042284) ## Improving your prompt Before you submit, use the AI Assistant to refine what you typed. Click the wand icon next to the input to open **Improve**, **Expand**, and **Condense**. ![AI Assistant menu with Improve, Expand, and Condense options](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ai-assistant-menu.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f7e131c6045ff5c7dc30d05fe5f2f079) * **Improve** — Rewrites your prompt to be more accurate and specific. * **Expand** — Adds detail: user roles, features, screens, and data models inferred from your idea. * **Condense** — Tightens a long or detailed prompt into a more direct version while keeping the key requirements. For example, typing "A booking app for a hair salon" and clicking **Improve** turns it into a structured brief with user roles, features, and data models: ![Improve result showing a structured prompt with user roles and features](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ai-assistant-improve-result.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=6560646ab85c162e83b357c7ad915a84) **Expand** takes the same short prompt further, adding more complete detail across every section: ![Expand result showing a more detailed and complete prompt](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ai-assistant-expand-result.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=391ed8dd0cfb08f28c627ed9b3326719) **Condense** works the other way: paste in a long, detailed description and it distills it down to the essentials: ![Condense result showing a shortened version of a long, detailed prompt](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/ai-assistant-condense-result.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=3ccc4556c0f4c3d16b67ad444b3ed185) Each option opens a preview with **Insert** and **Retry** buttons. Click **Insert** to replace your prompt with the generated version, or **Retry** to generate again. You can also type additional instructions below the preview to steer the result, or run another Improve, Expand, or Condense pass directly from the preview panel. ## Answering clarifying questions Before drafting the plan, Archie may show **A few quick questions** — a short set of decisions it would otherwise have to guess. Each question explains why it matters and takes one of three forms: single-choice, multi-select, or a short free-text answer. ![A few quick questions screen with single-choice, multi-select, and free-text questions](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/clarifying-questions-prompt.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=022e305b4846fcb370afadfbb877c6c3) You can answer some questions and leave others blank, then click **Continue**, or click **Skip — decide for me** to let Archie make every call. Skipped questions aren't left silent — Archie states its assumption directly in the plan instead. ## What happens during generation Generation usually completes in 30–60 seconds. While it runs: * The plan page shows a waiting state with status updates * The connection reconnects automatically if you close the tab and come back When generation finishes, the plan page populates with all its sections (Overview, Modules, User types, System services, Integrations). Archie also picks a tech stack for the build behind the scenes — it is not part of the plan and is not shown or editable there. ## What you get The first plan is a starting point, not a final answer. Expect: * **Modules** that capture the major features in your idea * **User types** inferred from the description (admin, customer, and so on) * **System services** chosen based on what your app needs (auth, file storage) * **Integrations** matched to features in your idea (Stripe if you mentioned payments) If the first pass is wrong in places, that is normal. Editing is the next step. ## Reviewing clarifying questions later Once the plan exists, a **Clarifying Questions** / **Plan** toggle appears at the top of the plan page. Switch to **Clarifying Questions** to see the same questions again — each one shows either the answer you picked or an **Archie decided** badge with the assumption Archie made for a skipped question. ![Clarifying Questions view showing Archie's assumptions for skipped questions](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/clarifying-questions-decided.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f2ac142c3f2e28280cc204a1ed6985f5) This view is read-only. To change an answer, edit the relevant plan section directly or ask the [plan chat](/docs/features/plan/iterating) to apply the change. ## Re-generating You can regenerate a plan section if it is significantly off. Use the regenerate dropdown in the section header. Regeneration costs credits — usually a small amount per section. For smaller changes, edit directly or use the [plan chat](/docs/features/plan/iterating). ## FAQ > **What if generation fails?** > > The plan page shows an error and lets you retry. If retries also fail, contact [support](/docs/introduction/community-support/contacting-support). > **Does generation use credits?** > > Initial plan generation is included. Regenerating individual sections may use a small number of credits. > **Can I import an existing app description?** > > Paste it as your idea. Archie reads structured notes (bulleted lists, headings) the same as flowing prose. > **What if I skip all the clarifying questions?** > > Archie makes its best call on every one and states each assumption in the plan instead of leaving it silent. Review the **Clarifying Questions** view afterward to see what it decided. # Editing integrations Source: https://archie.com/docs/plan/integrations Add third-party services to your plan. Integrations are third-party services your app talks to — for example, Stripe for payments, SendGrid for email, Twilio for SMS. This page covers how to add and edit them in the plan. ## What an integration looks like Each integration in the plan has a name (the service), a description of what it does in your app, and any modules that depend on it. Adding an integration in the plan signals to the generator that it should wire up the SDK calls, configuration, and webhook routes for that service. ![Integrations panel showing added integrations with Remove, and available integrations to Add](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/integrations-browse-all.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=70d413668b7a8758e63e80aece21ccad) ## Adding an integration 1. Open the **Integrations** card. 2. Click **Add integration**. 3. Pick a service from the list, or describe a custom one. 4. Add a one-line description of what it does in your app ("Stripe handles checkout for course purchases"). 5. Save. If your idea mentioned a specific service ("send confirmation emails via Resend"), Archie usually adds it during initial generation. Use this flow to add ones that were missed or to swap one service for another. ## Removing an integration Open the integration and delete. Make sure the modules that depended on it are updated — for example, if you remove Stripe, the checkout module needs a new payment story. ## Common integrations See the [Integrations section](/docs/features/backend/integrations/overview) for the integrations Archie ships with first-class support: * [Stripe](/docs/features/backend/integrations/overview) — payments * [SendGrid](/docs/features/backend/integrations/overview) — transactional email * [Twilio](/docs/features/backend/integrations/overview) — SMS and voice * [Slack](/docs/features/backend/integrations/overview) — notifications * [Shopify](/docs/features/backend/integrations/overview) — commerce You can also add [custom integrations](/docs/features/backend/integrations/building-custom) for services Archie does not ship out of the box. ## Configuration vs. selection Adding an integration in the plan selects it. **Configuring** it (API keys, webhook secrets, account-specific settings) happens in App settings after the build. Do not worry about credentials at the plan stage. ## FAQ > **Does adding an integration require a paid account on that service?** > > You will need an account on each integrated service to use it in production. Most have free tiers for development. > **Can I swap one integration for another later?** > > Yes. Edit the plan, swap the integration, and rebuild. Existing custom code may need updating. > **What if my service is not on the list?** > > Add it as a custom integration. Describe its purpose; the generator will scaffold the request and webhook plumbing. # Iterating on the plan Source: https://archie.com/docs/plan/iterating Use the plan chat to refine your plan in conversation. The plan chat is a streaming conversation tied to your plan. Use it for changes that span multiple sections, or for changes you can describe but do not want to manually click through. This page covers how to iterate effectively. ## Opening the plan chat The plan chat is available from the plan page. Open it from the side panel. The chat scrolls beside the plan, so you can watch sections update as you talk to it. ![Plan chat open in the side panel](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/plan-chat-side-panel.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=94eb4ccdf31b5ac25d47dcdb2db395ff) ## What it does well * **Multi-section changes** — "drop the messaging module, simplify the user types to just admin and customer, switch from Stripe to Lemon Squeezy." * **Refinements** — "the checkout module should support guest checkout." * **Asking questions** — "what does the admin user type include right now?" * **Trying alternatives** — "show me what this would look like with a marketplace structure instead of a single-tenant app." ## How to phrase changes Describe what you want, not what to do step-by-step. The chat understands the plan structure and will edit the right sections. Examples: * Good: "Make this an internal tool — drop the public marketing pages." * Good: "Add a billing dashboard for admins." * Avoid: "Open the modules card. Click the Marketing module. Click delete." (verbose; not necessary) ## Conversation history The chat keeps a history of your conversation. You can scroll back, pick up where you left off, or start a new conversation thread. ## When direct edit is better If you are doing a small, surgical change (rename a module, tweak a description), just edit the section directly. The chat is for changes that touch several places at once. ## FAQ > **Does the chat run a build?** > > No. The chat edits the plan. Generation is explicit — you trigger Prototype, Frontend, or Backend generation from the [Generate menu](/docs/features/plan/when-to-build). > **Will the chat lose my place if I refresh?** > > No. The chat reconnects automatically. Conversation history persists across reloads. > **What if the chat's change is wrong?** > > Tell it. The chat is conversational — "no, keep the messaging module, just simplify it" is fine. # Editing modules Source: https://archie.com/docs/plan/modules Add, remove, and refine the modules in your plan. Modules are the major features of your app — for example, "user accounts," "course catalog," "checkout," "admin dashboard." This page covers how to edit them. ## What a module looks like Each module in the plan has a name, a short description, and a connection to the user types and services it uses. Modules are the building blocks the generator turns into pages, components, and APIs. ![Module entry with name, description, and supported user types](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/modules-card-entries.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=28b8d9ae0a64570286545635a0867165) ## Adding a module 1. Open the **Modules** card on the plan page. 2. Click **Add module**. 3. Type a name and a short description. 4. Save. ![Add module form with name, icon, and description](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/adding-a-module.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=55cdc1185c0908c27d13d6bb7181a7bf) The new module is wired into the plan immediately. The generator will pick it up on the next build. ## Removing a module Click the module to open it, then delete. Removing a module also removes references to it from related sections — for example, if a user type's permissions referenced the module. ![Module menu with Remove option](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/removing-a-module.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f0992b09313196637139377cdc047df9) ## Refining a module You can refine a module two ways: * **Direct edit** — open the module, change its name or description, save. Best for small changes. * **Plan chat** — describe what you want ("the checkout module should support guest checkout, not just logged-in users"). The chat updates the module in place. Best for substantive changes. ## Common patterns * **Split** a vague module into two: "Account" → "User profile" + "Account settings." * **Merge** two related modules: "Notifications" + "Email preferences" → "Notifications." * **Rename** for clarity: "Admin stuff" → "Admin dashboard." * **Drop** anything you do not actually need yet — building it later is easier than removing it later. ## FAQ > **How many modules should an app have?** > > Most apps have between three and twelve. Anything beyond that suggests modules you should merge or features you can defer to a later build. > **Will edits propagate to user types?** > > Edits do not auto-propagate. If you remove a module, check the related user types and update their permissions. # Reviewing the plan Source: https://archie.com/docs/plan/reviewing Read your generated plan and identify what to refine. This page covers how to read your generated plan and what to look for. Spending 10 minutes reviewing before you build saves an hour later. ## Read top to bottom Start at the top of the plan page. Each section is a card you can read independently: 1. **Overview** — does the one-paragraph summary match what you intended? 2. **Modules** — are the features right? Anything missing or extra? 3. **User types** — does each user type's description match the role you imagined? 4. **System services** — are the chosen services (auth, storage) the ones you want? 5. **Integrations** — are the third-party services correct? You will also see a **Preview section** that renders sample screens. The preview is a quick visual check — if it looks like a different app, the plan probably is. ## What to look for * **Missing features.** Things you said in your prompt that did not make it into modules. Add them. * **Hallucinated features.** Modules Archie added that you did not ask for. Remove them. * **Wrong user types.** A "guest" type when your app is internal-only. Edit or remove. * **Wrong integrations.** SendGrid when you wanted Resend. Replace. ## When to edit vs. iterate vs. regenerate * **Direct edit** — small text changes (rename a module, tweak a description) are fastest as direct edits. * **Plan chat** — multi-step changes ("drop the messaging module and add billing") are easier in the [plan chat](/docs/features/plan/iterating). * **Regenerate** — if a whole section is off the mark, regenerate that section from the section header. Use this sparingly. ![Modules section on the plan page](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/full-plan-six-sections.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=e85e94315f1b71994746491cbb7a1e08) ## FAQ > **Should I review every section before building?** > > Yes. The build takes the whole plan as input, so an unread section becomes guesswork in the output. > **How long should review take?** > > For a first project, 10–20 minutes. Once you are used to the format, five. > **What if I disagree with a section but cannot articulate why?** > > Open the plan chat and describe what is off. Archie will ask follow-up questions and propose changes. # Editing services Source: https://archie.com/docs/plan/services Configure built-in services like authentication, file storage, and roles. System services are built-in capabilities your app uses — authentication, file storage, role management, email verification. This page covers how to edit them. ## What is available Archie ships with these system services: * **Authentication providers** — API key, AWS Cognito, Google OAuth, OAuth2, Apache Shiro * **File storage** — Azure Blob Storage, Filestack, Google Cloud Storage * **Roles** — fine-grained role definitions tied to user types * **Email verification** — verify user emails on signup The system services card on the plan shows which services are selected and their key configuration. ![Core services on the plan page](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/core-services-section.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=2f0e6ae9acc0c2fd1c8f6af44f4981c2) ![Core services management panel](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/core-services.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=e2d4ec9c90393b1b48d74e178254fde9) ## Choosing an authentication provider Open the **System services** card and select an auth provider. Each provider has its own configuration: * **API key** — header name and validation URL * **AWS Cognito** — user pool ID, region, identity pool ID * **Google OAuth** — client ID, redirect URI, hosted domain (optional), scopes * **OAuth2** — authorization URL, token URL, user info URL, scopes * **Apache Shiro** — realm class, authentication strategy, session timeouts Pick the one that matches your existing identity infrastructure. If you do not have one, the default provider is the simplest path. ## Choosing a file storage provider For apps that handle uploads, pick a storage backend: * **Azure Blob Storage** — container name, account name, access key * **Filestack** — bucket name and Filestack credentials * **Google Cloud Storage** — bucket name, GCP project ID, service credentials JSON Configuration happens after the build in [App settings → File storage](/docs/features/backend/app-services/file-manager). The plan just sets the backend choice. ## Adding or removing services Some services are required (authentication). Others (file storage, email verification) are optional. Toggle them on or off from the System services card. ## FAQ > **Can I switch auth providers after the build?** > > Yes, but it is nontrivial — you will need to migrate existing user records. Choose the right provider during plan editing if you can. > **Do I need credentials before building?** > > No. The plan just specifies which provider you will use. Add credentials in [App settings](/docs/features/backend/app-services/authentication-providers) after the build. > **Can I bring my own auth?** > > Yes. The OAuth2 provider lets you integrate any standards-compliant identity provider. # Editing user types Source: https://archie.com/docs/plan/user-types Define who uses your app and what each user type can do. User types are the roles that interact with your app — for example, "customer," "admin," "instructor," "viewer." This page covers how to edit them. ## What a user type defines Each user type has a name, a description, and the modules or actions it can access. The generator uses user types to set up authentication, authorization, and the navigation each role sees. ![User types on the plan page](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/user-types-section.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=647ebd22f8d9ca1330beddb7c7fb9981) ![User type detail with name, description, and module access](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/user-types-permissions.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=946369a02c9086038e221388f9356e5c) ## Adding a user type 1. Open the **User types** card. 2. Click **Add user type**. 3. Provide a name, a short description of who this person is, and what they should be able to do. 4. Save. If the new user type needs a different sign-in flow (for example, an admin invited by email vs. a customer who self-registers), note that in the description. Archie will configure auth accordingly. ## Removing a user type Open the user type and delete. Modules that referenced this user type will lose those permissions — review them after deletion. ## Refining a user type Use the description to spell out: * What this person sees on first login * Which modules they can access * Any actions they explicitly cannot do * Whether they can invite others Specific descriptions produce better builds than vague ones. "Admin" is weaker than "Internal admin who manages courses, can invite instructors, sees usage analytics, can refund payments." ## Common patterns * A B2C app often has **End user** and **Admin**. * A marketplace usually has **Buyer**, **Seller**, **Admin**. * An internal tool may have only **Operator** and **Admin**. * Add a **Guest** user type only if unauthenticated visitors are part of your product. ## FAQ > **What happens if two user types overlap?** > > Archie generates the auth system based on each user type's modules. Overlap is fine, but distinct names and descriptions make the result clearer. > **How do I represent a free vs. paid tier?** > > Either as separate user types ("Free user" and "Paid user") or as a single user type with a billing flag. Either works — the description matters more than the structure. > **Can a user have multiple roles?** > > Yes, if the description says so. Be explicit ("a customer can also be a referrer earning commissions"). # When to run the build Source: https://archie.com/docs/plan/when-to-build Signals that your plan is ready, and how frontend and backend generation split from there. Generating costs time and credits. Generating from a plan that is not ready wastes both. This page covers the signals that you are ready. ## Ready to build You are ready when: * You have read every section of the plan * The Modules section lists everything you need and nothing you do not * User types are named and described concretely * System services and integrations match your intent * You can describe what the built app should do without referring back to the plan If those are true, generate the frontend. ## Not ready to build Hold off if: * You have not read the plan sections yet * A section "feels off" but you have not articulated why * Modules describe features you are not sure you need * User types are still labeled with placeholders ("User", "Admin") * You have never opened the plan chat to ask a question In each case, an extra few minutes of editing now saves a wasted build. ## Two generate actions, not one build There is no single "build" button. Once your plan is ready, generation splits into two independent tracks, each triggered from the action bar at the bottom of the plan page: * **Generate Frontend** — kicks off the frontend track: Archie lays out the UI architecture first, then you generate an interactive prototype from the screens you choose, then the real app. See [Generating the frontend](/docs/features/frontend/generating) for how that flow works. * **Generate Backend** — generates the schema, database, and resolvers straight from the plan. It does not depend on the frontend track, so you can run it before, after, or alongside it. ![Plan page ready to generate, with the Generate Frontend action in the bottom bar](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/plan-ready-to-generate.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=c3928759547e55459f553e4fb9a30d1b) ## FAQ > **Which should I generate first, frontend or backend?** > > Either order works — they don't depend on each other. Most projects start with the frontend to validate the UI architecture and prototype before wiring up real data. > **What if a generation fails?** > > The page shows the error and credit usage. Failed generations are not charged in full. # Overview Source: https://archie.com/docs/plans-and-credits Compare Archie's Free, Pro, Team, and Enterprise plans, see Archie Core backend pricing, and learn how AI credits work. Archie has four plans for the platform — Free, Pro, Team, and Enterprise — plus three tiers for Archie Core, the backend that runs every app you ship. AI credits are the currency you spend on AI-assisted work; manual edits, deploying, and exporting your code are always free. ## Compare plans > **Free** > > For exploring an idea and shaping a plan. No credit card required. > > * Comprehensive app plan > * Complete app prototype > * AI-based editing > * Unlimited projects > * Unlimited collaborators (viewers) > * Export plan as PDF > * 50 AI credits per month > **Pro** > > For building, deploying, and hosting a real full-stack application. > > * Everything in Free > * Full-stack, production-grade apps > * Three dev modalities — prompt, visual, and IDE > * 6 AI agents across every layer > * Database, API, auth, and file storage built in > * Production hosting and global CDN > * Remove "Built by Archie" branding > * GitHub sync and full code ownership > * 200 AI credits per month, scalable through a credit selector > **Team** > > For teams shipping production apps together. > > * Everything in Pro > * 5–25 seats with role-based access > * App versioning and real-time collaboration > * 7 AI agents — including DevOps > * Per-member credit limits and team activity feed > * 3 Starter backends included > * Knowledge import — custom RAG context > * Scheduled functions and CI/CD > * 2,000 pooled AI credits per month > **Enterprise** > > For organizations with compliance, governance, and scale needs. > > * Everything in Team > * Unlimited seats, projects, and backends > * Enterprise SSO, MFA, and data residency > * SOC 2, HIPAA, and GDPR (add-on) > * SLA up to 99.95% > * Multi-region — AWS, GCP, and Azure > * White labeling and agency tools > * Dedicated CSM and priority support ## Available paid plans All prices are in USD. Annual plans are billed yearly at a 20% discount compared to monthly. ### Pro Pro starts at \$25 per month and scales through a credit selector. Pick the credit level that matches how much AI work you do — you can move up or down at any time. | AI credits / month | Monthly | Annual (per month, billed yearly) | | - | - | - | | 200 | \$25 | \$20 | | 500 | \$50 | \$40 | | 1,000 | \$100 | \$80 | | 2,000 | \$200 | \$160 | | 4,000 | \$400 | \$320 | | 8,000 | \$800 | \$640 | | 15,000 | \$1,500 | \$1,200 | ### Team Team is billed per seat, with a minimum of 5 seats and a maximum of 25. Credits are pooled across the workspace, and admins can set per-member limits. | Seats | Pooled AI credits / month | Monthly | Annual (per month, billed yearly) | | - | - | - | - | | 5 | 2,000 | \$150 | \$120 | | 10 | 4,000 | \$300 | \$240 | | 15 | 6,000 | \$450 | \$360 | | 20 | 8,000 | \$600 | \$480 | | 25 | 10,000 | \$750 | \$600 | Need Enterprise? Pricing is custom. [Get in touch](/docs/introduction/community-support/contacting-support) for SSO, dedicated infrastructure, compliance add-ons, and volume seats. ## Archie Core backends Every Archie app runs on Archie Core. Pro includes one Starter backend; Team includes three; Enterprise includes unlimited backends with all environments included. ### Starter \$25 per backend per month. Shared compute with a \$10 monthly compute credit. Good for MVPs and early products. * 1 GB database storage included (50 GB max) * 1 GB file storage included (25 GB max) * 5 GB egress per month * 50,000 monthly active users * Production environment only * US region (default), AWS ### Professional \$150 per backend per month. Dedicated compute with a \$50 monthly compute credit. Built for production apps with real users. * 8 GB database storage included (500 GB max) * 10 GB file storage included * 250 GB egress per month * 100,000 monthly active users * Multi-environment (\$25 per additional environment) * Selectable region, AWS ### Enterprise Custom pricing. Dedicated and isolated compute, custom-sized for compliance and multi-region deployments. * Custom database, file storage, and egress quotas * Unlimited monthly active users * All environments included * Multi-region — AWS, GCP, or Azure * Self-hosted option available Compute scales from Micro (1 vCPU, 1 GB RAM, \~\$10/mo) up to 16XL (64 dedicated vCPU, 256 GB RAM, \~\$3,520/mo) on Enterprise. Overages on database storage, file storage, and egress are billed per usage above your tier's included limits. ## AI credits AI credits are the currency for AI-assisted work in Archie. Generating code, designing a data model, building a feature — each AI task consumes credits weighted by complexity. Heavier tasks show their cost before you run them. ### What uses credits | Action | Uses credits | | - | - | | Generating a plan, prototype, or app | Yes | | AI-driven edits to your plan, frontend, or backend | Yes | | Generating a data model or seed data | Yes | | Manual edits in the visual editor or IDE | No | | Deploying your app | No | | Exporting code to GitHub | No | | Inviting viewers and collaborators | No | ### Where credits are displayed Your remaining credits are shown in the top right of the Archie app and on your account page. Heavier AI tasks display their estimated cost before they run, and your project activity feed records every credit spend. ### Top-ups If you need more credits before your next renewal, top-up packs start at \$15. Top-ups are one-time, never expire while your subscription is active, and stack on top of your monthly allotment. If you find yourself buying top-ups regularly, moving up to a higher credit level on Pro is cheaper per credit. ### Rollovers Unused credits roll over within a window: * **Monthly plans** — credits roll over for one billing cycle. * **Annual plans** — credits roll over for three billing cycles. * **Top-up credits** — never expire while your subscription is active. ### Running out of credits If you hit zero mid-flight, your completed work is preserved and manual editing still works. Only new AI tasks block until you buy a top-up, choose a higher credit level on Pro, or wait for renewal. ## FAQ > **How do I upgrade to a paid plan?** > > Go to your account billing page and pick a plan. Pro upgrades take effect immediately — your credits are added right away and the first month is pro-rated. Team upgrades create a workspace with seat assignment. > **Can I change plans or credit levels later?** > > Yes. Up-leveling within Pro happens immediately with pro-rated billing — you get the additional credits right away. Down-leveling and plan downgrades take effect at the start of your next billing cycle. Pro to Team or Pro to Enterprise upgrades happen through your account settings. > **Can I cancel my subscription?** > > Yes. You can cancel any time from your account settings. Your subscription stays active through the end of the current billing cycle, and your projects remain accessible — only AI tasks pause once your credits run out. > **What happens to my projects if I downgrade or cancel?** > > Your plans and code stay yours. If you downgrade to Free, deployed apps stay reachable until your billing cycle ends; after that, you'll need a Pro or higher plan to keep them deployed. You can export code to GitHub at any time. > **Where do I find invoices and payment history?** > > Invoices and payment history live on your account page under billing. Every invoice can be downloaded as a PDF. Update your payment method, billing email, and tax ID from the same page. > **What happens when I run out of credits?** > > Your completed work is preserved and manual editing still works. Only new AI tasks block until you buy a top-up pack, select a higher credit tier on Pro, or wait for renewal. > **Do top-up credits ever expire?** > > No, not while your subscription is active. Top-ups stack on top of your monthly credits and don't reset at the end of your billing cycle. > **Do annual plans actually save money?** > > Yes. Annual plans are 20% cheaper than monthly. Annual subscribers also get a longer credit rollover window — three billing cycles instead of one — which helps if you build in bursts. ## Common issues and troubleshooting > **My payment failed** > > Update your payment method on your billing page. We retry failed charges automatically for a short window before pausing AI tasks. If retries keep failing, contact [support@archie.com](mailto:support@archie.com). > **I upgraded but my credits aren't showing** > > Refresh your project page. Pro upgrades and top-ups apply within a minute. If credits still aren't there after a refresh, check the email receipt to confirm the charge went through, then reach out to support. > **I can't access a feature my plan should include** > > Sign out and back in to refresh your session. If a feature on your tier still isn't available, send the project URL and a screenshot to [support@archie.com](mailto:support@archie.com). For the latest pricing, visit [archie.com/pricing](https://archie.com/pricing). # Quick start Source: https://archie.com/docs/quick-start Submit a prompt, shape the plan, generate the prototype, and go full-stack — Archie's idea-to-app flow, end to end. The fastest way to understand Archie is to run through it once. You create an account, describe the app, shape the plan Archie generates, click through a prototype of your screens, and then turn it into a real, deployed full-stack application. **1. Create your account** There are two ways in, and both start you on the **Free** plan with no credit card: * **Sign up at [app.archie.com](https://app.archie.com)** with Google, Microsoft, GitHub, or email. If you use email, open the verification link Archie sends before you build. Signing in with GitHub also makes GitHub sync smoother later. * **Start from a prompt at [archie.com](https://archie.com).** Type your idea into the prompt bar without an account. Archie remembers what you typed, creates your account when you finish signing in, and continues straight into plan generation — a good way to see what Archie does before you commit. Free includes 50 AI credits per month, unlimited plan and prototype projects, plan export as PDF, unlimited viewers, and Discord community support. **2. Submit your prompt** Type what you want to build into the prompt bar. One or two sentences that name the product and its users is enough — for example, "A scheduling app for hairstylists where clients book appointments by service and stylist." Archie may ask a few clarifying questions, and you can attach reference material to give it more context. See [Generating a plan](/docs/features/plan/generating). **3. Shape your plan** Archie generates a [plan](/docs/features/plan/overview) — the structured definition of your app: * **Overview** — what the app is for * **Modules** — the feature areas (e.g. Scheduling, Stylist profiles, Notifications) * **User types** — who uses the app and what each can do (e.g. Client, Stylist, Salon Admin) * **System services** — built-in services like authentication, file storage, and roles * **Integrations** — third-party services like Stripe, SendGrid, and Twilio A preview section shows sample screens so you can see how the plan becomes UI. Archie chooses and manages the tech stack for you, so it doesn't appear in the plan. Shape the app by editing the plan, not by re-prompting. Click into any [module](/docs/features/plan/editing-modules), [user type](/docs/features/plan/editing-user-types), [service](/docs/features/plan/editing-services), or [integration](/docs/features/plan/editing-integrations) to change it. For larger changes, use the [plan chat](/docs/features/plan/iterating) — describe the change in plain language and Archie applies it across the right sections. **4. Generate the frontend** When the plan is ready ([here's how to tell](/docs/features/plan/when-to-build)), click **Generate Frontend**. Archie first lays out the UI architecture: a sitemap of every screen your app needs, grouped by module and color-coded by who can access it. Review a screen's spec before it's built, then pick the screens you want and click **Generate**. The result is a click-through [prototype](/docs/features/frontend/prototype/overview) with your design system applied — real navigation, no real data yet. See [Generating the frontend](/docs/features/frontend/generating). **5. Generate the backend** Click **Generate Backend** to build your database schema and APIs on [Archie Core](/docs/features/backend/overview). The backend track doesn't depend on the frontend, so you can run it before, after, or alongside it. **6. Go full-stack** Once the prototype holds together, click **Generate Full-Stack**. Archie turns your prototype and plan into a real, deployed application: a React frontend on a global CDN, connected to a backend with a relational database, GraphQL and REST APIs, authentication, and role-based access. From there, keep building by [talking to Archie](/docs/features/frontend/talking-to-archie), with the [visual editor](/docs/features/frontend/visual-editor), or in the [code editor](/docs/features/frontend/code-editor). > [!NOTE] > > Converting a prototype into a deployed application requires a paid plan. See [Plans and credits](/docs/introduction/getting-started/plans-and-credits). ## FAQ > **I never received my verification email. What should I do?** > > Check your spam folder first. If it's still missing after a minute, try signing in and use the resend link, or open the password reset flow — both send a fresh email. Corporate email filters sometimes block the first message. > **I signed up with email but want to use Google instead. Can I switch?** > > Yes. Open [Profile and preferences](/docs/introduction/account/profile-and-preferences) and link a Google, Microsoft, or GitHub identity. Once linked, you can sign in with any connected provider — they all point to the same account. > **I'm joining an existing organization. Should I sign up first or wait for an invite?** > > Either works. If you accept an invitation before creating an account, signup lands you straight on the organization's plan. If you already have an account, ask the admin to invite that same email and you'll join from your existing account. > **What if my prompt isn't specific enough?** > > The plan is where Archie sharpens vague ideas. If your first prompt is light, Archie still drafts a reasonable structure, and you refine it from there — adding modules, renaming user types, swapping integrations. Editing the plan is faster and more accurate than re-prompting. See [Best practices](/docs/introduction/getting-started/prompting-best-practices). > **Do I have to generate every screen at once?** > > No. Tick only the screens you want and click Generate. The rest stay pending, and you can generate them later, one at a time or in batches. Most builders generate a handful, check the pattern, then generate the rest. > **Should I generate the frontend or the backend first?** > > Either order works — the two tracks don't depend on each other. Most projects start with the frontend to validate the screens and flows before wiring up real data. > **What happens if generation fails partway?** > > The page shows the error and the credit usage. Your plan and anything already generated stay intact, and failed generations are not charged in full. Re-run the generation once you've checked the error. > **Can I change the plan after I generate?** > > Yes. Edit the plan and generate again — the next build picks up your changes. For small changes to a running app, use chat or the visual editor instead. ## What's next > **[Best practices](/docs/introduction/getting-started/prompting-best-practices)** > > How to prompt and shape a plan so the build lands close to what you want. > **[Frontend](/docs/features/frontend/overview)** > > Prototype, visual editor, code editor, theming, and deployment. > **[Backend](/docs/features/backend/overview)** > > Data model, GraphQL and REST APIs, environments, and settings. > **[Plans and credits](/docs/introduction/getting-started/plans-and-credits)** > > Pricing, AI credits, and Archie Core backend tiers. # JavaScript SDK Source: https://archie.com/docs/sdk/js-sdk Use @archie/js-sdk to call your Archie backend from the browser or Node.js. One TypeScript client covers auth, GraphQL, files, realtime, notifications, and REST. `@archie/js-sdk` is the core TypeScript SDK for the Archie backend. It is isomorphic, so the same client runs in the browser and in Node.js. Use it when you build a frontend or a server-side script that talks to an Archie project. The React and Node.js SDKs build on this package. > [!NOTE] > > Package: [`@archie/js-sdk` on npm](https://www.npmjs.com/package/@archie/js-sdk). ## Install ```bash theme={null} npm install @archie/js-sdk # or pnpm add @archie/js-sdk ``` ## Quick start Create a client with `createClient`, then use its modules. ```typescript theme={null} import { createClient } from '@archie/js-sdk'; const archie = createClient({ projectId: 'your-project-uuid', apiKey: 'anon your-api-key', apiUrl: 'https://your-project.archiecore.com', environment: 'master', }); // Sign in const { session } = await archie.auth.signIn({ email: 'user@example.com', password: 'secret', }); // Query data const { data, error } = await archie.graphql.query('{ users { id email } }'); ``` The client exposes these modules: > **archie.auth** > > Sign up, sign in, sessions, and auth events. > **archie.graphql** > > Queries and mutations. > **archie.files** > > Upload, download, and CSV import. > **archie.realtime** > > GraphQL subscriptions and table channels. > **archie.notifications** > > Live in-app notifications and read state. > **archie.rest** > > Custom REST endpoints. ## Configuration Only `projectId` is required. | Option | Type | Default | Description | | - | - | - | - | | `projectId` | `string` | required | Project UUID. | | `apiKey` | `string` | `undefined` | API key, sent as-is in the `Authorization` header. | | `getAccessToken` | `() => string \| null` | `undefined` | External token provider, for example Auth0. | | `environment` | `string` | `'master'` | Environment name. | | `apiUrl` | `string` | `'https://api.archie.dev'` | API Manager URL. | | `authUrl` | `string` | same as `apiUrl` | Auth service URL. | | `realtimeUrl` | `string` | same as `apiUrl` | WebSocket URL. Auto-converts to `ws://`. | | `autoRefreshToken` | `boolean` | `true` | Refresh the JWT 60 seconds before expiry. | | `persistSession` | `boolean` | `true` | Persist the session in storage. | | `storageAdapter` | `StorageAdapter` | auto-detect | `localStorage` in the browser, memory in Node.js. | | `headers` | `Record<string,string>` | `{}` | Extra headers on every request. | | `timeout` | `number` | `30000` | Request timeout in ms. `0` disables it. | | `retries` | `number` | `0` | Max retry attempts for transient errors. | | `retryDelay` | `number` | `200` | Initial backoff delay in ms. | | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch for testing, proxies, or edge runtimes. | | `debug` | `boolean` | `false` | Enable console logging. | | `logger` | `Logger` | noop | Custom logger. Overrides `debug`. | ### Authorization priority The SDK picks the `Authorization` header in this order: 1. **External token.** If `getAccessToken()` returns a token, the SDK sends `Authorization: Bearer {token}`. 2. **User JWT.** After `signIn`, the SDK sends `Authorization: Bearer {jwt}`. 3. **API key.** The SDK sends `Authorization: {apiKey}` as-is. Examples: `'anon xxxxx'`, `'Bearer eyJhbG...'`. 4. **Nothing.** The SDK sends no `Authorization` header. ### External auth If your app authenticates users elsewhere, such as Auth0, pass the token through `getAccessToken`. The SDK calls it on every HTTP request and every WebSocket connect. You never need to recreate the client when the token refreshes. ```typescript theme={null} let currentToken: string | null = null; // your app updates this on Auth0 refresh const archie = createClient({ projectId: 'your-project-uuid', apiUrl: 'https://your-project.archiecore.com', environment: 'master', getAccessToken: () => currentToken, // raw token; the SDK adds the `Bearer ` prefix }); ``` > [!NOTE] > > Writes such as mutations require a real user identity. Use a user token, not an anonymous `apiKey`. ## Auth Use `archie.auth` for registration, login, session management, auto-refresh, and auth events. > **Sign up and confirm** > > ```typescript theme={null} > const { userId, message } = await archie.auth.signUp({ > email: 'new@user.com', > password: 'StrongP@ss1', > firstName: 'Jane', // optional > lastName: 'Doe', // optional > roleId: 'role-uuid', // optional > }); > > // After the user receives the verification code by email: > const { session } = await archie.auth.confirmSignUp({ > email: 'new@user.com', > code: '123456', > }); > // The user is now signed in > ``` > **Sign in and sign out** > > ```typescript theme={null} > const { session } = await archie.auth.signIn({ > email: 'user@example.com', > password: 'secret', > }); > > console.log(session.user); // { id, email, firstName, lastName, roles } > console.log(session.accessToken); // JWT > console.log(session.expiresAt); // Unix timestamp (seconds) > > await archie.auth.signOut(); // clears session and storage, stops auto-refresh > ``` > **Current session and user** > > ```typescript theme={null} > const session = archie.auth.getSession(); // Session | null > const user = archie.auth.getUser(); // User | null > ``` > > On page load, the SDK restores the session from storage asynchronously. Wait for it before you check auth state: > > ```typescript theme={null} > await archie.auth.waitForInit(); > const user = archie.auth.getUser(); // now guaranteed to be loaded > ``` > **Auth state changes** > > `onAuthStateChange` returns an unsubscribe function. > > ```typescript theme={null} > const unsubscribe = archie.auth.onAuthStateChange((event, session) => { > // event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED' > if (event === 'SIGNED_IN') { > console.log('Logged in as', session.user.email); > } > }); > > unsubscribe(); > ``` > **Password recovery** > > ```typescript theme={null} > // Step 1: request the recovery email > await archie.auth.recoverPassword({ email: 'user@example.com' }); > > // Step 2: reset the password with the code from the email > await archie.auth.resetPassword({ > email: 'user@example.com', > code: '123456', > newPassword: 'NewStr0ngP@ss', > }); > ``` > **Manual refresh and JWKS** > > The SDK refreshes tokens automatically when `autoRefreshToken` is `true`. You can also refresh manually or fetch the JSON Web Key Set for server-side JWT verification. > > ```typescript theme={null} > const { session } = await archie.auth.refreshSession(); > const { keys } = await archie.auth.getJWKS(); > ``` > **Exchange codes** > > `auth.exchangeCode({ code })` redeems a single-use, 60-second code for a normal session as the user the code was issued for, with that user's real roles. Your server issues the code with `POST /auth/admin/exchange-code`, which requires an admin key. Never call that endpoint from a browser. > > ```typescript theme={null} > const { session } = await archie.auth.exchangeCode({ code }); > session.user.roles; // the user's real role names > ``` > > An exchange session has no refresh token. It ends when its access token expires, which is 15 minutes by default (the project's access-token TTL). With `autoRefreshToken` on, the SDK emits `SESSION_EXPIRING` 60 seconds before expiry. Get a fresh code and call `exchangeCode` again to replace the session in place. If nothing replaces it, `SIGNED_OUT` fires at expiry. > > ```typescript theme={null} > archie.auth.onAuthStateChange((event) => { > if (event === 'SESSION_EXPIRING') askTheHostForANewCode(); > }); > ``` > > Failures are `AuthError`s. Their `code` is one of `EXCHANGE_CODE_INVALID`, `EXCHANGE_CODE_EXPIRED`, `EXCHANGE_CODE_ALREADY_USED`, `EXCHANGE_USER_NOT_FOUND`, `EXCHANGE_USER_DISABLED`, or `EXCHANGE_RATE_LIMITED`. Branch on `code`, never on the message text. ### Auto-refresh and persistence When `autoRefreshToken` is `true` (default): * After sign in, the SDK schedules a token refresh 60 seconds before expiry. * On success, it emits `TOKEN_REFRESHED` and schedules the next refresh. * On failure, it calls `signOut()` automatically. When `persistSession` is `true` (default): * The SDK saves the session after every sign in and token refresh. * On client creation, it restores the session from storage. * If the stored token has expired, it tries a refresh automatically. * The storage key format is `archie-auth-{projectId}-{environment}`. ## GraphQL Use `archie.graphql` to run queries and mutations against the `/graphql` endpoint of the Archie API Manager. `query()` and `mutate()` return `{ data, error }`. They do not throw for GraphQL-level errors. ```typescript theme={null} interface User { id: string; email: string; name: string; } const { data, error } = await archie.graphql.query<{ user: User }>( `query GetUser($id: ID!) { user(id: $id) { id email name } }`, { id: 'user-123' }, ); if (error) { console.error(error.message, error.code); } else { console.log(data.user); } ``` ```typescript theme={null} const { data, error } = await archie.graphql.mutate<{ createUser: User }>( `mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id email } }`, { input: { email: 'new@user.com', name: 'New User' } }, ); ``` ### Request options Both methods accept a third parameter with `headers` and `signal`. ```typescript theme={null} const controller = new AbortController(); const { data } = await archie.graphql.query( '{ users { id } }', {}, { headers: { 'x-custom': 'value' }, // extra headers for this request signal: controller.signal, // AbortSignal for cancellation }, ); controller.abort(); // cancel the request ``` ### Raw request Use `request()` when you want the data directly and prefer that errors throw a `GraphQLError`. ```typescript theme={null} try { const data = await archie.graphql.request<{ users: User[] }>({ query: '{ users { id email } }', variables: {}, operationName: 'GetUsers', }); console.log(data.users); // no { data, error } wrapper } catch (err) { // Throws GraphQLError on failure } ``` ### Response shape ```typescript theme={null} // Success { data: T, error: null } // GraphQL-level errors (partial data may exist) { data: T | null, error: GraphQLError } // Network or HTTP error { data: null, error: ArchieError } ``` ## Files Use `archie.files` to upload, download, and manage files through GraphQL multipart uploads. ```typescript theme={null} const input = document.querySelector<HTMLInputElement>('#fileInput'); const file = input.files[0]; const { url, fileId } = await archie.files.upload(file, { filename: 'photo.jpg', contentType: 'image/jpeg', providerType: 's3', // optional: storage provider hint onProgress: (pct) => console.log(`${pct}% uploaded`), // optional }); ``` `upload` accepts a `File`, a `Blob`, or a `Uint8Array`. The `Uint8Array` form also works in Node.js. ```typescript theme={null} const blob = new Blob(['Hello, world!'], { type: 'text/plain' }); await archie.files.upload(blob, { filename: 'hello.txt' }); const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); await archie.files.upload(bytes, { filename: 'hello.bin' }); ``` > **Import a CSV into a table** > > ```typescript theme={null} > const csvFile = document.querySelector<HTMLInputElement>('#csvInput').files[0]; > > const { result } = await archie.files.uploadCsv(csvFile, { > tableName: 'products', > transactionality: true, // optional: roll back everything on error > limit: 1000, // optional: max rows to import > }); > > console.log(result.success); // boolean > console.log(result.rowsImported); // number > console.log(result.errors); // import-level errors > ``` > **Download a file** > > ```typescript theme={null} > const blob = await archie.files.download('file-id-123'); > > // In the browser, trigger a download: > const url = URL.createObjectURL(blob); > const a = document.createElement('a'); > a.href = url; > a.download = 'my-file.pdf'; > a.click(); > URL.revokeObjectURL(url); > ``` > **Get a file URL** > > `getUrl` builds the URL locally. It makes no network request. > > ```typescript theme={null} > const url = archie.files.getUrl('file-id-123'); > // "https://example.platform.com/files?id=file-id-123" > ``` ## Realtime Use `archie.realtime` for WebSocket subscriptions. It uses the `graphql-transport-ws` protocol, connects lazily, and reconnects automatically. ```typescript theme={null} const sub = archie.realtime.subscribe<{ orderUpdated: Order }>( `subscription { orderUpdated { id status total } }`, {}, // variables { onData: (data) => console.log('Order updated:', data.orderUpdated), onError: (error) => console.error('Subscription error:', error.message), onComplete: () => console.log('Subscription ended'), }, ); sub.unsubscribe(); // stop listening ``` ### Channels The channel API is a shorter way to listen to table-level events. Event names are `INSERT`, `UPDATE`, `DELETE`, and `*`. ```typescript theme={null} const channel = archie.realtime.channel('orders'); channel .on('INSERT', (payload) => console.log('New order:', payload)) .on('UPDATE', (payload) => console.log('Order updated:', payload)) .on('DELETE', (payload) => console.log('Order deleted:', payload)) .on('*', (payload) => console.log('Any event:', payload)); channel.subscribe(); // start listening channel.unsubscribe(); // stop listening ``` ### Connection state ```typescript theme={null} console.log(archie.realtime.currentState); // 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'RECONNECTING' const unsubscribe = archie.realtime.onConnectionStateChange((state) => { if (state === 'RECONNECTING') showReconnectingBanner(); }); ``` When the connection drops, the SDK reconnects with exponential backoff: 1s, 2s, 4s, 8s, 16s, then 30s at most. The counter resets after a successful `connection_ack`. The connection closes when the last subscription is unsubscribed. On `TOKEN_REFRESHED`, the SDK reconnects with the new credentials. ## Notifications Use `archie.notifications` to receive live in-app notifications and manage read state. `receiverId` is the authenticated user's id, available from `archie.auth.getUser()?.id`. ```typescript theme={null} const receiverId = archie.auth.getUser()?.id ?? ''; const channel = archie.notifications.subscribe(receiverId, { onError: (err) => console.error('Notification channel error:', err), }); channel .on('welcome', (n) => toast(n.actionText ?? 'Welcome!')) // filter by notification key .on('*', () => badge.increment()); // every notification channel.unsubscribe(); // clean up on unmount or sign-out ``` ### List, count, and mark as read ```typescript theme={null} const { items, count, totalCount } = await archie.notifications.list(receiverId, { unreadOnly: true, first: 20, skip: 0, orderBy: { createdAt: 'DESC' }, }); const unread = await archie.notifications.getUnreadCount(receiverId); // number const updated = await archie.notifications.markAsRead(notificationId); // updated Notification const markedCount = await archie.notifications.markAllAsRead(receiverId); // number marked ``` | Option | Type | Description | | - | - | - | | `unreadOnly` | `boolean` | Return only unread notifications. Default `false`. | | `first` | `number` | Page size. | | `skip` | `number` | Offset for pagination. | | `orderBy` | `Record<string, 'ASC'\|'DESC'>` | Field-to-direction map. Default `{ createdAt: 'DESC' }`. | | `createdBefore` | `string` | Keyset cursor. Returns rows older than this ISO `createdAt`. | | `excludeIds` | `string[]` | Tie-safe keyset. Ids already loaded at the `createdBefore` timestamp. | | `includeTotal` | `boolean` | Request `count` and `totalCount`. Default `true`. `false` skips the COUNT. | > **Paginate history (infinite scroll)** > > `list` supports keyset pagination through `createdBefore`. Pass the `createdAt` of the oldest row you already have to fetch the next older page. > > ```typescript theme={null} > const first = await archie.notifications.list(receiverId, { first: 20 }); > const oldest = first.items[first.items.length - 1]; > > const older = await archie.notifications.list(receiverId, { > first: 20, > orderBy: { createdAt: 'DESC' }, > createdBefore: oldest.createdAt, > // Tie-safe: pass ids already loaded at the cursor's exact createdAt. > excludeIds: first.items.filter((n) => n.createdAt === oldest.createdAt).map((n) => n.id), > // Skip the COUNT aggregate on deep pages. > includeTotal: false, > }); > > const hasMore = older.items.length === 20; // full page means more may remain > ``` > > The `@archie/react-sdk` `useNotifications` hook does all of this for you. Use the raw options when you paginate outside React. ### Notification shape All fields are camelCase. The SDK normalizes backend inconsistencies before it delivers notifications to you. ```typescript theme={null} interface Notification { id: string; receiverId: string; notificationKey: string; // event key, for example 'welcome' notificationType?: string | null; notificationTypeId?: string | null; actionText?: string | null; // CTA label actionUrl?: string | null; // CTA destination; validate before rendering isRead: boolean; readAt?: string | null; // ISO date expiresAt?: string | null; // ISO date metadata?: Record<string, unknown> | null; params?: Record<string, unknown> | null; // template variables dedupKey?: string | null; createdAt: string; // ISO date updatedAt?: string | null; } ``` > [!WARNING] > > The Archie backend enforces authorization, not the SDK. `receiverId` and notification `id` are request parameters, not a client-side trust boundary. The backend scopes all reads and writes to the authenticated principal. > > Treat `actionUrl` as untrusted data. Before you render it as a link, check that the scheme is `http:` or `https:`. Reject `javascript:` and `data:` URLs, which enable stored XSS. > > Treat `metadata` and `params` as untrusted too. If you deep-merge them into other objects, use a merge that guards against `__proto__`, `constructor`, and `prototype` keys. ```typescript theme={null} function safeActionUrl(url: string | null | undefined): string | undefined { if (!url) return undefined; try { const { protocol } = new URL(url); if (protocol === 'http:' || protocol === 'https:') return url; } catch { // not a valid URL } return undefined; } ``` ## REST Use `archie.rest` to call custom REST APIs created through the Archie gateway. The SDK injects the standard headers (`x-project-id`, `authorization`, `environment`) for you. ```typescript theme={null} const products = await archie.rest.get<Product[]>('/api/products', { params: { category: 'electronics', limit: '10' }, // GET /api/products?category=electronics&limit=10 }); const created = await archie.rest.post<Product>('/api/products', { name: 'Widget', price: 9.99 }); await archie.rest.put('/api/products/123', { name: 'Updated Widget', price: 12.99 }); await archie.rest.patch('/api/products/123', { price: 14.99 }); await archie.rest.delete('/api/products/123'); ``` Every method accepts an options object with `headers`, `params`, and `signal`. REST methods throw `ArchieError` on non-2xx responses. ```typescript theme={null} try { await archie.rest.get('/api/products/missing'); } catch (err) { if (err instanceof ArchieError) { console.log(err.status); // 404 console.log(err.code); // 'HTTP_404' console.log(err.message); // server-provided message } } ``` ## Error handling All SDK errors extend `ArchieError`. | Class | When | Key properties | | - | - | - | | `ArchieError` | Base class for all SDK errors. | `message`, `code`, `status`, `details`, `hint` | | `AuthError` | Any failure of an `auth.*` call. | Same as `ArchieError`. | | `GraphQLError` | GraphQL response errors. | `path`, `locations`, plus `ArchieError` props. | | `NetworkError` | Network issues: timeout, DNS, disconnected. | `hint`, for example "Check your internet...". | ```typescript theme={null} import { ArchieError, AuthError, GraphQLError, NetworkError } from '@archie/js-sdk'; try { await archie.auth.signIn({ email: 'test@test.com', password: 'wrong' }); } catch (err) { if (err instanceof AuthError) { console.log(err.code); // 'AUTH_INVALID_CREDENTIALS', or 'AUTH_ERROR' console.log(err.status); // 401 console.log(err.message); // safe to show the user } else if (err instanceof NetworkError) { // Could not reach the service. Not a credential problem. console.log(err.hint); } else if (err instanceof ArchieError) { // The service answered without a verdict: 5xx, timeout, gateway page. console.log(err.code, err.status, err.details); // for example 'HTTP_500' or 'TIMEOUT' } } ``` GraphQL errors from `query()` and `mutate()` come back in the response: ```typescript theme={null} const { data, error } = await archie.graphql.query('{ invalidField }'); if (error) { console.log(error.message); // 'Cannot query field "invalidField"' console.log(error.code); // 'GRAPHQL_VALIDATION_FAILED' console.log(error.path); // ['invalidField'] console.log(error.locations); // [{ line: 1, column: 3 }] } ``` ### Auth error codes A failed `auth.*` call throws one of two families. Handle both. * **The server reached a verdict.** Examples: wrong password, unverified email, expired code. The SDK always throws an `AuthError`, even when the backend reports the rejection as an HTTP 200 with a GraphQL `errors[]` body. `err.message` carries the backend's wording and is safe to show the user. * **The service could not answer.** Examples: network failure, timeout, a 5xx or gateway page. These stay `NetworkError` or `ArchieError`. Treat them as unavailability ("try again in a moment"), not as a credential problem. Do not test only for `AuthError`. A login form that does goes silent whenever the auth service is down. `err.code` carries the `extensions.code` value from the server. When the server sends none, it falls back to `AUTH_ERROR`. | Code | Description | | - | - | | `AUTH_INVALID_CREDENTIALS` | Wrong email or password. | | `AUTH_EMAIL_EXISTS` | Email already registered. | | `AUTH_EMAIL_NOT_VERIFIED` | Email confirmation required. | | `AUTH_ACCOUNT_LOCKED` | Account temporarily locked. | | `AUTH_INVALID_CODE` | Invalid verification or recovery code. | | `AUTH_TOKEN_EXPIRED` | Access token has expired. | | `AUTH_TOKEN_INVALID` | Malformed or revoked token. | | `AUTH_NOT_CONFIGURED` | Auth not enabled for the project. | | `AUTH_WEAK_PASSWORD` | Password does not meet strength requirements. | | `AUTH_ERROR` | Auth failure the server sent no code for. | | `AUTH_INVALID_RESPONSE` | Auth service returned an unusable payload. | ### Automatic 401 retry When a request returns 401, the SDK refreshes the token and retries the original request. If the refresh fails, it signs the user out and throws the error. Your code does not need to handle this. ## Retry and timeout ```typescript theme={null} const archie = createClient({ projectId: 'your-project-uuid', retries: 3, // retry up to 3 times on transient errors retryDelay: 200, // 200ms, then 400ms, then 800ms (with jitter) timeout: 15_000, // abort requests after 15 seconds }); ``` The SDK retries on these conditions: | Condition | Status codes or errors | | - | - | | Server errors | `500`, `502`, `503`, `504` | | Rate limiting | `429` (respects the `Retry-After` header) | | Network failures | `TypeError` (DNS, connectivity) | | Timeouts | `TIMEOUT` errors | Non-retryable errors (`400`, `401`, `403`, `404`, `409`, and similar) throw immediately. Backoff is exponential with jitter: `delay = min(baseDelay × 2^(attempt-1) + random_jitter, 30s)`. For timeouts: * When `timeout > 0`, each individual request, including retries, aborts after `timeout` milliseconds. * The SDK throws `ArchieError` with `code: 'TIMEOUT'` and `status: 408`. * Set `timeout: 0` to disable the timeout. * A user-provided `AbortSignal` takes precedence over the timeout. ## Custom fetch Inject your own `fetch` for testing, proxies, or edge runtimes such as Cloudflare Workers and Deno. ```typescript theme={null} const archie = createClient({ projectId: '...', fetch: async (url, init) => { console.log('→', init?.method, url); const response = await globalThis.fetch(url, init); console.log('←', response.status); return response; }, }); ``` ## Health check `ping()` returns `true` when the API responds with a `2xx` status and `false` otherwise. It never throws. ```typescript theme={null} const isUp = await archie.ping(); ``` ## Storage adapters The SDK persists sessions through a `StorageAdapter`. The default is `localStorage` in the browser and memory in Node.js. ```typescript theme={null} interface StorageAdapter { getItem(key: string): string | null | Promise<string | null>; setItem(key: string, value: string): void | Promise<void>; removeItem(key: string): void | Promise<void>; } ``` Two adapters are built in. Use `MemoryStorage` for SSR, tests, or environments without `localStorage`. ```typescript theme={null} import { BrowserLocalStorage, MemoryStorage } from '@archie/js-sdk'; const archie = createClient({ projectId: '...', storageAdapter: new MemoryStorage(), // or new BrowserLocalStorage() }); ``` You can also write your own, for example with AsyncStorage on React Native: ```typescript theme={null} import AsyncStorage from '@react-native-async-storage/async-storage'; const archie = createClient({ projectId: '...', storageAdapter: { getItem: (key) => AsyncStorage.getItem(key), setItem: (key, value) => AsyncStorage.setItem(key, value), removeItem: (key) => AsyncStorage.removeItem(key), }, }); ``` ## Logging Two logger constants are available for the `logger` option. `consoleLogger` writes to `console.debug`, `console.info`, `console.warn`, and `console.error`. `noopLogger` silences all logging and is the default. ```typescript theme={null} import { consoleLogger, noopLogger } from '@archie/js-sdk'; const archie = createClient({ projectId: '...', logger: consoleLogger }); ``` ## TypeScript types The package exports all of its types. ```typescript theme={null} import type { ArchieClientOptions, Session, User } from '@archie/js-sdk'; ``` > **Auth types** > > ```typescript theme={null} > import type { > AuthSignUpParams, > AuthSignInParams, > AuthConfirmParams, > AuthRecoverParams, > AuthResetPasswordParams, > AuthEvent, // 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED' > AuthEventCallback, > } from '@archie/js-sdk'; > ``` > **GraphQL types** > > ```typescript theme={null} > import type { > GraphQLResponse, // { data: T | null; error: ArchieError | null } > GraphQLRequestOptions, // { headers?, signal? } > GraphQLRawRequest, // { query, variables?, operationName? } > } from '@archie/js-sdk'; > ``` > **File types** > > ```typescript theme={null} > import type { > FileUploadOptions, // { filename?, contentType?, providerType?, onProgress? } > FileUploadResult, // { url: string; fileId: string } > CsvUploadOptions, // { tableName, transactionality?, limit? } > } from '@archie/js-sdk'; > ``` > **Realtime types** > > ```typescript theme={null} > import type { > Subscription, // { unsubscribe: () => void } > SubscriptionCallbacks, // { onData, onError?, onComplete? } > RealtimeEvent, // 'INSERT' | 'UPDATE' | 'DELETE' | '*' > RealtimeChannel, // { on, subscribe, unsubscribe } > ConnectionState, // 'CONNECTING' | 'CONNECTED' | 'DISCONNECTED' | 'RECONNECTING' > ConnectionStateCallback, > } from '@archie/js-sdk'; > ``` > **REST types** > > ```typescript theme={null} > import type { > RestRequestOptions, // { headers?, params?, signal? } > } from '@archie/js-sdk'; > ``` > **Module interfaces and classes** > > Use the interfaces for dependency injection and testing. Use the concrete classes for `instanceof` checks or advanced typing. > > ```typescript theme={null} > import type { > IAuthModule, > IGraphQLModule, > IFileModule, > IRealtimeModule, > IRestModule, > IHttpClient, > Logger, > TokenAccessor, > StorageAdapter, > } from '@archie/js-sdk'; > > import { AuthModule, GraphQLModule, FileModule, RealtimeModule, RestModule } from '@archie/js-sdk'; > ``` ## Links * [`@archie/js-sdk` on npm](https://www.npmjs.com/package/@archie/js-sdk) * License: MIT # Node SDK Source: https://archie.com/docs/sdk/node-sdk Use @archie/node-sdk to verify Archie JWTs, manage users, and call your project's GraphQL and REST APIs with service-role access from your server. It also ships middleware for Express, Fastify, and AWS Lambda. `@archie/node-sdk` is the server SDK for Archie. Use it in trusted backend code to verify JWTs, manage users as an admin, run service-role GraphQL and REST requests, and protect routes with framework middleware. It is built for backend developers who run their own server or serverless functions next to an Archie project. For browser code, use the `@archie/js-sdk` package instead. > [!WARNING] > > The Node SDK authenticates with a **service-role API key**. This key has admin-level access. Use it only in server-side code, load it from environment variables, and never ship it to a browser, mobile app, or public repository. ## Install Install the SDK together with `@archie/js-sdk`, which is a required peer dependency. ```bash theme={null} # npm npm install @archie/js-sdk @archie/node-sdk # pnpm pnpm add @archie/js-sdk @archie/node-sdk ``` Framework integrations are optional. Install `express` or `fastify` only if you use them. The SDK requires Node.js 18.0.0 or later. ## Quick start Create an admin client with your project ID and service-role API key. ```typescript theme={null} import { createAdminClient } from '@archie/node-sdk'; const archie = createAdminClient({ projectId: 'your-project-uuid', apiKey: 'your-service-role-api-key', apiUrl: 'https://your-project.archiecore.com', environment: 'master', retries: 3, timeout: 15_000, }); // Health check const healthy = await archie.ping(); // Verify a JWT const { user, error } = await archie.auth.verifyToken(token); // List users const { data, error: listErr } = await archie.auth.listUsers({ page: 1, limit: 20 }); // Service-role GraphQL const { data: roles } = await archie.graphql.query('{ roles { items { id name } } }'); ``` ## Configuration `createAdminClient(options)` returns an `ArchieAdminClient`. Only `projectId` and `apiKey` are required. | Option | Type | Default | Description | | - | - | - | - | | `projectId` | `string` | None | **Required.** Project UUID. | | `apiKey` | `string` | None | **Required.** Service-role API key. | | `environment` | `string` | `'master'` | Environment name. | | `apiUrl` | `string` | `'https://api.archie.dev'` | Base URL for the API. | | `authUrl` | `string` | Same as `apiUrl` | Base URL for the Auth service. | | `timeout` | `number` | `30000` | Request timeout in milliseconds. Set `0` to disable. | | `retries` | `number` | `0` | Max retry attempts for transient errors (429, 500–504, network). | | `retryDelay` | `number` | `200` | Initial delay in ms for exponential backoff with jitter. | | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation for testing or proxies. | | `logger` | `ArchieLogger` | `{}` | Logger for request lifecycle events. | The client exposes these modules: * `archie.auth` for JWT verification and admin user operations. * `archie.graphql` for service-role GraphQL queries and mutations. * `archie.rest` for service-role REST requests. * `archie.notifications` for emitting notifications from the server. ## Verify JWTs Use `archie.auth.verifyToken(token, options?)` to verify JWTs issued by Archie. The SDK checks signatures against the project's JWKS (JSON Web Key Set). It caches keys for 5 minutes and shares one fetch between concurrent calls. ```typescript theme={null} const { user, error } = await archie.auth.verifyToken('Bearer eyJhbG...'); ``` The `token` can include a `Bearer ` prefix. The SDK strips it for you. | Option | Type | Description | | - | - | - | | `options.requiredRoles` | `string[]` | Require at least one of these roles. Returns 403 if missing. | | `options.requireEmailVerified` | `boolean` | Require `emailVerified: true`. Returns 403 if false. | | `options.clockTolerance` | `number` | Clock tolerance in seconds. Default: `30`. | The call resolves to a `VerifyTokenResult`: ```typescript theme={null} interface VerifyTokenResult { user: TokenUser | null; error: ArchieError | null; } interface TokenUser { id: string; email: string; firstName?: string; lastName?: string; roles: string[]; exp: number; iat: number; emailVerified: boolean; } ``` ```typescript theme={null} // Basic verification const { user, error } = await archie.auth.verifyToken(jwt); if (error) { console.error(error.code); // AUTH_TOKEN_INVALID, AUTH_TOKEN_EXPIRED, etc. } // Require the admin role await archie.auth.verifyToken(jwt, { requiredRoles: ['admin'] }); // Require a verified email await archie.auth.verifyToken(jwt, { requireEmailVerified: true }); ``` ## Manage users as an admin Admin operations use service-role authentication and return `{ data, error }`. | Method | Returns `data` of | | - | - | | `archie.auth.createUser(params)` | The created user | | `archie.auth.listUsers(params?)` | `{ users: User[], total: number, page: number, limit: number }` | | `archie.auth.getUser(userId)` | `User` (`id`, `email`, `firstName`, `lastName`, `roles`) | | `archie.auth.updateUser(userId, updates)` | The updated user. `updates` is a `Partial<User>`. | | `archie.auth.blockUser(userId)` | `boolean` | | `archie.auth.unblockUser(userId)` | `boolean` | | `archie.auth.deleteUser(userId)` | `boolean` | | `archie.auth.resendVerification(userId)` | `boolean` | | `archie.auth.resetUserPassword(userId, newPassword)` | `boolean` | > **Create a user** > > ```typescript theme={null} > const { data, error } = await archie.auth.createUser({ > email: 'user@example.com', > password: 'SecurePass123!', > firstName: 'John', // optional > lastName: 'Doe', // optional > roleId: 'role-uuid', // optional > emailVerified: true, // optional > }); > ``` > **List users** > > ```typescript theme={null} > const { data, error } = await archie.auth.listUsers({ > page: 1, // optional > limit: 20, // optional > search: 'john', // optional, searches email and name > status: 'active', // optional: 'active' | 'locked' | 'unverified' > orderBy: 'createdAt', // optional > }); > ``` > **Update a user** > > ```typescript theme={null} > const { data, error } = await archie.auth.updateUser('user-uuid', { > firstName: 'Jane', > lastName: 'Smith', > }); > ``` > **Block, unblock, and delete a user** > > ```typescript theme={null} > await archie.auth.blockUser('user-uuid'); > await archie.auth.unblockUser('user-uuid'); > await archie.auth.deleteUser('user-uuid'); > ``` ## Service-role GraphQL Run GraphQL operations with admin-level permissions. ```typescript theme={null} // Query const { data, error } = await archie.graphql.query<{ users: User[] }>( '{ users { id email roles } }', ); // Mutation with variables const { data: result } = await archie.graphql.mutate( `mutation UpdateRole($userId: ID!, $roleId: ID!) { assignRole(userId: $userId, roleId: $roleId) { id roles } }`, { userId: 'user-uuid', roleId: 'admin-role' }, ); ``` Both methods use the signature `(gql, variables?, options?)`. ### Impersonate a user Pass `impersonateUserId` to run an operation as a specific user while keeping service-role access. The SDK adds the `x-impersonate-user` header to the request. ```typescript theme={null} const { data } = await archie.graphql.query( '{ myOrders { id total } }', {}, { impersonateUserId: 'user-uuid' }, ); ``` ## Service-role REST Call REST endpoints directly with admin credentials. ```typescript theme={null} const items = await archie.rest.get<Item[]>('/api/v1/items'); const created = await archie.rest.post<Item>('/api/v1/items', { name: 'New' }); const updated = await archie.rest.put<Item>('/api/v1/items/1', { name: 'Updated' }); const patched = await archie.rest.patch<Item>('/api/v1/items/1', { status: 'active' }); await archie.rest.delete('/api/v1/items/1'); ``` REST methods throw an `ArchieError` on non-2xx responses. The error code includes the HTTP status. ```typescript theme={null} try { await archie.rest.get('/api/v1/missing'); } catch (err) { // err.code === 'HTTP_404' // err.status === 404 } ``` > [!NOTE] > > Endpoints that return `204 No Content` or `Content-Length: 0` resolve with `null` data instead of parsing an empty body. ## Emit notifications `archie.notifications.emit()` dispatches a notification through the backend `emitNotification` mutation. `eventKey`, `target`, and `sourceService` are required. ```typescript theme={null} const { data, error } = await archie.notifications.emit({ eventKey: 'welcome', target: { userIds: ['<uuid>'] }, // or { audience: 'ALL_PROJECT_USERS' } sourceService: 'auth', // required: identifies the emitting service params: { name: 'Ana' }, channels: ['email', 'in_app'], // 'email' | 'in_app' | 'slack' | 'sms' | 'whatsapp' // optional: action: { text, url }, dedupKey, expiresIn: '7d', broadcastConfirm }); if (data?.accepted) { console.log(data.targetingMode, data.recipientCount, data.fanoutJobId); } ``` Admin `emit()` always returns `{ data, error }` and never throws. This differs from the browser-side `@archie/js-sdk` notification methods (`list`, `getUnreadCount`, `markAsRead`, `markAllAsRead`), which throw `ArchieError` on failure. ## Middleware and framework integrations Each integration lives in its own subpath export and verifies the JWT in the `Authorization` header. **Express** Install Express, then import from `@archie/node-sdk/express`. ```bash theme={null} npm install express ``` `createArchieMiddleware(client, options?)` verifies the JWT and populates `req.archieUser`. ```typescript theme={null} import express from 'express'; import { createAdminClient } from '@archie/node-sdk'; import { createArchieMiddleware, requireRole } from '@archie/node-sdk/express'; const archie = createAdminClient({ projectId: '...', apiKey: '...' }); const app = express(); // Optional auth: req.archieUser may be undefined app.use(createArchieMiddleware(archie, { required: false })); app.get('/api/profile', (req, res) => { if (!req.archieUser) return res.status(401).json({ error: 'Unauthorized' }); res.json({ user: req.archieUser }); }); // Required auth: returns 401 automatically app.use('/api/admin', createArchieMiddleware(archie, { required: true })); app.get('/api/admin/dashboard', requireRole('admin'), (req, res) => { res.json({ admin: true, user: req.archieUser }); }); ``` | Option | Type | Default | Description | | - | - | - | - | | `required` | `boolean` | `false` | Return 401 for unauthenticated requests. | | `onError` | `function` | None | Custom error handler: `(err, req, res, next) => void`. | `requireRole(...roles)` checks that `req.archieUser` has at least one of the given roles and returns 403 if not. Use it after `createArchieMiddleware`. ```typescript theme={null} app.get('/admin', requireRole('admin'), handler); app.get('/editor', requireRole('admin', 'editor'), handler); ``` The middleware augments Express types, so `req.archieUser` is typed as `TokenUser | undefined`. **Fastify** Install Fastify, then import `archiePlugin` from `@archie/node-sdk/fastify`. The plugin verifies the JWT and decorates `request.archieUser`. ```bash theme={null} npm install fastify ``` ```typescript theme={null} import Fastify from 'fastify'; import { createAdminClient } from '@archie/node-sdk'; import { archiePlugin } from '@archie/node-sdk/fastify'; const archie = createAdminClient({ projectId: '...', apiKey: '...' }); const fastify = Fastify(); fastify.register(archiePlugin, { client: archie, required: false, // set true to enforce auth on all routes }); fastify.get('/api/profile', async (request) => { return { user: request.archieUser }; }); ``` **Lambda** `withArchieAuth(client, handler)` from `@archie/node-sdk/lambda` wraps a Lambda handler. It verifies the JWT and injects the user into the context. ```typescript theme={null} import { createAdminClient } from '@archie/node-sdk'; import { withArchieAuth } from '@archie/node-sdk/lambda'; const archie = createAdminClient({ projectId: '...', apiKey: '...' }); export const handler = withArchieAuth(archie, async (event, context) => { const { user, client } = context.archie; // Use the admin client const { data } = await client.auth.listUsers(); return { statusCode: 200, body: JSON.stringify({ userId: user.id, totalUsers: data?.total }), }; }); ``` The wrapper returns 401 if the `Authorization` header is missing or the token is invalid. ## Error handling Each part of the SDK reports errors in a consistent way: * **Auth operations** return `{ data, error }`, where `error` is `ArchieError | null`. * **Verify operations** return `{ user, error }`, where `error` is `AuthError | null`. * **REST operations** throw `ArchieError` on non-2xx responses. * **GraphQL operations** return `{ data, error }` with structured GraphQL errors. | Code | Status | Description | | - | - | - | | `AUTH_TOKEN_INVALID` | 401 | Token is missing, malformed, or has invalid signature. | | `AUTH_TOKEN_EXPIRED` | 401 | Token has expired. | | `AUTH_INSUFFICIENT_ROLE` | 403 | User lacks a required role. | | `AUTH_EMAIL_NOT_VERIFIED` | 403 | Email is not verified but was required. | | `AUTH_JWKS_FETCH_FAILED` | Varies | Failed to fetch JWKS keys. | | `ADMIN_ERROR` | Varies | Admin operation returned an error. | | `HTTP_<status>` | Varies | REST request failed with the given HTTP status. | | `TIMEOUT` | 408 | Request timed out. | | `NETWORK_ERROR` | 0 | Network-level failure. | ## Retries and timeouts All HTTP requests go through one engine with configurable retry and timeout. ```typescript theme={null} const archie = createAdminClient({ projectId: '...', apiKey: '...', retries: 3, // retry up to 3 times retryDelay: 200, // start with a 200 ms delay timeout: 10_000, // 10 s timeout }); ``` The SDK retries on these conditions: * HTTP status codes `429`, `500`, `502`, `503`, and `504`. * Network errors, such as DNS failures and refused connections. * Timeout errors. Backoff is exponential with jitter: `delay × 2^attempt + random jitter`, capped at 30 seconds. If a `429` response includes a `Retry-After` header, the SDK respects it. Requests time out after 30 seconds by default. Set `timeout: 0` to disable timeouts. A timed-out request throws an `ArchieError` with `code: 'TIMEOUT'` and `status: 408`. ## Health check `archie.ping()` sends a `GET /health` request. It returns `true` if the server responds with 2xx within the configured timeout, and `false` on any error. ```typescript theme={null} const healthy = await archie.ping(); if (!healthy) { console.error('Archie API is unreachable'); } ``` ## Custom fetch and logging Pass a custom `fetch` for unit tests with a mocked fetch, HTTP proxies or agents (for example `undici` with a proxy), or transport-level logging. ```typescript theme={null} const archie = createAdminClient({ projectId: '...', apiKey: '...', fetch: myCustomFetch, }); ``` Pass a `logger` to observe request lifecycle events. Any logger works, including pino, winston, and `console`. All methods are optional, so implement only the levels you need. ```typescript theme={null} import pino from 'pino'; import { createAdminClient } from '@archie/node-sdk'; const logger = pino({ level: 'debug' }); const archie = createAdminClient({ projectId: '...', apiKey: '...', logger: { debug: (msg, meta) => logger.debug(meta, msg), warn: (msg, meta) => logger.warn(meta, msg), }, }); ``` The SDK logs these events: | Level | Event | Meta | | - | - | - | | `debug` | `Request` | `{ method, url }` | | `debug` | `Response` | `{ url, status }` | | `warn` | `Retrying request` | `{ url, attempt, delay }` | | `warn` | `Rate limited, waiting Retry-After` | `{ url, retryAfter }` | | `warn` | `Network error, will retry` | `{ url, attempt, error }` | ## Security best practices * Keep `apiKey` in server-side environment variables or a secrets manager. * Never import the admin client in code that runs in the browser or in a mobile app. * Use `requiredRoles` or `requireRole` to restrict sensitive routes, and `requireEmailVerified` when you need verified accounts. * Use `required: true` on the middleware for routes that must be authenticated. * Do not log API keys or raw tokens through the `logger` or a custom `fetch`. ## TypeScript The SDK ships with types. Import them with `import type`. ```typescript theme={null} import type { AdminClientOptions, ArchieLogger, VerifyTokenOptions, VerifyTokenResult, TokenUser, CreateUserParams, ListUsersParams, ListUsersResult, MiddlewareOptions, IAdminAuthModule, IAdminGraphQLModule, IAdminRestModule, AdminResult, } from '@archie/node-sdk'; ``` The SDK also re-exports these types from `@archie/js-sdk`: `User`, `Session`, `GraphQLResponse`, `ArchieError`, `AuthError`, `GraphQLError`, and `NetworkError`. The `ArchieAdminClient` class and the module classes `AdminAuthModule`, `AdminGraphQLModule`, and `AdminRestModule` are exported for `instanceof` checks, advanced typing, or mocking. `new ArchieAdminClient({ projectId: '...', apiKey: '...' })` is equivalent to `createAdminClient`. ## Resources > **[@archie/node-sdk on npm](https://www.npmjs.com/package/@archie/node-sdk)** > > Package page, versions, and install instructions. > **[Backend overview](/docs/backend)** > > Learn about the Backend Console and the APIs your project exposes. # React SDK Source: https://archie.com/docs/sdk/react-sdk Use @archie/react-sdk to add React hooks, a provider, and components to your app. It handles auth state, GraphQL and REST data fetching, real-time subscriptions, file uploads, and notifications. `@archie/react-sdk` provides React hooks, a provider, and components for your Archie backend. It builds on [`@archie/js-sdk`](https://www.npmjs.com/package/@archie/js-sdk) and adds reactive auth state, declarative data fetching, real-time subscriptions, file uploads with progress tracking, and route guards. Use it when you build a React frontend that talks to an Archie project. The package is available on [npm](https://www.npmjs.com/package/@archie/react-sdk). ## Installation Install the React SDK together with the JavaScript SDK. ```bash theme={null} npm install @archie/js-sdk @archie/react-sdk # or pnpm add @archie/js-sdk @archie/react-sdk ``` > [!NOTE] > > Peer dependencies: `react >=18` and `@archie/js-sdk`. ## Quick start Create a client with `createClient`, wrap your app in `ArchieProvider`, and use the hooks in any child component. ```tsx theme={null} import { createClient } from '@archie/js-sdk'; import { ArchieProvider, useAuth, useQuery, AuthGuard } from '@archie/react-sdk'; const archie = createClient({ projectId: 'your-project-uuid', apiKey: 'anon your-api-key', apiUrl: 'https://your-project.archiecore.com', environment: 'master', retries: 3, // automatic retry with backoff (inherited from js-sdk) timeout: 15_000, // request timeout in ms }); function App() { return ( <ArchieProvider client={archie}> <AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />}> <Dashboard /> </AuthGuard> </ArchieProvider> ); } function Dashboard() { const { user, signOut } = useAuth(); const { data, isLoading } = useQuery<{ orders: Order[] }>('{ orders { id total status } }'); return ( <div> <p>Welcome, {user?.email}</p> <button onClick={signOut}>Sign out</button> {isLoading ? <Spinner /> : <OrderList orders={data?.orders ?? []} />} </div> ); } ``` ## ArchieProvider `ArchieProvider` makes the Archie client and the auth session available to every hook through React context. ```tsx theme={null} import { createClient } from '@archie/js-sdk'; import { ArchieProvider } from '@archie/react-sdk'; const archie = createClient({ projectId: '...', apiKey: 'anon ...' }); function App() { return ( <ArchieProvider client={archie}> <MyApp /> </ArchieProvider> ); } ``` | Prop | Type | Description | | - | - | - | | `client` | `ArchieClient` | Client instance from `createClient()` | | `children` | `ReactNode` | Child components | On mount, the provider calls `client.auth.waitForInit()` to restore a persisted session. It then subscribes to `onAuthStateChange` and keeps the session in context up to date. All child hooks read `{ client, session, isLoading }` from this context. ### Client options The provider uses the options you pass to `createClient()`. Hooks inherit these options automatically. | Option | Type | Default | Description | | - | - | - | - | | `timeout` | `number` | `30000` | Request timeout in ms (0 = disabled) | | `retries` | `number` | `0` | Max retry attempts for transient errors | | `retryDelay` | `number` | `200` | Initial backoff delay in ms | | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation | Retries cover transient errors (429, 500, 502, 503, 504, and network failures) with exponential backoff and jitter. You can also pass a `logger`, such as `consoleLogger` from `@archie/js-sdk`. ## Hooks | Hook | Use it to | | - | - | | [`useArchieClient`](#usearchieclient) | Get the raw `ArchieClient` instance | | [`useAuth`](#useauth) | Read auth state and sign users in, up, and out | | [`useQuery`](#usequery) | Run GraphQL queries with caching and polling | | [`useMutation`](#usemutation) | Run GraphQL mutations and invalidate queries | | [`useSubscription`](#usesubscription) | Receive real-time GraphQL updates | | [`useFileUpload`](#usefileupload) | Upload files with progress tracking | | [`useRest`](#userest) | Call REST endpoints imperatively | | [`useRestQuery`](#userestquery) | Fetch custom REST endpoints with GET | | [`useRestMutation`](#userestmutation) | Send POST, PUT, PATCH, or DELETE requests | | [`useNotifications`](#usenotifications) | Show live in-app notifications and manage read state | ### useArchieClient `useArchieClient` returns the raw `ArchieClient` instance. Use it for advanced cases outside the provided hooks. ```tsx theme={null} const archie = useArchieClient(); // Direct access to any module const jwks = await archie.auth.getJWKS(); ``` > [!WARNING] > > `useArchieClient` throws if you call it outside `<ArchieProvider>`. ### useAuth `useAuth` exposes the full authentication state and operations. All async methods return `{ data/session, error }` and never throw. ```tsx theme={null} const { user, // User | null session, // Session | null isLoading, // boolean, true until the initial session check completes isAuthenticated, // boolean, shorthand for session !== null signIn, // (params) => Promise<{ session, error }> signUp, // (params) => Promise<{ data, error }> exchangeCode, // ({ code }) => Promise<{ session, error }> signOut, // () => Promise<void> confirmSignUp, // (params) => Promise<{ session, error }> recoverPassword, // (params) => Promise<{ data, error }> resetPassword, // (params) => Promise<{ data, error }> refreshSession, // () => Promise<{ session, error }> } = useAuth(); ``` `exchangeCode` takes a single-use code from a trusted server. A session created this way cannot be refreshed. All methods are memoized with `useCallback`, so you can pass them as props without causing extra re-renders. > **Sign in** > > The session updates in context automatically, so you do not need to set state yourself. > > ```tsx theme={null} > function LoginForm() { > const { signIn, isLoading } = useAuth(); > const [error, setError] = useState<string | null>(null); > > const handleSubmit = async (e: FormEvent) => { > e.preventDefault(); > const { session, error } = await signIn({ email, password }); > if (error) { > setError(error.message); > } > }; > > return ( > <form onSubmit={handleSubmit}> > {error && <p className="error">{error}</p>} > {/* inputs */} > <button disabled={isLoading}>Sign in</button> > </form> > ); > } > ``` > **Sign up and confirm** > > ```tsx theme={null} > const { signUp, confirmSignUp } = useAuth(); > > // Step 1: Register > const { data, error } = await signUp({ email, password }); > // data = { userId: '...', message: 'Check your email' } > > // Step 2: Confirm with the code from the email > const { session, error: confirmError } = await confirmSignUp({ > email, > code: '123456', > }); > ``` > **Password recovery** > > ```tsx theme={null} > const { recoverPassword, resetPassword } = useAuth(); > > // Step 1: Request a reset > await recoverPassword({ email }); > > // Step 2: Reset with the code > await resetPassword({ email, code: '123456', newPassword: 'newPass!' }); > ``` ### useQuery `useQuery` runs declarative GraphQL queries with caching, stale-while-revalidate, polling, and automatic deduplication. ```tsx theme={null} const { data, error, isLoading, isRefetching, refetch } = useQuery<T>( gql, // GraphQL query string variables?, // Record<string, unknown> options?, // UseQueryOptions ); ``` **Options** | Option | Type | Default | Description | | - | - | - | - | | `enabled` | `boolean` | `true` | Set `false` to skip the fetch (conditional queries) | | `refetchInterval` | `number` | none | Poll interval in ms | | `refetchOnWindowFocus` | `boolean` | `false` | Refetch when the window regains focus | | `onSuccess` | `(data: T) => void` | none | Called on a successful fetch | | `onError` | `(error: ArchieError) => void` | none | Called on error | **Return values** | Field | Type | Description | | - | - | - | | `data` | `T \| null` | Query result | | `error` | `ArchieError \| null` | Error if the query failed | | `isLoading` | `boolean` | True on initial load | | `isRefetching` | `boolean` | True on background refetch | | `refetch` | `() => Promise<void>` | Manually triggers a refetch | **Behavior** * **Stale-while-revalidate:** shows cached data immediately and refetches in the background. * **Deduplication:** components with the same query and variables share a single network request. * **Auto-refetch on auth change:** when the user's token changes, all active queries refetch. * **Auto-refetch on invalidation:** a mounted query re-runs as soon as its key is invalidated, for example by a `useMutation` with `invalidateQueries`. Concurrent consumers of the same key still share one request. * **Latest response wins:** a response already in flight when its key is invalidated is discarded, so pre-mutation data never repaints the view. * **Errors never blank the view:** a failed revalidation sets `error` but keeps the data already on screen. * **Variable tracking:** variables are compared with `JSON.stringify`, and the query refetches when they change. ```tsx theme={null} // Basic query const { data, isLoading } = useQuery<{ users: User[] }>('{ users { id email name } }'); // With variables const { data } = useQuery<{ user: User }>('query($id: ID!) { user(id: $id) { id email } }', { id: userId, }); // Conditional query (wait for userId) const { data } = useQuery<{ user: User }>( 'query($id: ID!) { user(id: $id) { id email } }', { id: userId }, { enabled: !!userId }, ); // Polling every 5 seconds const { data } = useQuery<{ stats: Stats }>('{ stats { activeUsers requests } }', undefined, { refetchInterval: 5000, }); ``` ### useMutation `useMutation` runs imperative GraphQL mutations and can invalidate cached queries. ```tsx theme={null} const { mutate, mutateAsync, data, error, isLoading, reset } = useMutation<T>( gql, // GraphQL mutation string options?, // UseMutationOptions ); ``` **Options** | Option | Type | Description | | - | - | - | | `onSuccess` | `(data: T) => void` | Called after a successful mutation | | `onError` | `(error: ArchieError) => void` | Called on error | | `onSettled` | `() => void` | Called after success or error | | `invalidateQueries` | `string[]` | Query substrings to invalidate after success. Every mounted `useQuery` whose key matches refetches. | **Return values** | Field | Type | Description | | - | - | - | | `mutate` | `(vars?) => void` | Fire-and-forget mutation | | `mutateAsync` | `(vars?) => Promise<T \| null>` | Awaits the result | | `data` | `T \| null` | Last successful result | | `error` | `ArchieError \| null` | Last error | | `isLoading` | `boolean` | True while executing | | `reset` | `() => void` | Clears data, error, and isLoading | ```tsx theme={null} const { mutate, isLoading } = useMutation<{ createUser: User }>( 'mutation($input: CreateUserInput!) { createUser(input: $input) { id email } }', { invalidateQueries: ['users'], onSuccess: (data) => toast.success(`Created ${data.createUser.email}`), }, ); const handleCreate = () => { mutate({ input: { email: 'new@user.com', name: 'John' } }); }; ``` > [!NOTE] > > `invalidateQueries` matches by substring. A pattern like `'id'` refetches every mounted query whose text contains `id`, and each one is a real network request. Use patterns distinctive enough to name the entity you changed. You can pass several patterns, because the whole array is applied as one batch and a query matched more than once still refetches only once. ### useSubscription `useSubscription` opens a real-time GraphQL subscription over WebSocket. It subscribes on mount and unsubscribes on unmount. ```tsx theme={null} const { data, error, connectionState } = useSubscription<T>( gql, // GraphQL subscription string variables?, // Record<string, unknown> options?, // UseSubscriptionOptions ); ``` **Options** | Option | Type | Default | Description | | - | - | - | - | | `enabled` | `boolean` | `true` | Set `false` to skip the subscription | | `onData` | `(data: T) => void` | none | Called on each new value | | `onError` | `(error: ArchieError) => void` | none | Called on error | **Return values** | Field | Type | Description | | - | - | - | | `data` | `T \| null` | Latest subscription value | | `error` | `ArchieError \| null` | Last error | | `connectionState` | `ConnectionState` | `'DISCONNECTED' \| 'CONNECTING' \| 'CONNECTED' \| 'RECONNECTING'` | ```tsx theme={null} function LiveOrders() { const { data, connectionState } = useSubscription<{ orderUpdated: Order }>( 'subscription { orderUpdated { id status total } }', ); return ( <div> <Badge>{connectionState}</Badge> {data && <OrderCard order={data.orderUpdated} />} </div> ); } ``` The underlying `@archie/js-sdk` realtime module reconnects automatically with exponential backoff. ### useFileUpload `useFileUpload` uploads files and tracks progress. ```tsx theme={null} const { upload, progress, isUploading, data, error, reset } = useFileUpload(); ``` | Field | Type | Description | | - | - | - | | `upload` | `(file: File \| Blob, opts?: FileUploadOptions) => Promise<void>` | Starts the upload | | `progress` | `number` | 0 to 100 | | `isUploading` | `boolean` | True while uploading | | `data` | `FileUploadResult \| null` | `{ url, fileId }` on success | | `error` | `ArchieError \| null` | Error if the upload failed | | `reset` | `() => void` | Clears all state | `FileUploadOptions` is `{ filename?, contentType?, providerType?, onProgress? }`. ```tsx theme={null} function FileUploader() { const { upload, progress, isUploading, data, error, reset } = useFileUpload(); return ( <div> <input type="file" onChange={(e) => { const file = e.target.files?.[0]; if (file) upload(file, { contentType: file.type, filename: file.name }); }} /> {isUploading && <ProgressBar value={progress} />} {data && <p>Uploaded: {data.url}</p>} {error && <p className="error">{error.message}</p>} {data && <button onClick={reset}>Upload another</button>} </div> ); } ``` ### useRest `useRest` returns the REST module for imperative calls to your REST API. ```tsx theme={null} const rest = useRest(); // rest.get<T>(path, options?) // rest.post<T>(path, body?, options?) // rest.put<T>(path, body?, options?) // rest.patch<T>(path, body?, options?) // rest.delete<T>(path, options?) ``` ```tsx theme={null} function ProductActions() { const rest = useRest(); const handleExport = async () => { const csv = await rest.get<string>('/api/products/export'); downloadCsv(csv); }; return <button onClick={handleExport}>Export CSV</button>; } ``` ### useRestQuery `useRestQuery` is a declarative hook for GET requests to custom REST endpoints. ```tsx theme={null} const { data, error, isLoading, refetch } = useRestQuery<T>(path, options?); ``` The options extend `RestRequestOptions` (`{ headers?, params?, signal? }`) with `enabled?: boolean`. | Field | Type | Description | | - | - | - | | `data` | `T \| null` | Response data | | `error` | `ArchieError \| null` | Error if the request failed | | `isLoading` | `boolean` | True on initial load | | `refetch` | `() => Promise<void>` | Manually triggers a refetch | ```tsx theme={null} const { data, isLoading } = useRestQuery<Product[]>('/api/products'); const { data: product } = useRestQuery<Product>(`/api/products/${id}`, { enabled: !!id }); ``` ### useRestMutation `useRestMutation` is an imperative hook for POST, PUT, PATCH, and DELETE requests. ```tsx theme={null} const { execute, data, error, isLoading, reset } = useRestMutation<T>(path, method?); ``` | Param | Type | Default | Description | | - | - | - | - | | `path` | `string` | none | REST endpoint path | | `method` | `'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | `'POST'` | HTTP method | | Field | Type | Description | | - | - | - | | `execute` | `(body?, overrideOptions?: RestRequestOptions) => Promise<T \| null>` | Executes the request | | `data` | `T \| null` | Last successful result | | `error` | `ArchieError \| null` | Last error | | `isLoading` | `boolean` | True while executing | | `reset` | `() => void` | Clears data, error, and isLoading | The optional `overrideOptions` argument of `execute` lets you pass custom `headers`, `params`, or `signal` for a single request. ```tsx theme={null} const { execute, isLoading } = useRestMutation<Product>('/api/products', 'POST'); const handleCreate = async () => { const product = await execute({ name: 'Widget', price: 9.99 }); if (product) router.push(`/products/${product.id}`); }; // With custom headers per request await execute(payload, { headers: { 'x-idempotency-key': uuid() } }); ``` ### useNotifications `useNotifications` subscribes to live in-app notifications and manages read state. It opens a WebSocket channel on mount and closes it on unmount. ```tsx theme={null} const { user } = useAuth(); const receiverId = user?.id ?? ''; const { notifications, latest, unreadCount, isLoading, error, connectionState, markAsRead, markAllAsRead, refetch, } = useNotifications(receiverId, { enabled: !!user, limit: 20 }); ``` **Options** | Option | Type | Default | Description | | - | - | - | - | | `enabled` | `boolean` | `true` | Set `false` to skip loading and subscription, for example while the user is null | | `unreadOnly` | `boolean` | `false` | The initial load returns only unread notifications | | `limit` | `number` | none | Max items to load in the initial fetch | | `onNotification` | `(n: Notification) => void` | none | Called for each new live notification, for example for toasts or sounds | **Return values** | Field | Type | Description | | - | - | - | | `notifications` | `Notification[]` | History plus live arrivals, most recent first, deduplicated by `id` | | `latest` | `Notification \| null` | Last notification received live (convenient for toasts) | | `unreadCount` | `number` | Live unread count, updated automatically | | `isLoading` | `boolean` | True during the initial history load | | `error` | `ArchieError \| null` | Error from the latest failed operation | | `connectionState` | `ConnectionState` | `'CONNECTING' \| 'CONNECTED' \| 'DISCONNECTED' \| 'RECONNECTING'` | | `markAsRead` | `(id: string) => Promise<void>` | Marks one notification as read and updates local state optimistically | | `markAllAsRead` | `() => Promise<void>` | Marks all loaded notifications as read and reconciles `unreadCount` with the server (normally 0) | | `refetch` | `() => Promise<void>` | Reloads the notification history | > [!WARNING] > > Always call the hook unconditionally, to follow the React rules of hooks. When there is no authenticated user, pass `receiverId=''` and `enabled: false`. The hook then does not load or subscribe. > **Infinite scroll** > > `useNotifications` pages backward through history without dropping or duplicating live items. > > ```tsx theme={null} > const { notifications, fetchMore, hasMore, isFetchingMore, error } = useNotifications(user.id, { > limit: 20, // page size for fetchMore() > }); > > // For example, an IntersectionObserver on a sentinel at the bottom of the list. > // Gate on `!error` so a failed page does not drive a tight auto-retry loop. > useEffect(() => { > if (hasMore && !isFetchingMore && !error) void fetchMore(); > }, [hasMore, isFetchingMore, error, fetchMore]); > ``` > > * `fetchMore()` loads the next older page and appends it (deduplicated, newest first). It does nothing when `!hasMore`, when a fetch is already in flight, when the hook is disabled, or when a previous `fetchMore` failed. Call `refetch()`, or change `receiverId` or `unreadOnly`, to retry. > * `hasMore` is true when more older records exist for the active filter. > * `isFetchingMore` is true while a `fetchMore()` call is in flight. Use it to drive a "loading older" indicator. > > Changing `receiverId` or `unreadOnly` resets pagination and re-seeds from the first page. > **Notification bell with unread badge** > > ```tsx theme={null} > import { useAuth, useNotifications } from '@archie/react-sdk'; > > function NotificationBell() { > const { user } = useAuth(); > const receiverId = user?.id ?? ''; > const { unreadCount, connectionState } = useNotifications(receiverId, { enabled: !!user }); > > return ( > <button aria-label="Notifications"> > <BellIcon /> > {unreadCount > 0 && <span className="badge">{unreadCount > 99 ? '99+' : unreadCount}</span>} > {connectionState !== 'CONNECTED' && <span className="dot reconnecting" />} > </button> > ); > } > ``` > **Notification list with mark as read** > > ```tsx theme={null} > function NotificationList() { > const { user } = useAuth(); > const receiverId = user?.id ?? ''; > const { notifications, isLoading, unreadCount, markAsRead, markAllAsRead } = useNotifications( > receiverId, > { enabled: !!user, limit: 20 }, > ); > > if (isLoading) return <Spinner />; > > return ( > <div> > <header> > <span>{unreadCount} unread</span> > <button onClick={() => markAllAsRead()} disabled={unreadCount === 0}> > Mark all read > </button> > </header> > <ul> > {notifications.map((n) => ( > <li > key={n.id} > className={n.isRead ? 'read' : 'unread'} > onClick={() => !n.isRead && markAsRead(n.id)} > > > <p>{n.actionText ?? n.notificationKey}</p> > <time>{new Date(n.createdAt).toLocaleString()}</time> > </li> > ))} > </ul> > </div> > ); > } > ``` > [!WARNING] > > Before you render `n.actionUrl` as a link, check that its scheme is `http:` or `https:`. Never allow `javascript:` or `data:` URLs. Treat `n.metadata` and `n.params` as untrusted server data. ## Components ### AuthGuard `AuthGuard` renders content based on authentication state and, optionally, user roles. ```tsx theme={null} <AuthGuard fallback={<LoginPage />} loadingComponent={<Spinner />} requiredRoles={['admin']}> <ProtectedContent /> </AuthGuard> ``` | Prop | Type | Default | Description | | - | - | - | - | | `children` | `ReactNode` | none | Content shown when authorized | | `fallback` | `ReactNode` | `null` | Content shown when the user is NOT authenticated or unauthorized | | `loadingComponent` | `ReactNode` | `null` | Content shown while the session loads | | `requiredRoles` | `string[]` | none | The user must have at least one of these roles | `AuthGuard` evaluates these rules in order: **1. Session is loading** It renders `loadingComponent`. **2. User is not authenticated** It renders `fallback`. **3. Roles required and the user has none of them** It renders `fallback`. **4. Otherwise** It renders `children`. You can nest guards, for example to protect an admin area inside an authenticated layout: ```tsx theme={null} <AuthGuard fallback={<LoginPage />}> <AppLayout> <AuthGuard requiredRoles={['admin']} fallback={<Unauthorized />}> <AdminPanel /> </AuthGuard> </AppLayout> </AuthGuard> ``` ## Error handling All `useAuth` methods return `{ data/session, error }` and never throw. For `useQuery`, `useMutation`, and the other data hooks, errors appear in the `error` field of the return object. These error classes come from `@archie/js-sdk`: | Class | When | | - | - | | `AuthError` | Authentication failures (sign in, sign up, token refresh) | | `GraphQLError` | GraphQL response errors | | `NetworkError` | Network failures and timeouts | | `ArchieError` | Base class for all Archie errors | ```tsx theme={null} const { error } = useQuery<{ users: User[] }>('{ users { id } }'); if (error) { if (error instanceof AuthError) { // Token expired, redirect to login } else if (error instanceof NetworkError) { // Show offline indicator } else { // Show generic error } } ``` ## Server-side rendering All hooks are safe to use with server-side rendering (SSR): * Hooks do not access `window` or `document` on the initial render. * `refetchOnWindowFocus` checks `typeof window` before it adds listeners. * `ArchieProvider` initializes the session asynchronously, so it does not block SSR. * `AuthGuard` renders `loadingComponent` during server render when the session is unknown. In frameworks like Next.js, create the client outside the component tree. ```tsx theme={null} // lib/archie.ts import { createClient } from '@archie/js-sdk'; export const archie = createClient({ projectId: process.env.NEXT_PUBLIC_ARCHIE_PROJECT_ID!, apiKey: process.env.NEXT_PUBLIC_ARCHIE_ANON_KEY!, }); // app/providers.tsx 'use client'; import { ArchieProvider } from '@archie/react-sdk'; import { archie } from '@/lib/archie'; export function Providers({ children }: { children: React.ReactNode }) { return <ArchieProvider client={archie}>{children}</ArchieProvider>; } ``` ## TypeScript All hooks are generic, so you can type each response. ```tsx theme={null} interface User { id: string; email: string; name: string; } // Typed query response const { data } = useQuery<{ users: User[] }>('{ users { id email name } }'); // ^? { users: User[] } | null // Typed mutation response const { mutateAsync } = useMutation<{ createUser: User }>('mutation ...'); const result = await mutateAsync({ input: { email: 'a@b.com' } }); // ^? { createUser: User } | null // Typed subscription const { data } = useSubscription<{ orderUpdated: Order }>('subscription ...'); // ^? { orderUpdated: Order } | null // Typed REST const { data } = useRestQuery<Product[]>('/api/products'); // ^? Product[] | null ``` ### Re-exported types `@archie/react-sdk` re-exports the essential types from `@archie/js-sdk`, so you can import from one package. ```tsx theme={null} import { // Client createClient, type ArchieClient, type ArchieClientOptions, // Auth types type Session, type User, type AuthSignUpParams, type AuthSignInParams, type AuthConfirmParams, type AuthRecoverParams, type AuthResetPasswordParams, type AuthEvent, type AuthEventCallback, // GraphQL types type GraphQLResponse, type GraphQLRequestOptions, type GraphQLRawRequest, // File types type FileUploadOptions, type FileUploadResult, type CsvUploadOptions, // Realtime types type Subscription, type SubscriptionCallbacks, type RealtimeEvent, type RealtimeChannel, type ConnectionState, type ConnectionStateCallback, // REST types type RestRequestOptions, // Error classes ArchieError, AuthError, GraphQLError, NetworkError, // Storage type StorageAdapter, BrowserLocalStorage, MemoryStorage, // Interfaces type IAuthModule, type IGraphQLModule, type IFileModule, type IRealtimeModule, type IRestModule, type IHttpClient, type Logger, type TokenAccessor, } from '@archie/react-sdk'; // React-specific prop types import type { ArchieProviderProps, AuthGuardProps } from '@archie/react-sdk'; ``` ## Health check The client exposes `ping()` to check that your project is reachable. ```tsx theme={null} const archie = useArchieClient(); const isUp = await archie.ping(); ``` ## Resources > **[@archie/react-sdk on npm](https://www.npmjs.com/package/@archie/react-sdk)** > > Package page, versions, and install instructions. > **[@archie/js-sdk on npm](https://www.npmjs.com/package/@archie/js-sdk)** > > The underlying client with retry, timeout, and custom fetch details. # Overview Source: https://archie.com/docs/support Get help with Archie — troubleshoot common issues, reach the support team, or join the community on Discord. Stuck on something, found a bug, or have a question about your account? This section shows you where to go and how to get an answer fast. > **[Troubleshooting](/docs/support/troubleshooting)** > > Fixes for common issues — sign-in, build failures, deployments, missing data, and API keys. > **[Contacting support](/docs/support/contacting-support)** > > Reach the Archie team on Discord or by email, and learn which one to use. > **[Community](/docs/support/community)** > > Join the Archie Discord to ask questions, share what you've built, and follow updates. ## Where to go | If you need to... | Go here | | - | - | | Fix something that isn't working | [Troubleshooting](/docs/support/troubleshooting) first — most common issues are covered there | | Ask a how-to question, report a bug, or request a feature | [Discord](https://discord.gg/EwKqQH5TQU) | | Get help with your account, billing, or private project data | [support@archie.com](mailto:support@archie.com) | | Report a security issue | [security@archie.com](mailto:security@archie.com) — never in Discord or public GitHub issues | | Talk to sales about Enterprise | [sales@archie.com](mailto:sales@archie.com) | ## What to include when you ask for help The more specific you are, the faster the team can help. Include: * Your account email * The project ID (you can find it in the project URL) * What you expected to happen, and what actually happened * The steps to reproduce the problem * A screenshot, if the problem is visible # Community Source: https://archie.com/docs/support/community The Archie Discord is where builders share work, ask questions, and talk to the team — plus LinkedIn for company updates. The Archie community lives on [Discord](https://discord.gg/EwKqQH5TQU). It's the fastest place to get help, share what you've built, and talk to the people building Archie. ## Discord channels ### General > **#announcements** > > Archie product updates and major news. > **#releases** > > New release notifications. ### Community > **#chit-chat** > > Casual conversation with other builders. > **#showcase** > > Share what you've built. > **#resources** > > Shared resources and materials. > **#introductions** > > New member introductions. > **#random** > > Off-topic. ### Product help > **#support** > > Questions about using Archie. > **#bug-report** > > Report bugs you've hit. > **#status** > > Service status and incidents. > **#feature-requests** > > Suggest features. [Join the Discord →](https://discord.gg/EwKqQH5TQU) ## LinkedIn Follow [Archie on LinkedIn](https://linkedin.com/company/archie/) for company news, hiring, and longer-form posts from the team. ## FAQ > **How do I join the Discord?** > > Click the [invite link](https://discord.gg/EwKqQH5TQU). The link doesn't expire. After joining, read the pinned welcome message and pick the channels that match what you're doing. > **Where should I ask product questions?** > > Use **#get-help** for questions about how to use Archie. Use **#backend** if your question is specifically about Archie Core. Both are watched by the Archie team and the broader community. > **What's the etiquette for showing my project?** > > **#showcase** is for finished or near-finished work. Include a one-line description, what you built it with (Pro / Team), and a link or screenshot. Feedback is welcome but not required. > **Where do I report a bug?** > > Drop it in **#bug-report** with the project ID, what you expected, what happened, and a screenshot. For account-sensitive bugs (billing, security, data), email [support@archie.com](mailto:support@archie.com) instead — see [Contacting support](/docs/introduction/community-support/contacting-support). > **Will the Archie team see my message?** > > Yes. The team reads every product channel daily and replies in **#get-help**, **#bug-report**, and **#feature-requests**. Response times vary; for time-sensitive account or billing issues, email is faster. # Contacting support Source: https://archie.com/docs/support/contacting-support Two ways to reach Archie support — Discord for most questions, email for anything account, billing, security, or private. There are two ways to reach Archie support: Discord for most questions, and email for anything account-related, billing, security, or otherwise private. > **[Discord](https://discord.gg/EwKqQH5TQU)** > > For most questions — how-to, bugs, feature requests. Fastest channel during business hours, with help from the Archie team and other builders. > **[support@archie.com](mailto:support@archie.com)** > > For account, billing, security, or anything you don't want public. Use email when the issue involves your specific account or sensitive data. ## FAQ > **How fast will I hear back?** > > Discord questions usually get a reply within an hour during business hours. Email response time depends on your plan: Free is best-effort, Pro is 1–2 business days, Team is 24 hours, Enterprise has dedicated CSM coverage with faster SLAs. > **What should I include in a support email?** > > Include your account email, the project ID (visible in the project URL), what you expected, what actually happened, repro steps, and a screenshot if the issue is visible. The more specific you are, the faster the team can route and resolve. > **Should my question go to Discord or email?** > > Public, generic, or how-to questions go to Discord — other builders may already have the answer. Anything tied to your account, billing, security, or private project data goes to email so we can verify your identity and pull up your account. > **Where do I report a security issue?** > > Email [security@archie.com](mailto:security@archie.com). Don't post security disclosures in Discord or public GitHub issues. The team acknowledges within 24 hours and follows a coordinated disclosure policy. > **What about Enterprise sales?** > > Email [sales@archie.com](mailto:sales@archie.com) for Enterprise pricing, custom contracts, compliance questions (SOC 2, HIPAA, GDPR), or pilot programs. The team responds within 1 business day. See [Enterprise overview](/docs/introduction/enterprise/overview) for what's covered. # Troubleshooting Source: https://archie.com/docs/support/troubleshooting Common Archie issues and their fixes — sign-in, build failures, deployment problems, missing data, and more. This page covers the most common issues builders run into. If your problem is not here, check [contacting support](/docs/introduction/community-support/contacting-support). ## I cannot sign in * **Verification email did not arrive** — check spam, then resend from the sign-in page. If it still does not arrive after 5 minutes, the email may be blocked by your domain. Try a different email or contact support. * **Forgot password** — click **Forgot password** on the sign-in page. Reset link arrives by email. * **OAuth provider error** — if Google or GitHub sign-in fails, try the email/password flow instead. Check that the OAuth account uses an email already registered with Archie. * **Locked out** — too many failed attempts trigger a temporary lockout. Wait 15 minutes or contact support. ## My build failed Open **Project → Activity** and find the build entry. The error message points to which agent failed and why. Common causes: * **Insufficient credits** — check [Plans and credits](/docs/introduction/getting-started/plans-and-credits). Top up or upgrade. * **Plan inconsistencies** — a module references a service that does not exist, or a user type with no permissions. Open the plan and resolve the warnings before re-running. * **Conflicting hand edits** — if you edited code outside Archie and the build cannot reconcile, the build error names the conflict. Resolve in the IDE, then re-run. For repeated build failures with no obvious cause, contact support with the project ID and the build log. ## My deployment is broken * **404 on the deployed URL** — the deployment may not have finished. Wait 1–2 minutes after a deploy, then refresh. * **Custom domain not working** — DNS records may not have propagated. Verify in [project settings](/docs/features/backend/settings/network). Propagation can take up to 48 hours. * **Recent change broke something** — roll back to a previous deployment from the deployment history. See [deployment](/docs/features/frontend/deployment). ## My data is missing or wrong * **Migration did not apply** — check **Backend → Environments**. If the migration is pending, run it manually. See [environments](/docs/features/backend/environments/overview). * **Wrong environment** — confirm you are looking at production data, not preview or staging. The environment switcher is in the project nav. * **RLS or permissions** — if a user cannot see data they should, check [role-based access](/docs/features/backend/app-services/role-based-access). ## The visual editor is not selecting elements * Click **Start inspecting** first. * Server-only or background elements (computed in middleware, hydrated late) may not be inspectable. Edit those in the IDE. * Hard-refresh the preview to reload the inspector bridge. ## My API key is rejected * API keys are scoped per environment. Check that the key was generated in the environment you are calling. * Confirm you are sending the key as a Bearer token: `Authorization: Bearer <key>`. * See [API keys](/docs/features/backend/settings/api-keys) for the full troubleshooting flow. ## Still stuck? If none of the above resolves your issue, [contact support](/docs/introduction/community-support/contacting-support) with: account email, project ID, what you expected, what happened, and reproduction steps. # Welcome to Archie Source: https://archie.com/docs/welcome Archie turns ideas into production-ready, full-stack apps. It defines your app first — then builds it. Clarity before code. **Archie turns ideas into production-ready, full-stack apps — by defining them before it builds them.** Most AI app builders jump from a prompt straight to code. You get something that looks right in minutes, then spend weeks fighting it, because nothing underneath defines how the app should work. Archie starts somewhere else. It turns your idea into a structured plan, lets you shape it, and only then generates a real application from it: frontend, backend, database, APIs, and hosting. We call this **clarity before code**. > **[Quick start](/docs/introduction/getting-started/quick-start)** > > Create your account and go from your first prompt to a generated app, step by step. > **[Archie vs. other tools](/docs/faq/archie-vs-other-tools)** > > How Archie compares to Lovable, Bolt, Base44, v0, Replit, and Cursor. ## What makes Archie different > **Definition first** > > Archie generates a plan — modules, user types, services, and integrations — before it writes any code. You steer it while changes are still cheap. > **Full-stack by default** > > Every app ships with a real backend: a relational database, GraphQL and REST APIs, authentication, and role-based access, all on Archie Core. > **Built to ship** > > The output is a deployed application on a global CDN, not a throwaway demo. Keep building on it with chat, the visual editor, code, or GitHub. ## How Archie works **1. Describe your idea** Write what you want to build in plain language. Archie may ask clarifying questions, and you can attach reference material. See [Best practices](/docs/introduction/getting-started/prompting-best-practices). **2. Shape the plan** Archie generates a structured [plan](/docs/features/plan/overview): an overview, the modules your app needs, the user types who use it, the system services it relies on (auth, file storage, roles), and the integrations it talks to. Edit it directly or through the plan chat until it matches what you mean. Archie picks the tech stack for you. **3. Generate the frontend and backend** From the plan, you run two independent tracks. **Generate Frontend** lays out every screen in a sitemap, then builds a click-through [prototype](/docs/features/frontend/prototype/overview) from the screens you choose. **Generate Backend** builds the database schema and APIs on [Archie Core](/docs/features/backend/overview). See [When to run the build](/docs/features/plan/when-to-build). **4. Go full-stack** Once the prototype looks right, **Generate Full-Stack** turns it into a real, deployed application: a React frontend on a global CDN, connected to a backend with a relational database, GraphQL and REST APIs, authentication, and role-based access. **5. Keep iterating** Talk to Archie, use the visual editor, or edit the code. For structural changes — a new module, a new user type — go back to the plan and generate again. ## Why the plan comes first The plan is structured text, so it's cheap to change. A build is expensive: it costs time and credits, and a build from the wrong idea gets thrown away. Archie moves your decisions to the point where they cost the least. It's also why Archie apps hold up after the demo. When an app is generated straight from a prompt, nothing defines its structure — who the users are, what each can do, how the data fits together — and that's why so many AI-built apps stall before they become real products. In Archie, the plan provides that structure before any code exists, and the build follows it. > [!TIP] > > Spend your time on the plan. A few extra minutes of editing there usually saves a full rebuild later. ## Develop and debug your app **Talk to Archie** The chat is the main way to edit the running app. Describe a change in plain language ("add a search field to the orders page", "make the dashboard sidebar collapsible") and Archie applies it across the relevant files. The chat understands your project's structure and writes code that matches the existing patterns. See [Talking to Archie](/docs/features/frontend/talking-to-archie). **Visual editor** The visual editor is point-and-click. Click an element in the live preview and the inspector shows its properties — text, color, padding, position — with changes applied live. Use it for layout tweaks, copy edits, image swaps, and styling. For precise visual requests, mark up the preview with [Draw mode](/docs/features/frontend/draw-mode) and send your marks to chat. See [Visual editor](/docs/features/frontend/visual-editor). ![Visual editor with an element selected and the inspector panel](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/visual-editor-inspector.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f6362dfa5a629c0bf899758b936b9eb6) **Code and GitHub** Edit code directly in the built-in [code editor](/docs/features/frontend/code-editor), or connect GitHub for two-way sync: Archie pushes generated code to a repository you choose and pulls back commits made outside Archie, so you can work in Cursor, Claude Code, or any editor. See [Organization integrations](/docs/introduction/organizations/organization-integrations). **Activity and logs** The activity feed shows every action across your project — builds, deployments, plan edits, and comments. Backend [Logs](/docs/features/backend/logs), with live streaming across services and environments, are coming soon. **Project structure** Generated frontends follow a predictable `src/` structure: `app/` for routes, `components/` for reusable UI, `domain/` and `infrastructure/` for business logic and data access, plus `hooks/`, `lib/`, and `styles/`. The data model lives on the backend, which exposes it through GraphQL and REST automatically. See the [Backend overview](/docs/features/backend/overview). ![Project file tree with generated source directories](https://mintcdn.com/archie-e998dbf6/944gHGp2sN9dUVTM/features/plan/file-tree-directories.png?fit=max\&auto=format\&n=944gHGp2sN9dUVTM\&q=85\&s=f71c695355d3561016861c9e2c5cd58b) ## Explore the docs > **[Plan](/docs/features/plan/overview)** > > The definition of your app: modules, user types, services, and integrations. > **[Frontend](/docs/features/frontend/overview)** > > UI architecture, the prototype, the generated React app, and the editors you use after the build. > **[Backend](/docs/features/backend/overview)** > > Archie Core: data model, auto-generated APIs, auth, roles, environments, and integrations. > **[Assets](/docs/features/assets/overview)** > > Upload and manage the images, videos, and documents your app uses, and reference them anywhere. ### Common tasks > **[Edit your data model](/docs/features/backend/data-model/overview)** > > Add tables, fields, and relationships. APIs regenerate automatically. > **[Add an integration](/docs/features/backend/integrations/overview)** > > Stripe, SendGrid, Twilio, Slack, Shopify, or build your own. > **[Manage environments](/docs/features/backend/environments/overview)** > > Branch development and staging environments, compare schemas, and merge changes to production. > **[Get an API key](/docs/features/backend/settings/api-keys)** > > Authenticate external clients with scoped, per-environment API keys. ## Who Archie is for Archie is for founders, product teams, designers, and developers who want to move fast without ending up with code they have to throw away. You don't need to know how to code — you describe the app and Archie builds it. If you do write code, you get full access to the code, the data model, and the infrastructure. > [!TIP] > > Start on the **Free** plan with no credit card. It covers plan and prototype generation. See [Plans and credits](/docs/introduction/getting-started/plans-and-credits) for what each plan includes. > [!NOTE] > > **Coming soon: Specifications.** A detailed layer between the plan and the code — functional, visual, and technical design documents that the build agents read before writing anything. When it ships, the flow becomes prompt → plan → specifications → code. ## Need help? Browse the [FAQ](/docs/faq/getting-started), join the [Discord community](/docs/introduction/community-support/community), or [contact support](/docs/introduction/community-support/contacting-support).