# VedaStudy SSB for developers

*Learn, Listen and Live Vedas.*

Build websites, apps and AI tools on VedaStudy SSB's library of sacred texts.

## Overview

VedaStudy SSB is a living library of sacred texts: every text in several language editions (IAST/English, Devanāgarī, Tamil, Telugu, Kannada, Russian…), each **always at its latest version**. When a text is corrected and re-uploaded, everything built on this platform shows the new version automatically — links, embeds and API answers never point at an old file.

What you can build with:

- **Embedded reader** — drop the full VedaStudy SSB reader into any page: PDF with page navigation, language switcher, **Chant & Read** (tap a line to hear it, follow along, slow down, repeat) and **line meanings** (tap a line for its in-depth meaning). One line of HTML.
- **Sign in with VedaStudy SSB** — your visitors sign in (or sign up) with their VedaStudy SSB account: an emailed code, on VedaStudy SSB's own page. You get their name, and can read/save texts in their library.
- **REST API** — folders, documents, every edition's current file, page text, chant timings, meanings, search, "what changed" per version.
- **MCP server** — connect Claude, ChatGPT, Cursor or any MCP client; the assistant can search the library, read texts and generate the integration code for you.
- **Webhooks** — get told the moment a new version is published.

### Getting access

A VedaStudy SSB admin creates an **app** for you (Admin → Developers) and gives you:

| Credential | Looks like | Where to use it |
| --- | --- | --- |
| Publishable key | `pk_live_…` | In web pages: embeds, the JS SDK, browser API calls. Only works from your app's **allowed origins** (e.g. `https://your-site.org`). It is also your OAuth `client_id`. |
| Secret key | `sk_live_…` | Only on your server (API calls, OAuth token exchange). Shown once; can be rotated. Never put it in a web page or a URL. |
| Webhook secret | `whsec_…` | To verify webhook signatures. |

Each app also has a **content access** setting chosen by VedaStudy SSB:

- **Signed-in** (default) — titles, folders and thumbnails are open to your key, but reading/listening needs the visitor to **sign in with VedaStudy SSB** (the embed handles this by itself).
- **Open** — anyone on your site can read and listen without an account.

Private (staff-only) items are never available through the platform.

### Base URLs

- API: `https://vedastudyssb.web.app/api/v1` (OpenAPI: `https://vedastudyssb.web.app/api/v1/openapi.json`)
- MCP: `https://vedastudyssb.web.app/api/mcp`
- OAuth metadata: `https://vedastudyssb.web.app/.well-known/oauth-authorization-server`
- JS SDK: `https://vedastudyssb.web.app/sdk/v1.js`


## Embed the reader

### The quickest way — web component

```html
<script src="https://vedastudyssb.web.app/sdk/v1.js" data-key="pk_live_YOUR_PUBLISHABLE_KEY" defer></script>

<vedastudy-reader doc="DOCUMENT_ID_OR_SLUG" lang="en" chant meanings height="85vh"></vedastudy-reader>
```

Attributes:

| Attribute | Meaning |
| --- | --- |
| `doc` | Document id or slug (required). Find them with the API or the MCP `search` tool. |
| `lang` | Edition to open: `en` (IAST), `hi`, `ta`, `te`, `kn`, `ru`… The reader still lets people switch. |
| `page` | Open on this page. |
| `line` | Open on (and highlight) this line key, e.g. `12:0`. |
| `chant` | Start with Chant & Read on (when the text has recordings). |
| `meanings` | Start with line meanings shown. |
| `ui` | Interface language of the reader's buttons (e.g. `hi`, `ta`, `es`); defaults to the visitor's browser language. |
| `hide-header` | Hide the title bar (when your page already shows the title). |
| `height` | CSS height of the frame (default `80vh`). |

The element fires DOM events you can listen to: `vedastudy:ready`, `vedastudy:page` (`detail.page`), `vedastudy:line` (`detail.key`, when a line is played or opened), `vedastudy:signin` (`detail.signedIn`).

