Build on Ascended Social

The Public API gives you read-only access to users, posts, videos, live sessions, books, SABS characters, and more — all paginated, all authenticated, all public data only.

🔑

Authentication

Bearer token in every request. Keys live in your account settings.

📡

13 Endpoints

Users, posts, videos, live, books, SABS, sigils — all read-only.

Rate Limited

100–10,000 req/min depending on tier. Headers tell you what's left.

🛡️

Privacy First

Only public data returned. PII, private posts, DMs never exposed.

Quick Start

1. Get an API Key

Create a free account at ascended.social, then go to Settings → Developer to generate a key. It will look like:

sk_live_REDACTED_EXAMPLE_KEY

2. Make your first request

The public health endpoint requires no key and tells you the API is up:

# No API key needed for health check
curl https://ascended.social/api/v1/healthz
# Response
{
  "status": "ok",
  "timestamp": "2026-06-03T12:00:00.000Z"
}

3. Fetch the post feed

curl -H "Authorization: Bearer sk_live_your_key" \
  "https://ascended.social/api/v1/posts?limit=5"
{
  "data": [
    {
      "id": "post_abc123",
      "authorId": "usr_xyz789",
      "content": "Just witnessed the most incredible sunrise 🌅",
      "createdAt": "2026-05-30T06:12:00.000Z"
    },
    // ... more posts
  ],
  "page": 0,
  "pageSize": 5,
  "hasMore": true
}

All endpoints except /healthz require the Authorization: Bearer sk_live_... header. Without it you get 401 Unauthorized.

Authentication

Pass your API key as a Bearer token in the Authorization header on every request:

Authorization: Bearer sk_live_<your_key_here>

Key types

Prefix Type Use
sk_live_ Production Live data — use in your backend only
sk_test_ Test / sandbox For development and CI pipelines

Never expose API keys in client-side code, browser JavaScript, or public repositories. Use environment variables (process.env.ASCENDED_API_KEY) or a secrets manager. Keys are hashed and cannot be recovered — if lost, revoke and regenerate.

Key management

  • Create and revoke keys at Settings → Developer
  • You can have multiple keys (useful for separating services or environments)
  • Revoked keys return 401 immediately and cannot be un-revoked
  • last_used_at is updated on every valid request for auditing

Base URL

All API endpoints are served under:

https://ascended.social/api/v1

Example — the posts feed is at:

GET https://ascended.social/api/v1/posts

The interactive API Explorer (Swagger UI) is pre-configured with this base URL. Use it to browse every endpoint and try requests live in your browser.

Versioning

The API version is embedded in the path (/api/v1). The current stable version is v1. We will not make breaking changes to v1 — any incompatible changes will be released under /api/v2 with ample advance notice.

Pagination

All list endpoints use page-based pagination. You control it with two query parameters:

Param Type Default Description
page integer 0 0-indexed page number
limit integer 20 Results per page (max 50)

Response envelope

Every paginated response wraps the array in a standard envelope:

{
  "data": [ /* array of results */ ],
  "page": 0,         // current page (0-indexed)
  "pageSize": 20,    // items per page you requested
  "hasMore": true    // false when you've reached the last page
}

Some endpoints add extra top-level keys (e.g. bookId in the chapters response, userId in character responses).

Walking all pages

async function fetchAll(endpoint) {
  const results = [];
  let page = 0;
  do {
    const res = await fetch(
      `https://ascended.social/api/v1/${endpoint}?page=${page}&limit=50`,
      { headers: { Authorization: `Bearer ${API_KEY}` } }
    );
    const json = await res.json();
    results.push(...json.data);
    if (!json.hasMore) break;
    page++;
  } while (true);
  return results;
}

const allPosts = await fetchAll('posts');

Be mindful of rate limits when paginating large datasets. Consider using limit=50 (the max) to minimize the number of requests.

Rate Limits

Every response includes these headers so you can track consumption in real time:

X-RateLimit-Limit: 100             # requests allowed per minute
X-RateLimit-Remaining: 87          # requests left this minute
X-RateLimit-Remaining-Month: 9134  # requests left this month (Free/Pro)

