API Documentation — Koora API (v1)
Ready-to-use API documentation for your Django/DRF project. It covers everything: auth, endpoints, request/response examples, filters/search/ordering, pagination, error handling, setup, and tips for production. Drop this into your repo as API_DOCUMENTATION.md or read it inline.
Base URL (app mounted in project-level urls.py):
http://<HOST>:<PORT>/api/v1/Example root: http://localhost:8000/api/v1/
This API uses JWT authentication (SimpleJWT) and Django REST Framework. Trailing slashes are disabled (trailing_slash=False).
Quick start (setup)
- Create & activate virtualenv.
- Install dependencies:
pip install django djangorestframework django-filter djangorestframework-simplejwt- Add to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
# ...
"rest_framework",
"django_filters",
"rest_framework_simplejwt.token_blacklist", # if using logout/blacklist
# ...
]- Add REST_FRAMEWORK config (example):
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication", # optional
),
"DEFAULT_PERMISSION_CLASSES": (
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
),
"DEFAULT_FILTER_BACKENDS": (
"django_filters.rest_framework.DjangoFilterBackend",
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
),
}- Run migrations:
python manage.py makemigrations
python manage.py migrate- Run dev server:
python manage.py runserverAuthentication
We support JWT via SimpleJWT.
Endpoints
- Obtain tokens: POST /api/v1/auth/token
Body: {"username":"alice","password":"secret"} - Refresh: POST /api/v1/auth/token/refresh
Body: {"refresh":"<refresh_token>"} - Register: POST /api/v1/auth/register
Body: {"username","email","password","password2", ...} - Logout (blacklist refresh): POST /api/v1/auth/logout
Body: {"refresh":"<refresh_token>"} (requires Authorization header) - Profile: GET/PUT /api/v1/auth/me (authenticated)
- Change password: POST /api/v1/auth/change-password (authenticated)
Example — obtain token
curl -X POST http://localhost:8000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"secret123!"}'Response:
{
"access": "<access_token>",
"refresh": "<refresh_token>"
}Using tokens
Send Authorization: Bearer <access_token> header for authenticated endpoints.
Common patterns
- Read endpoints generally open to anonymous users (unless model/view restricts). Create/update/delete require authentication.
- For read responses we return nested representations (e.g., blog returns team object + team_id used for writes).
- Dates use ISO 8601 (YYYY-MM-DD) and datetimes YYYY-MM-DDTHH:MM:SSZ (UTC unless configured).
- Pagination: default DRF pagination (if enabled in settings). If you didn't set pagination, endpoints return full lists — recommended to enable PageNumberPagination.
Endpoints (summary)
All paths are under /api/v1/. For each resource we list allowed methods and main fields.
Continents
/continents
Methods: GET, POST
/continents/{id} — GET, PUT, PATCH, DELETE
Fields: id, name, code
Example GET:
curl http://localhost:8000/api/v1/continentsCountries
/countries
Fields: id, name, code, continent (nested), continent_id (write)
Filterable: continent, name, code
Cities
/cities
Fields: id, name, country (nested), country_id (write)
Teams
/teams
Fields:
- id, name, slug, country (nested), country_id,
- city, city_id, logo_url, description, code, body
Create:
POST /api/v1/teams
{
"name":"Rabat FC",
"country_id": 2,
"city_id": 7,
"description":"A top team"
}Players
/players
Fields: id, name, arabic_name, slug, code, dob, nationality/nationality_id, position, photo_url, description, body
Contracts
/contracts
Fields: id, player/player_id, team/team_id, start_date, end_date, transfer_fee
Validation: start_date < end_date
Competitions
/competitions
Fields: id, name, title, slug, country/country_id, type, season, description, code, body
Seasons
/seasons
Fields: id, name, competition/competition_id, start_date, end_date
Validation: start_date < end_date
Groups
/groups
Fields: id, name, season/season_id, competition/competition_id, teams, team_ids, description
Write: set team_ids to array of ids.
Matches
/matches
Fields: id, competition/competition_id, home_team/home_team_id, away_team/away_team_id, date_time, venue, home_score, away_score, code, status
Validation: home_team != away_team.
Match Events
/match-events
Fields: id, match/match_id, player/player_id, event_type, minute
SeasonTeam / SeasonMatch / SeasonPlayer
/season-teams, /season-matches, /season-players
Fields correspond to models (use *_id for writes).
Categories
/categories
Fields: id, name, slug
Blogs
/blogs
Fields (read):
- id, title, slug, team/team_id, match/match_id,
- image_url, description, tags (array), body, created_at, category/category_id, comments (list)
When creating/updating blog:
POST /api/v1/blogs
{
"title":"Match review",
"team_id": 12,
"category_id": 3,
"tags": ["worldcup","review"],
"body":"Detailed review..."
}tags is represented as an array in the API (converted to CSV for DB storage).
News Comments
/comments
Fields: id, blog, user_name, user_email, content, parent, is_approved, created_at, updated_at
Permissions: create allowed to all (configurable). Approvals/edit may require auth.
Search / Filter / Ordering
All ViewSets include DRF filters:
- Search (text): ?search=term — searches configured fields (e.g., /blogs?search=worldcup)
- Filter by field: ?team=3&category=4
- Date filtering example: ?created_at__gte=2025-01-01 (if view supports filterset querying)
- Ordering: ?ordering=created_at or ?ordering=-created_at
Examples:
- /api/v1/players?search=ronaldo&position=FW
- /api/v1/blogs?category=2&ordering=-created_at
Tip: You can add custom filter fields in the viewsets by setting filterset_fields.
Pagination
If you enable DRF PageNumberPagination in settings.py, use:
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
...
}Requests: ?page=2
Responses will include count, next, previous, results.
Error responses
- Validation error: 400 Bad Request
{
"field_name": ["error message"]
}- Unauthorized: 401 Unauthorized
{"detail":"Authentication credentials were not provided."}- Forbidden: 403 Forbidden
{"detail":"You do not have permission to perform this action."}- Not found: 404 Not Found
Example flows
1) Create a blog (authenticated)
- Obtain access token.
- Create blog:
curl -X POST http://localhost:8000/api/v1/blogs \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"title":"Match day", "team_id":1, "category_id":2, "tags":["match","review"], "body":"..."}'2) Comment on a blog (anonymous allowed)
curl -X POST http://localhost:8000/api/v1/comments \
-H "Content-Type: application/json" \
-d '{"blog":1,"user_name":"Omar","content":"Great article"}'3) Update player info (authenticated)
PATCH /api/v1/players/5
Authorization: Bearer <token>
Body: {"photo_url":"https://.../photo.jpg"}Admin & file uploads
- Admin: use Django admin for model management.
- logo_url and photo_url are URLFields. If you want file upload, change to ImageField(upload_to=...) and configure MEDIA_ROOT, MEDIA_URL, and your storage backend.
Security & production tips
- Use HTTPS.
- Shorten ACCESS_TOKEN_LIFETIME for production; use refresh tokens with rotation.
- Enable token blacklist if you want server-side logout (rest_framework_simplejwt.token_blacklist).
- Add rate limiting / throttling for critical endpoints (login, register, comments).
- Validate and sanitize HTML if you accept rich text in body fields.
- Protect comment creation with CAPTCHA or rate limits to prevent spam.
- Use CORS settings if frontend served from different domain (install django-cors-headers).
Schema / Swagger / Docs
Generate OpenAPI docs with:
- drf-spectacular (recommended) or drf-yasg.
Example drf-spectacular quick setup:
pip install drf-spectacularsettings.py:
REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'drf_spectacular.openapi.AutoSchema'
SPECTACULAR_SETTINGS = {...}Then add URLs:
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns += [
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema')),
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema')),
]Database & migrations
After model changes:
python manage.py makemigrations
python manage.py migrateLarge TextField migrations are safe but always test on staging.
Troubleshooting
- Template errors (Browsable API & django-filter): ensure django_filters in INSTALLED_APPS and APP_DIRS=True in TEMPLATES.
- Slug uniqueness race: model generator tries to avoid collisions; if you need absolute guarantees, add IntegrityError retry logic or use DB-level unique index transactions.
- If browsable API not desired, remove BrowsableAPIRenderer from DEFAULT_RENDERER_CLASSES.
Postman / Example collection
You can import the following example cURL requests into Postman:
- POST /auth/token (obtain tokens)
- GET /players (list players)
- POST /blogs (create blog)
- POST /comments (create comment)
(If you want, I can generate a ready-made Postman collection JSON for you — tell me and I’ll produce it.)
Appendix: Common request/response examples
Create Team (request)
POST /api/v1/teams
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Rabat United",
"country_id": 3,
"city_id": 10,
"description": "Founded 1992"
}Response (201 Created):
{
"id": 21,
"name": "Rabat United",
"slug": "rabat-united",
"country": { "id":3, "name":"Morocco", "code":"MA" },
"country_id": 3,
"city": { "id":10, "name":"Rabat" },
"city_id": 10,
"logo_url": null,
"description": "Founded 1992",
"code": null,
"body": null
}Obtain Token (request)
POST /api/v1/auth/token
{
"username": "alice",
"password": "secret123!"
}Response:
{"access": "<access_token>", "refresh": "<refresh_token>"}If you want, I can:
- produce a Postman collection file,
- generate OpenAPI/Swagger JSON,
- create a small React example showing login + fetch blogs,
- add per-endpoint permission matrix (who can create/update/delete),
- or write a short migration plan for switching Blog.tags from CharField → ManyToMany Tag.
Which of those should I produce next?