Skip to content

API Reference

Base URL

  • Development: http://localhost:8000/api
  • Production: https://your-domain.com/api

Authentication

Authentication is handled by Better Auth. The frontend manages sign-in/sign-up via /api/auth/[...all] API routes, which set an HTTP-only session cookie (better-auth.session_token).

Backend endpoints validate requests by reading this session cookie:

curl -X GET http://localhost:8000/api/properties \
  -H "Cookie: better-auth.session_token=SESSION_TOKEN"

The backend also supports legacy JWT Bearer tokens during migration (via get_current_user_hybrid).

Endpoints

Authentication

Register User

POST /api/users/register

Request Body:

{
  "email": "user@example.com",
  "password": "securepassword",
  "full_name": "John Doe"
}

Response 201 Created:

{
  "id": 1,
  "email": "user@example.com",
  "full_name": "John Doe",
  "created_at": "2025-01-30T10:00:00Z"
}

Login

Authentication is handled client-side via the Better Auth SDK. The frontend calls authClient.signIn.email() which hits the Next.js /api/auth/sign-in/email route, setting a session cookie on success.

For the legacy endpoint (kept for backward compatibility):

POST /api/users/login

Request Body:

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

Response 200 OK: Sets better-auth.session_token cookie.


Properties

List Properties

GET /api/properties

Query Parameters:

Parameter Type Description
skip int Pagination offset (default: 0)
limit int Results per page (default: 100)

Response 200 OK:

[
  {
    "id": 1,
    "address": "56 Rue Notre-Dame des Champs",
    "postal_code": "75006",
    "city": "Paris",
    "asking_price": 850000,
    "surface_area": 65.5,
    "created_at": "2025-01-30T10:00:00Z"
  }
]

Create Property

POST /api/properties

Request Body:

{
  "address": "56 Rue Notre-Dame des Champs",
  "postal_code": "75006",
  "city": "Paris",
  "asking_price": 850000,
  "surface_area": 65.5,
  "property_type": "apartment",
  "rooms": 3
}

Get Property

GET /api/properties/{id}

List Properties with Synthesis

GET /api/properties/with-synthesis

Returns all properties enriched with synthesis preview data (risk level, costs, document/redesign counts).

Response 200 OK:

[
  {
    "id": 1,
    "address": "56 Rue Notre-Dame des Champs",
    "postal_code": "75006",
    "city": "Paris",
    "asking_price": 850000,
    "surface_area": 65.5,
    "synthesis": {
      "risk_level": "medium",
      "total_annual_cost": 3500.0,
      "total_one_time_cost": 15000.0,
      "key_findings": "Property analysis summary...",
      "document_count": 5,
      "redesign_count": 2
    }
  }
]

Update Property

PATCH /api/properties/{id}

Request Body (all fields optional):

{
  "address": "56 Rue Notre-Dame des Champs",
  "postal_code": "75006",
  "city": "Paris",
  "department": "75",
  "property_type": "apartment",
  "asking_price": 840000,
  "surface_area": 65.5,
  "rooms": 3,
  "floor": 4,
  "building_floors": 7,
  "building_year": 1920
}

Delete Property

DELETE /api/properties/{id}

Documents

Upload Single Document

POST /api/documents/upload
Content-Type: multipart/form-data

Form Fields:

Field Type Required Description
file file Yes PDF or image file
property_id int Yes Associated property
document_type string No Type (auto-classified if omitted)

Response 201 Created:

{
  "id": 1,
  "filename": "pv_ag_2024.pdf",
  "document_type": "pv_ag",
  "status": "processing",
  "property_id": 1,
  "created_at": "2025-01-30T10:00:00Z"
}

Bulk Upload Documents

POST /api/documents/bulk-upload
Content-Type: multipart/form-data

Form Fields:

Field Type Required Description
files file[] Yes Multiple PDF/image files
property_id int Yes Associated property

Response 202 Accepted:

{
  "status": "processing",
  "workflow_id": "bulk-processing-1-1706612400",
  "document_ids": [1, 2, 3],
  "total_files": 3,
  "message": "Successfully uploaded 3 documents. Processing..."
}

Get Bulk Processing Status

GET /api/documents/bulk-status/{workflow_id}

Response 200 OK:

{
  "workflow_id": "bulk-processing-1-1706612400",
  "property_id": 1,
  "status": "completed",
  "progress": {
    "total": 3,
    "completed": 3,
    "failed": 0,
    "percentage": 100
  },
  "documents": [
    {
      "id": 1,
      "filename": "pv_ag_2024.pdf",
      "document_type": "pv_ag",
      "status": "analyzed"
    }
  ],
  "synthesis": {
    "summary": "Property analysis complete...",
    "total_annual_cost": 3500.0,
    "total_one_time_cost": 15000.0,
    "risk_level": "medium",
    "recommendations": ["..."]
  }
}

List Documents

GET /api/documents

Query Parameters:

Parameter Type Description
property_id int Filter by property
document_type string Filter by type

Get Document