```js
document.querySelector("vedastudy-reader").addEventListener("vedastudy:line", (e) => console.log(e.detail));
```

### Plain iframe

```html
<iframe src="https://vedastudyssb.web.app/embed/DOCUMENT_ID?key=pk_live_YOUR_PUBLISHABLE_KEY&lang=en&chant=1"
        style="width:100%;height:85vh;border:0;border-radius:12px"
        allow="autoplay; fullscreen; clipboard-write" loading="lazy"></iframe>
```

The embed only loads on your app's **allowed origins** (it sends `Content-Security-Policy: frame-ancestors` with them). For local development ask for `http://localhost:3000` (any port you use) to be added.

### Signed-in apps

If your app is "signed-in", the reader shows a **Sign in with VedaStudy SSB** button inside the frame. It opens VedaStudy SSB's sign-in in a popup (new members can sign up there), and the reader remembers the visitor on your site afterwards. You don't need any code for this.

### Always the latest version

The embed resolves the edition's **current** version every time it loads. When an admin uploads a corrected PDF, every embed shows it — including the chant timings and meanings, which are re-placed on the new pages automatically.


## Sign in with VedaStudy SSB

Members sign in on VedaStudy SSB's own page — with an emailed 6-digit code — and new visitors can **create their VedaStudy SSB account right there**. Your app never sees a password or code; it receives an access token.

### In the browser (JS SDK)

```html
<script src="https://vedastudyssb.web.app/sdk/v1.js" data-key="pk_live_YOUR_PUBLISHABLE_KEY" defer></script>
<vedastudy-signin></vedastudy-signin>   <!-- a ready-made button -->

<script>
  window.addEventListener("vedastudy:auth", (e) => {
    const user = e.detail.user;        // { id, name, email, country, preferredLanguage } or null
    console.log(user ? "Hello " + user.name : "signed out");
  });

  async function custom() {
    const { user } = await VedaStudy.signIn({ scope: "profile library" }); // opens a popup
    const lib = await VedaStudy.api("/me/library");                        // calls the API as the member
    await VedaStudy.signOut();
  }
</script>
```

The SDK uses OAuth 2.1 with PKCE in a popup, keeps tokens in `localStorage` for your site, refreshes them automatically, and exposes `VedaStudy.getUser()`, `VedaStudy.getAccessToken()`, `VedaStudy.api(path, init)`, `VedaStudy.signIn()`, `VedaStudy.signOut()`.

### Server-side (any language) — standard OAuth 2.1

1. Register your callback URL with the VedaStudy SSB admin (e.g. `https://your-site.org/auth/vedastudy/callback`).
2. Send the visitor to:

```
https://vedastudyssb.web.app/oauth/authorize?response_type=code
  &client_id=pk_live_YOUR_PUBLISHABLE_KEY
  &redirect_uri=https://your-site.org/auth/vedastudy/callback
  &scope=profile%20library
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER&code_challenge_method=S256
```

3. VedaStudy SSB redirects back with `?code=…&state=…`. Exchange it from your server:

```bash
curl -X POST https://vedastudyssb.web.app/api/oauth/token \
  -u "pk_live_YOUR_PUBLISHABLE_KEY:sk_live_YOUR_SECRET" \
  -d grant_type=authorization_code -d code=CODE \
  -d redirect_uri=https://your-site.org/auth/vedastudy/callback \
  -d code_verifier=VERIFIER
```

Response: `{ "access_token": "vsat.…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "vsrt_…", "scope": "profile library" }`. Refresh with `grant_type=refresh_token` (refresh tokens rotate — store the new one). PKCE is optional when you authenticate with the secret key, required otherwise.

4. Call `GET https://vedastudyssb.web.app/api/oauth/userinfo` (or `/api/v1/me`) with `Authorization: Bearer <access_token>`.

