{ "api_meta": { "name": "Managinator Backend API", "overview": "Token-gated 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." } }, "authentication": { "strategy": "session_token", "login_endpoint": "/auth/login", "session_duration_seconds": 86400, "header": "X-Session-Token", "credential_source": "Clients must send the server-side AUTH_TOKEN value in the login body to receive a session token.", "notes": [ "All endpoints except POST /auth/login require a valid X-Session-Token header.", "Session tokens expire 24 hours after issuance; expired tokens trigger 401 responses and must be refreshed via /auth/login." ] }, "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": { "Session": { "description": "Session token issued after successful login.", "fields": [ { "name": "session_token", "type": "string", "nullable": false, "read_only": true, "description": "Opaque token to supply via X-Session-Token header." }, { "name": "expires_at", "type": "datetime", "nullable": false, "read_only": true, "description": "UTC expiry timestamp in ISO 8601 format with trailing Z." } ] }, "Profile": { "description": "Investment profile descriptor persisted in users_investmentprofile.", "fields": [ { "name": "profile_id", "type": "uuid", "nullable": false, "read_only": false, "description": "Primary key. Optional on create; autogenerated when omitted." }, { "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": "string", "nullable": false, "read_only": false, "description": "Primary key identifier." }, { "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": "login", "method": "POST", "path": "/auth/login", "authenticated": false, "description": "Exchange the static AUTH_TOKEN for a time-limited session token.", "request": { "headers": { "Content-Type": { "type": "string", "required": true, "example": "application/json" } }, "body": { "type": "object", "required_fields": ["token"], "fields": { "token": { "type": "string", "nullable": false, "description": "Server-configured AUTH_TOKEN value." } } } }, "responses": { "201": { "description": "Session token issued.", "body": { "$ref": "#/schemas/Session" } }, "400": { "description": "Token missing." }, "401": { "description": "Submitted token invalid." } } }, { "operation_id": "listProfiles", "method": "GET", "path": "/profiles", "authenticated": true, "description": "List all investment profiles ordered by most recent creation.", "request": { "headers": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [], "query_params": [], "body": null }, "responses": { "200": { "description": "Array of profiles.", "body": { "type": "array", "items": { "$ref": "#/schemas/Profile" } } }, "401": { "description": "Missing or invalid session." }, "503": { "description": "Database unavailable." } } }, { "operation_id": "getProfile", "method": "GET", "path": "/profiles/", "authenticated": true, "description": "Retrieve a single profile by UUID.", "request": { "headers": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "profile_id", "type": "uuid", "required": true, "description": "Profile identifier." } ], "query_params": [], "body": null }, "responses": { "200": { "description": "Profile payload.", "body": { "$ref": "#/schemas/Profile" } }, "401": { "description": "Missing or invalid session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "body": { "type": "object", "required_fields": ["name", "criteria"], "fields": { "profile_id": { "type": "uuid", "nullable": false, "description": "Optional explicit UUID. Auto-generated when omitted." }, "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 session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "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 session." }, "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": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "profile_id", "type": "uuid", "required": true, "description": "Profile identifier." } ], "body": null }, "responses": { "204": { "description": "Profile deleted. Empty body." }, "401": { "description": "Missing or invalid session." }, "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": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "body": null }, "responses": { "200": { "description": "Array of users.", "body": { "type": "array", "items": { "$ref": "#/schemas/User" } } }, "401": { "description": "Missing or invalid session." }, "503": { "description": "Database unavailable." } } }, { "operation_id": "getUser", "method": "GET", "path": "/users/", "authenticated": true, "description": "Retrieve a user by numeric id.", "request": { "headers": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "user_id", "type": "integer", "required": true, "description": "User primary key." } ], "body": null }, "responses": { "200": { "description": "User payload.", "body": { "$ref": "#/schemas/User" } }, "401": { "description": "Missing or invalid session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "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 session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "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 session." }, "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": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "user_id", "type": "integer", "required": true, "description": "User primary key." } ], "body": null }, "responses": { "204": { "description": "User deleted. Empty body." }, "401": { "description": "Missing or invalid session." }, "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": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "body": null }, "responses": { "200": { "description": "Array of scrapers.", "body": { "type": "array", "items": { "$ref": "#/schemas/Scraper" } } }, "401": { "description": "Missing or invalid session." }, "503": { "description": "Database unavailable." } } }, { "operation_id": "getScraper", "method": "GET", "path": "/scrapers/", "authenticated": true, "description": "Retrieve a single scraper by text id.", "request": { "headers": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "scraper_id", "type": "string", "required": true, "description": "Scraper identifier." } ], "body": null }, "responses": { "200": { "description": "Scraper payload.", "body": { "$ref": "#/schemas/Scraper" } }, "401": { "description": "Missing or invalid session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "body": { "type": "object", "required_fields": ["id"], "fields": { "id": { "type": "string", "nullable": false, "description": "Scraper primary key." }, "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 session." }, "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" }, "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "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 session." }, "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": { "X-Session-Token": { "type": "string", "required": true, "description": "Session token from /auth/login." } }, "path_params": [ { "name": "scraper_id", "type": "string", "required": true, "description": "Scraper identifier." } ], "body": null }, "responses": { "204": { "description": "Scraper deleted. Empty body." }, "401": { "description": "Missing or invalid session." }, "404": { "description": "Scraper not found." }, "503": { "description": "Database unavailable." } } } ] }