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
| Item | Value |
|---|---|
| Issuer | https://api.4s.link |
| Supported flow | Authorization Code + PKCE (S256) |
| Client authentication | client_secret_basic / client_secret_post / none (public + PKCE) |
| ID Token signature | RS256 |
| Access token lifetime | 1 hour |
| Refresh token lifetime | 60 days (rotating) |
# OIDC Discoveryhttps://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# UserInfohttps://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.
https://4s.link/oauth/authorizeThe 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_typestringrequiredMust be code
client_idstringrequiredClient ID issued in the Developer menu
redirect_uristringrequiredMust exactly match a registered redirect URI
statestringRandom value for CSRF protection, returned unchanged in the callback (strongly recommended)
noncestringRandom value returned as the nonce claim in the ID Token (recommended)
code_challengestringrequiredbase64url-encoded SHA-256 hash of the code_verifier
code_challenge_methodstringrequiredMust be S256
promptstringSet 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.
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
/.well-known/openid-configurationThe 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.
/.well-known/jwks.jsonThe 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).
curl https://api.4s.link/.well-known/openid-configuration
Token Endpoint
/oauth/tokenExchanges 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_typestringrequiredauthorization_code
codestringrequiredThe authorization code received in the callback
redirect_uristringrequiredSame value as in the authorization request
code_verifierstringrequiredThe PKCE verifier the code_challenge was derived from
Parameters for grant_type=refresh_token
grant_typestringrequiredrefresh_token
refresh_tokenstringrequiredA 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.
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
| Claim | Description |
|---|---|
sub | Unique user ID (the 4S user ID) |
iss | Issuer |
aud | Your client ID |
auth_time | When the user authorized (Unix seconds) |
nonce | The nonce from the authorization request (if sent) |
name / given_name / family_name / picture / profile / preferred_username / locale | Profile info (always included) |
email / email_verified | Email address (always included) |
{"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
/oauth/revokeRFC 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
tokenstringrequiredThe access token or refresh token to revoke
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
| Item | Detail |
|---|---|
| Base URL | https://api.4s.link |
| Auth | Authorization: Bearer <access_token> |
| Allowed scope | Only the method + path combinations listed in each category (GET / POST / PUT / PATCH / DELETE) |
| Pagination | List endpoints take page / perPage query params; responses include pageInfo (totalCount / totalPages / page / perPage) |
| Rate limits | 60 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) |
| Permissions | Per-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.
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
| Window | Default limit | Description |
|---|---|---|
| 1 minute | 60 | Caps short bursts of traffic |
| 1 hour | 1,000 | Overall 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
| Header | Description |
|---|---|
X-RateLimit-Limit-Minute | Limit for the 1 minute window |
X-RateLimit-Remaining-Minute | Remaining calls in the current 1 minute window |
X-RateLimit-Limit-Hour | Limit for the 1 hour window |
X-RateLimit-Remaining-Hour | Remaining calls in the current 1 hour window |
X-RateLimit-Reset | When the tighter of the two windows resets (Unix seconds) |
Retry-After | Returned 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.
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
{"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).
| Error | Where | Description |
|---|---|---|
invalid_client | token / revoke | Client authentication failed, or the app is suspended |
invalid_grant | token | Invalid, expired or already-used code / refresh token, PKCE verification failure, redirect_uri mismatch, or consent revoked |
invalid_request | token / authorize | Missing or malformed required parameters |
access_denied | authorize | The user denied the authorization request |
interaction_required | authorize | prompt=none was requested but interaction is required (silent auth is not supported) |
unsupported_grant_type | token | A grant_type other than authorization_code / refresh_token |
401 Unauthorized | Resource APIs | The access token is invalid, revoked or expired, or the app is suspended |
403 Forbidden | Resource APIs | Access to an endpoint outside the allowed GET list (only the endpoints listed in each category are usable) |
429 Too Many Requests | Resource APIs | Rate limit exceeded (errorCode: 60007). Wait the number of seconds in Retry-After before retrying (see "Rate Limits") |
{"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.
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
- GET
/usersList / search usersSearch and list users. `search` is AND-matched by whitespace across slug, name, title and bio. - GET
/users/:idUser detailRetrieve a user's public profile (email not included). - GET
/users/meOwn profileRetrieve the authenticated user's own profile, including private fields such as email. - PUT
/users/meUpdate own profileUpdate the authenticated user's own profile. Only the fields you send are updated (partial update); sending null clears a value. - POST
/users/me/slugSet own slugSet the slug used in the profile URL. A slug can only be changed once every 2 weeks: changing it to a different value within 14 days of the previous change returns 400 (errorCode 10012) with nextChangeableAt (UTC) in the response. Sending the current value is exempt from the limit and returns 200 without updating. - POST
/users/me/avatarUpload avatar imageUpload an avatar image as multipart/form-data and get the stored URL. The URL is also applied to the user's avatarUrl. - POST
/users/me/event-face-photoUpload event face photoUpload the face photo used for on-site identity checks. Required before registering for events with requiresFacePhoto enabled. - GET
/users/me/organizationsOwn managed organizationsList organizations where the authenticated user is an ADMIN. - GET
/users/me/organization_membersOwn organization membershipsList the authenticated user's organization memberships (work / education history) shown in the profile. - GET
/users/me/unread-countsAggregated unread countsReturn the authenticated user's unread chat, announcement, and notification counts in a single request. Intended for surfaces that render several badges at once, such as tab bars and app icons. - GET
/users/:id/sessionsSpeaking sessionsRetrieve the sessions where the user is a confirmed speaker. Only sessions of published events that are not drafts are returned, ordered by start time descending. - GET
/users/:id/eventsParticipated eventsRetrieve events the user participated in (confirmed / checked in), ordered by start date descending. Private events are excluded. - GET
/users/:id/communitiesCommunitiesRetrieve communities the user belongs to (approved, non-private). No pagination. - GET
/users/:id/organization_membersUser's organization membershipsList a user's publicly visible organization memberships. - POST
/organization_memberAdd organization membershipAdd a membership (work / education history) for the authenticated user. Unless you created the organization, the role is forced to MEMBER. - PUT
/organization_member/:idUpdate organization membershipUpdate one of your own memberships. Other users' memberships return 403. - DELETE
/organization_member/:idDelete organization membershipDelete one of your own memberships.
Organizations
- GET
/organizationsList / search organizationsSearch and list organizations (career data). - GET
/organizations/:idOrganization detailRetrieve an organization's detail. - GET
/manage/organizations/:idOrganization detail (for managers)Retrieve an organization for management. Only its admins can access it. - POST
/manage/organizationsCreate organizationCreate an organization. The creator is automatically registered as an ADMIN. No additional permission is required beyond being signed in. - PUT
/manage/organizations/:idUpdate organizationUpdate an organization. Only its admins can do this. - DELETE
/manage/organizations/:idDelete organizationDelete an organization. Admins only. - GET
/organizations/:id/membersList organization membersList the members of an organization. User objects contain public fields only and never include email. - GET
/manage/organizations/:id/membersList organization members (for managers)List members for management. Admins only. - POST
/manage/organizations/:id/membersAdd organization memberAdd an existing user as an organization member. Admins only. - POST
/manage/organizations/:id/logoUpload organization logoUpload the organization's logo image. - POST
/manage/organizations/:id/logo-darkUpload dark-theme logoUpload the logo image used in dark theme. - PUT
/manage/organizations/:orgId/members/:memberIdUpdate organization memberUpdate a member's title, period and role. Admins only. - DELETE
/manage/organizations/:orgId/members/:memberIdRemove organization memberRemove a member from the organization. Admins only. - PUT
/manage/organizations/:orgId/members/:memberId/roleChange member roleChange only a member's role. The last remaining ADMIN cannot be demoted.
Events
- GET
/eventsList / search eventsSearch and list events with rich filters. The public response omits startTime / endTime / onlineUrl (read schedules instead). groupByDate=true returns date-grouped results. - GET
/manage/eventsList manageable eventsList events where the authenticated user is registered as staff. - GET
/events/:idEvent detailRetrieve event detail. The path accepts an ID or a slug. Includes sessions, speakers, stages, tickets and organizers. - GET
/manage/events/:eventIdEvent detail (for managers)Retrieve an event for management. Requires staff with the eventRead permission. - POST
/manage/eventsCreate eventCreate an event in DRAFT state. The creator is automatically registered as ADMIN staff. No additional permission is required beyond being signed in. Organizers (EventOrganizer) are specified via `organizers`. - PUT
/manage/events/:eventIdUpdate eventUpdate an event. Requires the eventWrite permission (meeting-only updates also accept eventMeetingWrite). Organizers (EventOrganizer) are reconciled from the `organizers` array: organizers not present in the array are removed. - DELETE
/manage/events/:eventIdDelete eventDelete an event. Requires the eventWrite permission. - GET
/events/geojsonEvent map pinsReturns published events that have coordinates as a GeoJSON FeatureCollection. Not paginated; narrow the result with bbox. Filters have the same meaning as GET /events. - GET
/events/counts-by-dateDates that have eventsReturns only the dates within the given range that have events. Intended for marking days in a calendar; lighter than the list API. - GET
/events/my-entriesOwn participated eventsRetrieve events the authenticated user has registered for, ordered by entry date descending. - GET
/events/:eventId/is-staffCheck if you are event staffReturn whether the authenticated user is staff of the event. To obtain the role and its permission list, use GET /manage/events/:eventId/acl instead. - GET
/events/:eventId/chat-roomEvent chat roomGet the event's public chat room, creating it if it does not exist. Available to participants of events with chat enabled. - GET
/events/:eventId/participantsParticipantsRetrieve event participants (confirmed / checked in and accepting meetings). Access is controlled by the participant list visibility setting. - GET
/events/:eventId/stagesList stagesList the stages (venues) of an event. - GET
/manage/events/:eventId/stagesList stages (for managers)List stages for management. Requires the stagesRead permission. - POST
/manage/events/:eventId/stagesCreate stageCreate a stage. Requires the stagesWrite permission. - PUT
/manage/events/:eventId/stages/:stageIdUpdate stageUpdate a stage. Requires the stagesWrite permission. - DELETE
/manage/events/:eventId/stages/:stageIdDelete stageDelete a stage. Requires the stagesWrite permission. - GET
/events/:eventId/sessionsList sessionsList the published sessions (timetable) of an event. - GET
/manage/events/:eventId/sessionsList sessions (for managers)List all sessions including drafts. Requires the sessionsRead permission. - GET
/events/:eventId/sessions/:idSession detailRetrieve a single session. - GET
/manage/events/:eventId/sessions/:idSession detail (for managers)Retrieve a session for management. Requires the sessionsRead permission. - POST
/manage/events/:eventId/sessionsCreate sessionCreate a session. Requires the sessionsWrite permission. SessionSpeaker links are created from speakerIds / speakers. - PUT
/manage/events/:eventId/sessions/:idUpdate sessionUpdate a session. Requires the sessionsWrite permission. Sending speakerIds reconciles the SessionSpeaker links to match the array. - DELETE
/manage/events/:eventId/sessions/:idDelete sessionDelete a session; its SessionSpeaker links are removed as well. Requires the sessionsWrite permission. - GET
/events/:eventId/speakersList speakersList the published speakers of an event. - GET
/manage/events/:eventId/speakersList speakers (for managers)List all speakers including drafts. Requires the speakersRead permission. - GET
/events/:eventId/speakers/:speakerIdSpeaker detailRetrieve a single speaker, including their sessions. - GET
/manage/events/:eventId/speakers/:idSpeaker detail (for managers)Retrieve a speaker for management. Requires the speakersRead permission. - POST
/manage/events/:eventId/speakersCreate speakerCreate a speaker. Requires the speakersWrite permission. Specify userId to link the speaker to a 4S user. - PUT
/manage/events/:eventId/speakers/:idUpdate speakerUpdate a speaker. Requires the speakersWrite permission. - DELETE
/manage/events/:eventId/speakers/:idDelete speakerDelete a speaker; its SessionSpeaker links are removed as well. Requires the speakersWrite permission. - GET
/events/:eventId/announcementsList event announcementsList event announcements. Participants only. - GET
/manage/events/:eventId/announcementsList event announcements (for managers)List event announcements for management. Requires the announcementsRead permission. - GET
/events/:eventId/announcements/:idEvent announcement detailRetrieve a single event announcement. Participants only. - POST
/manage/events/:eventId/announcementsCreate event announcementCreate an announcement for participants. Requires the announcementsWrite permission. Use targetStates / targetTicketIds to narrow the audience (defaults to CONFIRMED + CHECKED_IN entrants across all tickets). sendEmail defaults to true, so omitting it sends email to the targeted recipients. - PUT
/manage/events/:eventId/announcements/:idUpdate event announcementUpdate an announcement. Requires the announcementsWrite permission. - DELETE
/manage/events/:eventId/announcements/:idDelete event announcementDelete an announcement. Requires the announcementsWrite permission. - GET
/events/:eventId/ticketsList ticketsList the ticket types on sale for an event. - GET
/manage/events/:eventId/ticketsList tickets (for managers)List all ticket types with sales figures. Requires the ticketsRead permission. - GET
/events/:eventId/tickets/:ticketIdTicket detailRetrieve a single ticket type. - POST
/manage/events/:eventId/ticketsCreate ticketCreate a ticket type. Requires the ticketsWrite permission. Paid tickets require price and paymentType, and the event must have Stripe Connect configured. - PUT
/manage/events/:eventId/tickets/:ticketIdUpdate ticketUpdate a ticket type. Requires the ticketsWrite permission. price and paymentType cannot be changed once sales have started. - DELETE
/manage/events/:eventId/tickets/:ticketIdDelete ticketDelete a ticket type. Tickets with existing registrations cannot be deleted. Requires the ticketsWrite permission. - GET
/events/:eventId/questionsList survey questionsList the survey questions asked during event registration. - GET
/manage/events/:eventId/questionsList survey questions (for managers)List all survey questions including drafts. Requires the questionsRead permission. - POST
/manage/events/:eventId/questionsCreate survey questionCreate a registration survey question. Requires the questionsWrite permission. - PUT
/manage/events/:eventId/questions/:questionIdUpdate survey questionUpdate a survey question. Requires the questionsWrite permission. - DELETE
/manage/events/:eventId/questions/:questionIdDelete survey questionDelete a survey question; its answers are removed as well. Requires the questionsWrite permission. - GET
/events/:eventId/my-questionsOwn survey answersReturn the survey questions and the authenticated user's own answers. - GET
/events/:eventId/meeting-spotsList meeting spotsList the meeting spots configured by the event organizers. - GET
/manage/events/:eventId/meeting-spotsList meeting spots (for managers)List all meeting spots including inactive ones. Requires the meetingSpotsRead permission. - POST
/manage/events/:eventId/meeting-spotsCreate meeting spotCreate a meeting spot. Requires the meetingSpotsWrite permission. - PUT
/manage/events/:eventId/meeting-spots/:spotIdUpdate meeting spotUpdate a meeting spot. Requires the meetingSpotsWrite permission. - DELETE
/manage/events/:eventId/meeting-spots/:spotIdDelete meeting spotDelete a meeting spot. Requires the meetingSpotsWrite permission. - GET
/events/:eventId/referral-rankingReferral influence rankingRetrieve the influence ranking (top 100) based on the event's invitation chain. - GET
/events/:eventId/entriesOwn entry stateRetrieve the authenticated user's entries (non-canceled) for the specified event. - GET
/manage/events/:eventId/entriesList participantsList the event's registrations. Requires the participantsRead permission. Supports filtering by state, ticket and keyword. - GET
/manage/events/:eventId/entries/:entryIdParticipant detailRetrieve a single registration including its survey answers. Requires the participantsRead permission. - DELETE
/events/:eventId/entries/:entryIdCancel a registrationCancel your own registration. Already-canceled or checked-in registrations cannot be canceled. Paid tickets are refunded automatically according to the event's cancellation policy. - GET
/events/:eventId/entries/:entryId/referral-ancestorsReferral ancestors (inviter chain)Walk the inviter chain upward from the given entry, returned nearest-first. Returns 403 unless the caller is a confirmed participant (CONFIRMED / CHECKED_IN) of the event. - GET
/events/:eventId/entries/:entryId/referral-descendantsReferral descendants (invite tree)Expand the invite tree downward from the given entry as root. Each node's direct children are paginated by childrenLimit + cursor and include a total child count (CONFIRMED / CHECKED_IN only). Returns 403 unless the caller is a confirmed participant. - POST
/events/:eventId/registerRegister for an eventRegister for an event. Free tickets are confirmed immediately as UNUSED (or PENDING for approval-based events). For paid tickets, complete payment first via the payment link or prepaid checkout endpoints. - POST
/events/:eventId/tickets/:ticketId/payment-linkCreate a payment linkCreate a Stripe payment link for a paid ticket. Send the user to the returned url; the registration is confirmed via webhook once payment completes. - POST
/events/:eventId/tickets/:ticketId/prepaid-checkoutCreate prepaid checkoutCreate a Stripe Checkout session after storing the survey answers up front. Inventory is held for a limited time and released if payment is not completed. - POST
/events/:eventId/tickets/:ticketId/payment-intentCreate a payment sheet PaymentIntentCreate a PaymentIntent so a mobile app can charge through the Stripe payment sheet. Like prepaid checkout, survey answers and profile fields are stored first and inventory is held for a limited time. Entries are created by the payment-completed webhook. - PUT
/events/:eventId/entries/:entryId/transferTransfer a ticketTransfer an unused (UNUSED) ticket from a multi-ticket purchase to another user. Only the purchaser can transfer, and tickets marked non-transferable cannot be transferred. - POST
/questions/:questionId/answerAnswer a survey questionSubmit or update an answer to a survey question after registering. An existing answer is overwritten. - POST
/questions/:questionId/answer-imageUpload survey answer imageUpload an image for an image-type survey question and get the stored URL. Pass the URL as `answer` to POST /questions/:questionId/answer. - GET
/manage/events/:eventId/aclOwn staff role and permissionsReturn the authenticated user's staff role for the event along with the permission list for each role. Use it to drive management UI visibility. - PUT
/manage/events/:eventId/thumbnailUpload event thumbnailUpload the event thumbnail image. Requires the eventWrite permission. - PUT
/manage/events/:eventId/main-imageUpload event main imageUpload the event page's main image. Requires the eventWrite permission. - POST
/manage/events/:eventId/description-imagesUpload description imageUpload an image to embed in the event description (Markdown) and get its URL. Requires the eventWrite permission. - GET
/manage/events/:eventId/staffList event staffList the event's staff. Requires the staffRead permission. - POST
/manage/events/:eventId/staffAdd event staffAdd an existing user as event staff. Requires the staffInviteWrite or staffWrite permission. - PUT
/manage/events/:eventId/staff/:staffIdUpdate event staff roleChange a staff member's role. Requires the staffWrite permission. - DELETE
/manage/events/:eventId/staff/:staffIdRemove event staffRemove an event staff member. Requires the staffWrite permission. - PUT
/manage/events/:eventId/stages/reorderReorder stagesReorder stages in bulk. Requires the stagesWrite permission. - PUT
/manage/events/:eventId/sessions/bulkBulk update sessionsUpdate multiple sessions at once, e.g. after dragging items in the timetable. Requires the sessionsWrite permission. - POST
/manage/events/:eventId/speakers/orderReorder speakersReorder speakers in bulk. Requires the speakersWrite permission. - POST
/manage/events/:eventId/speakers/avatarUpload speaker avatarUpload a speaker avatar image and get its URL; pass it as the speaker's avatarUrl. Requires the speakersWrite permission. - PUT
/manage/events/:eventId/speakers/:id/check-inUpdate speaker check-inUpdate a speaker's on-site check-in status. Requires the speakersWrite permission. - POST
/manage/events/:eventId/tickets/orderReorder ticketsReorder ticket types in bulk. Requires the ticketsWrite permission. - PUT
/manage/events/:eventId/questions/ordersReorder survey questionsReorder survey questions in bulk. Requires the questionsWrite permission. - PUT
/manage/events/:eventId/questions/bulkBulk update survey questionsCreate or update multiple questions at once, as used when saving the survey editor. Requires the questionsWrite permission. - GET
/manage/events/:eventId/questions/:questionId/answersList answers to a questionList the answers submitted for a specific question. Requires the questionsRead permission. - GET
/manage/events/:eventId/survey-responsesList survey responsesList survey responses across all participants. Requires the questionsRead permission. - GET
/manage/events/:eventId/speaker-survey-responsesList speaker survey responsesList responses to the speaker survey. Requires the questionsRead permission. - GET
/manage/events/:eventId/question-aggregationSurvey aggregationReturn aggregated answer counts for choice questions. Requires the questionsRead permission. - POST
/manage/events/:eventId/meeting-spot-imagesUpload meeting spot imageUpload an image for a meeting spot and get its URL. Requires the meetingSpotsWrite permission. - POST
/manage/events/:eventId/meeting-spots/reorderReorder meeting spotsReorder meeting spots in bulk. Requires the meetingSpotsWrite permission. - GET
/manage/events/:eventId/entries/countsParticipant countsReturn registration counts by state and ticket. Requires the participantsRead permission. - GET
/manage/events/:eventId/entries/by-checkin-tokenLook up participant by check-in tokenLook up a registration by the check-in token embedded in the participant's QR code, for use in a reception app. Requires the participantsRead permission. - GET
/manage/events/:eventId/entries/:entryId/activity-logsParticipant activity logsReturn the history of state changes, check-ins and other operations for a registration. Requires the participantsRead permission. - PUT
/manage/events/:eventId/entries/:entryId/stateChange entry state (check-in)Change a registration's state: CHECKED_IN performs an on-site check-in, CANCELED cancels it, and for approval-based events CONFIRMED / REJECTED approve or reject it. Requires the participantsWrite permission. - PUT
/manage/events/:eventId/entries/:entryId/ticketChange a participant's ticketChange the ticket type attached to a registration. No payment or refund of the price difference is performed. Requires the participantsWrite permission.
Communities
- GET
/communitiesList / search communitiesSearch and list public communities. `tags` is AND-matched across all tags. - GET
/communities/:idCommunity detailRetrieve a community's detail. Returns 404 for private communities you cannot access. - GET
/manage/communities/:idCommunity detail (for managers)Retrieve a community for management. Admins only. - POST
/manage/communitiesCreate communityCreate a community. The creator is automatically added as an ADMIN member. No additional permission is required beyond being signed in. - PUT
/manage/communities/:idUpdate communityUpdate a community. Admins only. - DELETE
/manage/communities/:idDelete communityDelete a community. Admins only. - GET
/communities/:id/playlistsCommunity playlistsRetrieve a community's public playlists (items not included). - GET
/communities/:id/playlists/:playlistIdCommunity playlist detailRetrieve a playlist that belongs to a community. - GET
/communities/:id/membersList community membersList community members. User objects contain public fields only and never include email. - GET
/manage/communities/:id/membersList community members (for managers)List members for management. Admins only. - POST
/manage/communities/:id/membersAdd community memberAdd an existing user as a community member. Admins only. - PUT
/manage/communities/:id/members/:memberIdUpdate community memberUpdate a member's role, status and title. Approving a pending join request (PENDING → APPROVED) is also done here. Admins only. - DELETE
/manage/communities/:id/members/:memberIdRemove community memberRemove a member from the community. Admins only. - GET
/communities/:id/members/countsCommunity member countsReturn member counts grouped by role and status. - GET
/manage/communities/:id/members/countsCommunity member counts (for managers)Return member counts grouped by role and status. Admins only. - GET
/communities/:id/eventsList community eventsList events organized by the community. - GET
/communities/:id/my-membershipOwn community membershipReturn the authenticated user's membership in the community, or null if not a member. - GET
/communities/:id/chat-roomCommunity chat roomGet the community's chat room, creating it if it does not exist. Members only. - POST
/communities/:id/joinJoin a communityJoin a community. With joinType FREE_JOIN the membership becomes APPROVED immediately; with APPROVAL_REQUIRED it becomes PENDING. INVITATION_ONLY communities cannot be joined this way. - DELETE
/communities/:id/leaveLeave a communityLeave a community. The last remaining admin cannot leave. - GET
/communities/:communityId/announcementsList community announcementsList community announcements. Members only. - GET
/manage/communities/:communityId/announcementsList announcements (for managers)List community announcements for management. Admins only. - GET
/communities/:communityId/announcements/:idCommunity announcement detailRetrieve a single community announcement. Members only. - POST
/manage/communities/:communityId/announcementsCreate community announcementCreate a community announcement. sendEmail defaults to true, so omitting it notifies members by email. - PUT
/manage/communities/:communityId/announcements/:idUpdate community announcementUpdate a community announcement. Admins only. - DELETE
/manage/communities/:communityId/announcements/:idDelete community announcementDelete a community announcement. Admins only. - POST
/manage/communities/:id/logoUpload community logoUpload the community's logo image. - POST
/manage/communities/:id/logo-darkUpload dark-theme logoUpload the logo image used in dark theme.
Playlists
- GET
/playlistsPlaylist listSearch and list public playlists. - GET
/manage/playlistsList playlists (for managers)List a community's playlists for management. The communityId query parameter is required and only that community's admins can access it. - GET
/playlists/:playlistIdPlaylist detailRetrieve a playlist's detail (items via a separate endpoint). - GET
/manage/playlists/:playlistIdPlaylist detail (for managers)Retrieve a playlist for management. Only the community's admins can access it. - POST
/manage/playlistsCreate playlistCreate a playlist for a community. Only that community's admins can do this. - PATCH
/manage/playlists/:playlistIdUpdate playlistUpdate a playlist. Only the fields you send are updated. - DELETE
/manage/playlists/:playlistIdDelete playlistDelete a playlist. Only the community's admins can do this. - GET
/playlists/:playlistId/itemsPlaylist itemsRetrieve playlist items. `page` uses numbered pagination, `cursor` uses cursor pagination. groupByDate=true returns date-grouped results. - GET
/manage/playlists/:playlistId/itemsList playlist items (for managers)List the items in a playlist for management. - POST
/manage/playlists/:playlistId/itemsAdd playlist itemAdd an event or external link to the playlist. Specify eventId for a 4S event, or externalUrl for an external link. - PATCH
/manage/playlists/:playlistId/items/:itemIdUpdate playlist itemUpdate a playlist item's title, description or display order. - DELETE
/manage/playlists/:playlistId/items/:itemIdDelete playlist itemRemove an item from the playlist. - GET
/playlists/:playlistId/geojsonPlaylist map pinsReturns the 4S events in the playlist that have coordinates as a GeoJSON FeatureCollection. Not paginated; narrow the result with bbox. Filters have the same meaning as GET /playlists/:playlistId/items. - POST
/playlists/:playlistId/preview-externalPreview an external URL for submissionFetch title, description, image and schedules from an external URL's OG / JSON-LD for the submission form. Only available on playlists that accept submissions. - POST
/playlists/:playlistId/item-requestsSubmit an eventSubmit an event to a playlist. The item is created with status=PENDING and stays hidden until a community admin approves it. Approval and rejection are done through the /manage/playlists endpoints. - POST
/manage/playlists/preview-externalPreview an external URLFetch the OGP metadata (title, description, image) of a URL before adding it to a playlist as an external item. - POST
/manage/playlists/:playlistId/thumbnailUpload playlist thumbnailUpload the playlist's thumbnail image. - POST
/manage/playlists/:playlistId/items/:itemId/thumbnailUpload playlist item thumbnailUpload the thumbnail image for a playlist item.
Chat
- GET
/chat-roomsList chat roomsList the chat rooms the authenticated user belongs to, including unread counts and the latest message. - GET
/chat-rooms/:idChat room detailRetrieve a chat room and its members. Only room members can access it. - POST
/chat-roomsCreate chat roomCreate a chat room. With two members (including yourself) it becomes a DIRECT room, otherwise a GROUP room. For DIRECT rooms an existing room is returned if one already exists. initialMessage is optional; passing it also creates the first message. A system message recording the creation is always added to a newly created room, whether or not initialMessage is given (not when an existing DIRECT room is returned). - PUT
/chat-rooms/:idRename chat roomRename a group chat. Only joined members can rename it. Direct messages (whose display name is derived from the participants) and event/community chats (which use their own name) cannot be renamed. Sending an empty string clears the name. - GET
/chat-rooms/:id/membersList chat room membersList a chat room's members with pagination. Includes members in the JOINED and INVITED states. Only rooms the authenticated user belongs to can be read. - POST
/chat-rooms/:id/membersInvite chat room membersInvite users to a chat room. Invitees enter the INVITED state and can join or decline. Only members in the JOINED state can invite. - DELETE
/chat-rooms/:id/members/:memberIdRemove a chat room memberRemove a member from a chat room. Only members in the JOINED state can remove others, and you cannot remove yourself (use leave instead). Not available for event or community chats. - GET
/chat-rooms/:id/mention-candidatesList mention candidatesList the people who can be @mentioned. Only users returned here are accepted in the mentions field when sending a message (the server applies the same check). For community chats the set is the approved community members, which differs from the members endpoint (ChatRoomMember rows). Event chats do not support mentions and always return an empty array. There is no pagination: results are capped by limit, so narrow them with q. - PUT
/chat-rooms/:id/joinJoin a chat roomAccept an invitation and join the chat room (INVITED → JOINED). - PUT
/chat-rooms/:id/declineDecline a chat room invitationDecline an invitation to a chat room. The membership record is deleted, so the room no longer appears in your list; you can join again if you are re-invited. - PUT
/chat-rooms/:id/leaveLeave a chat roomLeave a chat room (JOINED → LEFT). The membership record is retained with the LEFT status. - GET
/chat-rooms/:roomId/messagesList messagesList the room's messages using cursor-based pagination. Only room members can access it. - POST
/chat-rooms/:roomId/messagesSend a messagePost a message to a chat room. Send `content` as JSON for a text message, or a `file` as multipart/form-data for an image message. Only room members can post. - DELETE
/chat-rooms/:roomId/messages/:messageIdDelete a messageSoft-delete a message you sent. Deleted messages are excluded from listings, unread counts and the image list. Deleting an already deleted message succeeds. - GET
/chat-rooms/:roomId/messages/:messageId/reactionsList who reactedList the reactions on a message together with the users who added them. The message list only includes userIds, so use this endpoint when you need to show who reacted. - POST
/chat-rooms/:roomId/messages/:messageId/reactionsAdd a reactionAdd an emoji reaction to a message. One user can add several different emojis. Sending the same emoji twice does not add a duplicate (idempotent). Only emojis from a fixed allow list are accepted. - DELETE
/chat-rooms/:roomId/messages/:messageId/reactionsRemove a reactionRemove your own reaction. Succeeds even if you had not reacted (idempotent). The emoji is passed as a query parameter. - GET
/chat-rooms/:roomId/imagesList room imagesList images uploaded to the chat room, newest first. Images from deleted messages are excluded. - PUT
/chat-rooms/:roomId/notificationsToggle room notificationsTurn notifications (push, in-app and email) on or off for this chat room. Unread counts are unaffected — the badge still increases, you just are not notified. The current value is available as notificationsEnabled on your member entry in the room detail response. - GET
/chat-rooms/:roomId/filesList filesList the chat room's file library. Folders and files are returned in a single items array sorted by name regardless of type, together with breadcrumbs. Omit folderId for the root. There is no pagination: the whole contents of the folder are returned. - POST
/chat-rooms/:roomId/filesRegister an uploaded fileRegister a file that was PUT to S3. The server verifies the real size and rejects anything over 10MB. If a file with the same name exists in the folder, it is numbered as "report (2).pdf". Set postToChat to true to also post it to the chat. - PUT
/chat-rooms/:roomId/files/:fileIdRename or move a fileRename a file and/or move it to another folder. If the destination already has a file with that name, the name is numbered. - DELETE
/chat-rooms/:roomId/files/:fileIdDelete a fileSoft-delete a file you uploaded. Deleting an already deleted file succeeds. - POST
/chat-rooms/:roomId/files/upload-urlCreate an upload URLFiles are uploaded straight to S3, not through the API. PUT the file to the returned uploadUrl with exactly the requiredHeaders. Do not add an Authorization header (it breaks the signature). Register the upload afterwards with POST /chat-rooms/:roomId/files. The limit is 10MB. - GET
/chat-rooms/:roomId/files/:fileId/urlCreate a download URLFiles are private, so fetch a short-lived URL each time. disposition=inline is honoured only for allowed types such as PDF; anything else is forced to attachment. The URL carries the file name, so opening it as a link downloads the file. - GET
/chat-rooms/:roomId/foldersList foldersList every folder in the room as a flat array without pagination. Use it to build a tree for destination pickers. - POST
/chat-rooms/:roomId/foldersCreate a folderCreate a folder. Folders can nest up to 10 levels. Two folders in the same parent cannot share a name (unlike files, folders are not numbered). - PUT
/chat-rooms/:roomId/folders/:folderIdRename or move a folderRename a folder and/or move it. A folder cannot be moved into itself or one of its descendants, and a move that would exceed 10 levels is rejected. - DELETE
/chat-rooms/:roomId/folders/:folderIdDelete a folderSoft-delete a folder together with its files and subfolders. Only the creator can delete it, and the request is rejected if it contains files uploaded by others. The returned counts can be used in a confirmation dialog. - PUT
/chat-rooms/:roomId/readMark messages as readMark the room's messages as read, resetting the unread count to zero.
Notifications
- GET
/notificationsList notificationsList notifications addressed to the authenticated user, newest first. Deleted notifications are excluded. With `unreadFirst=true`, unread notifications come first and are ordered newest-first within that group (useful for a header dropdown). - GET
/notifications/unread-countUnread notification countReturn just the authenticated user's unread notification count. A lightweight endpoint for rendering a badge. - PUT
/notifications/:id/readMark a notification as readMark the given notification as read. Idempotent — calling it on an already-read notification succeeds. Returns 404 for notifications owned by another user or already deleted. - PUT
/notifications/read-allMark all notifications as readMark every unread notification of the authenticated user as read. Returns how many were updated.
Push notifications
- GET
/push/devicesList push devicesList the push devices the authenticated user registered through this app, newest first. Disabled devices are excluded. - POST
/push/devicesRegister a push deviceStore an FCM registration token so the user can receive push notifications for new chat messages. Registration is idempotent on token, so re-sending the same token does not create duplicates. If the token already belongs to another user (device hand-off or account switch), ownership moves to the authenticated user. A previously disabled token is re-enabled. - DELETE
/push/devices/:deviceIdUnregister a push deviceStop push notifications for the given device. Call this on sign-out. Returns 404 for devices registered by another app or owned by another user.
Bookmarks
- GET
/bookmarksBookmark listRetrieve events the authenticated user has bookmarked, newest first. - POST
/bookmarksAdd a bookmarkAdd an item to your bookmarks. The operation is idempotent: bookmarking an already-bookmarked item is not an error. For EVENT the target is checked for existence and 404 is returned if it does not exist. - GET
/bookmarks/idsBookmarked ID listRetrieve the array of bookmarked entity IDs for the authenticated user, newest first. - DELETE
/bookmarks/:entity/:entityIdRemove a bookmarkRemove a bookmark. Removing a bookmark that does not exist is not an error.
Misc
- GET
/slug/:slugResolve resource by slugResolve the resource (user / organization / community / event) for a slug. Matched in order: user → organization → community → event. - GET
/tags/masterTag suggestionsReturns the curated tag candidates. Use them to suggest values in tag inputs such as the skill tags on a profile. Sorted by `order` ascending, then by name. - GET
/tags/usageTags in useAggregates the tags actually attached to records, grouped by entity and category. Useful for search filter suggestions. - GET
/places/autocompletePlace autocompleteReturns place suggestions for the text being typed. The Google Places API is called on the server, so clients do not need an API key. Used for the location field on a profile. - GET
/places/:placeIdPlace detailReturns an object for the given placeId in the shape used to store a user's location. The Japanese rendering is included under languages.ja.