{ "api_meta": { "name": "Managinator Backend API", "overview": "Bearer-protected Flask service for managing investment profiles, users, and scrapers.", "base_url_example": "http://localhost:8000", "default_headers": { "Content-Type": "application/json" }, "cors": { "allow_origin": "*", "allow_credentials": true, "notes": "Server responds to OPTIONS preflight without authentication and accepts credentialed requests from any origin." }, "environment": { "API_TOKEN": "Bearer token expected on every request.", "FLUXIMMO_API_KEY": "Credential forwarded to Fluximmo analytics count endpoint.", "FLUXIMMO_COUNT_URL": "Optional override for Fluximmo count API base URL (defaults to https://analytics.fluximmo.io/properties/count)." } }, "authentication": { "strategy": "bearer_token", "header": "Authorization", "format": "Bearer ", "env_variable": "API_TOKEN", "notes": [ "Every request (except OPTIONS and Flask static assets) must carry the bearer token in the Authorization header.", "There is no login endpoint; the expected token value is configured server-side via the API_TOKEN environment variable.", "Missing, malformed, or incorrect bearer tokens return HTTP 401." ] }, "conventions": { "datetime_format": "Use ISO 8601 strings with timezone. Responses normalize to UTC with trailing Z.", "boolean_input": "Send JSON booleans true/false. The backend also accepts 1/0 and common string equivalents, but clients should prefer booleans.", "integer_input": "Integer fields accept JSON numbers. Numeric strings will be coerced server-side but should be avoided.", "null_handling": "Fields documented as nullable accept JSON null and will persist as SQL NULL.", "error_responses": { "typical_status_codes": [400, 401, 404, 409, 503], "body_format": "Flask default HTTP error responses; body may be plain text or HTML containing the description. Rely on HTTP status and message." } }, "schemas": { "Profile": { "description": "Investment profile descriptor persisted in users_investmentprofile.", "fields": [ {"name": "profile_id", "type": "uuid", "nullable": false, "read_only": true, "description": "Primary key generated server-side."}, {"name": "name", "type": "string", "nullable": false, "read_only": false, "description": "Human-readable profile label."}, {"name": "description", "type": "string", "nullable": true, "read_only": false, "description": "Optional free-form description. Empty string preserved."}, {"name": "criteria", "type": "json", "nullable": false, "read_only": false, "description": "Arbitrary JSON object/array describing selection rules."}, {"name": "created_at", "type": "datetime", "nullable": false, "read_only": false, "description": "Creation timestamp. Defaults to current UTC when omitted."}, {"name": "is_active", "type": "boolean", "nullable": false, "read_only": false, "description": "Activation flag. Defaults to true on create."} ] }, "User": { "description": "Auth user record persisted in auth_user.", "fields": [ {"name": "id", "type": "integer", "nullable": false, "read_only": true, "description": "Database primary key."}, {"name": "username", "type": "string", "nullable": false, "read_only": false, "description": "Unique login name."}, {"name": "password", "type": "string", "nullable": false, "read_only": false, "description": "Raw password value inserted as-is. Not returned in responses."}, {"name": "first_name", "type": "string", "nullable": false, "read_only": false, "description": "User first name."}, {"name": "last_name", "type": "string", "nullable": false, "read_only": false, "description": "User last name."}, {"name": "email", "type": "string", "nullable": false, "read_only": false, "description": "User email address."}, {"name": "is_superuser", "type": "boolean", "nullable": false, "read_only": false, "description": "Administrative superuser flag."}, {"name": "is_staff", "type": "boolean", "nullable": false, "read_only": false, "description": "Staff membership flag."}, {"name": "is_active", "type": "boolean", "nullable": false, "read_only": false, "description": "Account enabled flag."}, {"name": "date_joined", "type": "datetime", "nullable": false, "read_only": false, "description": "Account creation timestamp. Defaults to current UTC when omitted."}, {"name": "last_login", "type": "datetime", "nullable": true, "read_only": false, "description": "Most recent login timestamp."} ] }, "Scraper": { "description": "Scraper configuration persisted in scraper table.", "fields": [ {"name": "id", "type": "uuid", "nullable": false, "read_only": true, "description": "Primary key generated server-side."}, {"name": "params", "type": "string", "nullable": true, "read_only": false, "description": "Serialized scraper parameters."}, {"name": "last_seen_days", "type": "integer", "nullable": true, "read_only": false, "description": "Number of days since listings were last seen."}, {"name": "first_seen_days", "type": "integer", "nullable": true, "read_only": false, "description": "Number of days since listings were first seen."}, {"name": "frequency", "type": "string", "nullable": true, "read_only": false, "description": "Cron or human-readable frequency descriptor."}, {"name": "task_name", "type": "string", "nullable": true, "read_only": false, "description": "Associated task identifier."}, {"name": "enabled", "type": "integer", "nullable": true, "read_only": false, "description": "1/0 flag for activation. Stored as integer."}, {"name": "property_types", "type": "string", "nullable": true, "read_only": false, "description": "Comma-separated property type filters."}, {"name": "page_size", "type": "integer", "nullable": true, "read_only": false, "description": "Listings fetched per page."}, {"name": "max_pages", "type": "integer", "nullable": true, "read_only": false, "description": "Maximum number of pages to crawl."}, {"name": "enrich_llm", "type": "integer", "nullable": true, "read_only": false, "description": "Toggle for LLM enrichment (1 enabled, 0 disabled)."}, {"name": "only_match", "type": "integer", "nullable": true, "read_only": false, "description": "Toggle for strict matching (1 enabled, 0 disabled)."} ] } }, "endpoints": [ { "operation_id": "listProfiles", "method": "GET", "path": "/profiles", "authenticated": true, "description": "List all investment profiles ordered by most recent creation.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} } }, "responses": { "200": {"description": "Array of profiles.", "body": {"type": "array", "items": {"$ref": "#/schemas/Profile"}}}, "401": {"description": "Missing or invalid bearer token."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "getProfile", "method": "GET", "path": "/profiles/", "authenticated": true, "description": "Retrieve a single profile by UUID.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "profile_id", "type": "uuid", "required": true, "description": "Profile identifier."} ] }, "responses": { "200": {"description": "Profile payload.", "body": {"$ref": "#/schemas/Profile"}}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Profile not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "createProfile", "method": "POST", "path": "/profiles", "authenticated": true, "description": "Create a new investment profile.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "body": { "type": "object", "required_fields": ["name", "criteria"], "fields": { "name": {"type": "string", "nullable": false, "description": "Profile name."}, "description": {"type": "string", "nullable": true, "description": "Optional description. Use null to clear."}, "criteria": {"type": "json", "nullable": false, "description": "Required JSON criteria payload."}, "created_at": {"type": "datetime", "nullable": false, "description": "Optional creation timestamp. Defaults to now (UTC)."}, "is_active": {"type": "boolean", "nullable": false, "description": "Defaults to true."} } } }, "responses": { "201": {"description": "Profile created.", "body": {"$ref": "#/schemas/Profile"}}, "400": {"description": "Validation failure (missing criteria, invalid field formats, or empty request)."}, "401": {"description": "Missing or invalid bearer token."}, "409": {"description": "Constraint violation (e.g., duplicate profile_id)."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "updateProfile", "method": "PUT", "path": "/profiles/", "authenticated": true, "description": "Update mutable fields on an existing profile.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "profile_id", "type": "uuid", "required": true, "description": "Profile identifier."} ], "body": { "type": "object", "allowed_fields": ["name", "description", "criteria", "created_at", "is_active"], "min_fields": 1, "fields": { "name": {"type": "string", "nullable": false, "description": "New profile name."}, "description": {"type": "string", "nullable": true, "description": "Use null to clear description."}, "criteria": {"type": "json", "nullable": false, "description": "Updated criteria JSON."}, "created_at": {"type": "datetime", "nullable": false, "description": "New creation timestamp."}, "is_active": {"type": "boolean", "nullable": false, "description": "Activation flag."} } } }, "responses": { "200": {"description": "Profile updated.", "body": {"$ref": "#/schemas/Profile"}}, "400": {"description": "No updatable fields provided or invalid data."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Profile not found."}, "409": {"description": "Constraint violation."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "deleteProfile", "method": "DELETE", "path": "/profiles/", "authenticated": true, "description": "Remove a profile by UUID.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "profile_id", "type": "uuid", "required": true, "description": "Profile identifier."} ] }, "responses": { "204": {"description": "Profile deleted. Empty body."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Profile not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "listUsers", "method": "GET", "path": "/users", "authenticated": true, "description": "List all users ordered by ascending id.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} } }, "responses": { "200": {"description": "Array of users.", "body": {"type": "array", "items": {"$ref": "#/schemas/User"}}}, "401": {"description": "Missing or invalid bearer token."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "getUser", "method": "GET", "path": "/users/", "authenticated": true, "description": "Retrieve a user by numeric id.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "user_id", "type": "integer", "required": true, "description": "User primary key."} ] }, "responses": { "200": {"description": "User payload.", "body": {"$ref": "#/schemas/User"}}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "User not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "createUser", "method": "POST", "path": "/users", "authenticated": true, "description": "Create a new user record.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "body": { "type": "object", "required_fields": ["password", "username", "first_name", "last_name", "email"], "fields": { "password": {"type": "string", "nullable": false, "description": "Password to store. No hashing performed."}, "username": {"type": "string", "nullable": false, "description": "Unique username."}, "first_name": {"type": "string", "nullable": false, "description": "First name."}, "last_name": {"type": "string", "nullable": false, "description": "Last name."}, "email": {"type": "string", "nullable": false, "description": "Email address."}, "is_superuser": {"type": "boolean", "nullable": false, "description": "Defaults to false when omitted."}, "is_staff": {"type": "boolean", "nullable": false, "description": "Defaults to false when omitted."}, "is_active": {"type": "boolean", "nullable": false, "description": "Defaults to true when omitted."}, "date_joined": {"type": "datetime", "nullable": true, "description": "Optional. Defaults to current UTC when omitted or null."}, "last_login": {"type": "datetime", "nullable": true, "description": "Optional last login timestamp."} } } }, "responses": { "201": {"description": "User created.", "body": {"$ref": "#/schemas/User"}}, "400": {"description": "Validation failure."}, "401": {"description": "Missing or invalid bearer token."}, "409": {"description": "Constraint violation (e.g., duplicate username or email)."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "updateUser", "method": "PUT", "path": "/users/", "authenticated": true, "description": "Update mutable fields on a user.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "user_id", "type": "integer", "required": true, "description": "User primary key."} ], "body": { "type": "object", "allowed_fields": ["password", "username", "first_name", "last_name", "email", "is_superuser", "is_staff", "is_active", "date_joined", "last_login"], "min_fields": 1, "fields": { "password": {"type": "string", "nullable": false, "description": "New raw password."}, "username": {"type": "string", "nullable": false, "description": "New username."}, "first_name": {"type": "string", "nullable": false, "description": "New first name."}, "last_name": {"type": "string", "nullable": false, "description": "New last name."}, "email": {"type": "string", "nullable": false, "description": "New email."}, "is_superuser": {"type": "boolean", "nullable": false, "description": "Updated admin flag."}, "is_staff": {"type": "boolean", "nullable": false, "description": "Updated staff flag."}, "is_active": {"type": "boolean", "nullable": false, "description": "Updated active flag."}, "date_joined": {"type": "datetime", "nullable": false, "description": "Updated join timestamp."}, "last_login": {"type": "datetime", "nullable": true, "description": "Updated last login timestamp."} } } }, "responses": { "200": {"description": "User updated.", "body": {"$ref": "#/schemas/User"}}, "400": {"description": "No updatable fields provided or invalid data."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "User not found."}, "409": {"description": "Constraint violation."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "deleteUser", "method": "DELETE", "path": "/users/", "authenticated": true, "description": "Delete a user by id.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "user_id", "type": "integer", "required": true, "description": "User primary key."} ] }, "responses": { "204": {"description": "User deleted. Empty body."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "User not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "listScrapers", "method": "GET", "path": "/scrapers", "authenticated": true, "description": "List all scraper configurations ordered by id.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} } }, "responses": { "200": {"description": "Array of scrapers.", "body": {"type": "array", "items": {"$ref": "#/schemas/Scraper"}}}, "401": {"description": "Missing or invalid bearer token."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "getScraper", "method": "GET", "path": "/scrapers/", "authenticated": true, "description": "Retrieve a single scraper by text id.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "scraper_id", "type": "string", "required": true, "description": "Scraper identifier."} ] }, "responses": { "200": {"description": "Scraper payload.", "body": {"$ref": "#/schemas/Scraper"}}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Scraper not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "createScraper", "method": "POST", "path": "/scrapers", "authenticated": true, "description": "Create a scraper configuration.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "body": { "type": "object", "required_fields": [], "fields": { "params": {"type": "string", "nullable": true, "description": "Serialized parameter payload."}, "frequency": {"type": "string", "nullable": true, "description": "Execution cadence descriptor."}, "task_name": {"type": "string", "nullable": true, "description": "Linked task name."}, "property_types": {"type": "string", "nullable": true, "description": "Comma-separated property types."}, "last_seen_days": {"type": "integer", "nullable": true, "description": "Days since listing last seen."}, "first_seen_days": {"type": "integer", "nullable": true, "description": "Days since listing first seen."}, "page_size": {"type": "integer", "nullable": true, "description": "Fetch size per page."}, "max_pages": {"type": "integer", "nullable": true, "description": "Maximum crawl pages."}, "enabled": {"type": "integer", "nullable": true, "description": "Activation flag (1 or 0)."}, "enrich_llm": {"type": "integer", "nullable": true, "description": "LLM enrichment toggle (1 or 0)."}, "only_match": {"type": "integer", "nullable": true, "description": "Strict matching toggle (1 or 0)."} } } }, "responses": { "201": {"description": "Scraper created.", "body": {"$ref": "#/schemas/Scraper"}}, "400": {"description": "Validation failure."}, "401": {"description": "Missing or invalid bearer token."}, "409": {"description": "Constraint violation (e.g., duplicate id)."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "updateScraper", "method": "PUT", "path": "/scrapers/", "authenticated": true, "description": "Update mutable fields on a scraper configuration.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "scraper_id", "type": "string", "required": true, "description": "Scraper identifier."} ], "body": { "type": "object", "allowed_fields": ["params", "frequency", "task_name", "property_types", "last_seen_days", "first_seen_days", "page_size", "max_pages", "enabled", "enrich_llm", "only_match"], "min_fields": 1, "fields": { "params": {"type": "string", "nullable": true, "description": "Updated serialized parameters."}, "frequency": {"type": "string", "nullable": true, "description": "Updated cadence descriptor."}, "task_name": {"type": "string", "nullable": true, "description": "Updated task name."}, "property_types": {"type": "string", "nullable": true, "description": "Updated property type list."}, "last_seen_days": {"type": "integer", "nullable": true, "description": "Updated last seen days."}, "first_seen_days": {"type": "integer", "nullable": true, "description": "Updated first seen days."}, "page_size": {"type": "integer", "nullable": true, "description": "Updated page size."}, "max_pages": {"type": "integer", "nullable": true, "description": "Updated max pages."}, "enabled": {"type": "integer", "nullable": true, "description": "Updated enabled flag."}, "enrich_llm": {"type": "integer", "nullable": true, "description": "Updated enrichment flag."}, "only_match": {"type": "integer", "nullable": true, "description": "Updated only match flag."} } } }, "responses": { "200": {"description": "Scraper updated.", "body": {"$ref": "#/schemas/Scraper"}}, "400": {"description": "No updatable fields provided or invalid data."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Scraper not found."}, "409": {"description": "Constraint violation."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "deleteScraper", "method": "DELETE", "path": "/scrapers/", "authenticated": true, "description": "Delete a scraper configuration.", "request": { "headers": { "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "path_params": [ {"name": "scraper_id", "type": "string", "required": true, "description": "Scraper identifier."} ] }, "responses": { "204": {"description": "Scraper deleted. Empty body."}, "401": {"description": "Missing or invalid bearer token."}, "404": {"description": "Scraper not found."}, "503": {"description": "Database unavailable."} } }, { "operation_id": "countScraperProperties", "method": "POST", "path": "/scrapers/count", "authenticated": true, "description": "Return the Fluximmo property count derived from a scraper configuration.", "request": { "headers": { "Content-Type": {"type": "string", "required": true, "example": "application/json"}, "Authorization": {"type": "string", "required": true, "description": "Bearer "} }, "body": { "type": "object", "required_fields": ["params"], "fields": { "params": {"type": "object", "nullable": false, "description": "Base Fluximmo search filters. Accepts a JSON object or string-encoded JSON."}, "first_seen_days": {"type": "integer", "nullable": true, "description": "Days before now for firstSeenAt.min."}, "last_seen_days": {"type": "integer", "nullable": true, "description": "Days before now for lastSeenAt.max."} } } }, "responses": { "200": {"description": "Count retrieved.", "body": {"type": "object", "fields": {"count": {"type": "integer", "nullable": false}}}}, "400": {"description": "Validation failure."}, "401": {"description": "Missing or invalid bearer token."}, "502": {"description": "Fluximmo upstream error."} } } ] }