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 · v1
https://nidra.aurahimalaya.org/api/public/v1Bearer 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...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"
}
}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/signupCreate 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/loginSign 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/refreshRefresh 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/logoutAuth 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/meAuth 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
/tracksAuth 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
/categoriesList categories
Returns all categories in display order.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/categoriesjson
{
"data": [
{ "id": "uuid", "slug": "sleep", "name": "Sleep", "description": null, "sort_order": 0 }
]
}GET
/playlistsList published playlists
Returns published playlists, featured first.
curl
curl https://nidra.aurahimalaya.org/api/public/v1/playlistsjson
{
"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-sleepjson
{
"data": {
"id": "uuid",
"slug": "deep-sleep",
"name": "Deep Sleep",
"description": "...",
"cover_url": "https://...",
"is_featured": true,
"tracks": [ { "id": "uuid", "title": "...", "audio_url": "..." } ]
}
}GET
/onboardingAuth 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
/onboardingAuth 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
/recommendationsAuth 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/identityAuth 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/identityAuth 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
/subscriptionAuth 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
/subscriptionAuth 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/adaptyAuth 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 }