Social Providers
GET /orgs/{orgId}/api/v1/admin/social-providers
GET /orgs/{orgId}/api/v1/admin/social-providers/available
GET /orgs/{orgId}/api/v1/admin/social-providers/types
GET /orgs/{orgId}/api/v1/admin/social-providers/callback-urls
GET /orgs/{orgId}/api/v1/admin/social-providers/{providerId}
PUT /orgs/{orgId}/api/v1/admin/social-providers/{providerId}
PATCH /orgs/{orgId}/api/v1/admin/social-providers/{providerId}
DELETE /orgs/{orgId}/api/v1/admin/social-providers/{providerId}
POST /orgs/{orgId}/api/v1/admin/social-providers/{providerId}/enable
POST /orgs/{orgId}/api/v1/admin/social-providers/{providerId}/disable
POST /orgs/{orgId}/api/v1/admin/social-providers
Social providers allow users to sign in using their existing accounts from services like Google, GitHub, or Microsoft. The OAuth flow uses:
GET /orgs/{orgId}/api/v1/oauth/social/{provider}
Authentication
All social provider management endpoints require an admin API key or a Bearer token with admin:social:read / admin:social:write.
Each endpoint needs the credential's scope and a permission. A session or Bearer caller must
hold the permission; for an API key its creator must still hold it. A missing permission
returns 403 forbidden with details.required_permissions. See
Required permissions.
| Method | Endpoint | Permission | API-key / token scope |
|---|---|---|---|
| GET | /admin/social-providers | idps.view | admin:social:read |
| GET | /admin/social-providers/available | idps.view | admin:social:read |
| GET | /admin/social-providers/types | idps.view | admin:social:read |
| GET | /admin/social-providers/callback-urls | idps.view | admin:social:read |
| GET | /admin/social-providers/{providerId} | idps.view | admin:social:read |
| POST | /admin/social-providers | idps.create | admin:social:write |
| PUT PATCH | /admin/social-providers/{providerId} | idps.edit | admin:social:write |
| DELETE | /admin/social-providers/{providerId} | idps.delete | admin:social:write |
| POST | /admin/social-providers/{providerId}/enable | idps.edit | admin:social:write |
| POST | /admin/social-providers/{providerId}/disable | idps.edit | admin:social:write |
Social Provider Object
{
"id": "01JF3KSP...",
"provider": "google",
"displayName": "Google",
"clientId": "123456789.apps.googleusercontent.com",
"enabled": true,
"requestedScopes": ["openid", "profile", "email"],
"callbackUrl": "https://app.lumoauth.dev/orgs/acme-corp/auth/social/google/callback",
"defaultRoles": ["member"],
"createdAt": "2026-01-15T10:30:00Z"
}
clientSecret is write-only and will never be returned in responses.
callbackUrl is read-only. LumoAuth generates it (https://{host}/orgs/{orgId}/auth/social/{provider}/callback); a callbackUrl sent on create, PUT or PATCH is ignored. Rows that still held the old https://your-domain.com/... placeholder are returned with the generated URL.
defaultRoles
defaultRoles is a list of role slugs granted to every account the provider provisions on first sign-in. It is a role-assignment sink, so the no privilege gain rule applies on POST, PUT and PATCH:
- System roles and roles that confer administrator authority (
users.create,roles.edit,idps.create,settings.manage,jit.approve, and the other families listed in the roles guide) are refused. - The caller must hold every permission in each role. With an API key, the key's creator is the caller.
- Unknown slugs are ignored; a refused role fails the whole request and nothing is written.
{
"error": "invalid_request",
"message": "defaultRoles: role \"tenant-admin\" is a system or administrative role and cannot be a social-login default role",
"status": 400
}
{
"error": "invalid_request",
"message": "defaultRoles: Cannot assign role \"editor\": it contains permissions you do not hold.",
"status": 400
}
List Configured Providers
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
List Available Provider Types
Returns the full list of supported providers and their required configuration fields. GET /social-providers/types is an alias of this endpoint and returns the same payload.
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/available" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/available", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/available",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/available", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
{
"data": [
{
"provider": "google",
"name": "Google",
"requiredFields": ["clientId", "clientSecret"],
"optionalFields": ["hostedDomain"],
"defaultScopes": ["openid", "profile", "email"]
},
{
"provider": "github",
"name": "GitHub",
"requiredFields": ["clientId", "clientSecret"],
"optionalFields": [],
"defaultScopes": ["user:email", "read:user"]
},
{
"provider": "microsoft",
"name": "Microsoft",
"requiredFields": ["clientId", "clientSecret"],
"optionalFields": ["tenantId"],
"defaultScopes": ["openid", "profile", "email"]
}
]
}
Get Callback URLs
Before configuring a provider, retrieve the callback URL to register in your IdP's app settings:
- curl
- Node
- Python
- Go
curl "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/callback-urls" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/callback-urls", {
method: "GET",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.get(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/callback-urls",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("GET", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/callback-urls", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Configure a Provider (Upsert)
PUT is an upsert: it creates the provider if it does not exist, or fully replaces the configuration if it does. clientId is required for both create and update via PUT.
- curl
- Node
- Python
- Go
curl -X PUT https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"clientId": "123456789.apps.googleusercontent.com",
"clientSecret": "GOCSPX-...",
"enabled": true,
"requestedScopes": ["openid", "profile", "email"]
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", {
method: "PUT",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"clientId": "123456789.apps.googleusercontent.com",
"clientSecret": "GOCSPX-...",
"enabled": true,
"requestedScopes": ["openid", "profile", "email"]
})
});
console.log(await resp.json());
import os, requests
resp = requests.put(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"clientId": "123456789.apps.googleusercontent.com",
"clientSecret": "GOCSPX-...",
"enabled": True,
"requestedScopes": ["openid", "profile", "email"]
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"clientId": "123456789.apps.googleusercontent.com",
"clientSecret": "GOCSPX-...",
"enabled": true,
"requestedScopes": ["openid", "profile", "email"]
}`)
req, _ := http.NewRequest("PUT", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Returns 201 Created on creation, 200 OK on update.
- curl
- Node
- Python
- Go
curl -X PATCH https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", {
method: "PATCH",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({"enabled": false})
});
console.log(await resp.json());
import os, requests
resp = requests.patch(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={"enabled": False}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{"enabled": false}`)
req, _ := http.NewRequest("PATCH", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Enable / Disable a Provider
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/enable \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/enable", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/enable",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/enable", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/disable \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/disable", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/disable",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google/disable", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Create a Provider via POST
- curl
- Node
- Python
- Go
curl -X POST https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers \
-H "Authorization: ApiKey lmk_abc123" \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"displayName": "GitHub",
"clientId": "Ov23li...",
"clientSecret": "abc123...",
"enabled": true
}'
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers", {
method: "POST",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" },
body: JSON.stringify({
"provider": "github",
"displayName": "GitHub",
"clientId": "Ov23li...",
"clientSecret": "abc123...",
"enabled": true
})
});
console.log(await resp.json());
import os, requests
resp = requests.post(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers",
headers={"Authorization": os.environ["LUMO_AUTH"], "Content-Type": "application/json"},
json={
"provider": "github",
"displayName": "GitHub",
"clientId": "Ov23li...",
"clientSecret": "abc123...",
"enabled": True
}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
body := strings.NewReader(`{
"provider": "github",
"displayName": "GitHub",
"clientId": "Ov23li...",
"clientSecret": "abc123...",
"enabled": true
}`)
req, _ := http.NewRequest("POST", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers", body)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Delete a Provider
- curl
- Node
- Python
- Go
curl -X DELETE "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google" \
-H "Authorization: ApiKey lmk_abc123"
const resp = await fetch("https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", {
method: "DELETE",
headers: { "Authorization": process.env.LUMO_AUTH, "Content-Type": "application/json" }
});
console.log(await resp.json());
import os, requests
resp = requests.delete(
"https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google",
headers={"Authorization": os.environ["LUMO_AUTH"]}
)
print(resp.json())
import (
"io"
"net/http"
"os"
"strings"
)
req, _ := http.NewRequest("DELETE", "https://app.lumoauth.dev/orgs/acme-corp/api/v1/admin/social-providers/google", nil)
req.Header.Set("Authorization", os.Getenv("LUMO_AUTH"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
Initiate Social Login Flow
Users sign in via social providers using the OAuth flow:
GET /orgs/{orgId}/api/v1/oauth/social/{provider}?client_id=...&redirect_uri=...
client_id is your LumoAuth OAuth client and redirect_uri one of its registered redirect URIs. After authentication with the social provider, LumoAuth links the identity to the user's account and completes the OAuth flow. The simpler route is to send the provider hint on the authorization endpoint, which does the same within a normal authorization request.
Supported Providers
| Provider | providerId | Required Fields |
|---|---|---|
google | clientId, clientSecret | |
| GitHub | github | clientId, clientSecret |
| Microsoft | microsoft | clientId, clientSecret |
| Apple | apple | clientId, teamId, keyId, privateKey |
facebook | clientId, clientSecret | |
linkedin | clientId, clientSecret | |
| Generic OIDC | oidc | clientId, clientSecret, issuerUrl |