Skip to main content

Overview

User endpoints manage authentication sessions, user profiles, and account data. Most user operations are handled through OAuth flows documented in Authentication.

List Users

GET /{locale}/users
Authentication Required
List all users with pagination support.
limit
integer
default:"25"
Number of users per page
offset
integer
default:"0"
Number of users to skip
curl "http://localhost:8080/en/users?limit=10" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
  "data": [
    {
      "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
      "email": "[email protected]",
      "name": "John Doe",
      "kind": "standard",
      "individual_profile_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
      "profile_picture_uri": "https://avatars.githubusercontent.com/u/12345",
      "github_handle": "johndoe",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "cursor": {
    "next": "eyJvZmZzZXQiOjEwfQ==",
    "has_more": true
  }
}
id
string
Unique user ID (ULID)
email
string
User email address
name
string
User display name
kind
string
User kind: standard, admin
individual_profile_id
string
ID of user’s individual profile (if created)
github_handle
string
GitHub username (if connected)
bsky_handle
string
Bluesky handle (if connected)
x_handle
string
X/Twitter handle (if connected)

Get User by ID

GET /{locale}/users/{id}
Authentication Required
Retrieve a specific user by ID.
id
string
required
User ID (ULID)
curl "http://localhost:8080/en/users/01ARZ3NDEKTSV4RRFFQ69G5FAV" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
  "data": {
    "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "email": "[email protected]",
    "name": "John Doe",
    "kind": "standard",
    "individual_profile_id": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "profile_picture_uri": "https://avatars.githubusercontent.com/u/12345",
    "github_handle": "johndoe",
    "github_remote_id": "12345",
    "bsky_handle": "johndoe.bsky.social",
    "x_handle": "johndoe",
    "apple_remote_id": "001234.abcdef1234567890abcdef1234567890.1234",
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-03-07T10:30:00Z"
  }
}

Authentication Endpoints

See Authentication for complete OAuth flow documentation.

Initiate OAuth Login

GET /{locale}/auth/{authProvider}/login?redirect_uri=YOUR_REDIRECT_URI
Redirect users to OAuth provider login.
authProvider
string
required
OAuth provider: github, apple, bsky, x, youtube, linkedin
redirect_uri
string
required
URL to redirect to after authentication

OAuth Callback (GET)

GET /{locale}/auth/{authProvider}/callback?code=CODE&state=STATE
Handles OAuth callback and returns JWT token.

OAuth Callback (POST)

POST /{locale}/auth/{authProvider}/callback
Handles OAuth POST callback (used by Apple Sign In with response_mode=form_post).
code
string
required
Authorization code from provider
state
string
required
OAuth state parameter
user
string
Apple-specific user info (JSON string, first auth only)

Refresh Token

POST /{locale}/auth/refresh
Refresh JWT token before expiration.
Authorization
string
required
Bearer token to refresh
curl -X POST "http://localhost:8080/en/auth/refresh" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresAt": 1735689600
  }
}
token
string
New JWT token
expiresAt
integer
Token expiration timestamp (Unix epoch)

Logout

POST /{locale}/auth/logout
Invalidate the current session and create a new anonymous session.
curl -X POST "http://localhost:8080/en/auth/logout" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
Response:
{
  "data": {
    "status": "logged out"
  }
}
After logout, a new anonymous session is created automatically and set in the session cookie. The old JWT token is invalidated.

Session Management

Session Types

Anonymous Session

Created automatically for all visitors. Stores locale preferences and temporary data.

Authenticated Session

Created after OAuth login. Links to user account and individual profile.

Session Status

  • active - Currently valid session
  • logged_out - User explicitly logged out
  • expired - Session exceeded TTL (365 days)
  • terminated - Manually terminated (e.g., from another device)
Authenticated sessions set a cookie for same-domain requests:
  • Name: aya_session
  • Domain: .aya.is
  • Path: /
  • Secure: true (HTTPS only)
  • SameSite: Lax
  • HttpOnly: true
  • Max-Age: 365 days

Cross-Domain Authentication

For cross-domain requests (e.g., custom domains), use the JWT token in the Authorization header:
Authorization: Bearer YOUR_JWT_TOKEN
The API checks the cookie first, then falls back to the Bearer token.

OAuth Providers

GitHub

  • Scopes: read:user, user:email, public_repo, read:org
  • User data: email, name, avatar, username
  • Auto-creates: Managed GitHub profile link

Apple

  • Scopes: name, email
  • User data: email, name (first auth only)
  • Flow: POST callback with response_mode=form_post

Bluesky

  • User data: handle, display name, avatar
  • Requires: Bluesky account

X/Twitter

  • Scopes: tweet.read, users.read, offline.access
  • User data: handle, name, profile picture

YouTube

  • Scopes: youtube.readonly
  • User data: channel info
  • Used for: Profile link integration

LinkedIn

  • Scopes: openid, profile, email
  • User data: email, name
  • Used for: Login and profile links

Security

Token Security
  • Never expose JWT tokens in logs or client-side code
  • Always use HTTPS in production
  • Refresh tokens before expiration (365 days)
  • Implement token rotation for long-lived sessions
Best Practices
  • Store tokens securely (httpOnly cookies or encrypted storage)
  • Implement automatic token refresh on 401 errors
  • Clear tokens on logout
  • Monitor for suspicious login patterns

User Kinds

Standard User

Default user type with access to:
  • Create ONE individual profile
  • Create unlimited organization/product profiles
  • Join communities as member
  • Create stories and discussions

Admin User

Elevated privileges:
  • Access admin endpoints
  • Manage all profiles and content
  • Trigger system operations (e.g., bulletin processing)
  • View all user data
Admin status is set at the database level and cannot be changed via API.

Build docs developers (and LLMs) love