| Scope | Grants |
| --- | --- |
| *(always)* | Read texts as the member (content of "signed-in" apps) |
| `profile` | Name, email, country, preferred language |
| `library` | Read and change the member's saved texts |
| `meanings:write` | Write line meanings — only granted to VedaStudy SSB staff |
| `classes` | Classes, homework, tutor feedback (tutors: run their classes) |
| `classes:submit` | Send a recording to the tutor, only when the member asks |
| `practice` | Practice progress |
| `notes` | Their own highlights, bookmarks and notes |

Extra parameters: `prompt=consent` (always ask), `prompt=login` (switch account), `prompt=none` (silent; returns `error=login_required` / `consent_required`). Members see and disconnect apps under **Settings → Connected apps**; sign them out with `POST https://vedastudyssb.web.app/api/oauth/revoke` (`token=…`).

Discovery: `https://vedastudyssb.web.app/.well-known/oauth-authorization-server`.


## REST API

Base URL `https://vedastudyssb.web.app/api/v1`. Send `Authorization: Bearer <pk_live_… | sk_live_… | vsat.…>`. JSON in, JSON out. Full schema: `https://vedastudyssb.web.app/api/v1/openapi.json`.

```bash
curl -H "Authorization: Bearer sk_live_YOUR_SECRET" "https://vedastudyssb.web.app/api/v1/documents?limit=5"
```

```js
// In the browser with the publishable key (from an allowed origin)
const r = await fetch("https://vedastudyssb.web.app/api/v1/search?q=kavacam", { headers: { Authorization: "Bearer pk_live_YOUR_PUBLISHABLE_KEY" } });
```

| Endpoint | What |
| --- | --- |
| `GET /languages` | Content languages |
| `GET /folders?parent=` | Folders (top level or under a folder) |
| `GET /folders/{id}` | A folder: sub-folders, documents, breadcrumb |
| `GET /documents` | List/filter: `folder`, `q`, `kind`, `lang`, `updated_since`, `sort=updated`, `limit`, `cursor` |
| `GET /documents/{id}` | A document (id **or slug**) with every edition: current version number, date, changelog, size, pages, features, links |
| `GET /documents/{id}/thumbnail` | Preview image (works as `<img src="…?key=pk_live_…">`) |
| `GET /documents/{id}/file?lang=` | Signed link to the current file (`&redirect=1` to 302, `&download=1` to save) · content |
| `GET /documents/{id}/video?lang=` | Video player embed URL · content |
| `GET /documents/{id}/text?lang=&from=&to=` | Page-by-page text · content |
| `GET /documents/{id}/chant?lang=` | Chant & Read timings · content |
| `GET /documents/{id}/meanings?lang=` | Line meanings · content |
| `GET /documents/{id}/changes?lang=` | Pages changed in the current version |
| `GET /documents/{id}/embed?lang=&chant=1&meanings=1` | Ready-made embed HTML |
| `GET /search?q=&text=1` | Titles, folders and text inside documents |
| `GET /me` | The signed-in member (`profile`) |
| `GET /me/library`, `PUT/DELETE /me/library/{id}` | Their saved texts (`library`) |
| `PUT/DELETE /documents/{id}/meanings/{key}` | Write a meaning (staff, `meanings:write`) |

"content" = needs an "open" app or a member's access token. Titles are returned in English unless you pass `?locale=hi` (etc.); every object also carries `titles` in all languages.

Links you receive (`links.api`, `links.embed`, `links.web`) always resolve to the **current** version — store ids/slugs, not file URLs. File URLs from `/file` are signed and expire (15 min; 6 h for audio).


## Chant & Read and line meanings

### Chant & Read
Some texts have recordings synchronised line by line. `GET /documents/{id}/chant?lang=ta` returns:

```json
{
  "sections": [{ "index": 0, "id": "…", "title": "Devī Kavacam", "audioUrl": "https://…m4a (signed, 6h)" }],
  "lines": [{ "index": 0, "key": "0:0", "section": 0, "start": 0.0, "end": 7.94,
              "boxes": [{ "page": 3, "x": 0.12, "y": 0.18, "w": 0.7, "h": 0.02 }], "text": "…" }]
}
```

