managinator/frontend/api-schema.json
2025-10-14 16:11:46 +02:00

1290 lines
35 KiB
JSON

{
"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/<profile_id>",
"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/<profile_id>",
"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/<profile_id>",
"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/<user_id>",
"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/<user_id>",
"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/<user_id>",
"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/<scraper_id>",
"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/<scraper_id>",
"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/<scraper_id>",
"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."
}
}
}
]
}