{
  "openapi": "3.0.0",
  "info": {
    "title": "art-swipe-api",
    "version": "0.0.1",
    "description": "apps",
    "contact": {
      "name": "Lemerick",
      "email": "emerick.lafortune@footovision.com"
    }
  },
  "paths": {
    "/admin/jobs/{id}/retry": {
      "post": {
        "x-controller-name": "AdminWorkflowController",
        "x-operation-name": "retryJob",
        "tags": [
          "AdminWorkflowController"
        ],
        "responses": {
          "200": {
            "description": "Retry a failed/dead_letter job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "jobId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "AdminWorkflowController.retryJob"
      }
    },
    "/admin/scraping/run": {
      "post": {
        "x-controller-name": "AdminScrapingController",
        "x-operation-name": "startRun",
        "tags": [
          "AdminScrapingController"
        ],
        "responses": {
          "200": {
            "description": "Scraping run started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "success",
                    "runId",
                    "scrapingRunId"
                  ],
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "runId": {
                      "type": "string",
                      "description": "Apify actor run ID"
                    },
                    "scrapingRunId": {
                      "type": "string",
                      "description": "Internal scraping_run row ID (UUID)"
                    }
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "handles"
                ],
                "properties": {
                  "handles": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Instagram handles to scrape (@ prefix optional)"
                  },
                  "hashtags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Hashtags to scrape (# prefix optional)"
                  },
                  "postsLimit": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 500,
                    "description": "Max posts per handle/hashtag (default: 30)"
                  }
                }
              }
            }
          },
          "required": true
        },
        "operationId": "AdminScrapingController.startRun"
      }
    },
    "/admin/scraping/runs/{id}": {
      "get": {
        "x-controller-name": "AdminScrapingController",
        "x-operation-name": "getRun",
        "tags": [
          "AdminScrapingController"
        ],
        "responses": {
          "200": {
            "description": "Scraping run detail",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "actorId": {
                      "type": "string"
                    },
                    "inputJson": {
                      "type": "object"
                    },
                    "status": {
                      "type": "string"
                    },
                    "startedAt": {
                      "type": "string"
                    },
                    "endedAt": {
                      "type": "string"
                    },
                    "artworksInserted": {
                      "type": "number"
                    },
                    "artistsInserted": {
                      "type": "number"
                    },
                    "artistsSkipped": {
                      "type": "number"
                    },
                    "error": {
                      "type": "string"
                    },
                    "createdAt": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "AdminScrapingController.getRun"
      }
    },
    "/admin/scraping/runs": {
      "get": {
        "x-controller-name": "AdminScrapingController",
        "x-operation-name": "listRuns",
        "tags": [
          "AdminScrapingController"
        ],
        "responses": {
          "200": {
            "description": "List of scraping runs (most recent first)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "actorId": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string"
                      },
                      "startedAt": {
                        "type": "string"
                      },
                      "endedAt": {
                        "type": "string"
                      },
                      "artworksInserted": {
                        "type": "number"
                      },
                      "artistsInserted": {
                        "type": "number"
                      },
                      "artistsSkipped": {
                        "type": "number"
                      },
                      "error": {
                        "type": "string"
                      },
                      "createdAt": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "operationId": "AdminScrapingController.listRuns"
      }
    },
    "/admin/workflows/{id}/cancel": {
      "post": {
        "x-controller-name": "AdminWorkflowController",
        "x-operation-name": "cancelWorkflow",
        "tags": [
          "AdminWorkflowController"
        ],
        "responses": {
          "200": {
            "description": "Cancel workflow and pending jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "AdminWorkflowController.cancelWorkflow"
      }
    },
    "/admin/workflows/{id}": {
      "get": {
        "x-controller-name": "AdminWorkflowController",
        "x-operation-name": "getWorkflow",
        "tags": [
          "AdminWorkflowController"
        ],
        "responses": {
          "200": {
            "description": "Workflow with its jobs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "AdminWorkflowController.getWorkflow"
      }
    },
    "/admin/workflows": {
      "get": {
        "x-controller-name": "AdminWorkflowController",
        "x-operation-name": "listWorkflows",
        "tags": [
          "AdminWorkflowController"
        ],
        "responses": {
          "200": {
            "description": "List recent workflows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "operationId": "AdminWorkflowController.listWorkflows"
      }
    },
    "/animate/{id}": {
      "get": {
        "x-controller-name": "AnimationController",
        "x-operation-name": "getJobStatus",
        "tags": [
          "AnimationController"
        ],
        "responses": {
          "200": {
            "description": "Get animation job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnimationJobResponse"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "AnimationController.getJobStatus"
      }
    },
    "/animate": {
      "post": {
        "x-controller-name": "AnimationController",
        "x-operation-name": "animate",
        "tags": [
          "AnimationController"
        ],
        "responses": {
          "200": {
            "description": "Animation generation job queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnimateResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnimateRequestBody"
              }
            }
          }
        },
        "operationId": "AnimationController.animate"
      }
    },
    "/artists/{artistId}/artworks": {
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "findArtworksByArtist",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Array of Artwork model instances for an artist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Artwork"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "artistId",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.findArtworksByArtist"
      }
    },
    "/artists/{idOrSlug}/claim": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "claimArtist",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Claim artist profile with Instagram OAuth",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "instagramHandle": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "instagramCode",
                  "state"
                ],
                "properties": {
                  "instagramCode": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "x-parameter-index": 1
        },
        "operationId": "ArtistController.claimArtist"
      }
    },
    "/artists/{idOrSlug}/claim-intent": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "claimIntent",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Initiates Instagram OAuth claim flow. Returns a signed state and authorization URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authorizationUrl": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.claimIntent"
      }
    },
    "/artists/{idOrSlug}/claim-status": {
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "getClaimStatus",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Get claim status for artist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "instagramHandle": {
                      "type": "string"
                    },
                    "claimedAt": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.getClaimStatus"
      }
    },
    "/artists/{idOrSlug}/connect-onboarding": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "createConnectOnboarding",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Stripe Connect onboarding URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectOnboardingResponse"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.createConnectOnboarding"
      }
    },
    "/artists/{idOrSlug}/connect-status": {
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "getConnectStatus",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Stripe Connect account status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connected": {
                      "type": "boolean"
                    },
                    "chargesEnabled": {
                      "type": "boolean"
                    },
                    "payoutsEnabled": {
                      "type": "boolean"
                    },
                    "detailsSubmitted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.getConnectStatus"
      }
    },
    "/artists/{idOrSlug}/matches": {
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "findMatches",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Find matching artists using embeddings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mode": {
                      "type": "string"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "artistId": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "avatarUrl": {
                            "type": "string"
                          },
                          "bio": {
                            "type": "string"
                          },
                          "similarity": {
                            "type": "number"
                          },
                          "reasons": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "visualWeight",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "textWeight",
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "operationId": "ArtistController.findMatches"
      }
    },
    "/artists/{idOrSlug}/recompute-embeddings": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "recomputeEmbeddings",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Recompute artist embeddings from artworks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "artworksCount": {
                      "type": "number"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.recomputeEmbeddings"
      }
    },
    "/artists/{idOrSlug}/reject": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "rejectArtist",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Reject artist profile claim (admin only)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "x-parameter-index": 1
        },
        "operationId": "ArtistController.rejectArtist"
      }
    },
    "/artists/{idOrSlug}/verify": {
      "post": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "verifyArtist",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Verify artist profile claim (admin only)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.verifyArtist"
      }
    },
    "/artists/{idOrSlug}": {
      "patch": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "update",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Updated Artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artist"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArtistPatchBody"
              }
            }
          },
          "x-parameter-index": 1
        },
        "operationId": "ArtistController.update"
      },
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "findByIdOrSlug",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Artist model instance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artist"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "idOrSlug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtistController.findByIdOrSlug"
      }
    },
    "/artists": {
      "get": {
        "x-controller-name": "ArtistController",
        "x-operation-name": "findAll",
        "tags": [
          "ArtistController"
        ],
        "responses": {
          "200": {
            "description": "Array of Artist model instances",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Artist"
                  }
                }
              }
            }
          }
        },
        "operationId": "ArtistController.findAll"
      }
    },
    "/artworks/artist/{artistId}": {
      "get": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "findByArtistId",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "200": {
            "description": "Array of Artwork model instances for an artist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Artwork"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "artistId",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtworkController.findByArtistId"
      }
    },
    "/artworks/{id}/image": {
      "get": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "serveImage",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "302": {
            "description": "Found",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Redirect to image URL"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Artwork not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtworkController.serveImage"
      }
    },
    "/artworks/{id}": {
      "patch": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "updateById",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "200": {
            "description": "Updated Artwork",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artwork"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArtworkUpdateBody"
              }
            }
          },
          "x-parameter-index": 1
        },
        "operationId": "ArtworkController.updateById"
      },
      "get": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "findById",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "200": {
            "description": "Artwork model instance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artwork"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtworkController.findById"
      },
      "delete": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "deleteById",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Artwork deleted"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "ArtworkController.deleteById"
      }
    },
    "/artworks": {
      "post": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "create",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "200": {
            "description": "Created Artwork",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artwork"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ArtworkCreateBody"
              }
            }
          }
        },
        "operationId": "ArtworkController.create"
      },
      "get": {
        "x-controller-name": "ArtworkController",
        "x-operation-name": "findAll",
        "tags": [
          "ArtworkController"
        ],
        "responses": {
          "200": {
            "description": "Array of Artwork model instances",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Artwork"
                  }
                }
              }
            }
          }
        },
        "operationId": "ArtworkController.findAll"
      }
    },
    "/credits/purchase": {
      "post": {
        "x-controller-name": "CreditsController",
        "x-operation-name": "createPurchaseSession",
        "tags": [
          "CreditsController"
        ],
        "responses": {
          "200": {
            "description": "Created Stripe Checkout session for credits purchase",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditsPurchaseResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditsPurchaseBody"
              }
            }
          }
        },
        "operationId": "CreditsController.createPurchaseSession"
      }
    },
    "/credits/session/{sessionId}": {
      "get": {
        "x-controller-name": "CreditsController",
        "x-operation-name": "getSession",
        "tags": [
          "CreditsController"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Get Stripe checkout session details"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "CreditsController.getSession"
      }
    },
    "/credits/webhook/stripe": {
      "post": {
        "x-controller-name": "CreditsController",
        "x-operation-name": "webhookStripe",
        "tags": [
          "CreditsController"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Stripe webhook handler for credits"
                }
              }
            }
          }
        },
        "operationId": "CreditsController.webhookStripe"
      }
    },
    "/credits": {
      "get": {
        "x-controller-name": "CreditsController",
        "x-operation-name": "getCredits",
        "tags": [
          "CreditsController"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Get user credits balance and recent transactions"
                }
              }
            }
          }
        },
        "operationId": "CreditsController.getCredits"
      }
    },
    "/donations/artist/{artistId}": {
      "get": {
        "x-controller-name": "DonationController",
        "x-operation-name": "findByArtist",
        "tags": [
          "DonationController"
        ],
        "responses": {
          "200": {
            "description": "Donations for an artist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Donation"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "artistId",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "DonationController.findByArtist"
      }
    },
    "/donations/webhook/stripe": {
      "post": {
        "x-controller-name": "DonationController",
        "x-operation-name": "webhookStripe",
        "tags": [
          "DonationController"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Stripe webhook handler"
                }
              }
            }
          }
        },
        "operationId": "DonationController.webhookStripe"
      }
    },
    "/donations/webhook/{provider}": {
      "post": {
        "x-controller-name": "DonationController",
        "x-operation-name": "webhook",
        "tags": [
          "DonationController"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Generic webhook endpoint (deprecated, use /webhook/stripe)"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "DonationController.webhook"
      }
    },
    "/donations": {
      "post": {
        "x-controller-name": "DonationController",
        "x-operation-name": "create",
        "tags": [
          "DonationController"
        ],
        "responses": {
          "200": {
            "description": "Created donation (pending) + Stripe Checkout URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DonationCreateResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DonationCreateBody"
              }
            }
          }
        },
        "operationId": "DonationController.create"
      }
    },
    "/ping": {
      "get": {
        "x-controller-name": "PingController",
        "x-operation-name": "ping",
        "tags": [
          "PingController"
        ],
        "responses": {
          "200": {
            "description": "Ping Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PingResponse"
                }
              }
            }
          }
        },
        "operationId": "PingController.ping"
      }
    },
    "/stats/artists/{slug}": {
      "get": {
        "x-controller-name": "StatsController",
        "x-operation-name": "artistStats",
        "tags": [
          "StatsController"
        ],
        "responses": {
          "200": {
            "description": "Aggregated statistics for an artist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtistStats"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "StatsController.artistStats"
      }
    },
    "/stats/artworks/{id}": {
      "get": {
        "x-controller-name": "StatsController",
        "x-operation-name": "artworkStats",
        "tags": [
          "StatsController"
        ],
        "responses": {
          "200": {
            "description": "Viewing statistics for a single artwork",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtworkStats"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "operationId": "StatsController.artworkStats"
      }
    },
    "/stats/vernissage": {
      "get": {
        "x-controller-name": "StatsController",
        "x-operation-name": "vernissage",
        "tags": [
          "StatsController"
        ],
        "responses": {
          "200": {
            "description": "Aggregated vernissage statistics",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VernissageStats"
                }
              }
            }
          }
        },
        "operationId": "StatsController.vernissage"
      }
    },
    "/user/claim-artist": {
      "post": {
        "x-controller-name": "UserController",
        "x-operation-name": "claimArtist",
        "tags": [
          "UserController"
        ],
        "responses": {
          "200": {
            "description": "User linked to artist — returns new JWT with updated role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClaimArtistBody"
              }
            }
          }
        },
        "operationId": "UserController.claimArtist"
      }
    },
    "/user/login": {
      "post": {
        "x-controller-name": "UserController",
        "x-operation-name": "login",
        "tags": [
          "UserController"
        ],
        "responses": {
          "200": {
            "description": "JWT token + user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "operationId": "UserController.login"
      }
    },
    "/user/me": {
      "get": {
        "x-controller-name": "UserController",
        "x-operation-name": "me",
        "tags": [
          "UserController"
        ],
        "responses": {
          "200": {
            "description": "Current authenticated user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicUser"
                }
              }
            }
          }
        },
        "operationId": "UserController.me"
      }
    },
    "/user/register": {
      "post": {
        "x-controller-name": "UserController",
        "x-operation-name": "register",
        "tags": [
          "UserController"
        ],
        "responses": {
          "200": {
            "description": "Registered user + JWT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        },
        "operationId": "UserController.register"
      }
    },
    "/view-events/batch": {
      "post": {
        "x-controller-name": "ViewEventController",
        "x-operation-name": "batch",
        "tags": [
          "ViewEventController"
        ],
        "responses": {
          "200": {
            "description": "Number of view events inserted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ViewEventBatchResponse"
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ViewEventBatchBody"
              }
            }
          }
        },
        "operationId": "ViewEventController.batch"
      }
    },
    "/webhooks/apify": {
      "post": {
        "x-controller-name": "ApifyWebhookController",
        "x-operation-name": "handleApifyWebhook",
        "tags": [
          "ApifyWebhookController"
        ],
        "responses": {
          "200": {
            "description": "Webhook received and processed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "received": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "operationId": "ApifyWebhookController.handleApifyWebhook"
      }
    }
  },
  "servers": [
    {
      "url": "https://api.art.umamee.app"
    }
  ],
  "components": {
    "schemas": {
      "ViewEventBatchResponse": {
        "title": "ViewEventBatchResponse",
        "type": "object",
        "properties": {
          "inserted": {
            "type": "number"
          }
        },
        "required": [
          "inserted"
        ],
        "additionalProperties": false
      },
      "ViewEventInput": {
        "title": "ViewEventInput",
        "type": "object",
        "properties": {
          "artworkId": {
            "type": "string"
          },
          "durationMs": {
            "type": "number"
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "artworkId",
          "durationMs",
          "recordedAt"
        ],
        "additionalProperties": false
      },
      "ViewEventBatchBody": {
        "title": "ViewEventBatchBody",
        "type": "object",
        "properties": {
          "anonymousId": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ViewEventInput"
            }
          }
        },
        "required": [
          "events"
        ],
        "additionalProperties": false
      },
      "PublicUser": {
        "title": "PublicUser",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "artistId": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "email",
          "role"
        ],
        "additionalProperties": false
      },
      "AuthResponse": {
        "title": "AuthResponse",
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "user": {
            "$ref": "#/components/schemas/PublicUser"
          }
        },
        "required": [
          "token",
          "user"
        ],
        "additionalProperties": false
      },
      "RegisterRequest": {
        "title": "RegisterRequest",
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "password"
        ],
        "additionalProperties": false
      },
      "LoginRequest": {
        "title": "LoginRequest",
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "password"
        ],
        "additionalProperties": false
      },
      "ClaimArtistBody": {
        "title": "ClaimArtistBody",
        "type": "object",
        "properties": {
          "artistSlug": {
            "type": "string"
          }
        },
        "required": [
          "artistSlug"
        ],
        "additionalProperties": false
      },
      "TopArtwork": {
        "title": "TopArtwork",
        "type": "object",
        "properties": {
          "artworkId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "totalMs": {
            "type": "number"
          },
          "viewerCount": {
            "type": "number"
          }
        },
        "required": [
          "artworkId",
          "title",
          "totalMs",
          "viewerCount"
        ],
        "additionalProperties": false
      },
      "VernissageStats": {
        "title": "VernissageStats",
        "type": "object",
        "properties": {
          "totalViewMs": {
            "type": "number"
          },
          "uniqueVisitors": {
            "type": "number"
          },
          "totalArtworks": {
            "type": "number"
          },
          "totalArtists": {
            "type": "number"
          },
          "totalDonationsCents": {
            "type": "number"
          },
          "donationCount": {
            "type": "number"
          },
          "topArtworks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TopArtwork"
            }
          }
        },
        "required": [
          "totalViewMs",
          "uniqueVisitors",
          "totalArtworks",
          "totalArtists",
          "totalDonationsCents",
          "donationCount",
          "topArtworks"
        ],
        "additionalProperties": false
      },
      "ArtworkStats": {
        "title": "ArtworkStats",
        "type": "object",
        "properties": {
          "artworkId": {
            "type": "string"
          },
          "totalMs": {
            "type": "number"
          },
          "viewerCount": {
            "type": "number"
          },
          "avgMsPerViewer": {
            "type": "number"
          }
        },
        "required": [
          "artworkId",
          "totalMs",
          "viewerCount",
          "avgMsPerViewer"
        ],
        "additionalProperties": false
      },
      "ArtistStats": {
        "title": "ArtistStats",
        "type": "object",
        "properties": {
          "artistSlug": {
            "type": "string"
          },
          "artistId": {
            "type": "string"
          },
          "totalMs": {
            "type": "number"
          },
          "artworkCount": {
            "type": "number"
          },
          "totalDonationsCents": {
            "type": "number"
          },
          "donationCount": {
            "type": "number"
          }
        },
        "required": [
          "artistSlug",
          "artistId",
          "totalMs",
          "artworkCount",
          "totalDonationsCents",
          "donationCount"
        ],
        "additionalProperties": false
      },
      "Donation": {
        "title": "Donation",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "anonymousId": {
            "type": "string"
          },
          "artistId": {
            "type": "string"
          },
          "amountCents": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "succeeded",
              "failed",
              "refunded"
            ]
          },
          "provider": {
            "type": "string"
          },
          "providerRef": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "artistId",
          "amountCents"
        ],
        "additionalProperties": false
      },
      "DonationCreateResponse": {
        "title": "DonationCreateResponse",
        "type": "object",
        "properties": {
          "donation": {
            "$ref": "#/components/schemas/Donation"
          },
          "checkoutUrl": {
            "type": "string"
          },
          "sessionId": {
            "type": "string"
          }
        },
        "required": [
          "donation",
          "checkoutUrl"
        ],
        "additionalProperties": false
      },
      "DonationCreateBody": {
        "title": "DonationCreateBody",
        "type": "object",
        "properties": {
          "artistId": {
            "type": "string"
          },
          "amountCents": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "anonymousId": {
            "type": "string"
          }
        },
        "required": [
          "artistId",
          "amountCents"
        ],
        "additionalProperties": false
      },
      "CreditsPurchaseResponse": {
        "title": "CreditsPurchaseResponse",
        "type": "object",
        "properties": {
          "checkoutUrl": {
            "type": "string"
          }
        },
        "required": [
          "checkoutUrl"
        ],
        "additionalProperties": false
      },
      "CreditsPurchaseBody": {
        "title": "CreditsPurchaseBody",
        "type": "object",
        "properties": {
          "packId": {
            "type": "string"
          }
        },
        "required": [
          "packId"
        ],
        "additionalProperties": false
      },
      "Artwork": {
        "title": "Artwork",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "artistId": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "year": {
            "type": "number"
          },
          "medium": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "displayOrder": {
            "type": "number"
          },
          "provider": {
            "type": "string",
            "enum": [
              "local",
              "s3",
              "external",
              "instagram"
            ]
          },
          "sourceUrl": {
            "type": "string"
          },
          "spotifyTrackUrl": {
            "type": "string",
            "format": "uri"
          },
          "embedding": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "dirty": {
            "type": "boolean"
          }
        },
        "required": [
          "artistId",
          "title",
          "imageUrl",
          "provider"
        ],
        "additionalProperties": false
      },
      "ArtworkCreateBody": {
        "title": "ArtworkCreateBody",
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "year": {
            "type": "number"
          },
          "medium": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "local",
              "s3",
              "external",
              "instagram"
            ]
          },
          "sourceUrl": {
            "type": "string"
          },
          "spotifyTrackUrl": {
            "type": "string"
          },
          "displayOrder": {
            "type": "number"
          }
        },
        "required": [
          "title",
          "imageUrl"
        ],
        "additionalProperties": false
      },
      "ArtworkUpdateBody": {
        "title": "ArtworkUpdateBody",
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "year": {
            "type": "number"
          },
          "medium": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "local",
              "s3",
              "external",
              "instagram"
            ]
          },
          "sourceUrl": {
            "type": "string"
          },
          "spotifyTrackUrl": {
            "type": "string"
          },
          "displayOrder": {
            "type": "number"
          }
        },
        "additionalProperties": false
      },
      "Artist": {
        "title": "Artist",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "coverUrl": {
            "type": "string"
          },
          "avatarUrl": {
            "type": "string"
          },
          "donationUrl": {
            "type": "string"
          },
          "visualEmbedding": {
            "type": "string"
          },
          "textEmbedding": {
            "type": "string"
          },
          "stripeAccountId": {
            "type": "string"
          },
          "stripeAccountStatus": {
            "type": "string"
          },
          "stripeOnboardingComplete": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "claimStatus": {
            "type": "string",
            "enum": [
              "unverified",
              "claimed",
              "verified",
              "rejected"
            ]
          },
          "instagramHandle": {
            "type": "string"
          },
          "instagramUserId": {
            "type": "string"
          },
          "claimedByUserId": {
            "type": "string"
          },
          "claimedAt": {
            "type": "string",
            "format": "date-time"
          },
          "scrapedAt": {
            "type": "string",
            "format": "date-time"
          },
          "rejectionReason": {
            "type": "string"
          },
          "scrapeSource": {
            "type": "string"
          }
        },
        "description": "{\"indexInfo\":{\"slug\":{\"unique\":true}}}",
        "required": [
          "slug",
          "name"
        ],
        "additionalProperties": false
      },
      "ArtistPatchBody": {
        "title": "ArtistPatchBody",
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "coverUrl": {
            "type": "string"
          },
          "avatarUrl": {
            "type": "string"
          },
          "donationUrl": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ConnectOnboardingResponse": {
        "title": "ConnectOnboardingResponse",
        "type": "object",
        "properties": {
          "onboardingUrl": {
            "type": "string"
          },
          "accountId": {
            "type": "string"
          },
          "alreadyOnboarded": {
            "type": "boolean"
          }
        },
        "required": [
          "onboardingUrl"
        ],
        "additionalProperties": false
      },
      "AnimateResponse": {
        "title": "AnimateResponse",
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "jobId",
          "status"
        ],
        "additionalProperties": false
      },
      "AnimateRequestBody": {
        "title": "AnimateRequestBody",
        "type": "object",
        "properties": {
          "artworkId": {
            "type": "string"
          }
        },
        "required": [
          "artworkId"
        ],
        "additionalProperties": false
      },
      "AnimationJobResponse": {
        "title": "AnimationJobResponse",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "artworkId": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "resultUrl": {
            "type": "string"
          },
          "errorMessage": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "status"
        ],
        "additionalProperties": false
      },
      "PingResponse": {
        "type": "object",
        "title": "PingResponse",
        "properties": {
          "greeting": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "properties": {
              "Content-Type": {
                "type": "string"
              }
            },
            "additionalProperties": true
          }
        }
      }
    }
  }
}