Play a line: load `audioUrl`, set `currentTime = start`, pause at `end`. `boxes` are fractions of the page (0–1, from the top-left) on **this edition's current version**, so you can draw highlights over your own rendering. The same `key` identifies the line in every edition. The embedded reader does all of this for you.

### Line meanings
VedaStudy SSB staff write an in-depth meaning (word-by-word, translation, commentary) for individual lines. A meaning is written once and appears on **every language edition and every future version** of the text — in the VedaStudy SSB app, in every embed, and through the API:

```json
GET /documents/{id}/meanings?lang=hi
{ "sourceLanguage": "en",
  "meanings": [{ "key": "12:0", "verse": 12, "text": "…the line in IAST…",
                 "body": { "en": "**Word by word** …markdown…", "hi": "…" },
                 "boxes": [{ "page": 7, "x": 0.1, "y": 0.42, "w": 0.75, "h": 0.02 }] }] }
```

`body` is Markdown keyed by language. Staff (and AI assistants acting for staff with the `meanings:write` scope) can write them: `PUT /documents/{id}/meanings/{key}` with `{"body": {"en": "…"}, "status": "published"}` — or in the reader itself (tap a line with **Meanings** on).


## Classes, recordings & practice

Members can join a tutor's **chanting class**, get **homework** (lines to practise), **send a recording** of a line to the tutor, and read the tutor's **feedback**. Tutors run classes, set homework, listen and give feedback. All of it works for a signed-in member through the API and the MCP server, with the same rules as the app — a student only ever sees their own recordings, a tutor only their own classes.

| Scope | Lets the app… |
| --- | --- |
| `classes` | see the member's classes, homework, feedback; join/leave; and for tutors: start classes, set homework, hear recordings, give feedback |
| `classes:submit` | send a recording to the tutor — **only when the member asks** |
| `practice` | read their practice progress (takes, best scores, learn-by-heart reviews due) |
| `notes` | read and change their own highlights, bookmarks and notes |

