iOS API Documentation
Integration reference for the BEST MOVIES UG iOS application. Endpoints, authentication, request examples and implementation notes.
https://v2.bestmoviesug.comContent: /api/collections/{collection}/records · Accounts: /ios/account
The routes below reflect the current server configuration. Complete authenticated device tests before release. Apple purchase processing and some response fields still need integration testing.
Integration overview
The iOS application uses Firebase Authentication to obtain an ID token, then sends that token to the application server. The server verifies identity and provides approved catalogue data and user-owned account operations.
Use secure HTTPS for every production request.
Approved collections support authenticated GET and HEAD for iOS.
The server resolves account ownership from the verified Firebase UID.
| Component | Contract |
|---|---|
| Base URL | https://v2.bestmoviesug.com |
| Content API | /api/collections/{collection}/records |
| Account API | /ios/account |
| Authentication | Authorization: Bearer <FIREBASE_ID_TOKEN> |
| Content permissions | Authenticated GET/HEAD on allowlisted collections |
| Account identity | Server-derived; never trust client-supplied owner IDs |
Never embed gateway secrets, privileged service credentials, or Android package/signature compatibility headers in the iOS binary.
Endpoint status
| Area | Status | Evidence / limitation |
|---|---|---|
| Account routes | Deployed | Missing-token registration and profile returned HTTP 401. |
| Public gateway | Online | Production root returned HTTP 200 after restart. |
| Catalogue | Configured | iOS Firebase-authenticated GET/HEAD allowlist reviewed. |
| VJs & genres | Configured | Collections allowlisted; Android filtering contract reviewed. Anonymous tests returned 403. |
| Account uniqueness | Protected | Duplicate Firebase UID groups cleaned; unique index created; database check OK. |
| Real-user tests | Pending | Registration, profile, content and account operations need authenticated end-to-end testing. |
| iOS purchases | Pending | StoreKit purchase, restore and server-side transaction verification not yet implemented or confirmed. |
Firebase authentication
- Configure the iOS application with the correct Firebase project and approved sign-in providers.
- Sign in through the Firebase iOS SDK.
- Obtain the current Firebase ID token from the signed-in user.
- Attach it as a bearer token to content and account requests.
- Refresh through the SDK when necessary; do not permanently cache an ID token.
GET /api/collections/vjs/records?perPage=100 HTTP/1.1
Host: v2.bestmoviesug.com
Authorization: Bearer <FIREBASE_ID_TOKEN>A Firebase user may exist before an application profile has been created. Call the registration endpoint as part of onboarding, then retrieve the profile.
Registration & profile
| Method | Path | Purpose |
|---|---|---|
| POST | /ios/account/register | Create free profile or return an existing profile; existing subscription state is preserved. |
| GET | /ios/account/profile | Read safe fields for the authenticated user. |
| PATCH | /ios/account/profile | Update name and/or profileImageUrl only. |
Register or resolve existing profile
POST /ios/account/register HTTP/1.1
Host: v2.bestmoviesug.com
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{"name":"Jane"}Registration uses the verified Firebase UID to find an existing profile. A new profile receives non-privileged defaults. Expected statuses: 200 for an existing profile, 201 for a newly created profile, 400 for invalid input, and 401 for missing or invalid authentication.
Update permitted profile fields
PATCH /ios/account/profile HTTP/1.1
Host: v2.bestmoviesug.com
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{
"name": "Jane K.",
"profileImageUrl": "https://example.com/avatar.jpg"
}Do not send subscription activation, expiry, role, device limits, download counts, payment flags or other privileged fields in registration or profile updates.
Content catalogue
Authenticated iOS clients may retrieve records from the following allowlisted collections. List requests use the same paginated record format; individual record details use the record ID.
| Collection | Use |
|---|---|
media | Movies and series, detail pages, searches and filters |
banners | Home banners and carousels |
vjs | VJ category names |
genres | Genre category names |
short_videos | Short-form video content |
radio | Radio stations |
radio_categories | Radio categories |
radio_cities | Radio cities |
live_tv | Live television |
subscription_plans | Plan catalogue for display |
GET /api/collections/media/records?page=1&perPage=30
GET /api/collections/media/records/<MEDIA_RECORD_ID>
GET /api/collections/banners/records?page=1&perPage=30Pagination contract
{
"page": 1,
"perPage": 30,
"totalPages": 4,
"totalItems": 102,
"items": [ /* collection records */ ]
}Begin at page 1 and append items while page < totalPages. Guard against overlapping page requests, provide retry and empty states, and validate each collection's actual JSON fields before finalizing app models.
VJs & genres
The supplied Android CategoriesActivity has two tabs. It loads up to 100 VJs or genres, extracts the name field, sorts names alphabetically and displays a two-column category selector. Selecting an item opens a three-column media grid with 30 records per page and infinite scrolling.
| Feature | Method | Endpoint / query |
|---|---|---|
| VJ list | GET | /api/collections/vjs/records?perPage=100 |
| Genre list | GET | /api/collections/genres/records?perPage=100 |
| VJ-filtered media | GET | /api/collections/media/records with filter=vjs ~ 'VJ JUNIOR' |
| Genre-filtered media | GET | /api/collections/media/records with filter=genres ~ 'Action' |
GET /api/collections/media/records?page=1&perPage=30&filter=vjs%20~%20%27VJ%20JUNIOR%27
Authorization: Bearer <FIREBASE_ID_TOKEN>
# Conceptual filter values:
vjs ~ 'VJ JUNIOR'
genres ~ 'Action'- Fetch and display the VJ or Genre tab list, sorted by name.
- When a category is selected, use its name as the filter value, matching Android behavior.
- Build the filter as a query parameter with
URLComponentsandURLQueryItem. - Escape embedded quotes according to the backend filter syntax; URL encoding alone does not escape filter-language syntax.
- Load 30 records per page; stop when the last page is reached.
- Support back navigation, loading, no-results and retry states.
Android currently requests perPage=100 for VJs and genres. If a category collection exceeds 100 records, implement pagination for the category list too.
My List · Favorites
| Method | Path | Behavior |
|---|---|---|
| GET | /ios/account/my-list | List the signed-in user's favorites. |
| POST | /ios/account/my-list | Add a media record to favorites. |
| DELETE | /ios/account/my-list/{id} | Remove an owned favorite by favorite record ID. |
POST /ios/account/my-list HTTP/1.1
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{"media":"MEDIA_RECORD_ID"}The server derives the owner from the authenticated user. Do not include a user field. For deletion, use the favorites collection record ID, not the media record ID. Optimistic UI changes should roll back on failure.
Watch history & progress
| Method | Path | Behavior |
|---|---|---|
| GET | /ios/account/watch-history | List history belonging to the current user. |
| POST | /ios/account/watch-history | Create a record with media and playback progress. |
| PATCH | /ios/account/watch-history/{id} | Update progress and optional artwork. |
| DELETE | /ios/account/watch-history/{id} | Remove an owned history record. |
POST /ios/account/watch-history HTTP/1.1
Authorization: Bearer <FIREBASE_ID_TOKEN>
Content-Type: application/json
{
"media": "MEDIA_RECORD_ID",
"progress": {
"seasonKey": "1",
"episodeIndex": 0,
"timestamp": 120
},
"posterUrl": "https://example.com/poster.jpg"
}The progress object must contain seasonKey (string), episodeIndex (number) and timestamp (seconds). For movies, agree on a consistent season/episode convention and verify it against the live API. Save periodically and when playback pauses or closes.
PATCH permits progress, posterUrl and backdropUrl. It must not reassign a history entry to another user or media record.
Playback & entitlement gating
Catalogue responses may contain streaming URLs, but the gateway checks subscription entitlement and can remove playback links for inactive, expired or incomplete subscription states. HTTP 200 does not guarantee that a playable URL is present.
- Retrieve the selected media record using authenticated catalogue access.
- Inspect the returned data for an allowed, usable playback URL.
- If access is unavailable, display the appropriate locked or subscription state.
- Use
AVPlayerfor compatible HLS or progressive streams. - Test redirects, MIME types, URL expiry, network interruptions and HTTP range support.
Do not assume offline downloads are permitted. Implement downloads only after confirming content rights, entitlement rules and approved server behavior. The Android category screen can use an ad-access gate before series details; this does not automatically carry over to iOS.
Subscription plans & iOS purchases
GET /api/collections/subscription_plans/records
Authorization: Bearer <FIREBASE_ID_TOKEN>The plan collection is allowlisted for authenticated iOS catalogue requests. It provides plan information for display; it does not mean that an iOS purchase or subscription activation flow is ready.
Required iOS purchase work
- Configure eligible in-app subscription products in App Store Connect.
- Implement StoreKit purchase and restore experiences according to applicable App Store rules.
- Verify transactions on the server before granting or extending streaming entitlement.
- Handle renewals, cancellations, billing issues, expiry and entitlement restoration.
- Test sandbox purchases and cross-device entitlement behavior.
This guide does not specify a verified iOS purchase, restore, transaction-verification or App Store notification endpoint. Do not reuse Android mobile-money or card checkout inside the iOS app without reviewing the applicable Apple rules.
Radio, live TV, banners & shorts
| Screen | Collection endpoint |
|---|---|
| Radio stations | /api/collections/radio/records |
| Radio categories | /api/collections/radio_categories/records |
| Radio cities | /api/collections/radio_cities/records |
| Live TV | /api/collections/live_tv/records |
| Home banners | /api/collections/banners/records |
| Short videos | /api/collections/short_videos/records |
Use the same authenticated GET and pagination conventions. Validate production JSON field names, artwork URLs and stream formats before building final data models.
Test background audio, audio interruptions, Bluetooth, lock-screen controls and reconnection when needed.
Confirm HLS compatibility, stream stability and entitlement handling.
Use appropriate image/video caching, lifecycle handling and memory management.
Swift · Firebase + URLSession
This example demonstrates bearer-token acquisition, URL query construction and JSON requests. It is an integration reference, not a complete production networking layer. Define Codable response models only after checking actual API responses.
import Foundation
import FirebaseAuth
enum APIError: Error {
case notSignedIn
case invalidURL
case httpStatus(Int, Data)
}
struct BMUAPIClient {
private let baseURL = URL(string: "https://v2.bestmoviesug.com")!
func request(
_ path: String,
method: String = "GET",
query: [URLQueryItem] = [],
jsonBody: [String: Any]? = nil
) async throws -> Data {
guard let user = Auth.auth().currentUser else {
throw APIError.notSignedIn
}
let token = try await user.getIDToken()
let cleanPath = path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
var parts = URLComponents(
url: baseURL.appendingPathComponent(cleanPath),
resolvingAgainstBaseURL: false
)!
if !query.isEmpty { parts.queryItems = query }
guard let url = parts.url else { throw APIError.invalidURL }
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if let body = jsonBody {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: body)
}
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard (200...299).contains(http.statusCode) else {
throw APIError.httpStatus(http.statusCode, data)
}
return data
}
}
Fetch VJs, genres and filtered media
let api = BMUAPIClient()
let vjData = try await api.request(
"api/collections/vjs/records",
query: [URLQueryItem(name: "perPage", value: "100")]
)
let genreData = try await api.request(
"api/collections/genres/records",
query: [URLQueryItem(name: "perPage", value: "100")]
)
let mediaData = try await api.request(
"api/collections/media/records",
query: [
URLQueryItem(name: "page", value: "1"),
URLQueryItem(name: "perPage", value: "30"),
URLQueryItem(name: "filter", value: "vjs ~ 'VJ JUNIOR'")
]
)
// Decode vjData, genreData and mediaData into verified models.
Register, save favorite and update profile
let registration = try await api.request(
"ios/account/register",
method: "POST",
jsonBody: ["name": "Jane"]
)
let favorite = try await api.request(
"ios/account/my-list",
method: "POST",
jsonBody: ["media": "MEDIA_RECORD_ID"]
)
let updatedProfile = try await api.request(
"ios/account/profile",
method: "PATCH",
jsonBody: ["name": "Jane K."]
)
Handle token expiry, cancellation, timeouts, decoding failures, network reachability and structured server errors. Do not log bearer tokens, credentials or private account records.
HTTP errors & security rules
| HTTP status | Meaning / action |
|---|---|
200 / 201 | Success. Registration returns 200 for existing profile, 201 for new profile. |
400 | Invalid input or filter; validate submitted fields. |
401 | Missing/invalid account authentication; refresh token or sign in again. |
403 | Content access denied, missing valid token or route not allowlisted. |
404 | Record not found or inaccessible. |
405 | Method blocked on the iOS content API. |
429 / 5xx | Use safe retry/backoff; avoid automatically repeating non-idempotent mutations. |
- Use Firebase bearer tokens for both catalogue and account requests.
- Do not send gateway secrets, privileged credentials or Android impersonation headers.
- Account ownership is server-derived, never supplied by the client.
- Do not unlock playback from a cached local plan alone.
- Use authenticated GET/HEAD only on approved catalogue collections.
- Do not expose user identifiers, tokens or private account data in logs.
Release checklist
Tick these off as the implementation is verified on real devices. The checkmarks are stored only in this browser.
Implementation notes
Public root HTTP 200; unauthenticated registration and profile HTTP 401; anonymous VJ, genre and filtered-media HTTP 403; production code allowlists authenticated iOS GET/HEAD for these content collections. Account routes are deployed and database duplicate UID groups were resolved with an integrity check.
Real Firebase-authenticated iOS catalogue/account calls, actual media and radio/live TV response fields, streaming behavior on device, and the complete Apple purchase/entitlement lifecycle.
Keep the existing Android and website behavior unchanged. Reuse the secure application gateway and account routes, and add server functionality only when testing demonstrates a concrete requirement.