SeleID

API reference

Continue with SeleID (OIDC) API

All APIs · SDKs · Continue with SeleID (OIDC) · SeleStorage · SeleBase · Webhooks, queue, SeleMail, routing and push · SeleAI · SeleDomains search

Coming soon Version 1.0.0. OpenID Connect authorization-code flow with PKCE (S256) for outside apps. Public clients (mobile, browser) use no secret. Outside-app sign-in is switched on per environment; until then registered apps stay pending review.

Base URL

https://auth.seleid.dev SeleID issuer

Authentication and scopes

No API key. Public clients use the authorization code flow with PKCE (S256); confidential clients may also send a client secret. Scopes: openid, profile, email.

Rate limits and quotas

The token endpoint is rate limited per client and address; a limited call returns 429.

Endpoints

GET /.well-known/openid-configuration

OpenID Connect discovery document

No authentication.

curl

curl -X GET "https://auth.seleid.dev/.well-known/openid-configuration"

TypeScript

const res = await fetch('https://auth.seleid.dev/.well-known/openid-configuration', {
  method: 'GET',
});
const data = await res.json();

Python

import os, requests

res = requests.get(
    "https://auth.seleid.dev/.well-known/openid-configuration"
)
print(res.status_code, res.text)

Example response

{
  "issuer": "https://auth.seleid.dev",
  "authorization_endpoint": "https://auth.seleid.dev/authorize",
  "token_endpoint": "https://auth.seleid.dev/oauth/token",
  "jwks_uri": "https://auth.seleid.dev/.well-known/jwks.json",
  "response_types_supported": [
    "code"
  ],
  "code_challenge_methods_supported": [
    "S256"
  ],
  "scopes_supported": [
    "openid",
    "profile",
    "email"
  ]
}
StatusMeaning
200Discovery document.

GET /.well-known/jwks.json

Public signing keys (JWKS)

No authentication.

curl

curl -X GET "https://auth.seleid.dev/.well-known/jwks.json"

TypeScript

const res = await fetch('https://auth.seleid.dev/.well-known/jwks.json', {
  method: 'GET',
});
const data = await res.json();

Python

import os, requests

res = requests.get(
    "https://auth.seleid.dev/.well-known/jwks.json"
)
print(res.status_code, res.text)
StatusMeaning
200Key set.
503Signing keys are not configured.

GET /authorize

Start sign-in (open in the system browser). Requires client_id, an exact registered redirect_uri, response_type=code, scope including openid, state, a nonce of 16 to 256 characters and an S256 code_challenge of 43 to 128 characters. On success the person returns to redirect_uri with code and state.

No authentication.

ParameterInRequiredDescription
client_idqueryyesPublic client id.
redirect_uriqueryyesExact registered redirect URI.
response_typequeryyesAlways code.
scopequeryyesSpace separated; must include openid. Allowed: openid profile email.
statequeryyesOpaque value echoed back.
noncequeryyesBound into the ID token.
code_challengequeryyesBASE64URL(SHA256(code_verifier)).
code_challenge_methodqueryyesAlways S256.

curl

curl -X GET "https://auth.seleid.dev/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=YOUR_RESPONSE_TYPE&scope=YOUR_SCOPE&state=YOUR_STATE&nonce=YOUR_NONCE&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=YOUR_CODE_CHALLENGE_METHOD"

TypeScript

const res = await fetch('https://auth.seleid.dev/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=YOUR_RESPONSE_TYPE&scope=YOUR_SCOPE&state=YOUR_STATE&nonce=YOUR_NONCE&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=YOUR_CODE_CHALLENGE_METHOD', {
  method: 'GET',
});
const data = await res.json();

Python

import os, requests

res = requests.get(
    "https://auth.seleid.dev/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=YOUR_RESPONSE_TYPE&scope=YOUR_SCOPE&state=YOUR_STATE&nonce=YOUR_NONCE&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=YOUR_CODE_CHALLENGE_METHOD"
)
print(res.status_code, res.text)
StatusMeaning
302Redirect to the sign-in page, then back to redirect_uri.
400Invalid request; shown to the person, never redirected to an unregistered URI.

POST /oauth/token

Exchange a code or refresh a token

No authentication.

curl

curl -X POST "https://auth.seleid.dev/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=authorization_code&code=CODE&redirect_uri=com.example.app%3A%2Fseleid&client_id=YOUR_CLIENT_ID&code_verifier=VERIFIER'

TypeScript

const res = await fetch('https://auth.seleid.dev/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({"grant_type":"authorization_code","code":"CODE","redirect_uri":"com.example.app:/seleid","client_id":"YOUR_CLIENT_ID","code_verifier":"VERIFIER"}),
});
const data = await res.json();

Python

import os, requests

res = requests.post(
    "https://auth.seleid.dev/oauth/token",
    data={
        "grant_type": "authorization_code",
        "code": "CODE",
        "redirect_uri": "com.example.app:/seleid",
        "client_id": "YOUR_CLIENT_ID",
        "code_verifier": "VERIFIER",
    }
)
print(res.status_code, res.text)

Example response

{
  "access_token": "opaque",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "opaque",
  "id_token": "eyJ…",
  "scope": "openid profile email"
}
StatusMeaning
200Tokens.
400invalid_request, invalid_grant, invalid_client or unsupported_grant_type.
429Rate limited.

GET /oauth/userinfo

Profile claims for the access token. Advertised only when outside-app sign-in is on.

Authentication: required.

curl

curl -X GET "https://auth.seleid.dev/oauth/userinfo" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

TypeScript

const res = await fetch('https://auth.seleid.dev/oauth/userinfo', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
});
const data = await res.json();

Python

import os, requests

res = requests.get(
    "https://auth.seleid.dev/oauth/userinfo",
    headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"}
)
print(res.status_code, res.text)

Example response

{
  "sub": "user_123",
  "name": "Ada",
  "email": "ada@example.com",
  "email_verified": true
}
StatusMeaning
200Claims.
401Invalid or expired token.

POST /oauth/revoke

Revoke a refresh or access token. Advertised only when outside-app sign-in is on.

No authentication.

curl

curl -X POST "https://auth.seleid.dev/oauth/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'token=YOUR_TOKEN&client_id=YOUR_CLIENT_ID'

TypeScript

const res = await fetch('https://auth.seleid.dev/oauth/revoke', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({ token: "YOUR_TOKEN", client_id: "YOUR_CLIENT_ID" }),
});
const data = await res.json();

Python

import os, requests

res = requests.post(
    "https://auth.seleid.dev/oauth/revoke",
    data={
        "token": "YOUR_TOKEN",
        "client_id": "YOUR_CLIENT_ID",
    }
)
print(res.status_code, res.text)
StatusMeaning
200Revoked (also returned for unknown tokens).
400Invalid request.

The machine-readable OpenAPI 3.1 description for this API lives in the SeleID repository under docs/openapi/.

Last updated 2026-11-12Suggest an editReport a problem with this page