hasari-api / docs /API_GUIDE.md
erdoganpeker's picture
v0.3.0 — multimodal vehicle damage MVP
e327f0d

API Guide

Complete REST + WebSocket reference for the Hasarİ backend, with runnable curl examples for every endpoint.

Interactive Swagger UI: http://localhost:8000/docs — auto-generated from the FastAPI app.


Base URL & versioning

  • Local development: http://localhost:8000
  • Pilot production (Render.com): https://hasari-api.onrender.com
  • API version prefix: /api/v1/* for inspection endpoints. Auth and health are at the root.

All examples below assume the env var BASE is set:

export BASE=http://localhost:8000

PowerShell:

$env:BASE = "http://localhost:8000"

Authentication

The API accepts two auth schemes:

  1. JWT Bearer (preferred) — obtained via /auth/login or /auth/register, sent as Authorization: Bearer <access_token>.
  2. Legacy API key (fallback)X-API-Key: <key> header, used for service-to-service.

In ENVIRONMENT=dev only, an unauthenticated request is accepted and treated as a dev client. Never run a public deployment with ENVIRONMENT=dev.

Tokens:

  • Access token: TTL 30 min (configurable via ACCESS_TOKEN_MINUTES)
  • Refresh token: TTL 7 days (configurable via REFRESH_TOKEN_DAYS)
  • Algorithm: HS256 by default. Secret loaded from JWT_SECRET_KEY (≥32 chars required in non-dev).

See AUTH_FLOW.md for the full register → login → refresh → use sequence and per-platform token storage guidance.


Standard responses

Success

  • 200 OK — synchronous result inline
  • 201 Created — auth registration succeeded
  • 202 Accepted — async job queued

Errors

All errors share this envelope:

{
  "detail": "Human-readable Turkish or English message"
}

Common HTTP codes:

Code Meaning Typical cause
400 Bad Request malformed request, missing files, oversized image
401 Unauthorized missing/expired/invalid token
403 Forbidden authenticated but not the owner of the resource
404 Not Found inspection ID does not exist
409 Conflict registering with an existing email
413 Payload Too Large image exceeds MAX_IMAGE_SIZE_MB
415 Unsupported Media Type non-image MIME
422 Unprocessable Entity Pydantic validation failed
429 Too Many Requests per-IP or per-account rate limit
500 Internal Server Error unhandled exception (logged)
503 Service Unavailable Celery/Redis down

Auth endpoints

POST /auth/register — Create a new user

Creates an account and returns an access + refresh token pair.

Request body

{
  "email": "user@example.com",
  "password": "MyStrongPassword123",
  "full_name": "Ahmet Yılmaz"
}
Field Type Constraints
email string RFC 5322 email
password string 8–128 chars
full_name string|null ≤120 chars, optional

Response 201

{
  "access_token": "eyJhbGciOi…",
  "refresh_token": "eyJhbGciOi…",
  "token_type": "bearer",
  "expires_in": 1800
}

Curl

curl -X POST "$BASE/auth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "MyStrongPassword123",
    "full_name": "Ahmet Yılmaz"
  }'

Errors: 409 Bu email zaten kayitli if duplicate; 422 if password too short.


POST /auth/login — Sign in

Returns a fresh access + refresh token pair.

Request body

{ "email": "user@example.com", "password": "MyStrongPassword123" }

Response 200: same TokenPair shape as /auth/register.

Curl

curl -X POST "$BASE/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"MyStrongPassword123"}'

Errors: 401 Email veya parola hatali (timing-safe — invalid users still incur a bcrypt cost).


POST /auth/refresh — Rotate access token

Request body

{ "refresh_token": "eyJhbGciOi…" }

Response 200: a new TokenPair. The old refresh token remains valid until its 7-day TTL expires (no revocation list in v0.1 — planned for v0.2).

Curl

curl -X POST "$BASE/auth/refresh" \
  -H "Content-Type: application/json" \
  -d '{"refresh_token":"eyJhbGciOi…"}'

Errors: 401 Refresh token gecersiz (expired, wrong type, signature mismatch).


GET /auth/me — Current user

Headers: Authorization: Bearer <access_token> (required)

Response 200

{
  "id": "8a3c4e2f-…",
  "email": "user@example.com",
  "full_name": "Ahmet Yılmaz",
  "role": "user",
  "is_active": true,
  "created_at": "2026-05-15T14:30:00Z"
}

Curl

curl "$BASE/auth/me" -H "Authorization: Bearer $ACCESS_TOKEN"

Health & version

GET /health — Service health

No auth required. Used by load balancers and uptime monitors.

Response 200

{
  "status": "ok",
  "ml_loaded": true,
  "timestamp": "2026-05-15T14:30:00.000Z",
  "version": "0.1.0"
}

Curl

curl "$BASE/health"

/healthz is an alias preserved for backwards compatibility.


GET /api/v1/version — Build info

Response 200

{
  "version": "0.1.0",
  "git_sha": "abc1234",
  "build_time": "2026-05-15T10:00:00Z",
  "environment": "production"
}

Inspection endpoints

All inspection endpoints require authentication. The user can only see and modify their own inspections.

POST /api/v1/inspect — Create inspection (multi-image)

Accepts 1–20 images via multipart form data. Returns either an async job handle or a synchronous result depending on mode.

Query: mode=sync (max 5 images, blocks until done) or mode=async (default, max 20 images, queued).

Form fields:

  • files — one or more files, JPG/PNG/WebP, each ≤ MAX_IMAGE_SIZE_MB (default 12 MB)

Response 202 (async)

{
  "inspection_id": "8c1f…",
  "status": "queued",
  "status_url": "/api/v1/inspect/8c1f…",
  "created_at": "2026-05-15T14:30:00Z",
  "estimated_completion_seconds": 30
}

Headers include X-Inspection-Id: 8c1f….

Response 200 (sync)

Full inspection result in SyncInspectionResponse shape — see "Output format" in README.md.

Curl (async)

curl -X POST "$BASE/api/v1/inspect?mode=async" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "files=@front.jpg" \
  -F "files=@side.jpg" \
  -F "files=@rear.jpg"

Curl (sync, single image)

curl -X POST "$BASE/api/v1/inspect?mode=sync" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "files=@damage.jpg"

Errors:

  • 400 En az 1 goruntu gerekli — empty form
  • 400 Sync modda max 5 goruntu / Async modda max 20 goruntu — limit exceeded
  • 400 Goruntu N cok buyuk (>12MB) — file too large
  • 400 Goruntu N gecersiz MIME tipi — wrong content type
  • 400 Goruntu N okunamadi — corrupt or unsupported format
  • 503 Is kuyrugu su an kullanilamiyor — Celery enqueue failed

POST /api/v1/inspect/sync — Fast single-image inspection

Optimized for the mobile quick-check flow: one image, latency-sensitive. Identical to POST /api/v1/inspect?mode=sync with a single file.

Curl

curl -X POST "$BASE/api/v1/inspect/sync" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@damage.jpg"

GET /api/v1/inspect/{id} — Status + result

Poll this endpoint to track an async job, or read the cached result after completion.

Response 200

{
  "inspection_id": "8c1f…",
  "status": "completed",
  "result": { /* part-centric output, see README */ },
  "error": null,
  "created_at": "2026-05-15T14:30:00Z",
  "completed_at": "2026-05-15T14:30:08Z"
}