| Endpoint | What |
| --- | --- |
| `GET /me/classes` | Classes (taught ones include the join code), homework, recordings sent + feedback |
| `POST /me/classes/join` | `{"code": "AB3CD9"}` |
| `DELETE /me/classes/{id}` | Leave a class |
| `POST /classes` | Start a class `{"name": …}` (tutors) |
| `GET /classes/{id}` | Tutor: students, homework, recordings, join code · Student: homework + own recordings |
| `POST /classes/{id}/homework` | `{"document", "chapter", "verse_from", "verse_to"}` (verse numbers restart each chapter) or `{"document", "keys": ["12:0", …]}`, optional `due`, `note` (tutors) |
| `DELETE /homework/{id}` | Remove homework (tutors) |
| `POST /classes/{id}/recordings` | Send a recording: multipart `audio` file + `meta` JSON `{document, line_key, message?, homework_id?}`, or JSON with `audio_base64` + `mime_type` (≤ 6 MB, audio/*) |
| `GET /submissions/{id}/recording` | Signed link to hear it (sender or tutor; `?redirect=1` to 302) |
| `POST /submissions/{id}/feedback` | `{"text": …}` — the student is emailed (tutors) |
| `GET /me/practice?doc=` | Practice per line (or reviews due across texts) |
| `GET /me/notes?doc=` · `PUT /me/notes/{doc}/{line}` | Notes: `{highlight, bookmark, note}` |

```bash
curl -X POST https://vedastudyssb.web.app/api/v1/classes/CLASS_ID/recordings \
  -H "Authorization: Bearer vsat.…" \
  -F audio=@take.webm -F 'meta={"document":"dm-full-sb-master","line_key":"11:0","message":"Verse 12, please check my svaras"}'
```

Recordings sent through the API or an AI assistant arrive **"not rated"** with a "sent from an app" badge — scores are only given by the VedaStudy SSB app, which rates the chanting on the member's own device. Line keys (`"12:0"`) come from `find_lines` / the chant and meanings endpoints and are the same in every edition.


## MCP server (AI assistants)

`https://vedastudyssb.web.app/api/mcp` is a Model Context Protocol server (Streamable HTTP). Give it to your AI assistant and it can search the library, read texts and chant timings, look up meanings, and write the embed / sign-in code for the site you're building.

**Claude (claude.ai / Desktop) → Settings → Connectors → Add custom connector** — URL `https://vedastudyssb.web.app/api/mcp`. Claude signs you in with VedaStudy SSB (no key needed).

**Claude Code**

```bash
claude mcp add --transport http vedastudy https://vedastudyssb.web.app/api/mcp
# or with an app key:
claude mcp add --transport http vedastudy https://vedastudyssb.web.app/api/mcp --header "Authorization: Bearer sk_live_YOUR_SECRET"
```

**Cursor / VS Code / others** (`mcp.json`):

```json
{ "mcpServers": { "vedastudy": { "url": "https://vedastudyssb.web.app/api/mcp",
    "headers": { "Authorization": "Bearer sk_live_YOUR_SECRET" } } } }
```

Authentication: an app key (`sk_live_…`, or `pk_live_…`) in the `Authorization` header, **or** OAuth — clients that support MCP authorization discover `https://vedastudyssb.web.app/.well-known/oauth-protected-resource`, register themselves, and send you through Sign in with VedaStudy SSB.

Tools — library: `search`, `browse`, `list_documents`, `get_document`, `get_document_text`, `find_lines`, `get_chant_timings`, `get_meanings`, `write_meaning` (staff), `get_embed_code`, `get_file_link`, `get_recent_updates`, `get_version_changes`, `list_languages`. Member: `my_library`, `my_classes`, `class_details`, `join_class`, `leave_class`, `send_recording` (only when asked), `get_recording`, `my_practice`, `my_notes`. Tutors: `create_class`, `set_homework`, `remove_homework`, `give_feedback`. Also `whoami`, `developer_guide`. Prompt: `build_site`.


## Webhooks & staying up to date

Give the admin an https URL and VedaStudy SSB POSTs JSON to it:

```json
{ "id": "evt_…", "type": "document.version.published", "createdAt": "2026-…",
  "data": { "documentId": "…", "slug": "…", "language": "ta", "version": 7, "changelog": "…", "updatedPages": "3–5, 12" } }
```

Events: `document.version.published`, `meaning.published`.

Verify the `VedaStudy-Signature: t=<unix>,v1=<hex>` header: `v1 = HMAC_SHA256(whsec_…, "<t>.<raw body>")`.

```js
import crypto from "node:crypto";
function verify(raw, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const mac = crypto.createHmac("sha256", secret).update(t + "." + raw).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - t) < 300;
}
```

No webhook? Poll `GET /api/v1/documents?updated_since=<last sync ISO date>&sort=updated`.


## Limits, errors & guidelines

- **Rate limits** (per server instance, per minute): secret key 1200, publishable key 600 per visitor IP, member token 600. Over the limit → `429` with `Retry-After`.
- **Errors** are `{ "error": { "code": "…", "message": "…" } }` — codes: `unauthorized`, `forbidden`, `not_found`, `bad_request`, `sign_in_required`, `origin_not_allowed`, `secret_key_in_browser`, `insufficient_scope`, `rate_limited`, `app_suspended`, `server_error`.
- **Keys**: never ship `sk_live_` to a browser or commit it; ask an admin to rotate if it leaks. Publishable keys only work from your allowed origins.
- **Attribution**: keep the "VedaStudy SSB" credit the embed shows, and link texts back to VedaStudy SSB when you show them outside the embed.
- **Don't re-host files** — always link/stream through the API or embed, so readers get corrections the moment they're published.
- Respect the sacred nature of the material: no ads injected into the reader, no edits to the texts.
