Hassan Agmir Hassan Agmir

API Documentation — Koora API (v1)

Hassan Agmir
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)

  1. Create & activate virtualenv.
  2. Install dependencies:
pip install django djangorestframework django-filter djangorestframework-simplejwt
  1. Add to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
    # ...
    "rest_framework",
    "django_filters",
    "rest_framework_simplejwt.token_blacklist",  # if using logout/blacklist
    # ...
]
  1. 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",
    ),
}
  1. Run migrations:
python manage.py makemigrations
python manage.py migrate
  1. Run dev server:
python manage.py runserver

Authentication

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/continents

Countries

/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)

  1. Obtain access token.
  2. 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-spectacular

settings.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 migrate

Large 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?

Subscribe to my Newsletters

Stay updated with the latest programming tips, tricks, and IT insights! Join my community to receive exclusive content on coding best practices.

© Copyright 2025 by Hassan Agmir . Built with ❤ by Me