Introduction

"Sign in with 4S" is an OAuth 2.0 / OpenID Connect (OIDC) compliant authentication feature. External applications can let users sign in with their 4S account and access the 4S read (GET) APIs on their behalf.

Because the implementation is standards-compliant, common OIDC libraries such as NextAuth (Auth.js) and openid-client work out of the box. The only supported flow is Authorization Code with PKCE (S256).

Register your app from the Developer menu on 4S (https://4s.link/developer). You will receive a client_id and a client_secret. The client_secret is shown only once at registration — store it securely.

Basics

ItemValue
Issuerhttps://api.4s.link
Supported flowAuthorization Code + PKCE (S256)
Client authenticationclient_secret_basic / client_secret_post / none (public + PKCE)
ID Token signatureRS256
Access token lifetime1 hour
Refresh token lifetime60 days (rotating)
Endpoints
# OIDC Discovery
https://api.4s.link/.well-known/openid-configuration
# JWKS (公開鍵)
https://api.4s.link/.well-known/jwks.json
# 認可エンドポイント
https://4s.link/oauth/authorize
# トークンエンドポイント
https://api.4s.link/oauth/token
# UserInfo
https://api.4s.link/oauth/userinfo
# トークン失効
https://api.4s.link/oauth/revoke

Authentication Flow

The Authorization Code + PKCE flow works as follows: (1) your app generates a code_verifier and redirects the user to the authorization endpoint with its S256 hash (code_challenge); (2) the user signs in to 4S and grants consent, and an authorization code is returned to your redirect_uri; (3) your app exchanges the code together with the code_verifier for tokens at the token endpoint.

GEThttps://4s.link/oauth/authorize

The authorization endpoint is a URL you redirect the user's browser to (it is not an API). Attach the following query parameters.

Query parameters

response_typestringrequired

Must be code

client_idstringrequired

Client ID issued in the Developer menu

redirect_uristringrequired

Must exactly match a registered redirect URI

statestring

Random value for CSRF protection, returned unchanged in the callback (strongly recommended)

noncestring

Random value returned as the nonce claim in the ID Token (recommended)

code_challengestringrequired

base64url-encoded SHA-256 hash of the code_verifier

code_challenge_methodstringrequired

Must be S256

promptstring

Set to consent to force the consent screen even if already granted. none is not supported and returns interaction_required

After the user approves, the browser returns to your redirect_uri with ?code=...&state=.... If the user denies or validation fails, ?error=access_denied (or similar) is returned instead. Authorization codes are single-use and expire after 5 minutes.

Example authorization request
const crypto = require("crypto");
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
console.log({ codeVerifier, codeChallenge });

Discovery / JWKS

GET/.well-known/openid-configuration

The OIDC Discovery document. It returns metadata such as endpoint URLs and signing algorithms. Configure your OIDC library with this origin as the issuer and everything else resolves automatically.

GET/.well-known/jwks.json

The public keys (JWK Set) for verifying ID Token signatures. Verify the RS256 signature using the key whose kid matches the ID Token header. Keys may be rotated, so fetch the JWKS as needed or implement cache expiry (Cache-Control: max-age=300).

Fetch discovery document
curl https://api.4s.link/.well-known/openid-configuration

Token Endpoint

POST/oauth/token

Exchanges an authorization code for tokens, or refreshes tokens with a refresh token. Send the request body as application/x-www-form-urlencoded. Confidential clients authenticate with client_secret_basic (Authorization: Basic) or client_secret_post (in the body).

Parameters for grant_type=authorization_code

grant_typestringrequired

authorization_code

codestringrequired

The authorization code received in the callback

redirect_uristringrequired

Same value as in the authorization request

code_verifierstringrequired

The PKCE verifier the code_challenge was derived from

Parameters for grant_type=refresh_token

grant_typestringrequired

refresh_token

refresh_tokenstringrequired

A valid refresh token

Refresh tokens rotate: every refresh_token grant issues a new refresh token and invalidates the old one. If a revoked refresh token is reused, all tokens for that user-client pair are revoked as a safety measure. Always persist the latest refresh token.

Code exchange
curl -X POST https://api.4s.link/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://example.com/callback" \
-d "code_verifier=CODE_VERIFIER"

ID Token

The token response always contains an ID Token (an RS256-signed JWT). To validate it, verify (1) the signature with the JWKS key matching the kid, (2) iss equals the issuer, (3) aud equals your client_id, (4) exp is in the future, and (5) the nonce claim matches if you sent one. OIDC libraries handle all of this automatically.

Main claims

ClaimDescription
subUnique user ID (the 4S user ID)
issIssuer
audYour client ID
auth_timeWhen the user authorized (Unix seconds)
nonceThe nonce from the authorization request (if sent)
name / given_name / family_name / picture / profile / preferred_username / localeProfile info (always included)
email / email_verifiedEmail address (always included)
Example ID Token payload
{
"sub": "a1b2c3d4-....",
"iss": "https://api.4s.link",
"aud": "YOUR_CLIENT_ID",
"iat": 1783900000,
"exp": 1783903600,
"auth_time": 1783899990,
"nonce": "RANDOM_NONCE",
"name": "太郎 山田",
"given_name": "太郎",
"family_name": "山田",
"picture": "https://.../avatar.png",
"profile": "https://4s.link/taro",
"preferred_username": "taro",
"locale": "ja",
"email": "taro@example.com",
"email_verified": true
}

Token Revocation

POST/oauth/revoke

RFC 7009 compliant token revocation endpoint. Revoke tokens that are no longer needed, e.g. when the user signs out of your app. Revoking a refresh token also revokes the access tokens issued from it. Per RFC 7009, unknown tokens still return 200.

Body parameters

tokenstringrequired

The access token or refresh token to revoke

Revoke a token
curl -X POST https://api.4s.link/oauth/revoke \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=4s_rt_..."

Calling the APIs

Call the 4S APIs by sending your access token in the Authorization: Bearer header. OAuth access tokens can only call the read and write endpoints listed in the categories below (the allowlist); any other method + path combination returns 403.

Conventions

ItemDetail
Base URLhttps://api.4s.link
AuthAuthorization: Bearer <access_token>
Allowed scopeOnly the method + path combinations listed in each category (GET / POST / PUT / PATCH / DELETE)
PaginationList endpoints take page / perPage query params; responses include pageInfo (totalCount / totalPages / page / perPage)
Rate limits60 req/min and 1,000 req/hour per app × user by default. Every response carries X-RateLimit-* headers (see "Rate Limits")
Error (401)Token invalid, revoked or expired
Error (403)A method + path outside the allowlist (everything under /admin is always denied)
PermissionsPer-endpoint permission checks are identical to a normal signed-in session: operations requiring an event staff role or organization/community admin rights still require the authorizing user to hold them
Email addresses`email` fields are recursively removed from responses under /manage (free-text survey answers are not covered)

Every available endpoint is listed in the categories below. Trailing slashes are ignored and the /api and /v1 prefixes resolve to the same endpoints. Paths are case-sensitive, and the HEAD method is not supported.

Example request
curl https://api.4s.link/users/me \
-H "Authorization: Bearer 4s_at_..."

Rate Limits

API calls made with an access token are rate limited per app and user pair (app × user). Because the counter is per user rather than per app, one user hitting the limit does not affect calls made on behalf of your other users.

Limits

WindowDefault limitDescription
1 minute60Caps short bursts of traffic
1 hour1,000Overall volume cap, which also absorbs bursts that straddle a minute boundary

Limits can be raised or lowered per app. If the defaults do not fit your use case, contact the 4S team. You can check the limits currently applied to your app on its detail page in the developer menu, or from the response headers below.

Response headers

HeaderDescription
X-RateLimit-Limit-MinuteLimit for the 1 minute window
X-RateLimit-Remaining-MinuteRemaining calls in the current 1 minute window
X-RateLimit-Limit-HourLimit for the 1 hour window
X-RateLimit-Remaining-HourRemaining calls in the current 1 hour window
X-RateLimit-ResetWhen the tighter of the two windows resets (Unix seconds)
Retry-AfterReturned on 429 only. Seconds to wait before retrying

These headers are returned on every successful response, not just on 429. Pace your requests using the remaining counts and you can avoid hitting 429 at all. They are exposed via CORS, so browser-based apps can read them too.

Exceeding a limit returns 429 Too Many Requests with a body of { "error": "...", "errorCode": 60007 }. Requests made while over the limit still count toward it, so wait for the number of seconds in Retry-After before retrying. Retrying immediately in a loop only delays recovery.

Rate limits apply only to API calls made with an access token. Discovery, JWKS, the token endpoint (/oauth/token) and the revocation endpoint (/oauth/revoke) are not rate limited.

Inspecting the headers
curl -i https://api.4s.link/users/me \
-H "Authorization: Bearer 4s_at_..."
# HTTP/1.1 200 OK
# X-RateLimit-Limit-Minute: 60
# X-RateLimit-Remaining-Minute: 43
# X-RateLimit-Limit-Hour: 1000
# X-RateLimit-Remaining-Hour: 912
# X-RateLimit-Reset: 1755500460
429 response
{
"error": "Too many requests. Please slow down.",
"errorCode": 60007
}

Error Reference

The token endpoint, UserInfo and events APIs return RFC 6749 / RFC 6750 style errors ({ error, error_description }). Authorization endpoint errors are returned as query parameters on the redirect_uri (unless the redirect_uri itself is invalid).

ErrorWhereDescription
invalid_clienttoken / revokeClient authentication failed, or the app is suspended
invalid_granttokenInvalid, expired or already-used code / refresh token, PKCE verification failure, redirect_uri mismatch, or consent revoked
invalid_requesttoken / authorizeMissing or malformed required parameters
access_deniedauthorizeThe user denied the authorization request
interaction_requiredauthorizeprompt=none was requested but interaction is required (silent auth is not supported)
unsupported_grant_typetokenA grant_type other than authorization_code / refresh_token
401 UnauthorizedResource APIsThe access token is invalid, revoked or expired, or the app is suspended
403 ForbiddenResource APIsAccess to an endpoint outside the allowed GET list (only the endpoints listed in each category are usable)
429 Too Many RequestsResource APIsRate limit exceeded (errorCode: 60007). Wait the number of seconds in Retry-After before retrying (see "Rate Limits")
Example error response
{
"error": "invalid_grant",
"error_description": "PKCE verification failed"
}

Integration Examples

With generic OIDC libraries, setting the issuer is enough — endpoints resolve automatically via Discovery. A NextAuth (Auth.js) v5 configuration example is shown on the right. Don't forget to register your redirect_uri (e.g. https://your-app.com/api/auth/callback/4s) in the Developer menu.

Known limitations: prompt=none (silent authentication) and Dynamic Client Registration are not supported. ID Tokens do not include the at_hash claim.

NextAuth (Auth.js) v5
import NextAuth from "next-auth";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
{
id: "4s",
name: "4S",
type: "oidc",
issuer: "https://api.4s.link",
clientId: process.env.FOURS_CLIENT_ID,
clientSecret: process.env.FOURS_CLIENT_SECRET,
authorization: {
params: { scope: "openid profile email" },
},
// PKCE と state は NextAuth がデフォルトで有効化します
},
],
});

Demo App

A working "Sign in with 4S" sample built with Next.js + TypeScript. See how the login flow (Authorization Code + PKCE) and profile retrieval are implemented.

Users / Profile

Organizations

Events

Communities

Playlists

Chat

Notifications

Push notifications

Bookmarks

Misc