NidraSign in →
API Reference · v1

Build with Nidra — quietly.

A small, stable HTTP surface over the Nidra library. Authenticate users, list tracks, fetch playlists. JSON in, JSON out. CORS enabled on every endpoint.

Stable · v1https://nidra.aurahimalaya.org/api/public/v1
Authentication

Bearer tokens

Call POST /auth/login with email and password to receive an access_token. Send it as a Bearer header on protected requests. Refresh before expiry with POST /auth/refresh.

http
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Errors

Predictable, JSON-shaped

Errors use standard HTTP status codes and a uniform JSON body. Validation errors include a details field with field-level messages.

json
{
  "error": {
    "code": "invalid_credentials",
    "message": "Invalid login credentials"
  }
}
iOS integration

Adapty identify

Purchases only map to a Nidra account when Adapty's customerUserId equals the Nidra user id. After login, read it from GET /adapty/identity, pass it to Adapty.identify, then POST back to record the link and sync the entitlement. On sign-out call Adapty.logout().

swift
// After a successful Nidra login / session restore
struct AdaptyIdentity: Decodable { let customer_user_id: String }
struct Envelope<T: Decodable>: Decodable { let data: T }

func linkAdapty(accessToken: String) async throws {
    let base = URL(string: "https://nidra.aurahimalaya.org/api/public/v1")!

    // 1. Ask the backend which id to identify with
    var get = URLRequest(url: base.appendingPathComponent("adapty/identity"))
    get.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    let (data, _) = try await URLSession.shared.data(for: get)
    let identity = try JSONDecoder().decode(Envelope<AdaptyIdentity>.self, from: data).data

    // 2. Tell Adapty who this is
    let profile = try await Adapty.identify(identity.customer_user_id)

    // 3. Confirm the link and pull the entitlement
    var post = URLRequest(url: base.appendingPathComponent("adapty/identity"))
    post.httpMethod = "POST"
    post.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    post.setValue("application/json", forHTTPHeaderField: "Content-Type")
    post.httpBody = try JSONEncoder().encode(["adapty_profile_id": profile?.profileId])
    _ = try await URLSession.shared.data(for: post)
}

// On sign-out
try await Adapty.logout()
POST/auth/signup

Create an account

Registers a new user with email and password. Returns a session if email confirmation is disabled.
Request body
email
string · required
Valid email address.
password
string · required
Minimum 6 characters.
display_name
string
Optional name stored on the user profile.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"••••••••","display_name":"You"}'
json
{
  "user": { "id": "uuid", "email": "you@example.com", "created_at": "..." },
  "session": {
    "access_token": "eyJ...",
    "refresh_token": "...",
    "expires_in": 3600,
    "expires_at": 1750000000,
    "token_type": "bearer"
  }
}
POST/auth/login

Sign in with password

Exchanges email and password for an access token and refresh token.
Request body
email
string · required
Account email.
password
string · required
Account password.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"••••••••"}'
json
{
  "user": { "id": "uuid", "email": "you@example.com" },
  "session": {
    "access_token": "eyJ...",
    "refresh_token": "...",
    "expires_in": 3600,
    "expires_at": 1750000000,
    "token_type": "bearer"
  }
}
POST/auth/refresh

Refresh a session

Exchanges a refresh token for a new access token. Use before the previous access token expires.
Request body
refresh_token
string · required
Refresh token returned by login or signup.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"..."}'
json
{
  "session": {
    "access_token": "eyJ...",
    "refresh_token": "...",
    "expires_in": 3600,
    "expires_at": 1750003600,
    "token_type": "bearer"
  }
}
POST/auth/logout
Auth required

Revoke the current session

Invalidates the access token used to make the request.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/auth/logout \
  -H "Authorization: Bearer eyJ..."
json
{ "ok": true }
GET/auth/me
Auth required

Get the current user

Returns the authenticated user, profile, and assigned roles.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/auth/me \
  -H "Authorization: Bearer eyJ..."
json
{
  "id": "uuid",
  "email": "you@example.com",
  "display_name": "You",
  "avatar_url": null,
  "roles": ["user"],
  "created_at": "..."
}
GET/tracks
Auth optional

List published tracks

Returns paginated tracks. Sorted by sort_order then most recent.
Query parameters
category
string
Category slug or UUID to filter by.
q
string
Case-insensitive substring match on title.
limit
integer
1 to 100. Default 20.
offset
integer
Zero-based offset for pagination. Default 0.
curl
curl "https://nidra.aurahimalaya.org/api/public/v1/tracks?category=sleep&limit=10"
json
{
  "data": [
    {
      "id": "uuid",
      "title": "Theta Drift",
      "artist": "Nidra Studio",
      "description": "...",
      "audio_url": "https://...",
      "artwork_url": "https://...",
      "duration_seconds": 1800,
      "frequency_hz": 6.5,
      "binaural_type": "theta",
      "tags": ["sleep","calm"],
      "is_premium": false,
      "category": { "id": "uuid", "slug": "sleep", "name": "Sleep" },
      "created_at": "..."
    }
  ],
  "pagination": { "limit": 10, "offset": 0, "total": 42 }
}
GET/tracks/{id}
Auth optional

Get a single track

Returns a single published track by UUID.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/tracks/8f2c...
json
{ "data": { "id": "uuid", "title": "...", "audio_url": "..." } }
GET/categories

List categories

Returns all categories in display order.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/categories
json
{
  "data": [
    { "id": "uuid", "slug": "sleep", "name": "Sleep", "description": null, "sort_order": 0 }
  ]
}
GET/playlists

