{
  "openapi": "3.0.3",
  "info": {
    "title": "Affiliate Aggregator — Merchant API",
    "version": "v1",
    "description": "Signed merchant API for the Nepal affiliate aggregator. Authentication is API key + HMAC-SHA256: send X-API-Key, X-Timestamp (Unix seconds) and X-Signature (lowercase hex HMAC-SHA256 of `${X-Timestamp}.${rawRequestBody}` using your signing secret). The secret is shown ONCE at key creation and is never sent in a request. Timestamps must be within ±300s; reused signatures are rejected (replay protection). Writes accept an optional X-Idempotency-Key for safe retries. This spec documents only the merchant-facing v1 endpoints — admin/dashboard endpoints are intentionally excluded.",
    "contact": { "name": "Developer support" }
  },
  "servers": [
    { "url": "http://localhost:4100/api", "description": "Local development" },
    { "url": "https://api.your-domain.com/api", "description": "Production (replace with your host)" }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "tags": [
    { "name": "Products" },
    { "name": "Campaigns" },
    { "name": "Conversions" },
    { "name": "Order status" },
    { "name": "Webhooks" }
  ],
  "paths": {
    "/v1/products/import": {
      "post": {
        "tags": ["Products"], "summary": "Import products (idempotent)",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/RequestId" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductsPayload" } } } },
        "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportSummary" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "429": { "$ref": "#/components/responses/RateLimited" } }
      }
    },
    "/v1/products/upsert": {
      "post": {
        "tags": ["Products"], "summary": "Upsert products by externalProductId (idempotent)",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductsPayload" } } } },
        "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportSummary" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/products": {
      "get": {
        "tags": ["Products"], "summary": "List products",
        "parameters": [
          { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" },
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } },
          { "name": "pageSize", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["ACTIVE", "INACTIVE", "OUT_OF_STOCK", "ARCHIVED"] } },
          { "name": "category", "in": "query", "schema": { "type": "string" } },
          { "name": "search", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/products/{id}": {
      "get": {
        "tags": ["Products"], "summary": "Get a product",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }],
        "responses": { "200": { "description": "OK" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" } }
      },
      "patch": {
        "tags": ["Products"], "summary": "Update a product",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductUpdate" } } } },
        "responses": { "200": { "description": "OK" }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/v1/products/archive": {
      "post": {
        "tags": ["Products"], "summary": "Archive products",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["productIds"], "properties": { "productIds": { "type": "array", "items": { "type": "string" } } } } } } },
        "responses": { "200": { "description": "OK" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/campaigns": {
      "post": {
        "tags": ["Campaigns"], "summary": "Create a campaign (idempotent)",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CampaignCreate" } } } },
        "responses": { "201": { "description": "Created" }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      },
      "get": {
        "tags": ["Campaigns"], "summary": "List campaigns",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "name": "page", "in": "query", "schema": { "type": "integer" } }, { "name": "pageSize", "in": "query", "schema": { "type": "integer", "maximum": 200 } }, { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["DRAFT", "ACTIVE", "PAUSED", "EXPIRED", "ARCHIVED"] } }],
        "responses": { "200": { "description": "OK" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/campaigns/{id}": {
      "get": { "tags": ["Campaigns"], "summary": "Get a campaign", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }], "responses": { "200": { "description": "OK" }, "404": { "$ref": "#/components/responses/NotFound" } } },
      "patch": { "tags": ["Campaigns"], "summary": "Update a campaign", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CampaignCreate" } } } }, "responses": { "200": { "description": "OK" }, "404": { "$ref": "#/components/responses/NotFound" } } }
    },
    "/v1/campaigns/{id}/pause": { "post": { "tags": ["Campaigns"], "summary": "Pause a campaign", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }], "responses": { "200": { "description": "OK" } } } },
    "/v1/campaigns/{id}/activate": { "post": { "tags": ["Campaigns"], "summary": "Activate a campaign", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }, { "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }], "responses": { "200": { "description": "OK" } } } },
    "/v1/conversions": {
      "post": {
        "tags": ["Conversions"], "summary": "Report a conversion (idempotent)",
        "description": "Send the captured click_id for accurate attribution. A duplicate (merchant + externalOrderId) returns 202 with status DUPLICATE and creates no second commission.",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Conversion" } } } },
        "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AcceptedEvent" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/order-status": {
      "post": {
        "tags": ["Order status"], "summary": "Update order status (idempotent)",
        "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderStatus" } } } },
        "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AcceptedEvent" } } } }, "400": { "$ref": "#/components/responses/ValidationError" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/v1/webhooks/order-created": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order created", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookAccepted" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" } } } },
    "/v1/webhooks/order-paid": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order paid", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/order-delivered": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order delivered", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/order-cancelled": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order cancelled", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/order-returned": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order returned", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/order-refunded": { "post": { "tags": ["Webhooks"], "summary": "Webhook: order refunded", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/product-updated": { "post": { "tags": ["Webhooks"], "summary": "Webhook: product updated", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } },
    "/v1/webhooks/product-stock-updated": { "post": { "tags": ["Webhooks"], "summary": "Webhook: product stock updated", "parameters": [{ "$ref": "#/components/parameters/Timestamp" }, { "$ref": "#/components/parameters/Signature" }, { "$ref": "#/components/parameters/ExternalEventId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookBody" } } } }, "responses": { "202": { "description": "Accepted" } } } }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Public key id (ak_test_… / ak_live_…). Must be accompanied by X-Timestamp and X-Signature (HMAC-SHA256 of `${X-Timestamp}.${rawBody}`)." }
    },
    "parameters": {
      "Timestamp": { "name": "X-Timestamp", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Unix seconds; within ±300s of server time." },
      "Signature": { "name": "X-Signature", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Lowercase hex HMAC-SHA256 of `${X-Timestamp}.${rawBody}`." },
      "RequestId": { "name": "X-Request-ID", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Optional correlation id echoed into your API logs." },
      "IdempotencyKey": { "name": "X-Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Optional safe-retry key (24h replay window)." },
      "ExternalEventId": { "name": "X-External-Event-Id", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Unique merchant event id; the webhook dedup anchor." }
    },
    "responses": {
      "Unauthorized": { "description": "Authentication failed (bad key, signature, timestamp, replay, IP). The errorCode appears in your API request logs.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "ValidationError": { "description": "Body failed validation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Resource not found for your merchant.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Per-minute or per-hour limit exceeded.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "ProductInput": {
        "type": "object", "required": ["externalProductId", "name", "productUrl", "price"],
        "properties": {
          "externalProductId": { "type": "string", "maxLength": 120 },
          "name": { "type": "string", "maxLength": 255 },
          "description": { "type": "string" },
          "productUrl": { "type": "string", "format": "uri", "description": "Must be on your approved domain." },
          "imageUrl": { "type": "string", "format": "uri" },
          "category": { "type": "string" },
          "brand": { "type": "string" },
          "price": { "type": "string", "description": "Decimal-safe money string, e.g. \"2999.00\"." },
          "discountPrice": { "type": "string" },
          "currency": { "type": "string", "example": "NPR" },
          "stockStatus": { "type": "string" },
          "commissionType": { "type": "string", "enum": ["PERCENTAGE", "FIXED"] },
          "commissionValue": { "type": "string" },
          "campaignId": { "type": "string" }
        }
      },
      "ProductsPayload": { "type": "object", "required": ["products"], "properties": { "products": { "type": "array", "maxItems": 5000, "items": { "$ref": "#/components/schemas/ProductInput" } } } },
      "ProductUpdate": { "type": "object", "properties": { "name": { "type": "string" }, "price": { "type": "string" }, "discountPrice": { "type": "string" }, "stockStatus": { "type": "string" }, "commissionType": { "type": "string", "enum": ["PERCENTAGE", "FIXED"] }, "commissionValue": { "type": "string" }, "status": { "type": "string", "enum": ["ACTIVE", "INACTIVE", "OUT_OF_STOCK", "ARCHIVED"] }, "campaignId": { "type": "string" } } },
      "ProductList": { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "object" } }, "page": { "type": "integer" }, "pageSize": { "type": "integer" }, "total": { "type": "integer" } } },
      "ImportSummary": { "type": "object", "properties": { "received": { "type": "integer" }, "created": { "type": "integer" }, "updated": { "type": "integer" }, "failed": { "type": "integer" } } },
      "CampaignCreate": {
        "type": "object", "required": ["title"],
        "properties": {
          "title": { "type": "string", "maxLength": 160 },
          "description": { "type": "string" },
          "startDate": { "type": "string" },
          "endDate": { "type": "string" },
          "defaultCommissionType": { "type": "string", "enum": ["PERCENTAGE", "FIXED"] },
          "defaultCommissionValue": { "type": "string" },
          "cookieDurationDays": { "type": "integer", "minimum": 0, "maximum": 365 },
          "allowedTrafficSources": { "type": "array", "items": { "type": "string" } },
          "termsAndConditions": { "type": "string" },
          "approvalRequired": { "type": "boolean" }
        }
      },
      "Conversion": {
        "type": "object", "required": ["externalOrderId", "orderAmount"],
        "properties": {
          "clickId": { "type": "string", "description": "The affiliate_click_id you captured from the redirect. Drives attribution." },
          "externalOrderId": { "type": "string", "maxLength": 160, "description": "Unique per merchant — duplicates are ignored (status DUPLICATE)." },
          "externalProductId": { "type": "string" },
          "orderAmount": { "type": "string", "description": "Decimal-safe money string." },
          "currency": { "type": "string", "example": "NPR" },
          "orderStatus": { "type": "string", "enum": ["pending", "confirmed", "delivered", "cancelled", "returned", "refunded"] },
          "orderedAt": { "type": "string", "format": "date-time" },
          "couponCode": { "type": "string" },
          "metadata": { "type": "object" }
        }
      },
      "OrderStatus": {
        "type": "object", "required": ["externalOrderId", "status"],
        "properties": {
          "externalOrderId": { "type": "string" },
          "clickId": { "type": "string" },
          "status": { "type": "string", "enum": ["pending", "confirmed", "paid", "delivered", "cancelled", "returned", "refunded", "commission_approved", "commission_rejected"] },
          "statusUpdatedAt": { "type": "string", "format": "date-time" },
          "reason": { "type": "string" },
          "metadata": { "type": "object" }
        }
      },
      "WebhookBody": { "type": "object", "description": "Flexible JSON (snake_case or camelCase). Include external_order_id and (for order-created) click_id + order_amount.", "additionalProperties": true },
      "AcceptedEvent": { "type": "object", "properties": { "eventId": { "type": "string" }, "status": { "type": "string", "enum": ["RECEIVED", "DUPLICATE"] }, "duplicate": { "type": "boolean" }, "conversionId": { "type": "string" } } },
      "WebhookAccepted": { "type": "object", "properties": { "webhookEventId": { "type": "string" }, "status": { "type": "string", "enum": ["QUEUED", "DUPLICATE"] }, "duplicate": { "type": "boolean" } } },
      "Error": { "type": "object", "properties": { "statusCode": { "type": "integer" }, "message": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] }, "error": { "type": "string" } } }
    }
  }
}