Tiers

Tier Req / minute Req / month How to get it
Free 100 10,000 All accounts, immediate access
Pro 1,000 1,000,000 Active Pro subscription
Enterprise 10,000 Unlimited Contact us

When you're rate limited

The API returns 429 Too Many Requests. The response includes a Retry-After header (seconds) and a JSON body:

{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded: 100 requests per minute",
  "tier": "free",
  "retryAfter": 60
}

Handling 429s in code

async function fetchWithRetry(url, opts, retries = 3) {
  const res = await fetch(url, opts);
  if (res.status === 429 && retries > 0) {
    const wait = 1000 * (parseInt(res.headers.get('Retry-After') || '60'));
    await new Promise(r => setTimeout(r, wait));
    return fetchWithRetry(url, opts, retries - 1);
  }
  return res;
}

Error Handling

All error responses share a consistent shape:

{
  "error": "Not found",      // short identifier
  "message": "Post not found"  // human-readable explanation
}
Status When it happens Action
400 Invalid query parameter (e.g. non-numeric page) Fix the request parameters
401 Missing, malformed, or revoked API key Check the Authorization header format
404 Resource ID doesn't exist or profile is private The resource isn't available publicly
429 Per-minute or monthly quota exceeded Wait for Retry-After seconds
500 Unexpected server error Check status.ascended.social, retry with backoff

Robust error handling example

async function apiRequest(path, opts = {}) {
  const res = await fetch(`https://ascended.social/api/v1${path}`, {
    ...opts,
    headers: {
      Authorization: `Bearer ${process.env.ASCENDED_API_KEY}`,
      ...opts.headers,
    },
  });

  if (res.status === 404) return null;  // Not found — treat as absent
  if (res.status === 401) throw new Error('Invalid API key');
  if (res.status === 429) {
    const err = new Error('Rate limited');
    err.retryAfter = parseInt(res.headers.get('Retry-After') || '60');
    throw err;
  }
  if (!res.ok) throw new Error(`API error ${res.status}`);

  return res.json();
}

Privacy Model

The Ascended Social Public API is strictly read-only and returns only content users have made publicly visible:

  • Private profiles are excluded from all responses (404 is returned)
  • Direct messages and private posts are never accessible
  • PII (email addresses, phone numbers, IP data) is stripped at the query level
  • Row-Level Security (RLS) is enforced at the PostgreSQL layer — no code path can accidentally leak private data
  • All API key usage is logged per-key for audit and abuse prevention

By using this API you agree to the API Terms of Use. Do not use the API to build surveillance tools, scrape data in bulk for resale, or contact users without their consent.


API Reference

All endpoints are under https://ascended.social/api/v1. All except /healthz require Authorization: Bearer sk_live_.... For interactive docs, visit api-docs.ascended.social →

Health

GET /healthz PUBLIC No API key required

Returns server health. Used by uptime monitors and status pages. No authentication required.

Response

{
  "status": "ok",
  "timestamp": "2026-06-03T12:00:00.000Z"
}

Users

GET /users/{userId} Public profile

Returns a user's public profile. Returns 404 if the profile is private or the user doesn't exist.

Path Parameters

Param Type Description
userId string The user's ID

Response Fields

Field Type Description
id string Unique user ID
username string | null URL-safe username
displayName string | null Display name
bio string | null Short bio
profileImageUrl string | null Avatar image URL
createdAt ISO 8601 Account creation timestamp
curl -H "Authorization: Bearer sk_live_..." \
  https://ascended.social/api/v1/users/usr_abc123
GET /users/{userId}/posts Posts by user

Returns paginated posts authored by a user, newest first.

Query Parameters

Param Default Max
page 0
limit 20 50

Response

{
  "data": [{ "id", "content", "createdAt", "updatedAt" }],
  "page": 0, "pageSize": 20, "hasMore": false
}

Posts

GET /posts Public feed

Global public post feed, newest first. Returns each post with its author's ID.

