Skip to main content
Version: 1.0.0

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

PrefixEnvironment
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)

EndpointWindowLimitKey
POST /api/auth/signup1 hour10IP
POST /api/auth/login15 min10IP + email
POST /api/auth/magic-link1 hour5IP + email
POST /api/auth/refresh1 min30IP
POST /api/auth/password/reset1 hour5IP + email

On limit exceeded: HTTP 429, error code rate_limited.

Social sign-in (OAuth 2.0)

After you enable a provider in the dashboard:

  1. POST /api/auth/oauth/{provider} — returns authorizationUrl; redirect the user’s browser there.
  2. The IdP redirects to Locksmith’s callback; Locksmith then redirects to your app with ?code=… (exchange code) and provider.
  3. Your server calls POST /api/auth/oauth/token with that code (and X-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:

TypeScriptawait auth.initiateOAuth({ provider: 'github' }) → redirect browser → await auth.exchangeOAuthCode(code)
GoInitiateOAuth / ExchangeOAuthCode
Rubyinitiate_oauth / exchange_oauth_code
PHPinitiateOAuth / exchangeOAuthCode
Rustinitiate_oauth / exchange_oauth_code (async)
DartinitiateOAuth / 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

Project API key (lsm_live_… or lsm_sbx_…).

Security Scheme Type:

apiKey

Header parameter name:

X-API-Key