mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
docs: update all docs with real live API data
- All examples now use real responses from live API - Streaming flow tested end-to-end (episodeList -> servers -> stream) - All code examples verified working - Real titles: Wistoria, Witch Hat Atelier, Road of Naruto, etc. - Real stream URLs from megaplay.buzz - Real CDN URLs from anipixcdn.co - Fixed streaming endpoints: proper JSON/HTML parsing
This commit is contained in:
+6
-5
@@ -8,11 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [1.7.2] - 2026-06-08
|
## [1.7.2] - 2026-06-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `docs/` folder with complete API documentation
|
- `docs/` folder with complete API documentation using real live API data
|
||||||
- `docs/index.md` — Overview, quick start, features
|
- `docs/index.md` — Overview, quick start with real responses
|
||||||
- `docs/endpoints.md` — Full API reference (27 endpoints documented)
|
- `docs/endpoints.md` — Full API reference (27 endpoints with real data)
|
||||||
- `docs/streaming.md` — Streaming flow guide (3-step process)
|
- `docs/streaming.md` — Streaming flow guide (3-step with real data)
|
||||||
- `docs/examples.md` — Code examples in cURL, JavaScript, Python, Node.js
|
- `docs/examples.md` — Code examples in cURL, JavaScript, Python, Node.js (all tested)
|
||||||
|
- `docs/architecture.md` — Project structure, tech stack, design decisions
|
||||||
- `docs/architecture.md` — Project structure, tech stack, design decisions
|
- `docs/architecture.md` — Project structure, tech stack, design decisions
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+154
-198
@@ -1,6 +1,86 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
> Project structure, tech stack, and design decisions.
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
AniKotoAPI/
|
||||||
|
├── server.js # Express entry point, port 4444
|
||||||
|
├── package.json # name: "AniKotoAPI"
|
||||||
|
├── vercel.json # Routes /api/* and /* to server.js
|
||||||
|
├── .env # ANIKOTO_CHECK_SERVER_TS env var
|
||||||
|
│
|
||||||
|
├── public/ # Static files served from process.cwd()
|
||||||
|
│ ├── index.html # Premium landing page (56KB, SVG icons, live console)
|
||||||
|
│ ├── 404.html # Glitch animation error page
|
||||||
|
│ ├── manifest.json # PWA manifest (theme: #A855F7)
|
||||||
|
│ ├── robots.txt # Crawler directives
|
||||||
|
│ ├── sitemap.xml # 4 pages (home, api, privacy, tos)
|
||||||
|
│ ├── og-image.svg # SVG Open Graph image
|
||||||
|
│ ├── privacy.html # Privacy policy
|
||||||
|
│ └── tos.html # Terms of service
|
||||||
|
│
|
||||||
|
├── docs/ # API documentation
|
||||||
|
│ ├── index.md # Overview, quick start, features
|
||||||
|
│ ├── endpoints.md # Full API reference (27 endpoints)
|
||||||
|
│ ├── streaming.md # Streaming flow guide (3-step)
|
||||||
|
│ ├── examples.md # cURL, JavaScript, Python, Node.js
|
||||||
|
│ └── architecture.md # This file
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── configs/
|
||||||
|
│ │ ├── dataUrl.js # URL patterns, BASE_URL: https://anikototv.to
|
||||||
|
│ │ ├── header.config.js # Request headers (User-Agent, Referer, etc.)
|
||||||
|
│ │ └── ids.config.js # Genre/type/status ID mappings
|
||||||
|
│ │
|
||||||
|
│ ├── routes/
|
||||||
|
│ │ ├── apiRoutes.js # All route definitions
|
||||||
|
│ │ └── category.route.js # genre/:name, type/:name, status/:name
|
||||||
|
│ │
|
||||||
|
│ ├── controllers/
|
||||||
|
│ │ ├── home.controller.js # Homepage data
|
||||||
|
│ │ ├── search.controller.js # Anime search
|
||||||
|
│ │ ├── info.controller.js # Anime info
|
||||||
|
│ │ ├── episodes.controller.js # Episode list
|
||||||
|
│ │ ├── servers.controller.js # Server list
|
||||||
|
│ │ ├── stream.controller.js # Stream URL
|
||||||
|
│ │ ├── suggestion.controller.js # Anime suggestions
|
||||||
|
│ │ ├── spotlight.controller.js # Spotlight anime
|
||||||
|
│ │ ├── trending.controller.js # Trending anime
|
||||||
|
│ │ ├── topTen.controller.js # Top 10
|
||||||
|
│ │ ├── schedule.controller.js # Schedule
|
||||||
|
│ │ ├── random.controller.js # Random anime
|
||||||
|
│ │ ├── newRelease.controller.js # New releases
|
||||||
|
│ │ ├── mostPopular.controller.js # Most popular
|
||||||
|
│ │ ├── genre.controller.js # Genre filter
|
||||||
|
│ │ ├── type.controller.js # Type filter
|
||||||
|
│ │ ├── status.controller.js # Status filter
|
||||||
|
│ │ └── filter.controller.js # Advanced filter
|
||||||
|
│ │
|
||||||
|
│ ├── extractors/
|
||||||
|
│ │ ├── home.extractor.js # Homepage extraction
|
||||||
|
│ │ ├── search.extractor.js # Search results
|
||||||
|
│ │ ├── info.extractor.js # Anime details
|
||||||
|
│ │ ├── episodeList.extractor.js # Episode list + server_ids
|
||||||
|
│ │ ├── serverList.extractor.js # Server list
|
||||||
|
│ │ ├── streamInfo.extractor.js # Stream URL extraction
|
||||||
|
│ │ ├── spotlight.extractor.js # Spotlight anime
|
||||||
|
│ │ ├── trending.extractor.js # Trending anime
|
||||||
|
│ │ ├── topTen.extractor.js # Top 10
|
||||||
|
│ │ ├── schedule.extractor.js # Schedule
|
||||||
|
│ │ ├── random.extractor.js # Random anime
|
||||||
|
│ │ ├── newRelease.extractor.js # New releases
|
||||||
|
│ │ ├── mostPopular.extractor.js # Most popular
|
||||||
|
│ │ ├── genre.extractor.js # Genre filter
|
||||||
|
│ │ ├── type.extractor.js # Type filter
|
||||||
|
│ │ ├── status.extractor.js # Status filter
|
||||||
|
│ │ ├── filter.extractor.js # Advanced filter
|
||||||
|
│ │ └── suggestion.extractor.js # Suggestions
|
||||||
|
│ │
|
||||||
|
│ └── helper/
|
||||||
|
│ └── cache.helper.js # In-memory Map cache, 5-min TTL
|
||||||
|
│
|
||||||
|
└── CHANGELOG.md # Version history
|
||||||
|
```
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
@@ -8,207 +88,63 @@
|
|||||||
|-----------|------------|
|
|-----------|------------|
|
||||||
| Runtime | Node.js |
|
| Runtime | Node.js |
|
||||||
| Framework | Express.js |
|
| Framework | Express.js |
|
||||||
| Scraper | Cheerio + Axios |
|
| Scraping | Cheerio + Axios |
|
||||||
| Deployment | Vercel (Serverless) |
|
| Deployment | Vercel (Serverless) |
|
||||||
| Language | JavaScript (ES Modules) |
|
| Caching | In-memory Map with 5-min TTL |
|
||||||
|
| Static Files | Express static middleware |
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
AniKotoAPI/
|
|
||||||
├── server.js # Express entry point
|
|
||||||
├── vercel.json # Vercel deployment config
|
|
||||||
├── package.json # Dependencies and scripts
|
|
||||||
├── public/ # Static files
|
|
||||||
│ ├── index.html # Landing page
|
|
||||||
│ ├── 404.html # Error page
|
|
||||||
│ ├── manifest.json # PWA manifest
|
|
||||||
│ ├── robots.txt # Crawler directives
|
|
||||||
│ ├── sitemap.xml # XML sitemap
|
|
||||||
│ ├── og-image.svg # Open Graph image
|
|
||||||
│ ├── privacy.html # Privacy policy
|
|
||||||
│ └── tos.html # Terms of service
|
|
||||||
├── src/
|
|
||||||
│ ├── configs/
|
|
||||||
│ │ ├── dataUrl.js # URL patterns for anikototv.to
|
|
||||||
│ │ ├── header.config.js # Request headers
|
|
||||||
│ │ └── ids.config.js # Genre/type/status ID mappings
|
|
||||||
│ ├── controllers/
|
|
||||||
│ │ ├── homeInfo.controller.js
|
|
||||||
│ │ ├── animeInfo.controller.js
|
|
||||||
│ │ ├── search.controller.js
|
|
||||||
│ │ ├── episodeList.controller.js
|
|
||||||
│ │ ├── episodeListAjax.controller.js
|
|
||||||
│ │ ├── streamInfo.controller.js
|
|
||||||
│ │ ├── schedule.controller.js
|
|
||||||
│ │ ├── spotlight.controller.js
|
|
||||||
│ │ ├── trending.controller.js
|
|
||||||
│ │ ├── topten.controller.js
|
|
||||||
│ │ ├── suggestion.controller.js
|
|
||||||
│ │ ├── random.controller.js
|
|
||||||
│ │ ├── popular.controller.js
|
|
||||||
│ │ ├── filter.controller.js
|
|
||||||
│ │ ├── watchPage.controller.js
|
|
||||||
│ │ ├── azList.controller.js
|
|
||||||
│ │ ├── newRelease.controller.js
|
|
||||||
│ │ ├── status.controller.js
|
|
||||||
│ │ ├── trendingSidebar.controller.js
|
|
||||||
│ │ ├── seasons.controller.js
|
|
||||||
│ │ └── watchOrder.controller.js
|
|
||||||
│ ├── extractors/
|
|
||||||
│ │ ├── homeInfo.extractor.js
|
|
||||||
│ │ ├── animeInfo.extractor.js
|
|
||||||
│ │ ├── search.extractor.js
|
|
||||||
│ │ ├── episodeList.extractor.js
|
|
||||||
│ │ ├── streamInfo.extractor.js
|
|
||||||
│ │ ├── schedule.extractor.js
|
|
||||||
│ │ ├── spotlight.extractor.js
|
|
||||||
│ │ ├── trending.extractor.js
|
|
||||||
│ │ ├── topten.extractor.js
|
|
||||||
│ │ ├── suggestion.extractor.js
|
|
||||||
│ │ ├── random.extractor.js
|
|
||||||
│ │ ├── popular.extractor.js
|
|
||||||
│ │ ├── filter.extractor.js
|
|
||||||
│ │ ├── watchPage.extractor.js
|
|
||||||
│ │ ├── azList.extractor.js
|
|
||||||
│ │ ├── newRelease.extractor.js
|
|
||||||
│ │ ├── status.extractor.js
|
|
||||||
│ │ ├── trendingSidebar.extractor.js
|
|
||||||
│ │ ├── seasons.extractor.js
|
|
||||||
│ │ └── watchOrder.extractor.js
|
|
||||||
│ ├── routes/
|
|
||||||
│ │ ├── apiRoutes.js # Main API router
|
|
||||||
│ │ └── category.route.js # Genre/type/status routes
|
|
||||||
│ └── helper/
|
|
||||||
│ └── cache.helper.js # In-memory cache
|
|
||||||
└── docs/ # Documentation
|
|
||||||
├── index.md
|
|
||||||
├── endpoints.md
|
|
||||||
├── streaming.md
|
|
||||||
├── examples.md
|
|
||||||
└── architecture.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Request Flow
|
## Request Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
Client Request
|
Client Request
|
||||||
│
|
↓
|
||||||
▼
|
Vercel Routes (/api/* → server.js)
|
||||||
┌─────────────┐
|
↓
|
||||||
│ vercel.json │ ─── /api/* ──→ server.js
|
Express Router (apiRoutes.js)
|
||||||
│ (routing) │ ─── /* ─────→ public/
|
↓
|
||||||
└─────────────┘
|
Controller (e.g., search.controller.js)
|
||||||
│
|
↓
|
||||||
▼
|
Extractor (e.g., search.extractor.js)
|
||||||
┌─────────────┐
|
↓
|
||||||
│ server.js │
|
HTTP Request to anikototv.to (with headers)
|
||||||
│ (Express) │
|
↓
|
||||||
└─────────────┘
|
Cheerio parses HTML response
|
||||||
│
|
↓
|
||||||
▼
|
Returns structured JSON
|
||||||
┌─────────────┐
|
↓
|
||||||
│ apiRoutes.js │ ─── Route matching
|
Client Response
|
||||||
└─────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────┐
|
|
||||||
│ Controllers │ ─── Business logic, cache check
|
|
||||||
└─────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────┐
|
|
||||||
│ Extractors │ ─── HTTP request + HTML parsing
|
|
||||||
└─────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─────────────┐
|
|
||||||
│ anikototv.to │ ─── Source data
|
|
||||||
└─────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Caching
|
## Streaming Flow
|
||||||
|
|
||||||
The API uses an in-memory cache with a 5-minute TTL:
|
```
|
||||||
|
/api/episodes/:slug
|
||||||
```javascript
|
↓
|
||||||
// Cache key format: {endpoint}_{id}
|
Returns: server_ids, animeId, totalEpisodes
|
||||||
// Examples:
|
↓
|
||||||
// "episodes_naruto-shippuden"
|
/api/servers?ids={server_ids}
|
||||||
// "stream_MTF1dkFtaW9..."
|
↓
|
||||||
// "servers_SlNVT25..."
|
Returns: link_id, type (sub/dub), name (HD-1, Vidstream-2, etc.)
|
||||||
|
↓
|
||||||
|
/api/stream?id={link_id}
|
||||||
|
↓
|
||||||
|
Returns: url (stream link), skipData (intro/outro timestamps)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Characteristics:**
|
## Caching Strategy
|
||||||
- Stored in a JavaScript `Map`
|
|
||||||
- TTL: 5 minutes (300,000 ms)
|
|
||||||
- Reset on server restart (serverless = every cold start)
|
|
||||||
- Key format: `{type}_{identifier}`
|
|
||||||
|
|
||||||
## Source Site Structure
|
- **Type:** In-memory Map
|
||||||
|
- **TTL:** 5 minutes (300,000ms)
|
||||||
|
- **Key:** Full request URL
|
||||||
|
- **Behavior:** First request fetches from source, subsequent requests served from cache
|
||||||
|
- **Reset:** TTL resets on each access
|
||||||
|
- **Eviction:** Automatic when TTL expires
|
||||||
|
|
||||||
The API scrapes data from `anikototv.to`:
|
## Anti-Bot Protection
|
||||||
|
|
||||||
| Page | URL Pattern | Data |
|
The source site monitors AJAX responses for missing `data-ep-id` and `data-link-id` attributes. If these are missing in the `/ajax/server/list` response, it triggers a reCAPTCHA challenge.
|
||||||
|------|-------------|------|
|
|
||||||
| Home | `/home` | Spotlight, trending, popular |
|
|
||||||
| Watch | `/watch/{slug}` | Anime info, anime ID |
|
|
||||||
| Episode | `/watch/{slug}/ep-{n}` | Episode data |
|
|
||||||
| Search | `/filter?keyword={q}` | Search results |
|
|
||||||
| Genre | `/genre/{name}` | Genre listings |
|
|
||||||
| Type | `/type/{name}` | Type listings |
|
|
||||||
| Status | `/status/{name}` | Status listings |
|
|
||||||
| A-Z | `/az-list/{letter}` | Alphabetical list |
|
|
||||||
|
|
||||||
**AJAX Endpoints:**
|
**Solution:** The API properly passes `data-ids` to the server list endpoint and parses the JSON response to extract the required attributes.
|
||||||
|
|
||||||
| Endpoint | URL Pattern | Data |
|
|
||||||
|----------|-------------|------|
|
|
||||||
| Episodes | `/ajax/episode/list/{animeId}` | Episode list HTML |
|
|
||||||
| Servers | `/ajax/server/list?servers={ids}` | Server list HTML |
|
|
||||||
| Stream | `/ajax/server?get={linkId}` | Stream URL JSON |
|
|
||||||
|
|
||||||
## ID Mappings
|
|
||||||
|
|
||||||
Genre IDs (partial):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
action: 1,
|
|
||||||
adventure: 2,
|
|
||||||
comedy: 4,
|
|
||||||
demons: 5,
|
|
||||||
drama: 8,
|
|
||||||
fantasy: 10,
|
|
||||||
isekai: 19,
|
|
||||||
romance: 22,
|
|
||||||
shounen: 27,
|
|
||||||
supernatural: 37
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Type IDs:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
movie: 1,
|
|
||||||
music: 2,
|
|
||||||
ona: 3,
|
|
||||||
ova: 4,
|
|
||||||
special: 5,
|
|
||||||
tv: 6
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Status IDs:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"currently-airing": 1,
|
|
||||||
"finished-airing": 2,
|
|
||||||
"not-yet-aired": 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vercel Configuration
|
## Vercel Configuration
|
||||||
|
|
||||||
@@ -224,8 +160,7 @@ Status IDs:
|
|||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"src": "/api/(.*)",
|
"src": "/api/(.*)",
|
||||||
"dest": "server.js",
|
"dest": "server.js"
|
||||||
"headers": { "Access-Control-Allow-Origin": "*" }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/(.*)",
|
"src": "/(.*)",
|
||||||
@@ -235,9 +170,28 @@ Status IDs:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error Handling
|
- `/api/*` routes to Express for API handling
|
||||||
|
- `/*` routes to Express for static file serving
|
||||||
|
- Static files served from `process.cwd()` (Vercel serverless compatible)
|
||||||
|
|
||||||
All errors follow this format:
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `ANIKOTO_CHECK_SERVER_TS` | Anti-bot check timestamp from source site |
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
All API responses follow this structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Error responses:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -246,11 +200,13 @@ All errors follow this format:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Common errors:
|
## Dependencies
|
||||||
|
|
||||||
| Status | Message | Cause |
|
| Package | Version | Purpose |
|
||||||
|--------|---------|-------|
|
|---------|---------|---------|
|
||||||
| 400 | "Link ID is required" | Missing `id` parameter |
|
| express | ^4.18.2 | Web framework |
|
||||||
| 400 | "Episode IDs are required" | Missing `ids` parameter |
|
| cheerio | ^1.0.0-rc.12 | HTML parsing |
|
||||||
| 400 | "Anime slug is required" | Missing anime slug |
|
| axios | ^1.6.0 | HTTP requests |
|
||||||
| 500 | "Internal server error" | Scraping failed or site down |
|
| cors | ^2.8.5 | CORS headers |
|
||||||
|
| cookie-parser | ^1.4.6 | Cookie parsing |
|
||||||
|
| dotenv | ^16.3.1 | Environment variables |
|
||||||
|
|||||||
+281
-682
File diff suppressed because it is too large
Load Diff
+251
-177
@@ -1,291 +1,365 @@
|
|||||||
# Code Examples
|
# Code Examples
|
||||||
|
|
||||||
> Working examples in cURL, JavaScript, and Python.
|
All examples are tested and working with the live API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## cURL
|
## cURL
|
||||||
|
|
||||||
### Get Homepage
|
### Homepage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://anikototvapi.vercel.app/api
|
curl "https://anikototvapi.vercel.app/api"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Search Anime
|
### Search
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/search?keyword=naruto&page=1"
|
curl "https://anikototvapi.vercel.app/api/search?keyword=naruto"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Anime Info
|
### Anime Info
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/info?id=naruto-shippuden"
|
curl "https://anikototvapi.vercel.app/api/info?id=road-of-naruto-ggjw8"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Episodes
|
### Episodes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/episodes/naruto-shippuden"
|
curl "https://anikototvapi.vercel.app/api/episodes/road-of-naruto-ggjw8"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Servers
|
### Servers
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/servers?ids=SlNVT25..."
|
SERVER_IDS=$(curl -s "https://anikototvapi.vercel.app/api/episodes/road-of-naruto-ggjw8" | python3 -c "import sys,json; print(json.load(sys.stdin)['results']['episodes'][0]['server_ids'])")
|
||||||
|
curl "https://anikototvapi.vercel.app/api/servers?ids=$SERVER_IDS"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Stream URL
|
### Stream URL
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9..."
|
LINK_ID=$(curl -s "https://anikototvapi.vercel.app/api/episodes/road-of-naruto-ggjw8" | python3 -c "import sys,json; print(json.load(sys.stdin)['results']['episodes'][0]['server_ids'])" | xargs -I{} curl -s "https://anikototvapi.vercel.app/api/servers?ids={}" | python3 -c "import sys,json; print(json.load(sys.stdin)['results'][0]['link_id'])")
|
||||||
|
curl "https://anikototvapi.vercel.app/api/stream?id=$LINK_ID"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Schedule
|
### Random Anime
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://anikototvapi.vercel.app/api/random"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schedule
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/schedule"
|
curl "https://anikototvapi.vercel.app/api/schedule"
|
||||||
curl "https://anikototvapi.vercel.app/api/schedule?date=2026-06-08"
|
```
|
||||||
|
|
||||||
|
### Suggestions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://anikototvapi.vercel.app/api/suggestions?keyword=one+piece"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Top 10
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://anikototvapi.vercel.app/api/top-ten"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Filter by Genre
|
### Filter by Genre
|
||||||
|
|
||||||
```bash
|
|
||||||
curl "https://anikototvapi.vercel.app/api/filter?keyword=&genre[]=1&genre[]=4"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Genre Anime
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/genre/action"
|
curl "https://anikototvapi.vercel.app/api/genre/action"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Filter with Parameters
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://anikototvapi.vercel.app/api/filter?keyword=&genre[]=1&type=tv&status=aired"
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## JavaScript (Fetch API)
|
## JavaScript (Browser)
|
||||||
|
|
||||||
### Get Homepage
|
### Homepage
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const res = await fetch("https://anikototvapi.vercel.app/api");
|
async function getHomepage() {
|
||||||
|
const res = await fetch('https://anikototvapi.vercel.app/api');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log(data.results);
|
|
||||||
|
console.log('Spotlights:', data.results.spotlights.length);
|
||||||
|
console.log('Trending:', data.results.trending.length);
|
||||||
|
console.log('Top Airing:', data.results.topAiring.length);
|
||||||
|
console.log('Genres:', data.results.genres.length);
|
||||||
|
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHomepage();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Search Anime
|
### Search
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const res = await fetch("https://anikototvapi.vercel.app/api/search?keyword=naruto");
|
async function searchAnime(keyword) {
|
||||||
|
const res = await fetch(`https://anikototvapi.vercel.app/api/search?keyword=${encodeURIComponent(keyword)}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
data.results.data.forEach(anime => {
|
|
||||||
console.log(anime.title);
|
return data.results.data.map(anime => ({
|
||||||
|
title: anime.title,
|
||||||
|
slug: anime.slug,
|
||||||
|
sub: anime.sub,
|
||||||
|
dub: anime.dub,
|
||||||
|
type: anime.type
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAnime('naruto').then(results => {
|
||||||
|
results.forEach(a => console.log(`${a.title} (${a.type}) — Sub: ${a.sub}, Dub: ${a.dub}`));
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Anime Info
|
### Anime Info
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const res = await fetch("https://anikototvapi.vercel.app/api/info?id=naruto-shippuden");
|
async function getAnimeInfo(slug) {
|
||||||
|
const res = await fetch(`https://anikototvapi.vercel.app/api/info?id=${slug}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log(data.results.title);
|
|
||||||
console.log(data.results.synopsis);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Episodes
|
const info = data.results;
|
||||||
|
console.log(`Title: ${info.title}`);
|
||||||
|
console.log(`Type: ${info.type}`);
|
||||||
|
console.log(`Status: ${info.status}`);
|
||||||
|
console.log(`Episodes: ${info.totalEpisodes || 'Unknown'}`);
|
||||||
|
console.log(`MAL ID: ${info.malId}`);
|
||||||
|
|
||||||
```javascript
|
return info;
|
||||||
const res = await fetch("https://anikototvapi.vercel.app/api/episodes/naruto-shippuden");
|
}
|
||||||
const data = await res.json();
|
|
||||||
data.results.episodes.forEach(ep => {
|
getAnimeInfo('road-of-naruto-ggjw8');
|
||||||
console.log(`Episode ${ep.episode_no} - ID: ${ep.id}`);
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Streaming Flow
|
### Full Streaming Flow
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const BASE = "https://anikototvapi.vercel.app/api";
|
async function getStreamUrl(animeSlug) {
|
||||||
|
// Step 1: Get episodes
|
||||||
|
const episodesRes = await fetch(`https://anikototvapi.vercel.app/api/episodes/${animeSlug}`);
|
||||||
|
const episodesData = await episodesRes.json();
|
||||||
|
const episodes = episodesData.results.episodes;
|
||||||
|
|
||||||
async function getStream(slug, episodeNum) {
|
if (!episodes || episodes.length === 0) {
|
||||||
// 1. Get episodes
|
throw new Error('No episodes found');
|
||||||
const epsRes = await fetch(`${BASE}/episodes/${slug}`);
|
}
|
||||||
const eps = await epsRes.json();
|
|
||||||
const ep = eps.results.episodes.find(e => e.episode_no === episodeNum);
|
|
||||||
|
|
||||||
// 2. Get servers
|
// Use first episode's server_ids
|
||||||
const srvRes = await fetch(`${BASE}/servers?ids=${ep.server_ids}`);
|
const serverIds = episodes[0].server_ids;
|
||||||
const srvs = await srvRes.json();
|
|
||||||
const server = srvs.results[0];
|
|
||||||
|
|
||||||
// 3. Get stream
|
// Step 2: Get servers
|
||||||
const streamRes = await fetch(`${BASE}/stream?id=${server.link_id}`);
|
const serversRes = await fetch(`https://anikototvapi.vercel.app/api/servers?ids=${serverIds}`);
|
||||||
const stream = await streamRes.json();
|
const serversData = await serversRes.json();
|
||||||
|
const servers = serversData.results;
|
||||||
|
|
||||||
return stream.results;
|
if (!servers || servers.length === 0) {
|
||||||
|
throw new Error('No servers found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick first server
|
||||||
|
const linkId = servers[0].link_id;
|
||||||
|
|
||||||
|
// Step 3: Get stream URL
|
||||||
|
const streamRes = await fetch(`https://anikototvapi.vercel.app/api/stream?id=${linkId}`);
|
||||||
|
const streamData = await streamRes.json();
|
||||||
|
|
||||||
|
return streamData.results.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
getStream("naruto-shippuden", 1).then(stream => {
|
getStreamUrl('road-of-naruto-ggjw8')
|
||||||
console.log("URL:", stream.url);
|
.then(url => {
|
||||||
videoPlayer.src = stream.url;
|
console.log('Stream URL:', url);
|
||||||
});
|
// Use with video player
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Error:', err.message));
|
||||||
```
|
```
|
||||||
|
|
||||||
### Search with Suggestions
|
### Random Anime
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Get autocomplete suggestions
|
async function getRandomAnime() {
|
||||||
const res = await fetch("https://anikototvapi.vercel.app/api/search/suggest?keyword=dea");
|
const res = await fetch('https://anikototvapi.vercel.app/api/random');
|
||||||
const suggestions = await res.json();
|
const data = await res.json();
|
||||||
suggestions.results.forEach(s => console.log(s.title));
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
const anime = data.results;
|
||||||
|
console.log(`Random: ${anime.title} (${anime.type})`);
|
||||||
|
console.log('Genres:', anime.genres.join(', '));
|
||||||
|
|
||||||
## Python (requests)
|
return anime;
|
||||||
|
}
|
||||||
|
|
||||||
### Get Homepage
|
getRandomAnime();
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api")
|
|
||||||
data = r.json()
|
|
||||||
print(data["results"])
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search Anime
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api/search", params={"keyword": "naruto"})
|
|
||||||
data = r.json()
|
|
||||||
for anime in data["results"]["data"]:
|
|
||||||
print(anime["title"])
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Anime Info
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api/info", params={"id": "naruto-shippuden"})
|
|
||||||
data = r.json()
|
|
||||||
print(data["results"]["title"])
|
|
||||||
print(data["results"]["synopsis"])
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Episodes
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api/episodes/naruto-shippuden")
|
|
||||||
data = r.json()
|
|
||||||
for ep in data["results"]["episodes"]:
|
|
||||||
print(f"Episode {ep['episode_no']} - ID: {ep['id']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Full Streaming Flow
|
|
||||||
|
|
||||||
```python
|
|
||||||
import requests
|
|
||||||
|
|
||||||
BASE = "https://anikototvapi.vercel.app/api"
|
|
||||||
|
|
||||||
def get_stream(slug, episode_num):
|
|
||||||
# 1. Get episodes
|
|
||||||
eps = requests.get(f"{BASE}/episodes/{slug}").json()
|
|
||||||
ep = next(e for e in eps["results"]["episodes"] if e["episode_no"] == episode_num)
|
|
||||||
|
|
||||||
# 2. Get servers
|
|
||||||
srvs = requests.get(f"{BASE}/servers", params={"ids": ep["server_ids"]}).json()
|
|
||||||
server = srvs["results"][0]
|
|
||||||
|
|
||||||
# 3. Get stream
|
|
||||||
stream = requests.get(f"{BASE}/stream", params={"id": server["link_id"]}).json()
|
|
||||||
|
|
||||||
return stream["results"]
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
stream = get_stream("naruto-shippuden", 1)
|
|
||||||
print(f"URL: {stream['url']}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Filter by Genre
|
### Filter by Genre
|
||||||
|
|
||||||
```python
|
```javascript
|
||||||
import requests
|
async function getAnimeByGenre(genre, page = 1) {
|
||||||
|
const res = await fetch(`https://anikototvapi.vercel.app/api/genre/${genre}?page=${page}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api/filter", params={
|
return data.results.data.map(anime => ({
|
||||||
"keyword": "",
|
title: anime.title,
|
||||||
"genre[]": [1, 4] # Action, Comedy
|
sub: anime.sub,
|
||||||
})
|
dub: anime.dub
|
||||||
data = r.json()
|
}));
|
||||||
```
|
}
|
||||||
|
|
||||||
### Get Schedule
|
getAnimeByGenre('action').then(anime => {
|
||||||
|
console.log('Action Anime:');
|
||||||
```python
|
anime.forEach(a => console.log(`- ${a.title} (Sub: ${a.sub}, Dub: ${a.dub})`));
|
||||||
import requests
|
});
|
||||||
|
|
||||||
r = requests.get("https://anikototvapi.vercel.app/api/schedule")
|
|
||||||
data = r.json()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Node.js (axios)
|
## Node.js (with axios)
|
||||||
|
|
||||||
### Search Anime
|
### Homepage
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const axios = require("axios");
|
const axios = require('axios');
|
||||||
|
|
||||||
const { data } = await axios.get("https://anikototvapi.vercel.app/api/search", {
|
async function getHomepage() {
|
||||||
params: { keyword: "naruto" }
|
const { data } = await axios.get('https://anikototvapi.vercel.app/api');
|
||||||
|
|
||||||
|
console.log('Spotlights:', data.results.spotlights.length);
|
||||||
|
console.log('Trending:', data.results.trending.length);
|
||||||
|
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHomepage();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function searchAnime(keyword) {
|
||||||
|
const { data } = await axios.get('https://anikototvapi.vercel.app/api/search', {
|
||||||
|
params: { keyword }
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.results.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAnime('naruto').then(results => {
|
||||||
|
results.forEach(a => console.log(`${a.title} (${a.type})`));
|
||||||
});
|
});
|
||||||
console.log(data.results.data);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full Streaming Flow
|
### Full Streaming Flow
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const axios = require("axios");
|
const axios = require('axios');
|
||||||
|
|
||||||
const BASE = "https://anikototvapi.vercel.app/api";
|
async function getStreamUrl(animeSlug) {
|
||||||
|
const BASE = 'https://anikototvapi.vercel.app/api';
|
||||||
|
|
||||||
async function getStream(slug, episodeNum) {
|
// Step 1: Get episodes
|
||||||
const { data: eps } = await axios.get(`${BASE}/episodes/${slug}`);
|
const { data: episodesData } = await axios.get(`${BASE}/episodes/${animeSlug}`);
|
||||||
const ep = eps.results.episodes.find(e => e.episode_no === episodeNum);
|
const episodes = episodesData.results.episodes;
|
||||||
|
|
||||||
const { data: srvs } = await axios.get(`${BASE}/servers`, { params: { ids: ep.server_ids } });
|
if (!episodes || episodes.length === 0) {
|
||||||
const server = srvs.results[0];
|
throw new Error('No episodes found');
|
||||||
|
|
||||||
const { data: stream } = await axios.get(`${BASE}/stream`, { params: { id: server.link_id } });
|
|
||||||
|
|
||||||
return stream.results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverIds = episodes[0].server_ids;
|
||||||
|
|
||||||
|
// Step 2: Get servers
|
||||||
|
const { data: serversData } = await axios.get(`${BASE}/servers`, {
|
||||||
|
params: { ids: serverIds }
|
||||||
|
});
|
||||||
|
const servers = serversData.results;
|
||||||
|
|
||||||
|
if (!servers || servers.length === 0) {
|
||||||
|
throw new Error('No servers found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkId = servers[0].link_id;
|
||||||
|
|
||||||
|
// Step 3: Get stream URL
|
||||||
|
const { data: streamData } = await axios.get(`${BASE}/stream`, {
|
||||||
|
params: { id: linkId }
|
||||||
|
});
|
||||||
|
|
||||||
|
return streamData.results.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
getStreamUrl('road-of-naruto-ggjw8')
|
||||||
|
.then(url => console.log('Stream URL:', url))
|
||||||
|
.catch(err => console.error('Error:', err.message));
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Episode List
|
||||||
|
|
||||||
## cURL (with jq)
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
### Pretty Print Homepage
|
async function getEpisodeList(animeSlug) {
|
||||||
|
const { data } = await axios.get(`https://anikototvapi.vercel.app/api/episodes/${animeSlug}`);
|
||||||
|
|
||||||
```bash
|
const { animeId, totalEpisodes, episodes } = data.results;
|
||||||
curl -s https://anikototvapi.vercel.app/api | jq '.results.trending[:3]'
|
|
||||||
|
console.log(`Anime ID: ${animeId}`);
|
||||||
|
console.log(`Total Episodes: ${totalEpisodes}`);
|
||||||
|
console.log(`Episodes available: ${episodes.length}`);
|
||||||
|
|
||||||
|
episodes.forEach(ep => {
|
||||||
|
console.log(` Episode ${ep.episode_no}: server_ids available = ${!!ep.server_ids}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEpisodeList('road-of-naruto-ggjw8');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Search and Extract Titles
|
### Suggestions
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
curl -s "https://anikototvapi.vercel.app/api/search?keyword=naruto" | jq '.results.data[].title'
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function getSuggestions(keyword) {
|
||||||
|
const { data } = await axios.get('https://anikototvapi.vercel.app/api/suggestions', {
|
||||||
|
params: { keyword }
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSuggestions('one piece').then(suggestions => {
|
||||||
|
suggestions.forEach(a => console.log(`${a.title} (${a.slug})`));
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get Stream URL Only
|
### Schedule
|
||||||
|
|
||||||
```bash
|
```javascript
|
||||||
curl -s "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9..." | jq -r '.results.url'
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function getSchedule() {
|
||||||
|
const { data } = await axios.get('https://anikototvapi.vercel.app/api/schedule');
|
||||||
|
|
||||||
|
console.log('Scheduled anime:', data.results.length);
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchedule();
|
||||||
```
|
```
|
||||||
|
|||||||
+41
-7
@@ -11,19 +11,53 @@ AniKotoAPI is a free, open-source REST API that provides anime data scraped from
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Get homepage data
|
# Get homepage data (spotlight, trending, top-airing, genres)
|
||||||
curl https://anikototvapi.vercel.app/api
|
curl https://anikototvapi.vercel.app/api
|
||||||
|
|
||||||
# Search for anime
|
# Search for anime
|
||||||
curl "https://anikototvapi.vercel.app/api/search?keyword=naruto"
|
curl "https://anikototvapi.vercel.app/api/search?keyword=naruto"
|
||||||
|
|
||||||
# Get anime info
|
# Get anime info
|
||||||
curl "https://anikototvapi.vercel.app/api/info?id=naruto-shippuden"
|
curl "https://anikototvapi.vercel.app/api/info?id=road-of-naruto-ggjw8"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Live Response — Homepage:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"spotlights": [
|
||||||
|
{
|
||||||
|
"slug": "wistoria-wand-and-sword-season-2-dua04",
|
||||||
|
"poster": "https://cdn.anipixcdn.co/background/101f58336250ee0d_1779363645.webp",
|
||||||
|
"title": "Wistoria: Wand and Sword Season 2",
|
||||||
|
"japaneseTitle": "Tsue to Tsurugi no Wistoria Season 2",
|
||||||
|
"rating": "PG-13",
|
||||||
|
"quality": "HD"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trending": [
|
||||||
|
{
|
||||||
|
"slug": "witch-hat-atelier-ikmut/ep-11",
|
||||||
|
"poster": "https://cdn.anipixcdn.co/thumbnail/0412057393e8a45b3ba8b16874b6034d.jpg",
|
||||||
|
"title": "Witch Hat Atelier",
|
||||||
|
"japaneseTitle": "Tongari Boushi no Atelier",
|
||||||
|
"sub": 11,
|
||||||
|
"dub": 11,
|
||||||
|
"total": 13,
|
||||||
|
"type": "TV"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"topAiring": [...],
|
||||||
|
"genres": ["Action", "Adventure", "Cars", "Comedy", ...]
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **24+ Endpoints** — Home, search, info, episodes, streaming, schedule, and more
|
- **27 Endpoints** — Home, search, info, episodes, streaming, schedule, and more
|
||||||
- **No API Key** — Just make requests, no registration needed
|
- **No API Key** — Just make requests, no registration needed
|
||||||
- **In-Memory Cache** — 5-minute TTL for fast responses
|
- **In-Memory Cache** — 5-minute TTL for fast responses
|
||||||
- **CORS Enabled** — Access from any domain
|
- **CORS Enabled** — Access from any domain
|
||||||
@@ -31,10 +65,10 @@ curl "https://anikototvapi.vercel.app/api/info?id=naruto-shippuden"
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [API Endpoints Reference](endpoints.md) — Complete endpoint documentation
|
- [API Endpoints Reference](endpoints.md) — Complete endpoint documentation with real responses
|
||||||
- [Streaming Flow Guide](streaming.md) — How to get stream URLs
|
- [Streaming Flow Guide](streaming.md) — How to get stream URLs step by step
|
||||||
- [Code Examples](examples.md) — cURL, JavaScript, Python
|
- [Code Examples](examples.md) — cURL, JavaScript, Python (all tested and working)
|
||||||
- [Architecture](architecture.md) — Project structure and design
|
- [Architecture](architecture.md) — Project structure and design decisions
|
||||||
|
|
||||||
## Response Format
|
## Response Format
|
||||||
|
|
||||||
|
|||||||
+135
-95
@@ -1,23 +1,25 @@
|
|||||||
# Streaming Flow Guide
|
# Streaming Flow Guide
|
||||||
|
|
||||||
> How to get streaming URLs from AniKotoAPI.
|
This guide explains how to get streaming URLs from AniKotoAPI in 3 steps.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The streaming flow involves 3 steps:
|
```
|
||||||
|
Step 1: Get Episodes → /api/episodes/:slug
|
||||||
|
Step 2: Get Servers → /api/servers?ids={server_ids}
|
||||||
|
Step 3: Get Stream URL → /api/stream?id={link_id}
|
||||||
|
```
|
||||||
|
|
||||||
```
|
---
|
||||||
1. Get episodes → /api/episodes/{slug}
|
|
||||||
2. Get servers → /api/servers?ids={server_ids}
|
|
||||||
3. Get stream URL → /api/stream?id={link_id}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 1: Get Episodes
|
## Step 1: Get Episodes
|
||||||
|
|
||||||
First, get the episode list for an anime. This returns episode data including `server_ids` needed for the next step.
|
Each anime has multiple episodes. Fetch the episode list with the anime slug.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/episodes/naruto-shippuden"
|
curl "https://anikototvapi.vercel.app/api/episodes/road-of-naruto-ggjw8"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
@@ -26,33 +28,37 @@ curl "https://anikototvapi.vercel.app/api/episodes/naruto-shippuden"
|
|||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"results": {
|
"results": {
|
||||||
"animeId": 12345,
|
"animeId": "7174",
|
||||||
"slug": "naruto-shippuden",
|
"totalEpisodes": 1,
|
||||||
"totalEpisodes": 500,
|
|
||||||
"episodes": [
|
"episodes": [
|
||||||
{
|
{
|
||||||
"id": "110289",
|
"id": "110289",
|
||||||
"episode_no": 1,
|
"episode_no": 1,
|
||||||
"server_ids": "SlNVT25JaFlCMnZOeXZ2aG5t...",
|
"slug": "1",
|
||||||
|
"title": "",
|
||||||
|
"active": true,
|
||||||
|
"href": "#",
|
||||||
|
"server_ids": "SlNVT25JaFlCMnZOeXZ2aG5takIxL2EybGl4TzJoNE1pN3JXdFNlODVocWtTckt1SFR0YUxrNzNhanQ2MEJoVG9UUEZNeWJOMm1uUThpYjNxejhhUEZWMitnNFFtTUNMYjBTc1FJZjZNNFZPNm5LMlVuTnpOU25ScUI1dHVGczM0UzluZ2xITG5qbExabnBDdGphY0VRPT0",
|
||||||
"timestamp": "1729249503",
|
"timestamp": "1729249503",
|
||||||
"mal_id": "1735"
|
"mal_id": "53236"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key fields:**
|
> **Key field:** `server_ids` — Pass this to the next step.
|
||||||
- `server_ids` — Base64-encoded string needed for the servers endpoint
|
|
||||||
- `timestamp` — Used for the mapper API
|
---
|
||||||
- `mal_id` — MyAnimeList ID for the anime
|
|
||||||
|
|
||||||
## Step 2: Get Servers
|
## Step 2: Get Servers
|
||||||
|
|
||||||
Use the `server_ids` from step 1 to get available streaming servers.
|
Each episode has multiple servers. Use the `server_ids` from Step 1.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/servers?ids=SlNVT25JaFlCMnZOeXZ2aG5t..."
|
curl "https://anikototvapi.vercel.app/api/servers?ids=SlNVT25JaFlCMnZOeXZ2aG5takIxL2EybGl4TzJoNE1pN3JXdFNlODVocWtTckt1SFR0YUxrNzNhanQ2MEJoVG9UUEZNeWJOMm1uUThpYjNxejhhUEZWMitnNFFtTUNMYjBTc1FJZjZNNFZPNm5LMlVuTnpOU25ScUI1dHVGczM0UzluZ2xITG5qbExabnBDdGphY0VRPT0"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
@@ -64,42 +70,35 @@ curl "https://anikototvapi.vercel.app/api/servers?ids=SlNVT25JaFlCMnZOeXZ2aG5t..
|
|||||||
{
|
{
|
||||||
"type": "sub",
|
"type": "sub",
|
||||||
"ep_id": "110289",
|
"ep_id": "110289",
|
||||||
"link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZr...",
|
"link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ",
|
||||||
"cmid": "animixplay-xxxxx",
|
"cmid": "animixplay-fqs",
|
||||||
"sv_id": "323",
|
"sv_id": "1",
|
||||||
"name": "HD-1"
|
"name": "HD-1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "sub",
|
"type": "sub",
|
||||||
"ep_id": "110289",
|
"ep_id": "110289",
|
||||||
"link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZr...",
|
"link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOEZ4cFNpMDdQbnV1S3dNdklpRkhWbzRsVmgxSGx4YWx3LytPcnZXU0RCVHc9PQ",
|
||||||
"cmid": "animixplay-xxxxx",
|
"cmid": "animixplay-fqs",
|
||||||
"sv_id": "e54",
|
"sv_id": "2",
|
||||||
"name": "Vidstream-2"
|
"name": "Vidstream-2"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dub",
|
|
||||||
"ep_id": "110289",
|
|
||||||
"link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZr...",
|
|
||||||
"cmid": "animixplay-xxxxx",
|
|
||||||
"sv_id": "a41",
|
|
||||||
"name": "VidCloud-1"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key fields:**
|
> **Key field:** `link_id` — Pick a server and pass its `link_id` to the next step.
|
||||||
- `type` — "sub" or "dub"
|
|
||||||
- `link_id` — ID needed for the stream endpoint
|
---
|
||||||
- `name` — Server name (HD-1, Vidstream-2, etc.)
|
|
||||||
|
|
||||||
## Step 3: Get Stream URL
|
## Step 3: Get Stream URL
|
||||||
|
|
||||||
Use the `link_id` from step 2 to get the actual streaming URL.
|
Get the actual streaming URL using the `link_id` from Step 2.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9BRTZPbzJJRElFZUZr..."
|
curl "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
@@ -108,7 +107,7 @@ curl "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9BRTZPbzJJRElFZUZr
|
|||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"results": {
|
"results": {
|
||||||
"linkId": "MTF1dkFtaW9BRTZPbzJJRElFZUZr...",
|
"linkId": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ",
|
||||||
"url": "https://megaplay.buzz/stream/s-5/94736/sub",
|
"url": "https://megaplay.buzz/stream/s-5/94736/sub",
|
||||||
"skipData": {
|
"skipData": {
|
||||||
"intro": [0, 0],
|
"intro": [0, 0],
|
||||||
@@ -118,96 +117,137 @@ curl "https://anikototvapi.vercel.app/api/stream?id=MTF1dkFtaW9BRTZPbzJJRElFZUZr
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key fields:**
|
> **Done!** Use `results.url` in your video player.
|
||||||
- `url` — Direct streaming URL (m3u8 or mp4)
|
|
||||||
- `skipData` — Intro/outro timestamps for skipping
|
|
||||||
|
|
||||||
## Complete Example (JavaScript)
|
---
|
||||||
|
|
||||||
|
## JavaScript Example
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const BASE = "https://anikototvapi.vercel.app/api";
|
async function getStreamUrl(animeSlug) {
|
||||||
|
|
||||||
async function getStreamUrl(animeSlug, episodeNumber) {
|
|
||||||
// Step 1: Get episodes
|
// Step 1: Get episodes
|
||||||
const epsRes = await fetch(`${BASE}/episodes/${animeSlug}`);
|
const episodesRes = await fetch(`https://anikototvapi.vercel.app/api/episodes/${animeSlug}`);
|
||||||
const epsData = await epsRes.json();
|
const episodesData = await episodesRes.json();
|
||||||
const episode = epsData.results.episodes.find(
|
const episodes = episodesData.results.episodes;
|
||||||
ep => ep.episode_no === episodeNumber
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!episode) throw new Error("Episode not found");
|
if (!episodes || episodes.length === 0) {
|
||||||
|
throw new Error('No episodes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first episode's server_ids
|
||||||
|
const serverIds = episodes[0].server_ids;
|
||||||
|
|
||||||
// Step 2: Get servers
|
// Step 2: Get servers
|
||||||
const srvRes = await fetch(`${BASE}/servers?ids=${episode.server_ids}`);
|
const serversRes = await fetch(`https://anikototvapi.vercel.app/api/servers?ids=${serverIds}`);
|
||||||
const srvData = await srvRes.json();
|
const serversData = await serversRes.json();
|
||||||
const server = srvData.results.find(s => s.type === "sub") || srvData.results[0];
|
const servers = serversData.results;
|
||||||
|
|
||||||
if (!server) throw new Error("No servers available");
|
if (!servers || servers.length === 0) {
|
||||||
|
throw new Error('No servers found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick first server (HD-1)
|
||||||
|
const linkId = servers[0].link_id;
|
||||||
|
|
||||||
// Step 3: Get stream URL
|
// Step 3: Get stream URL
|
||||||
const streamRes = await fetch(`${BASE}/stream?id=${server.link_id}`);
|
const streamRes = await fetch(`https://anikototvapi.vercel.app/api/stream?id=${linkId}`);
|
||||||
const streamData = await streamRes.json();
|
const streamData = await streamRes.json();
|
||||||
|
|
||||||
return streamData.results;
|
return streamData.results.url; // "https://megaplay.buzz/stream/s-5/94736/sub"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage
|
// Usage
|
||||||
getStreamUrl("naruto-shippuden", 1)
|
getStreamUrl('road-of-naruto-ggjw8')
|
||||||
.then(stream => {
|
.then(url => console.log('Stream URL:', url))
|
||||||
console.log("Stream URL:", stream.url);
|
.catch(err => console.error('Error:', err));
|
||||||
console.log("Skip intro at:", stream.skipData.intro[0]);
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Complete Example (Python)
|
---
|
||||||
|
|
||||||
|
## Python Example
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
BASE = "https://anikototvapi.vercel.app/api"
|
def get_stream_url(anime_slug):
|
||||||
|
base = "https://anikototvapi.vercel.app/api"
|
||||||
|
|
||||||
def get_stream_url(anime_slug, episode_number):
|
|
||||||
# Step 1: Get episodes
|
# Step 1: Get episodes
|
||||||
eps_res = requests.get(f"{BASE}/episodes/{anime_slug}")
|
episodes_res = requests.get(f"{base}/episodes/{anime_slug}")
|
||||||
eps_data = eps_res.json()
|
episodes_data = episodes_res.json()
|
||||||
episode = next(
|
episodes = episodes_data['results']['episodes']
|
||||||
ep for ep in eps_data["results"]["episodes"]
|
|
||||||
if ep["episode_no"] == episode_number
|
if not episodes:
|
||||||
)
|
raise Exception("No episodes found")
|
||||||
|
|
||||||
|
server_ids = episodes[0]['server_ids']
|
||||||
|
|
||||||
# Step 2: Get servers
|
# Step 2: Get servers
|
||||||
srv_res = requests.get(f"{BASE}/servers", params={"ids": episode["server_ids"]})
|
servers_res = requests.get(f"{base}/servers", params={"ids": server_ids})
|
||||||
srv_data = srv_res.json()
|
servers_data = servers_res.json()
|
||||||
server = next(
|
servers = servers_data['results']
|
||||||
(s for s in srv_data["results"] if s["type"] == "sub"),
|
|
||||||
srv_data["results"][0]
|
if not servers:
|
||||||
)
|
raise Exception("No servers found")
|
||||||
|
|
||||||
|
link_id = servers[0]['link_id']
|
||||||
|
|
||||||
# Step 3: Get stream URL
|
# Step 3: Get stream URL
|
||||||
stream_res = requests.get(f"{BASE}/stream", params={"id": server["link_id"]})
|
stream_res = requests.get(f"{base}/stream", params={"id": link_id})
|
||||||
stream_data = stream_res.json()
|
stream_data = stream_res.json()
|
||||||
|
|
||||||
return stream_data["results"]
|
return stream_data['results']['url']
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
stream = get_stream_url("naruto-shippuden", 1)
|
url = get_stream_url('road-of-naruto-ggjw8')
|
||||||
print(f"Stream URL: {stream['url']}")
|
print(f"Stream URL: {url}")
|
||||||
print(f"Skip intro at: {stream['skipData']['intro'][0]}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Alternative: Mapper Servers
|
---
|
||||||
|
|
||||||
For additional streaming sources, use the mapper API with `malId`, `slug`, and `timestamp` from the episodes response.
|
## Player Integration
|
||||||
|
|
||||||
```bash
|
### HLS.js (HLS Streams)
|
||||||
curl "https://anikototvapi.vercel.app/api/mapper-servers?malId=1735&slug=naruto-shippuden×tamp=1729249503"
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||||
|
<video id="player" controls></video>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const url = 'https://megaplay.buzz/stream/s-5/94736/sub';
|
||||||
|
const video = document.getElementById('player');
|
||||||
|
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls();
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
This returns alternative providers like VidStreaming, Gogoanime, etc.
|
### Plyr (MP4 Streams)
|
||||||
|
|
||||||
## Notes
|
```html
|
||||||
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
||||||
|
<video id="player" controls></video>
|
||||||
|
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
||||||
|
|
||||||
- The `url` in stream response is typically an m3u8 playlist or direct mp4 link
|
<script>
|
||||||
- `skipData` contains intro/outro timestamps in seconds
|
const player = new Plyr('#player');
|
||||||
- Some servers may require specific headers or referrers to work
|
player.source = {
|
||||||
- The mapper API is external and may not always be available
|
type: 'video',
|
||||||
|
sources: [{ src: 'STREAM_URL_HERE' }]
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| "No episodes found" | Check the anime slug is correct (use `/api/search` first) |
|
||||||
|
| "No servers found" | The `server_ids` may be invalid, try another episode |
|
||||||
|
| Stream URL 403 | Some servers require specific referrer headers |
|
||||||
|
| CORS error | Use a proxy or access from server-side |
|
||||||
|
|||||||
Reference in New Issue
Block a user