Query Parameters

Param Default Max
page 0
limit 20 50

Response Fields (per item)

Field Type Description
id string Post ID
authorId string ID of the post author
content string Post text content
createdAt ISO 8601 Publication time
GET /posts/{postId} Single post

Returns a single post by ID. Returns 404 if it doesn't exist or is private.

Response Fields

Field Type Description
id string Post ID
authorId string Author's user ID
content string Post text
createdAt ISO 8601 Published at
updatedAt ISO 8601 | null Last edited at

Books

GET /books Library

Paginated list of public books in the library, newest first.

Response Fields (per item)

Field Type
id string
title string
description string | null
createdAt ISO 8601
GET /books/{bookId} Single book

Returns a single book by ID. Same fields as the list endpoint.

GET /books/{bookId}/chapters Chapters in a book

Paginated chapters for a book. Response also includes the top-level bookId field.

Response

{
  "bookId": "book_abc123",
  "data": [{ "id", "bookId", "title", "createdAt" }],
  "page": 0, "pageSize": 20, "hasMore": false
}

Videos

GET /videos Elements video feed

Paginated list of public videos from the Elements feed, newest first.

Response Fields (per item)

Field Type
id string
title string | null
description string | null
createdAt ISO 8601

Live Sessions

GET /live/sessions Live & recent sessions

Returns live and recently ended video sessions, newest first.

Response Fields (per item)

Field Type Description
id string Session ID
title string | null Session title
description string | null Description
status string | null "live", "ended", etc.
createdAt ISO 8601 Session start time

SABS — Soul-Bound Ascension System

SABS is Ascended Social's spiritual progression game. Users create characters, join campaigns, and level up through real-world spiritual practices.

GET /sabs/campaigns Public campaigns

Returns paginated public SABS campaigns, newest first.

Response Fields (per item)

Field Type Description
id string Campaign ID
title string | null Campaign name
description string | null Description
status string | null "active", "completed", etc.
createdAt ISO 8601 Creation time
GET /sabs/characters/by-user/{userId} Characters by user

Returns paginated SABS characters owned by a user. The top-level response also includes the userId.

Response

{
  "userId": "usr_abc123",
  "data": [{ "id", "userId", "name", "createdAt" }],
  "page": 0, "pageSize": 20, "hasMore": false
}

Sigils

Sigils are unique spiritual symbols minted by users. They can be offered for trade with other members of the community.

GET /sigils/trades/{tradeId} Trade listing

Returns public details for a sigil trade by its ID.

Response Fields

Field Type Description
id string Trade ID
state string | null "pending", "completed", "expired"
expiresAt ISO 8601 | null When the offer expires
completedAt ISO 8601 | null When the trade was accepted
createdAt ISO 8601 When the offer was created

JavaScript / TypeScript

Minimal API client

A lightweight wrapper you can copy into any project:

// ascended-api.ts
const BASE = "https://ascended.social/api/v1";

async function ascendedFetch(path: string, params: Record<string, any> = {}) {
  const url = new URL(BASE + path);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));

  const res = await fetch(url.toString(), {
    headers: { Authorization: `Bearer ${process.env.ASCENDED_API_KEY}` },
  });

  if (res.status === 429) {
    const retry = parseInt(res.headers.get("Retry-After") || "60");
    throw Object.assign(new Error("Rate limited"), { retryAfter: retry });
  }
  if (!res.ok) throw new Error(`Ascended API error: ${res.status}`);
  return res.json();
}

// Usage
const posts = await ascendedFetch("/posts", { limit: 50, page: 0 });
const user  = await ascendedFetch(`/users/${userId}`);
const books = await ascendedFetch("/books", { page: 0 });

Fetch all pages (TypeScript)

async function fetchAllPages<T>(path: string): Promise<T[]> {
  const results: T[] = [];
  let page = 0;
  while (true) {
    const res: { data: T[]; hasMore: boolean } =
      await ascendedFetch(path, { page, limit: 50 });
    results.push(...res.data);
    if (!res.hasMore) break;
    page++;
  }
  return results;
}

