Spaces:
Running
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:
- JWT Bearer (preferred) — obtained via
/auth/loginor/auth/register, sent asAuthorization: Bearer <access_token>. - 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 inline201 Created— auth registration succeeded202 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 form400 Sync modda max 5 goruntu/Async modda max 20 goruntu— limit exceeded400 Goruntu N cok buyuk (>12MB)— file too large400 Goruntu N gecersiz MIME tipi— wrong content type400 Goruntu N okunamadi— corrupt or unsupported format503 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"
}
status ∈ queued | 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 ID403 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_type ∈ annotated | 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 bulunamadi404 annotated gorsel henuz uretilmemis— render not yet complete403 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
pageis 1-based.page_sizeis capped at 200 (the backend rejects larger values with 422).- Always include
totalin 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_DSNis set. - Prometheus:
/metricsendpoint exposes request count, latency histogram, ML inference duration.
For incident response, see DEPLOY_GUIDE.md.