{
  "openapi": "3.1.0",
  "info": {
    "title": "Affinsy API",
    "version": "1.0.0",
    "description": "Push order data, trigger Market Basket Analysis or RFM Segmentation reports, and retrieve results programmatically.",
    "contact": {
      "email": "hello@affinsy.com"
    }
  },
  "servers": [
    {
      "url": "https://www.affinsy.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/data/orders": {
      "post": {
        "operationId": "pushOrders",
        "summary": "Push order data",
        "description": "Upload order line items into a dataset. Each request can contain up to 10,000 line items. Data is processed asynchronously — use the returned `import_id` to check status.",
        "tags": ["Data"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["orders"],
                "properties": {
                  "orders": {
                    "type": "array",
                    "description": "Array of orders with line items.",
                    "items": {
                      "type": "object",
                      "required": ["order_id", "line_items"],
                      "properties": {
                        "order_id": { "type": "string", "description": "Unique order identifier." },
                        "customer_id": { "type": "string", "description": "Customer identifier (required for RFM)." },
                        "order_date": { "type": "string", "format": "date-time", "description": "ISO 8601 date." },
                        "line_items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "product_name": { "type": "string" },
                              "product_sku": { "type": "string" },
                              "unit_price": { "type": "number" },
                              "quantity": { "type": "integer" },
                              "custom_fields": {
                                "type": "object",
                                "additionalProperties": { "type": "string" }
                              }
                            }
                          }
                        }
                      }
                    }
                  },
                  "dataset_id": {
                    "type": "string",
                    "default": "default",
                    "description": "Target dataset slug."
                  }
                }
              },
              "example": {
                "dataset_id": "my-store",
                "orders": [
                  {
                    "order_id": "ORD-1001",
                    "customer_id": "CUST-42",
                    "order_date": "2025-03-15T10:30:00Z",
                    "line_items": [
                      { "product_name": "Running Shoes", "unit_price": 129.99, "quantity": 1 },
                      { "product_name": "Sport Socks 3-Pack", "unit_price": 14.99, "quantity": 2 }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Data accepted for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" },
                    "import_id": { "type": "string" },
                    "rows_accepted": { "type": "integer" },
                    "orders_count": { "type": "integer" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "get": {
        "operationId": "listImports",
        "summary": "Check import status",
        "description": "Check the status of a specific import or list recent imports.",
        "tags": ["Data"],
        "parameters": [
          {
            "name": "import_id",
            "in": "query",
            "description": "Specific import job ID to check.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Import status or list of recent imports.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": { "type": "string" },
                    "status": { "type": "string", "enum": ["pending", "processing", "completed", "failed"] },
                    "rows": {
                      "type": "object",
                      "properties": {
                        "total": { "type": "integer" },
                        "inserted": { "type": "integer" },
                        "updated": { "type": "integer" },
                        "failed": { "type": "integer" }
                      }
                    },
                    "created_at": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/reports": {
      "post": {
        "operationId": "generateReport",
        "summary": "Generate a report",
        "description": "Trigger a new MBA or RFM analysis. You can either specify `report_type` + `params` manually, or use a `template` to apply pre-configured settings. The report is processed asynchronously — poll `GET /reports/{id}` for results, or provide a `webhook_url` to be notified on completion.",
        "tags": ["Reports"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "report_type": {
                    "type": "string",
                    "enum": ["MBA", "RFM"],
                    "description": "Type of analysis. Not required if `template` is provided."
                  },
                  "template": {
                    "type": "string",
                    "enum": ["standard_mba", "first_order", "repeat_purchase", "high_value_baskets", "churning_analysis", "standard_rfm", "new_customer_cohort"],
                    "description": "Pre-configured analysis template. Sets report_type, name, customer_scope, scope, and business context automatically. Explicit params override template defaults."
                  },
                  "name": {
                    "type": "string",
                    "description": "Report name. Auto-generated if using a template."
                  },
                  "dataset_id": {
                    "type": "string",
                    "default": "default",
                    "description": "Dataset slug to analyze."
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL to receive a POST when the report completes or fails."
                  },
                  "filters": {
                    "type": "object",
                    "description": "Filter the data before analysis.",
                    "properties": {
                      "dateRange": {
                        "type": "object",
                        "description": "Restrict to orders within a date range.",
                        "properties": {
                          "start": { "type": "string", "format": "date", "description": "Start date (YYYY-MM-DD)." },
                          "end": { "type": "string", "format": "date", "description": "End date (YYYY-MM-DD)." }
                        }
                      },
                      "conditions": {
                        "type": "array",
                        "description": "Row-level filters. Combined with AND logic.",
                        "items": {
                          "type": "object",
                          "required": ["field", "operator", "value"],
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": ["product_name", "customer_id", "order_id", "line_item_value", "quantity"],
                              "description": "Field to filter on. Use `custom:<key>` for custom fields."
                            },
                            "operator": {
                              "type": "string",
                              "enum": ["equals", "not_equals", "contains", "not_contains", "greater_than", "less_than", "between"],
                              "description": "Comparison operator. `contains`/`not_contains` for strings; `greater_than`/`less_than`/`between` for numbers."
                            },
                            "value": {
                              "type": ["string", "number"],
                              "description": "Filter value. For `between`, this is the minimum."
                            },
                            "valueTo": {
                              "type": "number",
                              "description": "Upper bound for `between` operator."
                            }
                          }
                        }
                      }
                    }
                  },
                  "params": {
                    "type": "object",
                    "description": "Analysis parameters. Override template defaults if both are provided.",
                    "properties": {
                      "min_support": { "type": "number", "default": 0.01, "description": "MBA: Minimum support threshold (0-1). Lower values find more rules including weaker ones." },
                      "min_confidence": { "type": "number", "default": 0.1, "description": "MBA: Minimum confidence threshold (0-1). Lower values find more rules." },
                      "scope": { "type": "string", "enum": ["order", "customer"], "default": "order", "description": "MBA: `order` = per-transaction baskets, `customer` = all orders per customer merged into one basket." },
                      "product_identifier": { "type": "string", "enum": ["name", "sku"], "default": "name", "description": "MBA: How products are identified. Use `name` for product titles, `sku` for SKU/variant IDs." },
                      "customer_scope": {
                        "type": "string",
                        "enum": ["all", "first_order", "repeat", "high_value", "churning", "new_90d"],
                        "default": "all",
                        "description": "Filter which customers to include. `all` = everyone, `first_order` = only first purchases, `repeat` = returning customers only, `high_value` = top 20% by spend, `churning` = no order in 90+ days, `new_90d` = acquired in last 90 days."
                      },
                      "context": { "type": "string", "description": "Business context (e.g. store type, goals) passed to AI for better insights. Max 500 chars." }
                    }
                  }
                }
              },
              "examples": {
                "with_template": {
                  "summary": "Using a template",
                  "value": {
                    "template": "high_value_baskets",
                    "dataset_id": "my-store",
                    "webhook_url": "https://your-server.com/hooks/affinsy"
                  }
                },
                "manual_mba": {
                  "summary": "Manual MBA with filters",
                  "value": {
                    "report_type": "MBA",
                    "name": "Q1 2025 Bundle Analysis",
                    "dataset_id": "my-store",
                    "webhook_url": "https://your-server.com/hooks/affinsy",
                    "filters": {
                      "dateRange": { "start": "2025-01-01", "end": "2025-03-31" },
                      "conditions": [
                        { "field": "line_item_value", "operator": "greater_than", "value": 10 }
                      ]
                    },
                    "params": {
                      "min_support": 0.02,
                      "min_confidence": 0.15,
                      "scope": "order",
                      "product_identifier": "name"
                    }
                  }
                },
                "rfm": {
                  "summary": "RFM with business context",
                  "value": {
                    "report_type": "RFM",
                    "name": "March 2025 Segmentation",
                    "dataset_id": "my-store",
                    "params": {
                      "context": "DTC skincare brand, $45 AOV, subscription model. Focus on identifying churn risk."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Report created and processing started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "report_id": { "type": "string" },
                    "status": { "type": "string", "example": "Processing" },
                    "report_type": { "type": "string" },
                    "dataset_id": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": {
            "description": "Monthly report limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" },
                    "limit": { "type": "integer" },
                    "used": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listReports",
        "summary": "List reports",
        "description": "List reports, optionally filtered by dataset or type.",
        "tags": ["Reports"],
        "parameters": [
          { "name": "dataset_id", "in": "query", "schema": { "type": "string" } },
          { "name": "report_type", "in": "query", "schema": { "type": "string", "enum": ["MBA", "RFM"] } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": {
          "200": {
            "description": "List of reports.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "reports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReportSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/reports/{id}": {
      "get": {
        "operationId": "getReport",
        "summary": "Get report results",
        "description": "Fetch a report's status and results. While processing, only status is returned. Once completed, full results including association rules (MBA) or segments (RFM) are included.",
        "tags": ["Reports"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Report ID returned from POST /reports."
          }
        ],
        "responses": {
          "200": {
            "description": "Report with results.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/MBAReport" },
                    { "$ref": "#/components/schemas/RFMReport" }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Report not found." }
        }
      }
    },
    "/datasets": {
      "get": {
        "operationId": "listDatasets",
        "summary": "List datasets",
        "description": "List all datasets for the authenticated user.",
        "tags": ["Datasets"],
        "responses": {
          "200": {
            "description": "List of datasets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "datasets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": { "type": "string" },
                          "name": { "type": "string" },
                          "created_at": { "type": "string", "format": "date-time", "nullable": true }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key with `afn_` prefix. Generate keys in Dashboard → Integrations."
      }
    },
    "schemas": {
      "ReportSummary": {
        "type": "object",
        "properties": {
          "report_id": { "type": "string" },
          "name": { "type": "string" },
          "report_type": { "type": "string", "enum": ["MBA", "RFM"] },
          "dataset_id": { "type": "string" },
          "status": { "type": "string", "enum": ["Processing", "Completed", "Failed", "Completed with warnings"] },
          "fail_reason": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "AssociationRule": {
        "type": "object",
        "properties": {
          "antecedents": { "type": "array", "items": { "type": "string" }, "description": "Products that trigger the rule." },
          "consequents": { "type": "array", "items": { "type": "string" }, "description": "Products predicted by the rule." },
          "support": { "type": "number", "description": "Proportion of orders containing both antecedents and consequents." },
          "confidence": { "type": "number", "description": "Probability of consequent given antecedent." },
          "lift": { "type": "number", "description": "Strength of association vs. random chance. >1 = positive association." },
          "impact_rating": { "type": "string", "enum": ["High", "Medium", "Low"] },
          "impact_analysis": {
            "type": "object",
            "properties": {
              "revenue": { "type": "number" },
              "lift": { "type": "number" },
              "conversion_rate": { "type": "number" },
              "occurrences": { "type": "integer" }
            }
          }
        }
      },
      "RFMSegment": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "description": "Segment name (e.g. Champions, At Risk)." },
          "count": { "type": "integer" },
          "percentage": { "type": "number" },
          "avg_recency": { "type": "number", "description": "Average days since last purchase." },
          "avg_frequency": { "type": "number", "description": "Average purchase count." },
          "avg_monetary": { "type": "number", "description": "Average total spend." },
          "top_products": { "type": "array", "items": { "type": "object" } },
          "ai_observation": { "type": "string" },
          "ai_recommendation": { "type": "string" }
        }
      },
      "MBAReport": {
        "type": "object",
        "properties": {
          "report_id": { "type": "string" },
          "name": { "type": "string" },
          "report_type": { "type": "string", "example": "MBA" },
          "dataset_id": { "type": "string" },
          "status": { "type": "string" },
          "summary": {
            "type": "object",
            "properties": {
              "orders_processed": { "type": "integer" },
              "total_revenue_processed": { "type": "number" },
              "average_order_value": { "type": "number" },
              "average_basket_size": { "type": "number" },
              "association_rules_found": { "type": "integer" }
            }
          },
          "association_rules": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/AssociationRule" }
          },
          "ai_insights": { "type": "object", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "RFMReport": {
        "type": "object",
        "properties": {
          "report_id": { "type": "string" },
          "name": { "type": "string" },
          "report_type": { "type": "string", "example": "RFM" },
          "dataset_id": { "type": "string" },
          "status": { "type": "string" },
          "metrics": { "type": "object", "nullable": true },
          "segments": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/RFMSegment" }
          },
          "ai_insights": { "type": "object", "nullable": true },
          "ai_strategic_insights": { "type": "object", "nullable": true },
          "customer_count": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "description": "Payload sent to your webhook_url when a report completes or fails. Signed with `X-Affinsy-Signature: sha256=<hmac>` header using your API key hash as the secret. Verify with HMAC-SHA256(request_body, your_api_key_hash).",
        "properties": {
          "event": { "type": "string", "enum": ["report.completed", "report.failed"] },
          "report_id": { "type": "string" },
          "report_type": { "type": "string", "enum": ["MBA", "RFM"] },
          "status": { "type": "string" },
          "fail_reason": { "type": "string", "description": "Only present on failure." },
          "timestamp": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request body or parameters.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" } }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" } }
            }
          }
        }
      },
      "Forbidden": {
        "description": "API access requires Max plan or higher.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": { "error": { "type": "string" } }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "reportCompleted": {
      "post": {
        "summary": "Report completed or failed",
        "description": "Sent to the `webhook_url` provided when generating a report.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookPayload" }
            }
          }
        },
        "responses": {
          "200": { "description": "Webhook received." }
        }
      }
    }
  }
}