const allBooks = await fetchAllPages<Book>("/books");

Next.js API Route example

// app/api/feed/route.ts
import { NextResponse } from "next/server";

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const page = searchParams.get("page") || "0";

  const res = await fetch(
    `https://ascended.social/api/v1/posts?page=${page}&limit=20`,
    { headers: { Authorization: `Bearer ${process.env.ASCENDED_API_KEY}` },
      next: { revalidate: 60 } }  // Cache for 60s
  );

  if (!res.ok) return NextResponse.json({ error: "upstream error" }, { status: 502 });
  return NextResponse.json(await res.json());
}

Python

Using httpx

# pip install httpx
import httpx, os

BASE = "https://ascended.social/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ASCENDED_API_KEY']}"}

def fetch_posts(page=0, limit=20):
    r = httpx.get(f"{BASE}/posts", params={"page": page, "limit": limit}, headers=HEADERS)
    r.raise_for_status()
    return r.json()

def fetch_user(user_id: str):
    r = httpx.get(f"{BASE}/users/{user_id}", headers=HEADERS)
    if r.status_code == 404:
        return None
    r.raise_for_status()
    return r.json()

def fetch_all_books():
    books, page = [], 0
    while True:
        resp = fetch_posts.__globals__['httpx'].get(
            f"{BASE}/books", params={"page": page, "limit": 50}, headers=HEADERS
        ).json()
        books.extend(resp["data"])
        if not resp["hasMore"]: break
        page += 1
    return books

# Example
feed = fetch_posts(limit=50)
print(f"Got {len(feed['data'])} posts, hasMore={feed['hasMore']}")

Using requests

# pip install requests
import requests, os

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {os.environ['ASCENDED_API_KEY']}"
})

BASE = "https://ascended.social/api/v1"

resp = session.get(f"{BASE}/posts", params={"page": 0, "limit": 20})
resp.raise_for_status()
data = resp.json()

for post in data["data"]:
    print(post["id"], post["content"][:60])

cURL

# Health check (no key needed)
curl https://ascended.social/api/v1/healthz

# Post feed
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  "https://ascended.social/api/v1/posts?limit=20&page=0"

# User profile
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/users/usr_abc123

# Books library
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  "https://ascended.social/api/v1/books?page=0&limit=50"

# Chapters in a book
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/books/book_abc123/chapters

# Video feed
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/videos

# Live sessions
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/live/sessions

# SABS campaigns
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/sabs/campaigns

# SABS characters for a user
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/sabs/characters/by-user/usr_abc123

# Sigil trade
curl -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/sigils/trades/trade_abc123

# Save rate limit headers to a file for inspection
curl -D headers.txt -H "Authorization: Bearer $ASCENDED_API_KEY" \
  https://ascended.social/api/v1/posts -o /dev/null 2>&1
grep -i "X-RateLimit" headers.txt

Changelog

v1.0.0 — June 2026

  • Initial public release of the Ascended Social Public API
  • 13 read-only endpoints: users, posts, books, chapters, videos, live sessions, SABS campaigns, SABS characters, sigil trades, and health
  • SHA-256 API key hashing for O(1) indexed key lookup
  • Per-minute and per-month rate limiting with response headers
  • Full usage tracking to api_usage table (per-request endpoint, method, status, response time)
  • Row-Level Security enforced at the database layer for all API tables
  • Interactive Swagger UI at api-docs.ascended.social
  • OpenAPI 3.1 spec at api-docs.ascended.social/openapi.yaml
  • Status monitoring at status.ascended.social

Support

💬

Developer Support

Questions about the API, rate limit increases, enterprise plans:

api@ascended.social →

🔍

Interactive Explorer

Browse every endpoint and try requests in your browser with no setup.

api-docs.ascended.social →

📊

Service Status

Real-time uptime, latency, and incident history for all API services.

status.ascended.social →

🛡️

Security Issues

Found a vulnerability? Please report it through our responsible disclosure policy.

Disclosure policy →