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:
Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ
2026-06-08 21:06:58 +05:30
parent 12aa1f5235
commit e125fe4646
6 changed files with 864 additions and 1160 deletions
+6 -5
View File
@@ -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
View File
@@ -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 |
+269 -670
View File
File diff suppressed because it is too large Load Diff
+252 -178
View File
@@ -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 data = await res.json(); const res = await fetch('https://anikototvapi.vercel.app/api');
console.log(data.results); const data = await res.json();
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 data = await res.json(); const res = await fetch(`https://anikototvapi.vercel.app/api/search?keyword=${encodeURIComponent(keyword)}`);
data.results.data.forEach(anime => { const data = await res.json();
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 data = await res.json(); const res = await fetch(`https://anikototvapi.vercel.app/api/info?id=${slug}`);
console.log(data.results.title); const data = await res.json();
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 } }); const serverIds = episodes[0].server_ids;
return stream.results; // 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
View File
@@ -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
View File
@@ -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&timestamp=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 |