statusqueued | running | completed | failed. When failed, error contains a human-readable Turkish message.

Curl

curl "$BASE/api/v1/inspect/8c1f…" -H "Authorization: Bearer $ACCESS_TOKEN"

Errors:

  • 404 Inceleme bulunamadi — wrong ID
  • 403 Bu incelemeye erisim yetkiniz yok — owned by another user

GET /api/v1/inspect/{id}/visualization/{viz_type} — Visualization PNG

Returns a 302 redirect to a presigned S3/MinIO URL for the requested visualization.

Path: viz_typeannotated | parts | damages.

Curl (follow redirect, save to file)

curl -L "$BASE/api/v1/inspect/8c1f…/visualization/annotated" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -o annotated.png

Errors:

  • 404 Inceleme bulunamadi
  • 404 annotated gorsel henuz uretilmemis — render not yet complete
  • 403 Yetki yok

GET /api/v1/inspect — List inspections

Paginated history of the authenticated user's inspections, newest first.

Query: page (≥1, default 1), page_size (1–200, default 20)

Response 200

{
  "items": [
    {
      "inspection_id": "8c1f…",
      "created_at": "2026-05-15T14:30:00Z",
      "status": "completed",
      "damage_count": 3,
      "total_cost_midpoint_tl": 10650,
      "thumbnail_url": null
    }
  ],
  "total": 47,
  "page": 1,
  "page_size": 20
}

Curl

curl "$BASE/api/v1/inspect?page=1&page_size=20" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

DELETE /api/v1/inspect/{id} — Delete inspection

Permanent. Removes the DB row; S3 objects are tombstoned by a nightly sweep (planned).

Response 204: empty body.

Curl

curl -X DELETE "$BASE/api/v1/inspect/8c1f…" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Errors: 404, 403 (not owner).


WS /api/v1/inspect/{id}/stream — Real-time progress

WebSocket channel that pushes status updates and the final result for an async job.

Auth: send the access token as the token query parameter (WebSocket clients can't easily set headers in the browser).

Connection

wss://hasari-api.onrender.com/api/v1/inspect/8c1f…/stream?token=eyJhbGciOi…

Messages (server → client, JSON):

{ "type": "status", "inspection_id": "8c1f…", "status": "running", "progress": 0.45 }
{ "type": "completed", "inspection_id": "8c1f…", "result": { /* part-centric output */ } }
{ "type": "failed", "inspection_id": "8c1f…", "error": "ML inference timeout" }

The server closes the socket immediately after completed or failed. Clients should also implement a polling fallback against GET /api/v1/inspect/{id} for environments that block WebSockets.

Wscat example

npx wscat -c "ws://localhost:8000/api/v1/inspect/8c1f…/stream?token=$ACCESS_TOKEN"

Rate limits

Endpoint group Limit Headers
/auth/* 10 req / minute / IP Retry-After on 429
/api/v1/inspect (POST) 60 req / hour / account
Other reads 1000 req / hour / account

Limits are enforced in middleware; the response on breach is 429 Too Many Requests with a Turkish detail and a Retry-After header in seconds.


Pagination conventions

  • page is 1-based.
  • page_size is capped at 200 (the backend rejects larger values with 422).
  • Always include total in responses so the client can render "Sayfa 1 / 5".

Idempotency

POST /api/v1/inspect is not idempotent — a retry will create a new inspection. To avoid duplicates on flaky networks, the client should track in-flight inspection IDs locally and offer a "view existing" UX if a duplicate is detected.


Need to inspect failures?

  • Backend logs: structured JSON to stdout. In Docker: docker compose logs -f backend. On Render: dashboard → Logs.
  • Sentry: error events are forwarded when SENTRY_DSN is set.
  • Prometheus: /metrics endpoint exposes request count, latency histogram, ML inference duration.

For incident response, see DEPLOY_GUIDE.md.