{
  "openapi": "3.0.1",
  "info": {
    "title": "Replify External API",
    "version": "1.0.11",
    "description": "Manage campaign contacts via the Replify API Gateway.\n\n**Authentication**\nSee the [API overview](/api-reference/overview) for API keys (`x-api-key`), saving your key after creation, and required headers. This endpoint requires `Content-Type: application/json`."
  },
  "servers": [
    { "url": "https://api.heylibby.com/api/v1", "description": "Production" }
  ],
  "security": [{ "api_key": [] }],
  "paths": {
    "/campaigns/{campaignId}/contacts": {
      "post": {
        "summary": "Manage campaign contacts",
        "description": "Creates, updates, or removes contacts in a campaign.\n\n**Usage scenarios**\n- **Upsert contacts (default)**: Contacts will be added if they don't exist, or updated if they do. Contacts with a `disqualified` or `completed` status will not be added. If no `action` is specified, the API defaults to `upsert`.\n- **Update existing contacts only**: Set `action` to `\"updateOnly\"`. Only contacts already in the campaign will be updated. Contacts not in the campaign are ignored. This is useful when your process might include contacts that aren't in the campaign and you don't want them added.\n- **Remove contacts**: Set `action` to `\"remove\"` and provide at least `email` or `phone` for each contact to remove.\n\n**External IDs (CRM routing)**\n\n- **Configure in Manage Assistants** — Define **external IDs** per assistant so each one can reference CRMs outside Replify (for example HubSpot account IDs, storefront codes, or internal site codes).\n- **Example** — A key like `hubspotId` paired with values that distinguish Site A from Site B.\n- **In this API** — For **organization-wide / multi-assistant** campaigns, include optional **`externalIdKey`** and **`externalIdValue`**. Key names must match what you saved in Manage Assistants.\n- **Where to put them** — You can set **`externalIdKey`** / **`externalIdValue`** on individual contacts, at the request root, or both. See the schemas for those properties in this spec.\n\n**Key fields**\n- `action`: Controls how contacts are processed. Options are `\"upsert\"` (default), `\"updateOnly\"`, and `\"remove\"`. This applies to all contacts in the request.\n- `status`: Use `completed` or `disqualified` to stop further outreach from campaigns. Other status values are allowed but won't affect campaign behavior.\n- `contactMetadata`: Add any key:value pairs to inform your AI assistant's behavior. For example, if you set `\"tier\": \"pro\"`, you can configure your AI to identify and handle pro-tier contacts appropriately.\n- `externalIdKey` / `externalIdValue`: Optional; supported on request root and/or per-contact as described in schema field docs.",
        "tags": ["Campaigns"],
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "The unique ID of the campaign",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "externalIdKey": {
                    "type": "string",
                    "description": "Optional external ID **name** for this request batch (must match a key configured under Manage Assistants). Pair with `externalIdValue`.",
                    "example": "hubspotId"
                  },
                  "externalIdValue": {
                    "type": "string",
                    "description": "Optional external CRM **value** paired with root-level `externalIdKey` for routing in multi-assistant org-wide flows.",
                    "example": "1234"
                  },
                  "contacts": {
                    "type": "array",
                    "description": "List of contacts to add or update. When `action` is set to `\"remove\"`, these contacts are removed from the campaign.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "firstName": {
                          "type": "string",
                          "description": "Contact's first name.",
                          "example": "Jane"
                        },
                        "lastName": {
                          "type": "string",
                          "description": "Contact's last name.",
                          "example": "Doe"
                        },
                        "email": {
                          "type": "string",
                          "format": "email",
                          "description": "At least one of `email` or `phone` is required to identify the contact.",
                          "example": "jane.doe@example.com"
                        },
                        "phone": {
                          "type": "string",
                          "description": "At least one of `email` or `phone` is required to identify the contact.",
                          "example": "15551234567"
                        },
                        "status": {
                          "type": "string",
                          "description": "Optional status field. Use `completed` when the contact has achieved the campaign goal (e.g., signed up for trial, made purchase). Use `disqualified` when the contact is not a good fit. Other values are allowed but won't affect campaign behavior.",
                          "enum": ["completed", "disqualified"],
                          "example": "completed"
                        },
                        "contactMetadata": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Key:value pairs that inform your AI assistant's behavior. Use any custom data to help your AI respond differently in different scenarios. For example, set `\"tier\": \"pro\"` to help your AI identify and handle pro-tier contacts appropriately.",
                          "example": {
                            "source": "import",
                            "tier": "pro",
                            "won": true
                          }
                        },
                        "externalIdKey": {
                          "type": "string",
                          "description": "Optional external ID **name** for this contact (must match a key configured under Manage Assistants). Pair with `externalIdValue`.",
                          "example": "hubspotId"
                        },
                        "externalIdValue": {
                          "type": "string",
                          "description": "Optional external CRM **value** paired with this contact's `externalIdKey` when routing contacts in Multi-Assistant / organization-wide campaigns.",
                          "example": "1234"
                        }
                      }
                    }
                  },
                  "action": {
                    "type": "string",
                    "description": "Controls how contacts are processed. `upsert` (default) adds new contacts and updates existing ones. `updateOnly` updates only contacts already in the campaign and ignores others. `remove` removes the specified contacts.",
                    "enum": ["upsert", "updateOnly", "remove"],
                    "example": "upsert"
                  }
                },
                "required": ["contacts"]
              },
              "examples": {
                "UpsertContacts": {
                  "summary": "Upsert contacts (default)",
                  "description": "Adds contacts that don't exist in the campaign and updates those that do. Contacts with a disqualified or completed status will not be added. This is the default behavior if no action is specified.",
                  "value": {
                    "contacts": [
                      {
                        "firstName": "Ava",
                        "lastName": "Lopez",
                        "email": "ava@example.com",
                        "phone": "15551234567",
                        "contactMetadata": {
                          "source": "web_form",
                          "tier": "trial"
                        }
                      }
                    ],
                    "action": "upsert"
                  }
                },
                "UpdateOnlyContacts": {
                  "summary": "Update existing contacts only",
                  "description": "Updates only contacts that already exist in the campaign. Contacts not in the campaign are ignored. Useful when your process might include contacts that aren't in the campaign and you don't want them added.",
                  "value": {
                    "contacts": [
                      {
                        "email": "ava@example.com",
                        "status": "completed",
                        "contactMetadata": {
                          "won": true,
                          "conversion_source": "appointment_booked"
                        }
                      }
                    ],
                    "action": "updateOnly"
                  }
                },
                "RemoveContact": {
                  "summary": "Remove a contact",
                  "description": "Removes specific contacts from the campaign by setting the top-level action to \"remove\" and providing contact identifiers.",
                  "value": {
                    "contacts": [
                      {
                        "email": "ava@example.com"
                      }
                    ],
                    "action": "remove"
                  }
                },
                "UpsertWithExternalIds": {
                  "summary": "Upsert with per-contact CRM external IDs",
                  "description": "Each contact can carry different `externalIdKey`/`externalIdValue` pairs for multi-location routing. Roots may omit these fields.",
                  "value": {
                    "contacts": [
                      {
                        "firstName": "Ava",
                        "lastName": "Lopez",
                        "email": "ava@example.com",
                        "phone": "15551234567",
                        "externalIdKey": "hubspotId",
                        "externalIdValue": "1234"
                      },
                      {
                        "firstName": "Ben",
                        "lastName": "Nguyen",
                        "email": "ben@example.com",
                        "phone": "15559876543",
                        "externalIdKey": "hubspotId",
                        "externalIdValue": "5678"
                      }
                    ],
                    "action": "upsert"
                  }
                },
                "UpsertWithExternalIdsRoot": {
                  "summary": "Upsert batch with external IDs only on root",
                  "description": "When your payload sets `externalIdKey`/`externalIdValue` at the JSON root instead of nesting them on contacts.",
                  "value": {
                    "externalIdKey": "hubspotId",
                    "externalIdValue": "1234",
                    "contacts": [
                      {
                        "firstName": "Ava",
                        "lastName": "Lopez",
                        "email": "ava@example.com",
                        "phone": "15551234567"
                      }
                    ],
                    "action": "upsert"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request accepted and processed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Human-readable summary of the operation results.",
                      "example": "Processed 2 contacts successfully, 1 failed"
                    },
                    "success": {
                      "type": "array",
                      "description": "Contacts that were successfully created, updated, or deleted.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "phone": {
                            "type": "string",
                            "description": "Normalized phone number (if provided).",
                            "example": "15551234567"
                          },
                          "email": {
                            "type": "string",
                            "format": "email",
                            "description": "Email address (if provided).",
                            "example": "jane.doe@example.com"
                          },
                          "action": {
                            "type": "string",
                            "description": "Action taken for this contact.",
                            "enum": ["created", "updated"],
                            "example": "created"
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "description": "Contacts that failed to process with error details.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "email": {
                            "type": "string",
                            "format": "email",
                            "description": "Email associated with the error (if available).",
                            "example": "john.smith@example.com"
                          },
                          "phone": {
                            "type": "string",
                            "description": "Phone associated with the error (if available).",
                            "example": "15559876543"
                          },
                          "error": {
                            "type": "string",
                            "description": "Reason why the contact could not be processed.",
                            "example": "Contact contains email and phone for two different existing contacts"
                          }
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "allSuccess": {
                    "summary": "All contacts processed successfully",
                    "value": {
                      "message": "Processed 3 contacts successfully",
                      "success": [
                        {
                          "phone": "15551234567",
                          "email": "jane.doe@example.com",
                          "action": "created"
                        },
                        {
                          "phone": "15559876543",
                          "email": "john.smith@example.com",
                          "action": "updated"
                        }
                      ],
                      "errors": []
                    }
                  },
                  "mixedOutcome": {
                    "summary": "Some contacts succeeded, some failed",
                    "value": {
                      "message": "Processed 1 contacts successfully, 1 failed",
                      "success": [
                        {
                          "phone": "15551234567",
                          "email": "jane.doe@example.com",
                          "action": "created"
                        }
                      ],
                      "errors": [
                        {
                          "email": "john.smith@example.com",
                          "error": "Contact contains email and phone for two different existing contacts"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request — the request body contains invalid JSON or missing required fields.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": { "type": "string", "example": "BAD_REQUEST" },
                    "message": {
                      "type": "string",
                      "example": "Invalid JSON in request body"
                    },
                    "details": {
                      "type": "string",
                      "example": "SyntaxError: Unexpected token '}' in JSON at position 42"
                    }
                  }
                },
                "examples": {
                  "invalidJson": {
                    "summary": "Invalid JSON syntax",
                    "value": {
                      "code": "BAD_REQUEST",
                      "message": "Invalid JSON in request body",
                      "details": "SyntaxError: Unexpected token '}' in JSON at position 42"
                    }
                  },
                  "missingContacts": {
                    "summary": "Missing required contacts field",
                    "value": {
                      "code": "BAD_REQUEST",
                      "message": "Missing required field: contacts",
                      "details": "The request body must contain a 'contacts' array"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — API key is missing, invalid, or not authorized for this campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": { "type": "string", "example": "FORBIDDEN" },
                    "message": {
                      "type": "string",
                      "example": "Access to the resource is prohibited"
                    }
                  }
                },
                "examples": {
                  "missingKey": {
                    "summary": "Missing API key",
                    "value": {
                      "code": "FORBIDDEN",
                      "message": "API key is required"
                    }
                  },
                  "invalidKey": {
                    "summary": "Invalid API key",
                    "value": {
                      "code": "FORBIDDEN",
                      "message": "Invalid API key provided"
                    }
                  },
                  "unauthorizedCampaign": {
                    "summary": "Not authorized for this campaign",
                    "value": {
                      "code": "FORBIDDEN",
                      "message": "You are not authorized to access this campaign"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found — the specified campaign does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": { "type": "string", "example": "NOT_FOUND" },
                    "message": {
                      "type": "string",
                      "example": "Campaign not found"
                    }
                  }
                },
                "examples": {
                  "campaignNotFound": {
                    "summary": "Campaign does not exist",
                    "value": {
                      "code": "NOT_FOUND",
                      "message": "Campaign not found"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [{ "api_key": [] }]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "api_key": { "type": "apiKey", "name": "x-api-key", "in": "header" }
    }
  }
}
