Locksmith Public API
JWT authentication API for end-user apps. Every request (except magic-link verification) is scoped to a project and environment via the API key.
API keys and environments
| Prefix | Environment |
|---|---|
lsm_live_ | Production — counts toward plan user limits |
lsm_sbx_ | Sandbox — isolated data, no plan user limit |
Pass the key in the X-API-Key header. Do not expose it in browser code.
CORS
If the project has allowedOrigins configured, the Origin header must match. Empty list allows any origin (convenient for local development).
Response shape
Successful responses are wrapped as { "data": ... }. Errors are { "error": "<code>", "message": "..." }.
Rate limits (representative)
| Endpoint | Window | Limit | Key |
|---|---|---|---|
| POST /api/auth/signup | 1 hour | 10 | IP |
| POST /api/auth/login | 15 min | 10 | IP + email |
| POST /api/auth/magic-link | 1 hour | 5 | IP + email |
| POST /api/auth/refresh | 1 min | 30 | IP |
| POST /api/auth/password/reset | 1 hour | 5 | IP + email |
On limit exceeded: HTTP 429, error code rate_limited.
Social sign-in (OAuth 2.0)
After you enable a provider in the dashboard:
POST /api/auth/oauth/{provider}— returnsauthorizationUrl; redirect the user’s browser there.- The IdP redirects to Locksmith’s callback; Locksmith then redirects to your app with
?code=…(exchange code) andprovider. - Your server calls
POST /api/auth/oauth/tokenwith that code (andX-API-Key) to obtain Locksmith access and refresh tokens.
Provider slug examples: google, github, discord (see dashboard for the full list). Initiation errors still use the normal { error, message } envelope.
Hosted SSO (OIDC)
Pro plan projects act as an OIDC provider. Discovery: GET /api/oidc/{projectId}/.well-known/openid-configuration. The authorization endpoint redirects users to your login URL with a signed request_token; after login and consent, your backend calls POST /api/auth/oidc/grant to obtain a redirectUrl for the user’s browser. Standard /token and /userinfo endpoints follow OIDC/OAuth2 semantics and may return { error, error_description } (not the data wrapper) on failure.
Official SDKs
Use these clients from your backend (never expose your API key in a browser). Each SDK sends X-API-Key and parses Locksmith’s { "data": … } envelope.
Source lives in the Locksmith monorepo under sdks/ (mirrored to per-language GitHub repos). Full READMEs ship in each SDK folder.
TypeScript / JavaScript (@getlocksmith/sdk)
npm install @getlocksmith/sdk
import { LocksmithClient } from '@getlocksmith/sdk'
const auth = new LocksmithClient({ apiKey: process.env.LOCKSMITH_API_KEY! })
const { user, accessToken } = await auth.signIn({
email: 'user@example.com',
password: 'secure-password',
})
const me = await auth.getUser(accessToken)
Optional: import { createMiddleware } from '@getlocksmith/sdk/adapters/next', @getlocksmith/sdk/adapters/trpc.
Python (locksmith-py)
pip install locksmith-py
import os
from locksmith import LocksmithClient
c = LocksmithClient(api_key=os.environ["LOCKSMITH_API_KEY"])
r = c.sign_in(email="user@example.com", password="secure-password")
me = c.get_user(r["accessToken"])
Go (github.com/locksmith-app/sdk-go)
go get github.com/locksmith-app/sdk-go@latest
import (
"os"
locksmith "github.com/locksmith-app/sdk-go"
)
c, _ := locksmith.NewClient(os.Getenv("LOCKSMITH_API_KEY"), "")
out, _ := c.SignIn("user@example.com", "secure-password")
_, _ = c.GetUser(out.AccessToken)
Rust (crate getlocksmith)
getlocksmith = "0.1"
use getlocksmith::LocksmithClient;
let c = LocksmithClient::new(std::env::var("LOCKSMITH_API_KEY")?, None)?;
let r = c.sign_in("user@example.com", "secret").await?;
Ruby (locksmith-ruby)
gem "locksmith-ruby"
require "locksmith"
c = Locksmith::Client.new(api_key: ENV.fetch("LOCKSMITH_API_KEY"))
data = c.sign_in(email: "user@example.com", password: "secure-password")
PHP (Composer locksmith/sdk-php)
composer require locksmith/sdk-php
use Locksmith\LocksmithClient;
$client = new LocksmithClient(getenv('LOCKSMITH_API_KEY'));
$data = $client->signIn('user@example.com', 'secure-password');
C# / .NET (Locksmith.Sdk)
dotnet add package Locksmith.Sdk
await using var client = new LocksmithClient(Environment.GetEnvironmentVariable("LOCKSMITH_API_KEY")!);
var bundle = await client.SignInAsync("user@example.com", "secure-password");
var access = bundle.GetProperty("accessToken").GetString();
Java (app.locksmith:locksmith-java)
try (var c = new LocksmithClient(System.getenv("LOCKSMITH_API_KEY"))) {
var data = c.signIn("user@example.com", "secure-password");
var user = c.getUser(data.get("accessToken").asText());
}
Kotlin (app.locksmith — JVM)
val c = LocksmithClient(System.getenv("LOCKSMITH_API_KEY")!!)
val data = c.signIn("user@example.com", "secure-password")
val user = c.getUser(data["accessToken"].asText())
Dart (locksmith_dart)
import 'dart:io';
import 'package:locksmith_dart/locksmith_dart.dart';
final c = LocksmithClient(apiKey: Platform.environment['LOCKSMITH_API_KEY']!);
final data = await c.signIn('user@example.com', 'secure-password');
Elixir (locksmith_ex)
c = Locksmith.new(System.fetch_env!("LOCKSMITH_API_KEY"))
data = Locksmith.sign_in(c, "user@example.com", "secure-password")
Swift (SwiftPM product Locksmith)
import Locksmith
let client = try LocksmithClient(apiKey: ProcessInfo.processInfo.environment["LOCKSMITH_API_KEY"]!)
let bundle = try await client.signIn(email: "user@example.com", password: "secure-password")
Frameworks
Framework packages sit on top of the HTTP API: they keep X-API-Key on the server, optional cookie BFF sessions, and UI tuned for that stack.
Next.js
npm install @getlocksmith/nextjs next react react-dom
- Server:
import { createLocksmithRouteHandlers, locksmithServerClientFromEnv, createLocksmithMiddleware } from '@getlocksmith/nextjs/server' - Client (React):
import { LocksmithAuthProvider, LocksmithSignInForm, LocksmithSignUpForm } from '@getlocksmith/nextjs/client'
Mount one catch‑all route (e.g. app/api/locksmith/[[...path]]/route.ts) with createLocksmithRouteHandlers, then wrap your app in LocksmithAuthProvider using the same routePrefix. The browser talks only to your origin; the handler forwards to Locksmith with the API key and sets httpOnly cookies.
Full setup, env vars, themes, Free‑plan “Powered by” behavior, and passkeys: see the package README on npm or sdks/nextjs/README.md in the monorepo.
OAuth & OIDC with SDKs
After enabling a provider in the dashboard, start OAuth from your backend:
| TypeScript | await auth.initiateOAuth({ provider: 'github' }) → redirect browser → await auth.exchangeOAuthCode(code) |
| Go | InitiateOAuth / ExchangeOAuthCode |
| Ruby | initiate_oauth / exchange_oauth_code |
| PHP | initiateOAuth / exchangeOAuthCode |
| Rust | initiate_oauth / exchange_oauth_code (async) |
| Dart | initiateOAuth / exchangeOAuthCode |
Hosted SSO (Pro): complete the grant from your login UI with completeOidcGrant / complete_oidc_grant / CompleteOidcGrant (see OpenAPI POST /api/auth/oidc/grant).
Verify JWTs locally
Access tokens are RS256 JWTs. Fetch your project’s public PEM from the dashboard, then:
- TypeScript:
auth.verifyToken(accessToken, publicKeyPem) - Python:
c.verify_token(accessToken, public_key_pem) - Go:
locksmith.VerifyToken(accessToken, pem) - Rust:
getlocksmith::LocksmithClient::verify_token(&token, pem) - Ruby:
c.verify_token(access_token, pem) - PHP:
verifyTokenLocal($access, $pem) - C#:
LocksmithClient.VerifyToken(accessToken, pem) - Dart:
c.verifyToken(accessToken, pem) - Elixir:
Locksmith.verify_token(access_token, pem)
Swift ships decodeTokenPayload for issuer-checked payload; add full crypto verification for production hardening.
Authentication
- API Key: ApiKeyAuth
- HTTP: Bearer Auth
Project API key (lsm_live_… or lsm_sbx_…).
Security Scheme Type: | apiKey |
|---|---|
Header parameter name: | X-API-Key |
RS256 access token issued by Locksmith for this project and environment.
Security Scheme Type: | http |
|---|---|
HTTP Authorization Scheme: | bearer |
Bearer format: | JWT |