Skip to content

Accounts & sign-in ​

Since 14 October 2026, every application route requires a session. The landing page (/, /en, /pt), this documentation, the legal notices (/privacidad, /terminos) and the roles page stay public. There are no passwords: the account is created the first time a person signs in.

How to sign in ​

One screen, /login:

  1. Enter your email. We send a 6-digit code and a link in the same message; either works and both expire after 5 minutes. The response is identical whether or not the account exists.
  2. Type the code (or open the link). If the email had no account, it is created at that moment. Three failed attempts invalidate the code; request another.

Sign-in is by email only (code or link), sent from acceso@futuros.xyz through AgentMail. There is no Google sign-in: the button and the link in /cuenta are off unless the operator enables them (VITE_AUTH_GOOGLE=1 plus Google credentials).

After signing in you return to the page you asked for (?next=), validated as a same-site route; any other value sends you to /atlas.

First sign-in ​

One optional step: display name, language, and a note on what we store and how to request deletion. It can be skipped and does not reappear on that device.

What we store ​

DataWhereWhy
Email, display name, language, profile picture (only if Google is enabled)Neon Postgres, AWS São Paulo (sa-east-1)Identify you and send the code
Active sessions (browser, IP, date)Same databaseClose them; they expire after 30 days without activity
Codes and link tokensSame database, hashed, single useVerify access
Access events (sign-in, sign-out, failed codes, limits) with a hashed IPSame databaseSecurity; kept 12 months
Preferences, saved items (watchlist, views, compare trays, portfolios) and civic acts (positions, amendments, comments, consensus answers, pilot votes)Same databaseFollow you across devices; identified participation
auth_login, auth_signup, auth_onboarding, preferences_migrated, saved_item_created, civic_action, account_export_requested, account_deletedPostHogFunnel measurement; no email

What we do not store: passwords (there are none), the email in logs or analytics, the code in clear.

The legal detail is in the privacy notice (§2, §5, §8, §9).

Your workspace ​

While signed in, your preferences, saved items and civic acts live on your account and follow you across devices:

WhatWhere
Language, tour, chat model, interface settings, install promptuser_preference (closed key list; every value is validated and re-shaped on the server)
/mercados watchlist, /explorar views, pilot and persona compare trays, pilot portfoliossaved_item (up to 200 per kind)
Proposal positions and amendments, /consenso answers, comments, pilot votescivic_action (see Identified participation)

One-time migration of what you already had in the browser. The first account that signs in on a browser uploads what was in localStorage (watchlist, views, trays, portfolios and preferences) and then removes it or renames it to <key>.migrated. It is always "insert if absent": a new, empty or stale device never overwrites what the account already holds. A second account on the same browser inherits nothing. Keys the server refuses (altered or oversized values) are dropped; a transient network failure is retried at the next boot. Earlier anonymous votes, amendments and answers cannot be attributed and are not migrated; keys that hold emails in clear (futuros_comments_v1, futuros_comment_gate_v1) are deleted. Staying local, on purpose: the language, chat model and tour marks as a boot cache (read before the session loads), chat feedback and the demo queues.

Sessions, export and deletion ​

Everything lives on /cuenta; from any page, the header avatar opens the menu with Your account and Sign out (on narrow screens, at the top of the ≡ menu).

Sessions. The page lists every browser with an open session (browser and OS inferred from the User-Agent, last activity). Each session can be closed on its own; Sign out on every device deletes all of the user's session rows, the current one included. Sessions last 30 days and renew with use. If the operator enables Google, it can be linked and unlinked from the same page (the email code stays available).

Export. Request export (POST /api/account/export) builds a JSON file with profile, conversations and messages (with their citations as shown), preferences, saved items, civic acts and audit events, and emails the account address a signed link valid for 24 hours. Downloading needs both: the unaltered link and the open session of the same account it was issued for (another account, or no session, gets nothing). Above 4 MB the file is split into parts (the first with everything but conversations; the rest, whole conversations) and the email carries one link per part. One export per hour per person.

Deletion. Delete my account (POST /api/account/delete) requires typing the account email (case and whitespace are ignored). If you are the only owner of an organisation that has other members, the request is refused (409) until you transfer ownership. On confirmation:

WhenWhat happens
Immediatelydeleted_at is set; every session is closed; API keys are disabled and embed tokens revoked; any sign-in attempt is refused; the account_deleted analytics event is recorded under a pseudonym, never the email.
7 days later (daily cron /api/account-purge, guarded by CRON_SECRET)Profile, sessions, linked Google account, preferences, civic acts, private conversations with their messages and private saved items are erased. Conversations and items shared with an organisation stay with it with an empty owner ("former member"). Audit events keep an empty actor and the identifier replaced by a hash.

