{
  "openapi": "3.1.0",
  "info": {
    "title": "Reputive public integration API",
    "version": "2026-10-30",
    "description": "Repository-derived API description; version identifies this document, not an API compatibility promise. Custom API and Zapier collection require Pro or Business (including an eligible unexpired trial). Standard connector collection is available from Starter. API keys are created by the business owner in Settings → Integrations and are shown once. No third-party OAuth server or outbound webhook subscription API is implemented. Test POST requests can schedule real messages.",
    "contact": {
      "email": "martin@reputive.cz",
      "url": "https://reputive.cz/partnersky-program"
    }
  },
  "servers": [
    {
      "url": "https://app.reputive.cz"
    }
  ],
  "security": [
    {
      "BearerKey": []
    }
  ],
  "paths": {
    "/api/integrations/collect": {
      "get": {
        "operationId": "checkCollectionConnection",
        "summary": "Check a collection key without adding a customer",
        "description": "Requires collect:write and rejects power-bi keys. Does not check current plan entitlement and does not use the collect rate limiter. A successful check is not a promise that POST will be accepted.",
        "responses": {
          "200": {
            "description": "Connection OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "business": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ]
                    },
                    "connector": {
                      "type": "string"
                    },
                    "message": {
                      "const": "Connection OK"
                    }
                  },
                  "required": [
                    "success",
                    "business",
                    "connector",
                    "message"
                  ]
                },
                "example": {
                  "success": true,
                  "business": {
                    "id": "11111111-1111-4111-8111-111111111111",
                    "name": "Example business"
                  },
                  "connector": "api",
                  "message": "Connection OK"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Key does not allow collection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "collectCustomer",
        "summary": "Collect one customer and schedule the configured review-request sequence",
        "description": "Shared budget with batch: 60 requests per sliding 60 seconds, identified by Bearer key prefix; body-key requests use caller IP. Limiter outages are fail-open. No Idempotency-Key header is implemented. Deduplication is based on existing contact and location, review-written state and campaign cooldown (default 90 days after last send, configurable per customer). Eligible repeat requests cancel pending review-request messages and enqueue again; concurrent retries are not guaranteed exactly-once. Existing opt-outs are not reset. A successful response does not guarantee sending or delivery. Sequence must be enabled, have enabled steps, be included in the plan, and the owner email must be verified. Authentication requires either a Bearer key or a nonempty api_key in the JSON body; the empty security alternative represents that body-key fallback, not general anonymous access.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Customer"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "api_key": {
                        "type": "string",
                        "description": "Optional body fallback for systems unable to set Authorization. A usable Bearer header takes precedence; never expose keys in browser code."
                      }
                    }
                  }
                ]
              },
              "example": {
                "first_name": "Alex",
                "email": "alex@example.com",
                "gdpr_consent": true,
                "source": "api",
                "location_id": "11111111-1111-4111-8111-111111111111",
                "start_delay_hours": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Customer queued or skipped. queued means scheduled, not delivered. Current collection produces queued or skipped; sent is part of the result type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectSuccess"
                },
                "example": {
                  "success": true,
                  "customerId": "22222222-2222-4222-8222-222222222222",
                  "status": "queued"
                }
              }
            }
          },
          "202": {
            "description": "Stored for manual verification, without a queued message. Check receiptId; do not treat as sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingVerification"
                },
                "example": {
                  "success": true,
                  "status": "pending_verification",
                  "receiptId": "33333333-3333-4333-8333-333333333333",
                  "reason": "ambiguous_location",
                  "email": "alex@example.com"
                }
              }
            }
          },
          "400": {
            "description": "Customer validation failed; also legacy WIX_LOCATION_REQUIRED error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong capability, source mismatch, or plan does not include this integration. Plan refusal includes upgradeRequired: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Legacy WIX_CONTACT_ conflict error, if returned by collection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Collect request budget exhausted. No Retry-After header is supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected or collection error. Malformed JSON also currently reaches this response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "BearerKey": []
          },
          {}
        ]
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content; allows Content-Type and Authorization."
          }
        }
      }
    },
    "/api/integrations/collect/batch": {
      "post": {
        "operationId": "collectCustomersBatch",
        "summary": "Collect 1–50 customers",
        "description": "Bearer authentication only. Same customer fields, scopes, plan rules and shared 60 requests/minute budget as single collection; one batch counts as one request. Validation of any item rejects the whole request before collection. Processing is concurrent and non-atomic. HTTP 200 and top-level success true can contain per-item failures. Inspect every result in input order. sent counts queued or sent results, not confirmed delivery; quarantined counts pending_verification results. Retry failed items only and account for non-idempotent enqueue.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customers": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 50,
                    "items": {
                      "$ref": "#/components/schemas/Customer"
                    }
                  }
                },
                "required": [
                  "customers"
                ]
              },
              "example": {
                "customers": [
                  {
                    "first_name": "Alex",
                    "email": "alex@example.com",
                    "gdpr_consent": true,
                    "source": "api",
                    "location_id": "11111111-1111-4111-8111-111111111111",
                    "start_delay_hours": 0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item outcomes, including partial failure.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "sent": {
                      "type": "integer"
                    },
                    "skipped": {
                      "type": "integer"
                    },
                    "quarantined": {
                      "type": "integer"
                    },
                    "errors": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BatchItemResult"
                      }
                    },
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "issues": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "index",
                          "issues"
                        ]
                      }
                    }
                  },
                  "required": [
                    "success",
                    "sent",
                    "skipped",
                    "quarantined",
                    "errors",
                    "results"
                  ]
                },
                "example": {
                  "success": true,
                  "sent": 1,
                  "skipped": 0,
                  "quarantined": 0,
                  "errors": 0,
                  "results": [
                    {
                      "success": true,
                      "customerId": "22222222-2222-4222-8222-222222222222",
                      "status": "queued",
                      "email": "alex@example.com"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Batch validation failed before collection. details lists item paths and validation messages; a bad item rejects the entire batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong capability, source mismatch, or plan does not include this integration. Plan refusal includes upgradeRequired: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Collect request budget exhausted. No Retry-After header is supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected or collection error. Malformed JSON also currently reaches this response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content; allows Content-Type and Authorization."
          }
        }
      }
    },
    "/api/exports/power-bi/v1/businesses/{businessId}/campaign-metrics.csv": {
      "get": {
        "operationId": "exportPowerBiCampaignMetrics",
        "summary": "Read aggregate campaign metrics as CSV",
        "security": [
          {
            "PowerBiBasic": []
          }
        ],
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Must match the authenticated business."
          }
        ],
        "description": "Pro/Business, active business and analytics:read capability on a power-bi key. Fixed 730-day UTC history including today, maximum 10,000 rows. No query filters. CSV UTF-8 with BOM, comma delimiters and CRLF. Nullable IDs and step numbers are empty. Columns: schema_version, date_utc, business_id, location_id, campaign_id, channel, step_number, purpose, messages_total, messages_sent, messages_opened_approx, messages_clicked, click_events, messages_failed, messages_cancelled, messages_pending. schema_version is 1; date_utc YYYY-MM-DD; channel email/sms/other; purpose review_request/repeat; counts nonnegative integers. Rate limits: 10 requests/10 seconds per IP plus 6 refreshes/10 minutes per business shared with manual export.",
        "responses": {
          "200": {
            "description": "Aggregate CSV; not customer contact data. Open counts are approximate.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "X-Reputive-Schema-Version": {
                "schema": {
                  "const": "1"
                }
              }
            }
          },
          "401": {
            "description": "Invalid Basic credentials; WWW-Authenticate challenge is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong scope/capability, inactive account or insufficient plan.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Export disabled or business ID does not match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Export exceeds 10,000 rows; no truncated CSV is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "IP budget (Retry-After: 10) or business refresh budget (Retry-After: 600) exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Refresh protection unavailable (Retry-After: 300) or another refresh running (Retry-After: 30).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Export failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/widget/{token}": {
      "get": {
        "operationId": "readPublicWidget",
        "summary": "Read data for an enabled public review widget",
        "security": [],
        "parameters": [
          {
            "name": "Origin",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Origin URL for an allowed domain; Referer is used if Origin is absent/null. At least one must identify an allowed domain."
          },
          {
            "name": "Referer",
            "in": "header",
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Widget must be enabled with configured allowed domains. Returns up to five latest reviews from the configured Google/Heureka sources; no pagination or arbitrary review search. 120 requests per sliding 60 seconds per IP; 429 includes Retry-After: 60. Public cache s-maxage=300, stale-while-revalidate=600. Domain headers are embed controls, not API-key authentication.",
        "responses": {
          "200": {
            "description": "Widget display data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "const": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "businessName": {
                          "type": "string"
                        },
                        "language": {
                          "enum": [
                            "cs",
                            "en",
                            "sk",
                            "pl",
                            "hu"
                          ]
                        },
                        "copy": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          },
                          "description": "Localized widget labels."
                        },
                        "averageRating": {
                          "type": "number"
                        },
                        "totalCount": {
                          "type": "integer"
                        },
                        "sources": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "googleReviewsUrl": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "reviews": {
                          "type": "array",
                          "maxItems": 5,
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "initials": {
                                "type": "string"
                              },
                              "photo": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "rating": {
                                "type": "number"
                              },
                              "comment": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "date": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "source": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "name",
                              "initials",
                              "photo",
                              "rating",
                              "comment",
                              "date",
                              "source"
                            ]
                          }
                        },
                        "jsonLd": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "Schema.org LocalBusiness structured data when usable ratings exist."
                        },
                        "premium": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "businessName",
                        "language",
                        "copy",
                        "averageRating",
                        "totalCount",
                        "sources",
                        "googleReviewsUrl",
                        "reviews",
                        "jsonLd",
                        "premium"
                      ]
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "No allowed domains configured or request domain not allowed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Widget missing or disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "options": {
        "summary": "Widget CORS preflight",
        "security": [],
        "responses": {
          "204": {
            "description": "No content; allows GET, OPTIONS and Content-Type."
          }
        }
      },
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Widget token copied from widget configuration, not an API key."
        }
      ]
    }
  },
  "components": {
    "securitySchemes": {
      "BearerKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "rep_live_ API key; immutable connector scope and capabilities."
      },
      "PowerBiBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "Username reputive-powerbi; password is a power-bi scoped API key. Bearer is not accepted."
      }
    },
    "schemas": {
      "Customer": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "description": "Required given name. No maximum length is enforced by this schema."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional surname; empty string and null are treated as absent."
          },
          "email": {
            "anyOf": [
              {
                "type": "string",
                "format": "email"
              },
              {
                "const": ""
              },
              {
                "type": "null"
              }
            ],
            "description": "Valid email address. A nonempty email or phone is required."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact phone. A nonempty email or phone is required. The public schema does not validate or normalize phone format; use an international number."
          },
          "gdpr_consent": {
            "enum": [
              true,
              1,
              "1",
              "true"
            ],
            "description": "Required confirmation that this contact may receive the review request. Does not override an existing opt-out."
          },
          "source": {
            "type": "string",
            "enum": [
              "woocommerce",
              "shoptet",
              "wix",
              "zapier",
              "api",
              "upgates",
              "base",
              "altegio",
              "reenio"
            ],
            "description": "Must exactly match the API key scope. Third-party custom integrations use api. Self-service collect keys support woocommerce, shoptet, wix, zapier, api and upgates; the remaining schema values are for built-in connectors, not freely selectable attribution."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional source reference. Used as a fallback deduplication key for pending-verification receipts only; it does not make successful collection idempotent."
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "description": "Deduplicates pending-verification receipts, taking precedence over external_id. Not a successful-send or enqueue idempotency guarantee."
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Active location belonging to this business. For custom API omit only when there is exactly one active location; missing, invalid or ambiguous routing yields pending_verification without enqueue. Wix requires an explicit location. Upgates retains legacy fallback routing."
          },
          "start_delay_hours": {
            "description": "Optional start offset in hours, added before each configured sequence-step delay. Numbers and nonempty numeric strings are truncated to integers and clamped to 0..720; unusable values are ignored. This is not a send-at timestamp. Legacy delay_hours is ignored."
          },
          "product": {
            "description": "Optional product for the review-request email, not a product-review import. Any JSON is accepted; unusable values or fields are discarded, with warnings. Unknown fields (including url) are ignored. See OrderProduct for usable content. The accepted product object is described by #/components/schemas/OrderProduct; it is not a strict validation constraint on the request.",
            "examples": [
              {
                "name": "Coffee beans",
                "image_url": "https://example.com/product.jpg",
                "item_count": 2
              }
            ]
          }
        },
        "required": [
          "first_name",
          "gdpr_consent",
          "source"
        ],
        "description": "Unknown fields are stripped. At least one nonempty email or phone is required. Product and delay values are permissively sanitized, not request-failing validation errors.",
        "anyOf": [
          {
            "required": [
              "email"
            ],
            "properties": {
              "email": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          {
            "required": [
              "phone"
            ],
            "properties": {
              "phone": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        ]
      },
      "OrderProduct": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Normalized whitespace/control characters; raw names longer than 480 characters or malformed Unicode are discarded."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 500,
            "description": "HTTPS only, without embedded credentials; raw and normalized URL must fit. Invalid value is dropped."
          },
          "item_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999,
            "description": "Distinct items, not units. Invalid value is dropped."
          }
        },
        "required": [
          "name"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "const": false
          },
          "error": {
            "type": "string"
          },
          "details": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "upgradeRequired": {
            "const": true
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "error"
        ]
      },
      "CollectSuccess": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "customerId": {
            "type": "string"
          },
          "status": {
            "enum": [
              "queued",
              "sent",
              "skipped"
            ]
          },
          "reason": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "success",
          "customerId",
          "status"
        ]
      },
      "PendingVerification": {
        "type": "object",
        "properties": {
          "success": {
            "const": true
          },
          "status": {
            "const": "pending_verification"
          },
          "receiptId": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "status",
          "receiptId"
        ]
      },
      "BatchItemResult": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "success": {
                "const": true
              },
              "customerId": {
                "type": "string"
              },
              "status": {
                "enum": [
                  "queued",
                  "sent",
                  "skipped"
                ]
              },
              "reason": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            },
            "required": [
              "success",
              "customerId",
              "status"
            ]
          },
          {
            "$ref": "#/components/schemas/PendingVerification"
          },
          {
            "$ref": "#/components/schemas/Error"
          }
        ]
      }
    }
  }
}
