Integrations

Everything Baselyra talks to, and nothing it does not.

Every integration on this page is implemented in the repository and configured by environment variables. A provider whose credentials are not set is not merely hidden — it is not offered, so a client that renders “continue with” buttons from GET /auth/v1/providers only ever draws buttons that work.

Sign-in providers

Seven, each with its own quirks handled

The differences between OAuth providers are not cosmetic, and a client that pretends one shape fits all is broken for about half of them. Each provider is a descriptor — where its authorize and token endpoints are, whether it accepts PKCE, what its userinfo call needs, and how to read a profile out of the answer — never a branch buried in shared code.

Google

OIDC. PKCE, and the address is used only when Google says it is verified.

GitHub

The public email on /user is usually null, so the address comes from a second call to /user/emails.

LinkedIn

OIDC userinfo. Does not accept PKCE, so no challenge is sent.

Facebook

Returns nothing unless a fields list is named on the request.

Instagram

Graph host reads the token from the query string, not a header.

TikTok

Spells the credential client_key and nests the user under data.user.

Envato

No OIDC endpoint: identity from /whoami, profile from account.json, address from email.json.

.env
# A provider appears the moment both halves are set.
BASELYRA_OAUTH_GOOGLE_CLIENT_ID=…
BASELYRA_OAUTH_GOOGLE_CLIENT_SECRET=…

BASELYRA_OAUTH_GITHUB_CLIENT_ID=…
BASELYRA_OAUTH_GITHUB_CLIENT_SECRET=…

# Where a completed sign-in may be sent back to. An origin that is
# not on this list is refused outright rather than redirected to,
# which is the difference between an allow-list and an open redirect.
BASELYRA_OAUTH_REDIRECT_ALLOWLIST=https://app.example.com,https://staging.example.com
the whole client-side flow
// Which providers this instance actually has credentials for
const res = await fetch(`${BASE}/auth/v1/providers`);
const { providers } = await res.json();
// [{ id: 'google', name: 'Google', url: '…/authorize?provider=google' }]

// Send the browser there. The callback comes back to your site with
// the tokens in the URL fragment, so they never reach a server log
// or a Referer header the way a query string would.
location.href = provider.url + '&redirect_to=' + encodeURIComponent(SITE);
An unverified address never attaches to an existing account. Only a provider that asserts it controls the address may link a sign-in to a Baselyra user who already exists — otherwise anyone who can create an account somewhere with your email address could walk into yours.
State is single-use by construction. The callback claims it with a DELETE … RETURNING, so a replayed callback finds nothing however fast it comes back. That is a property of the statement, not of a check a second request could race past.

SMS

Six ways to deliver a one-time code

Phone sign-in sends a six-digit code and verifies it against a hash bound to that number. The sending is one fetch per provider, with no SDK: credentials arrive keyed by the environment variable name you set, so a missing one names itself in the error rather than being translated through a mapping table nobody reads.

Twilio

Form-encoded Messages resource. SMS_FROM may be a number or a messaging service SID; the right field is chosen for you.

Vonage

JSON, key and secret in the body.

MessageBird

JSON, AccessKey authorization.

Amazon SNS

SigV4 signed by hand, because calling SNS without the AWS SDK has no other correct form.

Plivo

JSON over basic auth with the auth id and token.

Your own webhook

A signed POST to a URL you own, for a provider Baselyra does not carry or an internal gateway.

.env
SMS_PROVIDER=twilio
SMS_FROM=+15550100
TWILIO_ACCOUNT_SID=…
TWILIO_AUTH_TOKEN=…

# Spend is bounded before a message is paid for, not after.
SMS_OTP_COOLDOWN=60
SMS_MAX_PER_NUMBER_PER_HOUR=5
SMS_DEFAULT_COUNTRY=44
With no provider set, codes are printed to the log. That is what you want on a laptop and never what you want in production — but a configured provider that refuses a message still throws, so a broken configuration cannot quietly degrade into silence.
the two calls
POST /auth/v1/otp     {"phone": "+15550100"}
// → 200 always. "If that number can receive messages, a code is on its way."

POST /auth/v1/verify  {"phone": "+15550100", "token": "482913"}
// → { access_token, refresh_token, user }

Email and models

One SMTP host, one optional model

SMTP, via nodemailer

Any host: Postmark, SES, Fastmail, Mailgun, a relay on the same box. Confirmation, magic link, one-time code, recovery, email change and invitation templates are editable in the Studio with a live preview and a test send that reports the server’s own error rather than a generic failure.

Leave SMTP_HOST empty and mail is printed to the log instead of sent — correct on a laptop, never in production.

DeepSeek

The only model provider wired in, over an OpenAI-compatible chat-completions call. It powers natural language to SQL, query explanation, ask-your-database and a chat relay your own users can call. Unset the key and every AI route answers 503 ai_disabled; the Studio hides its panels rather than showing a control that would fail.

What is sent, and what is not

Clients

One published client, and four documented ones

Being honest about this matters more than the count. @baselyra/client is a real package with types, a session that renews itself and one multiplexed socket. The others are single files in the documentation and the examples directory — complete, tested by hand against a running instance, and yours to paste into a project.

JavaScript / TypeScript

published Zero dependencies. Runs unchanged in browsers, Node 22, Deno, Bun and React Native. Supply a generated Database type and rows come back typed; leave it off and they are open records. No call ever rejects for an HTTP status — failures arrive as error next to a null data, including a dead network, where status is 0.

sdk/js

Dart / Flutter

single file Auth, the query builder, storage and realtime, needing only http and web_socket_channel. Failures throw, because Dart’s error channel is exceptions and a widget should not carry a branch it never wanted. The socket heartbeats, because a suspended phone comes back with a connection that claims to be open and carries nothing.

examples/flutter

PHP

single file PHP 8.1+, ext-curl and ext-json. The client is immutable: withToken() returns a new one, so a request handler cannot leave a shared instance authenticated as the previous visitor.

examples/php

Python

documented A worked guide using httpx, including the server-side pattern: forward the user’s token and let policies decide, rather than reaching for the service key because it is easier.

Python guide

Plain HTTP

always There is no protocol here that a language needs a library for: JSON over HTTP, one WebSocket, standard status codes and a single error shape. Go, Rust, Ruby, Elixir, Swift, Kotlin — all of it is curl with a different syntax.

REST reference

Framework guides

Complete, not sketches: Next.js App Router with server components and route handlers, React with Vite, Angular, React Native and Expo, Flutter and PHP. Each covers which key goes where, because that is the mistake that costs people their data.

All guides

Next.js

App Router, server components, route handlers, middleware.

React

Vite, and React Native / Expo with a storage adapter.

Angular

A typed service, signals, and an HTTP interceptor.

Flutter

The Dart client, plus a runnable example app.

Not integrated

What Baselyra does not talk to

Apple, Discord, Twitter, GitLab, Bitbucket, Slack…

Seven providers is seven, not thirty. If sign-in with Apple is a requirement, Supabase and Appwrite both carry far more providers than this and that is a real reason to pick one.

OpenAI, Anthropic, Gemini, Ollama

DeepSeek is the only model provider wired in. The relay is one fetch against an OpenAI-compatible endpoint, so pointing it elsewhere is a small change — but nothing ships pointing elsewhere today.

S3, Cloudflare R2, imgproxy, a CDN

Storage is the local disk of the host running the app, and files come back exactly as they were uploaded. Put a CDN in front of the public object routes yourself if you need one.