The same cron deletes audit events older than 12 months. There is no "undo": during the 7 days the account is closed, not suspended. Data-trust receipts are not deleted (they are the person's own provenance record, already pseudonymous).

Organizations ​

An organization brings a team together: it shares conversations and saved items among its members. Manage it at /cuenta/organizacion; the header selector (or the ≡ menu sheet on narrow screens) sets the active space, and Personal is the space without an organization.

RoleCan
OwnerEverything: change roles, transfer ownership, delete the organization, plus what admins can.
AdminEdit the name and identifier, invite, cancel invitations, remove members (not admins or owners), and edit or delete shared items, including those of former members.
MemberSee what is shared, create their own, and share it.
  • Create. Anyone; they become the owner. The identifier (the URL part) can be changed once. Limits: 5 organizations created per person per day, 20 per account.
  • Invite. An owner or admin enters an email and a role (admin or member). The invitation arrives by email (in the invitee's language if they already have an account, otherwise the inviter's) with a link to /invite/<id>, expires after 7 days and can only be accepted by the account with that same email. At most 50 pending per organization and, against abuse, 20 invitations per hour per person and 30 per hour per organization. If the email cannot be sent, the invitation is cancelled and you are told.
  • Share. Each conversation or saved item is private until its author shares it with the active organization (switch on /cuenta/organizacion, or in the /chat sidebar for conversations). You can only share with an organization you belong to. The author, owners and admins can edit or delete it; everyone else only sees it.
  • Leaving or being removed. What you shared stays with the organization, without an owner, and shows as from a former member; private items remain yours. The last owner cannot leave or change their own role: transfer ownership first (the rule runs inside a single SQL statement, so two owners leaving at once cannot leave the organization ownerless).
  • Delete the organization (owner only, by typing its identifier). The organization, its members and invitations are deleted. Conversations and items each member shared go back to being private to their author; those of former members are deleted with the organization. This cannot be undone.
  • Audit. Creation, edits, invitations (created, cancelled, accepted), role changes, transfers, departures and removals are written to the audit log, without emails.

API keys and embed tokens ​

Outside the browser there is no cookie, so you use credentials you issue yourself:

  • API keys (ft_live_<48 hex>, /cuenta/claves): for the public API /api/v1/* (and /data.json, /feeds/*), the MCP server /api/mcp and chat over the API /api/chat. Shown once and stored hashed. Ceiling of 600 requests per minute per key (charged on every request) and 500 chat messages per day per account. Chat by key is saved to your history in a conversation titled by the client or "API". At most 20 active keys. Guides: public API, MCP.
  • Embed tokens (ft_embed_…, /cuenta/embeds): for /embed/stat, /embed/chart and /embed/board iframes on other sites. You choose the allowed routes and the expiry (365 days by default, maximum 730); they open only those routes and the data files those widgets read. At most 25 active. Guide: Embeds.

Revoking a key or a token takes effect within a minute. If you delete your account, your keys are disabled and your tokens revoked immediately.

The access wall (for operators) ​

The security boundary is Vercel's Routing Middleware (middleware.ts, logic in server/auth/wall.ts), not the UI. For every request outside the public list:

  • It reads the session cookie and validates it with an internal call to the same deployment's /api/auth/get-session. A positive result is remembered 60 s in memory (key: hash of the token); a negative one never.
  • Without a valid session: a document navigation gets 302 /login?next=<path>; any fetch, API, embed or MCP request gets 401 {"error":"auth_required"} with WWW-Authenticate: Bearer.
  • If validation fails with an error (3 s timeout, 500, invalid JSON), it closes: 302/401 plus one [auth] log line. It never passes the request through and never returns a 500.
  • Machine credentials: API keys (Authorization: Bearer ft_live_… or X-Api-Key) are accepted on /api/v1/*, /data.json, /feeds/*, /api/mcp and /api/chat; embed tokens (?et= or X-Embed-Token) on /embed/* and on the data files those widgets read. A credential on the wrong surface is refused without a lookup. The wall verifies them with an internal call to the same deployment's /api/machine-verify (the platform origin, never the request's Host), caches a positive verdict for 60 s and never a negative one. The per-key ceiling is charged on every request, cached verdict included. If verification is impossible (no internal secret, database down) it fails closed: 503 with Retry-After, never a pass.
  • /api/machine-verify is on the public list only so the wall can call it; the function itself answers 401 to any request that does not carry AUTH_INTERNAL_SECRET (constant-time comparison). It does not use the scrape observer's exemption header, which is derivable when SCRAPE_INTERNAL_TOKEN is unset.

Exact public list: /, /en, /pt, /login, /invite/*, /privacidad, /terminos, /roles*, /api/auth/*, /api/health, /manifest.webmanifest, /sw.js, /robots.txt, /sitemap*.xml, /favicon*, /assets/*, /fonts/*, /og/*, /.well-known/*, /icons/*, the investor deck (/deck, /deck.html, /api/deck-access, /api/deck-slide), the outreach hop /o/* (/api/o), and the endpoints that carry their own secret: /api/alerts-digest (cron), /api/access-webhook (Stripe), /api/questions and /api/chat-turns (operator token). OPTIONS requests pass.

Environment variables:

VariableEffect
AUTH_WALL_DISABLED=1Emergency switch: disables the wall entirely. Logged on every cold start. Must be absent in production.
AUTH_INTERNAL_SECRETRequired for API keys and embed tokens (16+ characters; production and preview). Without it every machine credential gets 503.
EMBED_TOKEN_PEPPERPepper for the embed-token hash. Without it tokens can be neither created nor verified (503).
API_KEY_MAX_PER_MINUTE, CHAT_USER_DAILY_MAXAdjustable ceilings: 600 per key per minute, 500 chat messages per account per day.
AUTH_INTERNAL_ORIGINOrigin the wall calls to validate the session; defaults to the deployment's own.
VERCEL_AUTOMATION_BYPASS_SECRETWhen set, the wall forwards it so a protected preview can validate itself.
AUTH_SIGNUP_DISABLED, AUTH_GOOGLE_DISABLEDAuth-server levers; /login shows the matching error.

Status ​

CapabilityStatus
Access wall, /login by code and link (email via AgentMail), onboardingLive
Per-account chat historyLive
Per-account preferences and saved items, identified participation, /cuenta panel (profile, sessions, export, delete)Live
Organizations (roles, invitations, sharing), API keys, embed tokensLive

Every figure with its source — traceability is the contract.