Documentation
Hosted sign-in for your website or app. People sign in with a one-time code sent by email or text message; there are no passwords to store. Your site gets a signed ID token it can trust, through standard OpenID Connect, so any OpenID Connect library connects with one setting: the discovery URL.
Getting access
Sign in with your ScaledApps account and pick your organization: its authto.it
account starts on the Free plan (one site). Under Add site, give the site's name and the addresses it should
return to after sign-in (and, optionally, after sign-out). Return addresses must be
https; http is accepted only for localhost and 127.0.0.1, for development.
You receive a client ID and, for a server-side site, a client secret (shown once; keep it on your
server). Browser and mobile apps that can't keep a secret are set up as public clients and use PKCE alone. Each site has its own
people: the same email on two sites is two unrelated accounts, and each gets its own permanent ID.
A site is either invite-only (only people added to it can get a code, for staff areas) or open (anyone who proves their email or phone becomes one of the site's people on first sign-in). People on a site can be given a name and a role, which your site receives as claims, and can be suspended: a suspended or removed person is signed out everywhere and their refresh tokens stop working.
Per site you also choose:
- Sign-in methods: email, text message, or both.
- Branding for the sign-in page: the name shown, a logo, an accent colour, a support email, and links to your privacy policy and terms.
- How long people stay signed in on authto.it (a week unless you ask otherwise) and how long refresh tokens last (30 days unless you ask otherwise).
A new client secret can be issued at any time. The old one can keep working for up to 30 days alongside it, so you can deploy the new one without an outage.
Connect
| Discovery | https://letme.authto.it/.well-known/openid-configuration |
|---|---|
| Issuer | https://letme.authto.it |
| Endpoints | /authorize, /token, /userinfo, /logout, /jwks.json on the issuer |
| Flow | Authorization code (response_type=code, query response) with PKCE (S256); refresh tokens. No implicit or hybrid flow. |
| Scopes | openid (required), email, phone, profile (name and role), offline_access (refresh token) |
| Client authentication | client_secret_basic or client_secret_post; browser and mobile apps use PKCE alone |
| Signing | RS256, keys at https://letme.authto.it/jwks.json. Pick the key by the token's kid; keys are rotated, with the previous one published until the tokens it signed have expired, so fetch the set rather than copying a key. |
1. Send the person to sign in
GET https://letme.authto.it/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://your.site/auth/callback
&scope=openid%20email%20profile
&state=RANDOM_PER_BROWSER
&nonce=RANDOM_PER_BROWSER
&code_challenge=BASE64URL(SHA256(VERIFIER))
&code_challenge_method=S256
redirect_uri must match one you registered exactly; an unknown client ID or return address shows an error page and
never redirects. code_challenge is required for public clients and recommended for everyone. Add
login_hint= an email or phone number to fill it in on the sign-in page. Add prompt=login to always ask
for a code, or prompt=none to check silently (you get error=login_required back if they aren't signed
in). Someone already signed in to your site on authto.it comes straight back without a code.
Other problems come back to your redirect_uri as error=…&error_description=…&state=…:
unsupported_response_type, invalid_scope (no openid) or invalid_request.
A sign-in left unfinished for 30 minutes, or continued in a different browser from the one that started it, has to be started
again from your site.
2. Exchange the code
They return to redirect_uri?code=…&state=…&iss=https://letme.authto.it. Check state, then
within 60 seconds:
POST https://letme.authto.it/token
Authorization: Basic BASE64(CLIENT_ID:CLIENT_SECRET)
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=CODE&redirect_uri=https://your.site/auth/callback&code_verifier=VERIFIER
Server-side sites may send client_id and client_secret in the form body instead; public clients send
client_id and the code_verifier with no secret. The answer has id_token and
access_token (each valid an hour), token_type, expires_in, scope, and with
offline_access a refresh_token. A code works once. Errors are invalid_client (401),
invalid_grant or unsupported_grant_type.
3. Check the ID token
Verify the RS256 signature with the published keys, and that iss is the issuer above, aud is your
client ID, exp is in the future and nonce is the one you sent. Its claims:
sub: the person's permanent ID on your siteemail,email_verifiedorphone_number,phone_number_verifiednameandrole(withprofile), when set for themauth_time,sid,amr(["otp"]for an emailed code,["sms"]for a texted one)
A person signs in with either an email address or a phone number, so they have one or the other, not both.
GET or POST /userinfo with the access token as a bearer token returns the same person claims; it stops
working once the person is suspended.
Refresh and sign-out
A refresh token is replaced every time it is used (grant_type=refresh_token). Using one twice ends that sign-in, since
it means it was copied. To sign someone out of authto.it too, send them to
https://letme.authto.it/logout?client_id=…&post_logout_redirect_uri=…&state=…; an
id_token_hint can stand in for client_id. They return to post_logout_redirect_uri (with your
state) only if it is a registered sign-out address; otherwise authto.it shows a signed-out page.
The sign-in page
People sign in on letme.authto.it, on a page showing your site's name, logo and colour. They choose email or text (if your site offers both), enter their address or number, and type the code they receive. Phone numbers are stored in international format; a ten-digit number is read as US/Canada. Texted codes end with a line that lets phones offer to fill in the code automatically.
Sign in with Google or Microsoft
A site can also offer Continue with Google and Continue with Microsoft (site settings,
"Also sign in with"). The account must have an email address the provider vouches for: Google's verified addresses, personal
Microsoft accounts, and work or school accounts whose organization has verified the address's domain. That address is then
treated exactly like one proven with a code — on an invite-only site it must be on the People list — and the site receives
the same person (sub) whichever way they sign in. The ID token's amr is fed for these
sign-ins, otp or sms for codes.
Codes and limits
- Six digits, valid ten minutes, five tries each, usable once. A new code replaces the previous one.
- Sending is limited per address and per network; wrong codes are limited per address per day.
- Emails come from noreply@scaledapps.com as "Your Site via authto.it". A number agrees to receive texts from your site once, before its first code.
- Replying STOP to a text stops all texts from authto.it's number, for every site; replying START turns them back on. While a number is opted out, asking for a code by text says so instead of pretending it was sent.
- On an invite-only site, an address that isn't listed is told the same thing as one that is, and nothing is sent.
Plans
- Free: 1 site, 1,000 people signing in a month, email codes.
- Starter: 3 sites, 10,000 people signing in a month, email codes, your logo and colours.
- Pro: 10 sites, 50,000 people signing in a month, 200 text-message codes a month, your logo and colours.
More people than the plan includes are never turned away; the dashboard asks you to change plan. Plans are changed in ScaledApps.
Management API
Issue a key on the dashboard's API keys page (owners and admins) and send it as
Authorization: Bearer as_…. Scopes: sites:read, sites:write, people:read,
people:write, events:read. Base address https://authto.it/api/v1.
GET /sites,GET /sites/{id};POST /sites(name,redirect_uris, optionalclient_type,signup,methods, …) returns theclient_secretonce;PATCH /sites/{id}changes what you send;POST /sites/{id}/secretissues a new secret (grace_daysfor the old one, default 7).GET /sites/{id}/people?q=,POST /sites/{id}/people(identifier, optionalchannel,name,role),PATCH /sites/{id}/people/{person}(name,role,status: active or suspended),DELETE /sites/{id}/people/{person}. A person'sidis thesubyour site receives.GET /sites/{id}/events?type=&limit=: recent activity, addresses masked.
Errors: {"error": {"code": "…", "message": "…"}} — invalid_key, rate_limited,
not_found, invalid_request, site_limit, public_client.
AI tools (MCP)
Assistants that speak the Model Context Protocol can manage sites and people at https://authto.it/mcp with the same key.
Client secrets are never given to AI tools: a server-side site made there gets its secret on the dashboard.
claude mcp add --transport http authto https://authto.it/mcp --header "Authorization: Bearer as_…"