List published playlists

Returns published playlists, featured first.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/playlists
json
{
  "data": [
    { "id": "uuid", "slug": "deep-sleep", "name": "Deep Sleep", "description": "...", "cover_url": "https://...", "is_featured": true }
  ]
}
GET/playlists/{id}

Get a playlist with its tracks

Accepts a UUID or slug. Tracks are returned in playlist order.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/playlists/deep-sleep
json
{
  "data": {
    "id": "uuid",
    "slug": "deep-sleep",
    "name": "Deep Sleep",
    "description": "...",
    "cover_url": "https://...",
    "is_featured": true,
    "tracks": [ { "id": "uuid", "title": "...", "audio_url": "..." } ]
  }
}
GET/onboarding
Auth required

Read the caller's onboarding answers

Returns the stored onboarding profile used to personalize recommendations.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/onboarding -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "data": {
    "goals": ["deep_sleep"],
    "sleep_issues": ["waking_at_night"],
    "preferred_bands": ["delta"],
    "preferred_beds": ["rain"],
    "bedtime": "22:30",
    "typical_sleep_hours": 6.5,
    "noise_sensitivity": 4,
    "answers": {},
    "updated_at": "..."
  }
}
POST/onboarding
Auth required

Save onboarding answers

Creates or replaces the caller's onboarding profile. Send it once at the end of onboarding, and again whenever the user edits their preferences.
Request body
goals
string[]
e.g. deep_sleep, sleep_onset, relaxation.
sleep_issues
string[]
e.g. trouble_falling_asleep, waking_at_night, racing_thoughts.
preferred_bands
"delta" | "theta" | "alpha"[]
Explicit band choice. When omitted, bands are inferred from goals and issues.
preferred_beds
string[]
Nature beds the user likes: rain, ocean, river, wind, forest.
bedtime
string
24h local time, HH:MM.
typical_sleep_hours
number
0–24.
noise_sensitivity
number
1–5.
answers
object
Any additional raw onboarding answers.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/onboarding \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"goals":["deep_sleep"],"sleep_issues":["waking_at_night"],"preferred_beds":["rain"],"noise_sensitivity":4}'
json
{ "data": { "goals": ["deep_sleep"], "preferred_bands": [], "updated_at": "..." } }
GET/recommendations
Auth optional

Personalized track recommendations

Scores the published catalogue against the caller's onboarding answers and returns the best matches with a score and human-readable reasons. Without a token it returns a sensible default ordering (delta band first).
Query parameters
limit
number
1–50, default 10.
curl
curl "https://nidra.aurahimalaya.org/api/public/v1/recommendations?limit=5" -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "personalized": true,
  "bands": [{ "band": "delta", "hz": 3 }],
  "data": [
    {
      "id": "uuid",
      "title": "Rain over Delta",
      "audio_url": "https://...",
      "frequency_hz": 3,
      "match_score": 86,
      "reasons": ["3 Hz delta matches your sleep goal", "Includes your rain soundscape"]
    }
  ]
}
GET/adapty/identity
Auth required

Identity to pass to Adapty

Returns the exact string the iOS app must pass to Adapty.identify(_:) — always the Nidra user id — plus whether the link has already been recorded. Call it right after login or session restore.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/adapty/identity -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "data": {
    "customer_user_id": "6f5ed1e1-....",
    "email": "you@example.com",
    "identified": true,
    "adapty_customer_user_id": "6f5ed1e1-....",
    "adapty_profile_id": "adapty-profile-id",
    "last_synced_at": "..."
  }
}
POST/adapty/identity
Auth required

Confirm identify and sync

Call once Adapty.identify(customerUserId) succeeds in the app. It records the link between the Nidra account and the Adapty customer (so webhook events always map to the right user), then pulls the current entitlement from Adapty. Body is optional; send adapty_profile_id when the SDK gives you one.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/adapty/identity \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "adapty_profile_id": "adapty-profile-id" }'
json
{
  "data": { "customer_user_id": "6f5ed1e1-....", "identified": true, "synced": true, "tier": "plus", "status": "active" },
  "has_paid_access": true
}
GET/subscription
Auth required

Current entitlement

Returns the caller's stored subscription, kept up to date by Adapty webhooks.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/subscription -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "data": {
    "tier": "pro",
    "status": "active",
    "current_period_end": "2026-10-01T00:00:00Z",
    "provider": "adapty",
    "product_id": "nidra_plus_yearly",
    "store": "app_store",
    "will_renew": true,
    "is_trial": false,
    "last_synced_at": "..."
  },
  "has_paid_access": true
}
POST/subscription
Auth required

Sync entitlement from Adapty

Pulls the caller's paid access levels directly from Adapty (matched on customerUserId, which must be the Nidra user id) and stores the result. Call it right after a purchase or restore.
curl
curl -X POST https://nidra.aurahimalaya.org/api/public/v1/subscription -H "Authorization: Bearer $ACCESS_TOKEN"
json
{ "data": { "tier": "plus", "status": "active", "provider": "adapty" }, "has_paid_access": true }
POST/webhooks/adapty
Auth required

Adapty webhook receiver

Configured in Adapty as the webhook endpoint. Every event is logged, then the profile is re-read from Adapty and the entitlement written to the subscriptions table. Requests must carry the shared secret in an x-adapty-webhook-secret header (an Authorization: Bearer header also works).
json
{ "received": true, "event_type": "subscription_renewed", "processed": true }