GET /api/documents/{id}

Delete Document

DELETE /api/documents/{id}

Deleting a document automatically triggers synthesis regeneration for the property.

Bulk Delete Documents

POST /api/documents/bulk-delete

Request Body:

{
  "document_ids": [1, 2, 3]
}

Response 200 OK:

{
  "deleted_count": 3
}

Automatically triggers synthesis regeneration after deletion.

Rename Document

PATCH /api/documents/{document_id}

Request Body:

{
  "filename": "new-document-name"
}

The original file extension is preserved automatically.

Save Synthesis Overrides

PATCH /api/documents/synthesis/{property_id}/overrides

Saves user-defined overrides into the synthesis data (e.g., tantiemes values, cost adjustments). Overrides are preserved across synthesis regenerations.

Request Body:

{
  "user_overrides": {
    "lot_tantiemes": 150,
    "total_tantiemes": 10000
  }
}

Regenerate Overall Synthesis

POST /api/documents/synthesis/{property_id}/regenerate-overall

Manually triggers regeneration of the cross-document synthesis from all analyzed documents. User overrides are preserved.

Response 200 OK:

{
  "status": "success",
  "synthesis": {
    "summary": "Updated synthesis...",
    "total_annual_cost": 3500.0,
    "total_one_time_cost": 15000.0,
    "risk_level": "medium",
    "synthesis_data": { ... }
  }
}

Analysis

Get Price Analysis

GET /api/analysis/price

Query Parameters:

Parameter Type Required Description
address string Yes Property address
postal_code string Yes Postal code
property_type string No apartment/house

Response 200 OK:

{
  "address": "56 Rue Notre-Dame des Champs",
  "postal_code": "75006",
  "simple_analysis": {
    "sales_count": 3,
    "avg_price_per_sqm": 12500,
    "min_price": 600000,
    "max_price": 1350000,
    "sales": [...]
  },
  "trend_analysis": {
    "price_trend": 2.5,
    "projected_price_per_sqm": 12812,
    "confidence": "high",
    "data_points": 45
  },
  "recommendation": {
    "fair_price_range": [800000, 900000],
    "market_position": "slightly_above_market",
    "negotiation_suggestion": "Consider offering 5-8% below asking"
  }
}
GET /api/analysis/trends

Query Parameters:

Parameter Type Required Description
postal_code string Yes Area postal code
years int No Years of history (default: 5)

Response 200 OK:

{
  "postal_code": "75006",
  "trends": [
    {"year": 2021, "avg_price_per_sqm": 11200, "transactions": 245},
    {"year": 2022, "avg_price_per_sqm": 11800, "transactions": 231},
    {"year": 2023, "avg_price_per_sqm": 12100, "transactions": 198},
    {"year": 2024, "avg_price_per_sqm": 12400, "transactions": 167},
    {"year": 2025, "avg_price_per_sqm": 12650, "transactions": 89}
  ],
  "yoy_change": 2.0,
  "five_year_change": 12.9
}

Photos

Upload Photo

POST /api/photos/upload
Content-Type: multipart/form-data

Form Fields:

Field Type Required Description
file file Yes JPG/PNG image
property_id int Yes Associated property
room_type string No living_room, bedroom, etc.

Request Redesign

POST /api/photos/{id}/redesign

Request Body:

{
  "style": "modern",
  "preferences": {
    "color_scheme": "neutral",
    "furniture_style": "minimalist"
  }
}

Response 202 Accepted:

{
  "redesign_id": "redesign-123-456",
  "status": "generating",
  "estimated_time": 30
}

Get Redesign Status

GET /api/photos/redesign/{redesign_id}

Promote Redesign

PATCH /api/photos/{photo_id}/promote/{redesign_id}

Sets a redesign as the promoted (featured) redesign for a photo. The promoted redesign is displayed on the property overview page.

Response 200 OK:

{
  "id": 1,
  "filename": "living_room.jpg",
  "room_type": "living_room",
  "promoted_redesign": {
    "id": 5,
    "redesign_uuid": "abc-123",
    "style_preset": "modern",
    "prompt": "Modern Norwegian style...",
    "presigned_url": "https://...",
    "created_at": "2026-02-08T10:00:00Z"
  }
}

Remove Promoted Redesign

DELETE /api/photos/{photo_id}/promote

Clears the promoted redesign from a photo.


Webhooks

MinIO Event Webhook

POST /api/webhooks/minio

Internal endpoint for MinIO event notifications.

Error Responses

All endpoints return consistent error format:

{
  "detail": "Error message describing what went wrong"
}
Status Code Description
400 Bad Request - Invalid input
401 Unauthorized - Missing/invalid token
403 Forbidden - Insufficient permissions
404 Not Found - Resource doesn't exist
422 Validation Error - Schema mismatch
500 Internal Server Error

Rate Limiting

API requests are rate-limited per user:

  • Standard: 100 requests/minute
  • Bulk operations: 10 requests/minute

Exceeded limits return 429 Too Many Requests.