{
  "swagger": "2.0",
  "info": {
    "description": "rasa.io API v2",
    "version": "2",
    "title": "rasa.io API",
    "contact": {
      "email": "api.support@rasa.io"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "host": "api.rasa.io",
  "basePath": "/v2",
  "schemes": [
    "https"
  ],
  "securityDefinitions": {
    "RasaToken": {
      "type": "apiKey",
      "in": "header",
      "name": "rasa-token",
      "description": "JWT token obtained from POST /v2/tokens"
    }
  },
  "paths": {
    "/tokens": {
      "post": {
        "summary": "Create an API token",
        "tags": ["authentication"],
        "description": "OAuth2 Client Credentials grant. Returns a Bearer access token.\n\n**Regular clients:** token is scoped to the client's account — no additional parameters needed.\n\n**Super-user clients** (account_id 10000): two options:\n- Pass `account_guid` in the request body to receive a token scoped to that specific campaign account.\n- Omit `account_guid` to receive an unscoped super-user token (`scope: super`). All subsequent requests using this token must include an `X-Account-GUID` header with the target campaign account GUID.",
        "operationId": "v2CreateToken",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "description": "Token request body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/TokenCreateBody"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token created successfully",
            "schema": {
              "$ref": "#/definitions/ApiResponseTokenCreated"
            }
          },
          "400": {
            "description": "Bad request — missing or invalid fields"
          },
          "401": {
            "description": "Unauthorized — invalid credentials"
          }
        }
      },
      "delete": {
        "summary": "Delete all tokens for the authenticated user",
        "tags": ["authentication"],
        "description": "Invalidates all active tokens associated with the user.",
        "operationId": "v2DeleteAllTokens",
        "parameters": [
          {
            "name": "Authorization",
            "description": "HTTP Basic authentication",
            "in": "header",
            "type": "string",
            "required": true
          }
        ],
        "responses": {
          "204": {
            "description": "All tokens deleted"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/lists": {
      "get": {
        "summary": "Get all lists",
        "tags": ["lists"],
        "description": "Returns all lists for the authenticated community.",
        "operationId": "getLists",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "rasa-token",
            "in": "header",
            "description": "JWT token from POST /v2/tokens",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {
              "$ref": "#/definitions/ApiResponseList"
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Create a list",
        "tags": ["lists"],
        "description": "Creates a new list for the authenticated account. The new list is not archived. `options` is an arbitrary key-value object stored on the list (used by callers to mark provenance, e.g. `source_type: 'ftp'`).",
        "operationId": "createList",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/ListCreateBody"}
          }
        ],
        "responses": {
          "201": {
            "description": "List created",
            "schema": {"$ref": "#/definitions/ApiResponseList"}
          },
          "400": {"description": "Invalid body (missing or empty name)"},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/lists/{id}": {
      "get": {
        "summary": "Get a list by ID",
        "tags": ["lists"],
        "description": "Returns a single list identified by its ID.",
        "operationId": "getListById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "rasa-token",
            "in": "header",
            "description": "JWT token from POST /v2/tokens",
            "required": true,
            "type": "string"
          },
          {
            "name": "id",
            "in": "path",
            "description": "List ID",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {
              "$ref": "#/definitions/ApiResponseList"
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "List not found"
          }
        }
      }
    },
    "/messaging/newsletter": {
      "get": {
        "summary": "Get all newsletters",
        "tags": ["messaging"],
        "description": "Returns all active newsletters for the authenticated account, including associated lists and subscription types.",
        "operationId": "getNewsletters",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "skip",
            "in": "query",
            "description": "Number of records to skip (default 0)",
            "required": false,
            "type": "integer",
            "default": 0
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum records to return (default 50)",
            "required": false,
            "type": "integer",
            "default": 50
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {"$ref": "#/definitions/ApiResponseNewsletter"}
          },
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/messaging/newsletter/{id}": {
      "get": {
        "summary": "Get a newsletter by ID",
        "tags": ["messaging"],
        "description": "Returns a single newsletter by GUID, including associated lists and subscription types.",
        "operationId": "getNewsletterById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Newsletter GUID (the id field returned by GET /messaging/newsletter)",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {"$ref": "#/definitions/ApiResponseNewsletter"}
          },
          "401": {"description": "Unauthorized"},
          "404": {"description": "Newsletter not found"}
        }
      }
    },
    "/contacts": {
      "get": {
        "summary": "Get contacts",
        "tags": ["contacts"],
        "description": "Returns paginated contacts for the authenticated account. Optionally filter by list.",
        "operationId": "getContacts",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "list_id",
            "in": "query",
            "description": "Filter to contacts belonging to this list ID",
            "required": false,
            "type": "integer"
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Number of records to skip (default 0)",
            "required": false,
            "type": "integer",
            "default": 0
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum records to return (default 50, max 5000)",
            "required": false,
            "type": "integer",
            "default": 50
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Field to sort by: id, first_name, last_name, email, created, updated",
            "required": false,
            "type": "string",
            "default": "id"
          },
          {
            "name": "sort_direction",
            "in": "query",
            "description": "Sort direction: asc or desc",
            "required": false,
            "type": "string",
            "enum": ["asc", "desc"],
            "default": "asc"
          },
          {
            "name": "system",
            "in": "query",
            "description": "Filter by external system name (e.g. 'hubspot'). Can be combined with identifier and/or system_identifier.",
            "required": false,
            "type": "string"
          },
          {
            "name": "identifier",
            "in": "query",
            "description": "Filter by the contact's ID in the external system.",
            "required": false,
            "type": "string"
          },
          {
            "name": "system_identifier",
            "in": "query",
            "description": "Filter by external sub-system identifier (e.g. a HubSpot portal ID).",
            "required": false,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {"$ref": "#/definitions/ApiResponseContact"}
          },
          "401": {"description": "Unauthorized"}
        }
      },
      "post": {
        "summary": "Create a contact",
        "tags": ["contacts"],
        "description": "Creates a contact for the authenticated account, or returns the existing active contact if one already exists for the given email. Optionally adds the contact to a list and registers an external system identifier. Both `source_identifier` and `system` must be provided together to create an external identifier.",
        "operationId": "createContact",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/ContactCreateBody"}
          }
        ],
        "responses": {
          "201": {
            "description": "Contact created (or existing contact returned)",
            "schema": {"$ref": "#/definitions/ApiResponseContact"}
          },
          "400": {"description": "Bad request — email is required"},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/contacts/bulk": {
      "post": {
        "summary": "Create contacts in bulk",
        "tags": ["contacts"],
        "description": "Creates up to 1000 contacts in a single request. Each item is processed independently within its own database SAVEPOINT, so a failure on one row (e.g. missing email) does not stop the rest of the batch. The response returns a per-item status with either the created contact or an error message.\n\nReturns 201 if at least one item was created. Returns 400 if the envelope is invalid (missing `contacts` array, empty array, more than 1000 items) or if every item failed.",
        "operationId": "createContactsBulk",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/ContactBulkCreateBody"}
          }
        ],
        "responses": {
          "201": {
            "description": "Bulk processed. Inspect per-item status in results.",
            "schema": {"$ref": "#/definitions/ApiResponseContactBulk"}
          },
          "400": {"description": "Invalid envelope, empty array, more than 1000 items, or all items failed"},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/contacts/deleted": {
      "get": {
        "summary": "Get removed contacts for a list (tombstone feed)",
        "tags": ["contacts"],
        "description": "Tombstone counterpart to GET /contacts?list_id=<id>. Returns contacts that have left the list — either dropped from it (ContactList.is_archived) or archived globally (Contact.is_archived) — so a sync consumer can reconcile removals incrementally via archived_since instead of diffing a full snapshot. is_archived distinguishes the two modes (gone globally vs only off this list). NOTE: archived_date is a proxy (Contact.updated / ContactList.updated — the row's last-modified time, which for an archival is the archival time) until a dedicated archived_at column exists, mirroring the v1 /persons/deleted feed.",
        "operationId": "getRemovedContactsForList",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "list_id", "in": "query", "description": "List id to report removals for", "required": true, "type": "integer"},
          {"name": "archived_since", "in": "query", "description": "Only removals at/after this instant. Accepts a date (YYYY-MM-DD) or an ISO-8601 timestamp; UTC assumed when no offset. Invalid input returns 400.", "required": false, "type": "string"},
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseRemovedContact"}},
          "400": {"description": "Missing/invalid list_id, or invalid archived_since"},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/messaging/blast": {
      "get": {
        "summary": "Get all blasts",
        "tags": ["messaging"],
        "description": "Returns a flat list of sequence+message records for sequences associated with blast-type campaigns (is_visible=0 or name='Blasts').",
        "operationId": "getBlasts",
        "security": [{"RasaToken": []}],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseBlast"}},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/messaging/newsletter/{id}/contacts": {
      "get": {
        "summary": "Get contacts for a newsletter",
        "tags": ["messaging"],
        "description": "Returns contacts from the newsletter's target lists. Supports filtering by subscription status, pagination, and sorting.",
        "operationId": "getNewsletterContacts",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Newsletter GUID", "required": true, "type": "string"},
          {"name": "status", "in": "query", "description": "Filter: 'subscribed' or 'unsubscribed'. Omit for all.", "required": false, "type": "string", "enum": ["subscribed", "unsubscribed"]},
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50},
          {"name": "sort", "in": "query", "description": "Sort field: id, first_name, last_name, email, created, updated", "required": false, "type": "string", "default": "id"},
          {"name": "sort_direction", "in": "query", "description": "asc or desc", "required": false, "type": "string", "enum": ["asc", "desc"], "default": "asc"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseContact"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Newsletter not found"}
        }
      }
    },
    "/messaging/newsletter/{id}/contacts/deleted": {
      "get": {
        "summary": "Get removed contacts for a newsletter (tombstone feed)",
        "tags": ["messaging"],
        "description": "Returns contacts that were in the newsletter's audience but have since been removed — either archived globally (Contact.is_archived) or dropped from the newsletter's list(s) (ContactList.is_archived). Lets a sync consumer detect removals incrementally via archived_since instead of diffing a full snapshot. NOTE: archived_date is a proxy (Contact.updated / ContactList.updated — the row's last-modified time, which for an archival is the archival time) until a dedicated archived_at column exists, mirroring the v1 /persons/deleted feed.",
        "operationId": "getNewsletterRemovedContacts",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Newsletter GUID", "required": true, "type": "string"},
          {"name": "archived_since", "in": "query", "description": "Only removals at/after this instant. Accepts a date (YYYY-MM-DD) or an ISO-8601 timestamp; UTC assumed when no offset. Invalid input returns 400.", "required": false, "type": "string"},
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseRemovedContact"}},
          "400": {"description": "Invalid archived_since"},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Newsletter not found"}
        }
      }
    },
    "/messaging/blast/{id}": {
      "get": {
        "summary": "Get a blast by sequence ID",
        "tags": ["messaging"],
        "description": "Returns the flat sequence+message records for a single blast-type sequence.",
        "operationId": "getBlastById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Sequence ID", "required": true, "type": "integer"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseBlast"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Blast not found"}
        }
      }
    },
    "/messaging/blast/{id}/contacts": {
      "get": {
        "summary": "Get contacts for a blast",
        "tags": ["messaging"],
        "description": "Returns contacts from the blast sequence's target lists. Supports filtering by subscription status, pagination, and sorting.",
        "operationId": "getBlastContacts",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Blast sequence ID", "required": true, "type": "integer"},
          {"name": "status", "in": "query", "description": "Filter: 'subscribed' or 'unsubscribed'. Omit for all.", "required": false, "type": "string", "enum": ["subscribed", "unsubscribed"]},
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50},
          {"name": "sort", "in": "query", "description": "Sort field: id, first_name, last_name, email, created, updated", "required": false, "type": "string", "default": "id"},
          {"name": "sort_direction", "in": "query", "description": "asc or desc", "required": false, "type": "string", "enum": ["asc", "desc"], "default": "asc"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseContact"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Blast not found"}
        }
      }
    },
    "/messaging/sequence": {
      "get": {
        "summary": "Get all sequences",
        "tags": ["messaging"],
        "description": "Returns all active sequences for the authenticated account, including embedded messages, lists, and subscription types.",
        "operationId": "getSequences",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSequence"}},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/messaging/sequence/{id}": {
      "get": {
        "summary": "Get a sequence by ID",
        "tags": ["messaging"],
        "description": "Returns a single sequence by ID, including embedded messages, lists, and subscription types.",
        "operationId": "getSequenceById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Sequence ID", "required": true, "type": "integer"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSequence"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Sequence not found"}
        }
      }
    },
    "/messaging/sequence/{id}/contacts": {
      "get": {
        "summary": "Get contacts for a sequence",
        "tags": ["messaging"],
        "description": "Returns contacts from the sequence's target lists. Supports filtering by subscription status, pagination, and sorting.",
        "operationId": "getSequenceContacts",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Sequence ID", "required": true, "type": "integer"},
          {"name": "status", "in": "query", "description": "Filter: 'subscribed' or 'unsubscribed'. Omit for all.", "required": false, "type": "string", "enum": ["subscribed", "unsubscribed"]},
          {"name": "skip", "in": "query", "description": "Records to skip (default 0)", "required": false, "type": "integer", "default": 0},
          {"name": "limit", "in": "query", "description": "Max records to return (default 50)", "required": false, "type": "integer", "default": 50},
          {"name": "sort", "in": "query", "description": "Sort field: id, first_name, last_name, email, created, updated", "required": false, "type": "string", "default": "id"},
          {"name": "sort_direction", "in": "query", "description": "asc or desc", "required": false, "type": "string", "enum": ["asc", "desc"], "default": "asc"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseContact"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Sequence not found"}
        }
      }
    },
    "/messaging/sequence/{id}/message/{messageId}": {
      "get": {
        "summary": "Get a message within a sequence",
        "tags": ["messaging"],
        "description": "Returns a single message by ID, scoped to the given sequence, including its trigger.",
        "operationId": "getSequenceMessage",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Sequence ID", "required": true, "type": "integer"},
          {"name": "messageId", "in": "path", "description": "Message ID", "required": true, "type": "integer"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSequenceMessage"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Sequence or message not found"}
        }
      }
    },
    "/contacts/{id}": {
      "get": {
        "summary": "Get a contact by ID",
        "tags": ["contacts"],
        "description": "Returns a single contact including subscriptions and external identifiers.",
        "operationId": "getContactById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Contact GUID (the id field returned by GET /contacts)",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseContact"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Contact not found"}
        }
      },
      "put": {
        "summary": "Update a contact",
        "tags": ["contacts"],
        "description": "Updates editable fields on an existing contact. Only fields present in the request body are updated.\n\nTo link a contact to an external system, include `system` and `identifier` (and optionally `system_identifier`). If an external identifier already exists for the given `system` + `system_identifier` combination, it will be updated; otherwise a new one is created.",
        "operationId": "updateContact",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Contact GUID",
            "required": true,
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/ContactUpdateBody"}
          }
        ],
        "responses": {
          "200": {"description": "Contact updated", "schema": {"$ref": "#/definitions/ApiResponseContact"}},
          "400": {"description": "Bad request — contact id is required"},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Contact not found"}
        }
      },
      "delete": {
        "summary": "Archive a contact",
        "tags": ["contacts"],
        "description": "Archives the contact. The contact's list memberships are not affected. To remove a contact from a specific list, use DELETE /contact-lists/{id}/list/{list_id}.",
        "operationId": "archiveContact",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Contact GUID",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {"description": "Contact archived", "schema": {"$ref": "#/definitions/ApiResponseContactArchive"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Contact not found"}
        }
      }
    },
    "/contact-lists/{id}/list/{list_id}": {
      "delete": {
        "summary": "Remove a contact from a list",
        "tags": ["contacts"],
        "description": "Archives the contact_list row, removing the contact from the specified list. The contact itself is not archived.",
        "operationId": "removeContactFromList",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Contact GUID",
            "required": true,
            "type": "string"
          },
          {
            "name": "list_id",
            "in": "path",
            "description": "List ID",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {"description": "Contact removed from list", "schema": {"$ref": "#/definitions/ApiResponseContactListArchive"}},
          "400": {"description": "Bad request — contact id and list id are required"},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Contact or list membership not found"}
        }
      }
    },
    "/subscription-types": {
      "get": {
        "summary": "Get all subscription types",
        "tags": ["subscriptions"],
        "description": "Returns all active subscription types for the authenticated account, with their active mappings embedded.",
        "operationId": "getSubscriptionTypes",
        "security": [{"RasaToken": []}],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSubscriptionType"}},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/subscription-types/{id}": {
      "get": {
        "summary": "Get a subscription type by ID",
        "tags": ["subscriptions"],
        "description": "Returns a single subscription type by integer ID, with its active mappings.",
        "operationId": "getSubscriptionTypeById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {"name": "id", "in": "path", "description": "Subscription type ID", "required": true, "type": "integer"}
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSubscriptionType"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Subscription type not found"}
        }
      }
    },
    "/subscriptions": {
      "get": {
        "summary": "Get subscriptions for a contact",
        "tags": ["subscriptions"],
        "description": "Returns all subscriptions for a given contact (identified by integer contact_id).",
        "operationId": "getSubscriptions",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "contact_id",
            "in": "query",
            "description": "Contact GUID or integer contact ID (both are returned by GET /contacts)",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSubscription"}},
          "400": {"description": "Bad request — contact_id is required"},
          "401": {"description": "Unauthorized"}
        }
      },
      "post": {
        "summary": "Create or update a subscription",
        "tags": ["subscriptions"],
        "description": "Upserts a subscription on (contact_id, subscription_type_id, integration_id). If a matching subscription already exists it is updated; otherwise a new one is created.",
        "operationId": "upsertSubscription",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/SubscriptionCreateBody"}
          }
        ],
        "responses": {
          "201": {"description": "Subscription created or updated", "schema": {"$ref": "#/definitions/ApiResponseSubscription"}},
          "400": {"description": "Bad request — contact_id and subscription_type_id are required"},
          "401": {"description": "Unauthorized"}
        }
      }
    },
    "/subscriptions/{id}": {
      "get": {
        "summary": "Get a subscription by ID",
        "tags": ["subscriptions"],
        "description": "Returns a single subscription by its integer ID.",
        "operationId": "getSubscriptionById",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Subscription ID",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {"description": "Successful response", "schema": {"$ref": "#/definitions/ApiResponseSubscription"}},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Subscription not found"}
        }
      },
      "put": {
        "summary": "Update a subscription",
        "tags": ["subscriptions"],
        "description": "Updates fields on an existing subscription. Only fields present in the request body are changed. `status_changed` is always set to the current UTC time.",
        "operationId": "updateSubscription",
        "security": [{"RasaToken": []}],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Subscription ID",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {"$ref": "#/definitions/SubscriptionUpdateBody"}
          }
        ],
        "responses": {
          "200": {"description": "Subscription updated", "schema": {"$ref": "#/definitions/ApiResponseSubscription"}},
          "400": {"description": "Bad request — subscription id is required"},
          "401": {"description": "Unauthorized"},
          "404": {"description": "Subscription not found"}
        }
      }
    }
  },
  "definitions": {
    "TokenCreateBody": {
      "type": "object",
      "required": ["grant_type", "client_id", "client_secret"],
      "properties": {
        "grant_type": {
          "type": "string",
          "description": "Must be 'client_credentials'",
          "enum": ["client_credentials"]
        },
        "client_id": {
          "type": "string",
          "description": "OAuth2 client_id"
        },
        "client_secret": {
          "type": "string",
          "description": "OAuth2 client_secret"
        },
        "account_guid": {
          "type": "string",
          "description": "Super-user clients only: campaign account GUID to scope this token to. Omit to receive an unscoped super-user token that requires X-Account-GUID on every request."
        }
      }
    },
    "ApiResponseTokenCreated": {
      "type": "object",
      "properties": {
        "access_token": {"type": "string", "description": "Bearer token to use in Authorization header"},
        "token_type":   {"type": "string", "example": "Bearer"},
        "expires_in":   {"type": "integer", "description": "Token lifetime in seconds"},
        "scope":        {"type": "string", "description": "Present only for super-user tokens. Value: 'super'. Requests using this token must include X-Account-GUID header."}
      }
    },
    "ApiResponseList": {
      "type": "object",
      "properties": {
        "code": {"type": "integer", "example": 200},
        "status_code": {"type": "integer", "example": 200},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {"$ref": "#/definitions/List"}
        }
      }
    },
    "ApiResponseContact": {
      "type": "object",
      "properties": {
        "code": {"type": "integer", "example": 200},
        "status_code": {"type": "integer", "example": 200},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {"$ref": "#/definitions/Contact"}
        }
      }
    },
    "ApiResponseRemovedContact": {
      "type": "object",
      "properties": {
        "code": {"type": "integer", "example": 200},
        "status_code": {"type": "integer", "example": 200},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {"$ref": "#/definitions/RemovedContact"}
        }
      }
    },
    "RemovedContact": {
      "type": "object",
      "description": "A tombstone row: enough identity to match the legacy record, plus when/why the contact was removed.",
      "properties": {
        "id": {"type": "string", "description": "Contact GUID"},
        "email": {"type": "string", "description": "Contact email — always present; the primary key for matching the legacy record"},
        "newsletter_id": {"type": "string", "description": "Per-newsletter legacy identifier (external_identifier where system='newsletter'), when known", "x-nullable": true},
        "archived_date": {"type": "string", "format": "date-time", "description": "When the contact was removed. PROXY: Contact.updated / ContactList.updated until a dedicated archived_at column exists."},
        "is_archived": {"type": "boolean", "description": "True = the contact is archived globally (gone from every list — purge it); False = the contact is still live but was dropped from the list(s) in scope (unlink just that membership)"}
      }
    },
    "ResponseMetadata": {
      "type": "object",
      "properties": {
        "request": {"type": "string", "description": "Request URL"},
        "errors": {"type": "string"},
        "next_link": {"type": "string"},
        "record_count": {"type": "integer"},
        "response_time": {"type": "number"},
        "timestamp": {"type": "number"},
        "token_expiration": {"type": "number"}
      }
    },
    "List": {
      "type": "object",
      "description": "A rasa.io list (mailing list / segment)",
      "properties": {
        "id": {"type": "string"},
        "name": {"type": "string"}
      }
    },
    "ListCreateBody": {
      "type": "object",
      "required": ["name"],
      "description": "Body for POST /lists.",
      "properties": {
        "name":        {"type": "string", "description": "Display name. Required, non-empty."},
        "description": {"type": "string", "description": "Optional human-readable description."},
        "options":     {"type": "object", "description": "Arbitrary key-value attributes stored on the list (e.g. `{\"source_type\": \"ftp\"}`)."}
      }
    },
    "Contact": {
      "type": "object",
      "description": "A rasa.io contact",
      "properties": {
        "id":                       {"type": "string", "description": "Contact GUID — use this as the identifier in all API calls"},
        "email":                    {"type": "string", "format": "email"},
        "first_name":               {"type": "string"},
        "last_name":                {"type": "string"},
        "can_email":                {"type": "boolean"},
        "can_email_updated":        {"type": "string", "format": "date-time"},
        "can_email_code":           {"type": "string", "description": "Machine-readable code indicating why can_email was changed (e.g. 'manual', 'bounce', 'unsubscribe')"},
        "can_email_message":        {"type": "string", "description": "Human-readable message describing why can_email was changed"},
        "source":                   {"type": "string"},
        "title":                    {"type": "string"},
        "company":                  {"type": "string"},
        "created":                  {"type": "string", "format": "date-time"},
        "updated":                  {"type": "string", "format": "date-time"},
        "options":                  {"type": "object", "description": "Arbitrary key-value attributes stored on the contact. Present only when set."},
        "subscriptions": {
          "type": "array",
          "items": {"$ref": "#/definitions/Subscription"}
        },
        "identifiers": {
          "type": "array",
          "items": {"$ref": "#/definitions/ExternalIdentifier"}
        }
      }
    },
    "ApiResponseNewsletter": {
      "type": "object",
      "properties": {
        "code": {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {"type": "array", "items": {"$ref": "#/definitions/Newsletter"}}
      }
    },
    "Newsletter": {
      "type": "object",
      "properties": {
        "id":          {"type": "string", "description": "Newsletter GUID"},
        "name":        {"type": "string"},
        "description": {"type": "string"},
        "identifier":  {"type": "string"},
        "lists": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id":          {"type": "integer"},
              "name":        {"type": "string"},
              "description": {"type": "string"}
            }
          }
        },
        "subscription_types": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id":   {"type": "integer"},
              "name": {"type": "string"}
            }
          }
        },
        "created_at": {"type": "string", "format": "date-time"},
        "updated_at": {"type": "string", "format": "date-time"}
      }
    },
    "ExternalIdentifier": {
      "type": "object",
      "properties": {
        "system":            {"type": "string", "description": "The external system name"},
        "system_identifier": {"type": "string", "description": "An identifier within that system (e.g. account or namespace)"},
        "identifier":        {"type": "string", "description": "The contact's ID in the external system"}
      }
    },
    "ApiResponseBlast": {
      "type": "object",
      "properties": {
        "code": {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {"type": "array", "items": {"$ref": "#/definitions/Blast"}}
      }
    },
    "Blast": {
      "type": "object",
      "description": "A flat record combining sequence and message data for blast-type sequences.",
      "properties": {
        "id":                  {"type": "integer", "description": "Sequence ID"},
        "sequence_name":       {"type": "string"},
        "status":              {"type": "string"},
        "start_date":          {"type": "string", "format": "date-time"},
        "message_name":        {"type": "string"},
        "order":               {"type": "integer"},
        "message_status":      {"type": "string"},
        "subject": {
          "type": "object",
          "properties": {
            "type":    {"type": "string"},
            "content": {"type": "string"},
            "preview": {"type": "string"}
          }
        },
        "trigger_description": {"type": "string"},
        "trigger_time_since":  {"type": "integer"},
        "trigger_pause":       {"type": "boolean"},
        "trigger_skip":        {"type": "boolean"},
        "sequence_created":    {"type": "string", "format": "date-time"},
        "sequence_updated":    {"type": "string", "format": "date-time"},
        "message_created":     {"type": "string", "format": "date-time"},
        "message_updated":     {"type": "string", "format": "date-time"}
      }
    },
    "ApiResponseSequence": {
      "type": "object",
      "properties": {
        "code": {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {"type": "array", "items": {"$ref": "#/definitions/Sequence"}}
      }
    },
    "Sequence": {
      "type": "object",
      "properties": {
        "id":          {"type": "integer", "description": "Sequence ID"},
        "name":        {"type": "string"},
        "description": {"type": "string"},
        "status":      {"type": "string"},
        "start_date":  {"type": "string", "format": "date-time"},
        "lists": {
          "type": "array",
          "items": {"type": "object", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "description": {"type": "string"}}}
        },
        "exclude_lists": {
          "type": "array",
          "items": {"type": "object", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}
        },
        "subscription_types": {
          "type": "array",
          "items": {"type": "object", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}
        },
        "messages": {
          "type": "array",
          "items": {"$ref": "#/definitions/SequenceMessage"}
        },
        "created":     {"type": "string", "format": "date-time"},
        "updated":     {"type": "string", "format": "date-time"}
      }
    },
    "SequenceMessage": {
      "type": "object",
      "properties": {
        "id":          {"type": "integer"},
        "order":       {"type": "integer"},
        "name":        {"type": "string"},
        "description": {"type": "string"},
        "status":      {"type": "string"},
        "subject": {
          "type": "object",
          "properties": {
            "type":    {"type": "string"},
            "content": {"type": "string"},
            "preview": {"type": "string"}
          }
        },
        "trigger": {"$ref": "#/definitions/SequenceTrigger"},
        "created":     {"type": "string", "format": "date-time"},
        "updated":     {"type": "string", "format": "date-time"}
      }
    },
    "ApiResponseSequenceMessage": {
      "type": "object",
      "properties": {
        "code": {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata": {"$ref": "#/definitions/ResponseMetadata"},
        "results": {"type": "array", "items": {"$ref": "#/definitions/SequenceMessage"}}
      }
    },
    "SequenceTrigger": {
      "type": "object",
      "properties": {
        "description": {"type": "string"},
        "time_since":  {"type": "integer", "description": "Days since previous message before this one can be sent"},
        "pause":       {"type": "boolean", "description": "Whether to pause the sequence at this message"},
        "skip":        {"type": "boolean", "description": "Whether to skip this message"}
      }
    },
    "Subscription": {
      "type": "object",
      "description": "Subscription summary embedded in a Contact (GET /contacts/{id})",
      "properties": {
        "id":                {"type": "integer"},
        "subscription_type": {"type": "string"},
        "can_email":         {"type": "boolean"},
        "status_changed":    {"type": "string", "format": "date-time"}
      }
    },
    "SubscriptionFull": {
      "type": "object",
      "description": "Full subscription record returned by the /subscriptions endpoints",
      "properties": {
        "id":                   {"type": "integer"},
        "contact_id":           {"type": "integer"},
        "subscription_type_id": {"type": "integer"},
        "integration_id":       {"type": "integer"},
        "can_email":            {"type": "boolean"},
        "is_active":            {"type": "boolean"},
        "synced_at":            {"type": "string", "format": "date-time"},
        "status_changed":       {"type": "string", "format": "date-time"},
        "unsubscribe_code":     {"type": "string", "description": "Machine-readable unsubscribe reason code"},
        "unsubscribe_message":  {"type": "string", "description": "Human-readable unsubscribe reason message"}
      }
    },
    "ContactCreateBody": {
      "type": "object",
      "required": ["email"],
      "properties": {
        "email":             {"type": "string", "format": "email"},
        "first_name":        {"type": "string"},
        "last_name":         {"type": "string"},
        "source":            {"type": "string", "description": "Origin of the contact (e.g. 'hubspot', 'csv')"},
        "can_email":         {"type": "boolean", "description": "Sets the contact's emailability. Applied whether this POST creates a new contact or matches an existing active contact by email. Defaults to true on creation when omitted."},
        "list_id":           {"type": "integer", "description": "Add the contact to this list immediately"},
        "subscriptions":     {"type": "array", "description": "Optional subscriptions to create for the contact. Each entry upserts a canonical subscription (no integration) on (contact_id, subscription_type_id): if one already exists its can_email is updated rather than a duplicate being inserted.", "items": {"$ref": "#/definitions/ContactCreateSubscriptionItem"}},
        "options":           {"type": "object", "description": "Arbitrary key-value attributes stored on the contact. Persisted only when this POST creates a new contact; ignored when an active contact already exists for the email (same as first_name/last_name/source). Use PUT /contacts/{id} to merge options into an existing contact."},
        "system":            {"type": "string", "description": "External system name (e.g. 'hubspot'). Must be provided together with `identifier` to create an external identifier record."},
        "identifier":        {"type": "string", "description": "The contact's ID in the external system. Must be provided together with `system`."},
        "system_identifier": {"type": "string", "description": "Optional sub-system identifier (e.g. a HubSpot portal ID)."}
      }
    },
    "ContactCreateSubscriptionItem": {
      "type": "object",
      "required": ["subscription_type_id"],
      "description": "A subscription to create alongside a contact via POST /contacts. status_changed, is_active, and integration_id are set server-side.",
      "properties": {
        "subscription_type_id": {"type": "integer"},
        "can_email":            {"type": "boolean", "description": "Whether this subscription is emailable. Accepts 0/1 or true/false."}
      }
    },
    "ContactBulkCreateBody": {
      "type": "object",
      "required": ["contacts"],
      "description": "Envelope for POST /contacts/bulk. Each element follows the same shape as ContactCreateBody.",
      "properties": {
        "contacts": {
          "type": "array",
          "description": "Contacts to create. Must contain between 1 and 1000 items.",
          "minItems": 1,
          "maxItems": 1000,
          "items": {"$ref": "#/definitions/ContactCreateBody"}
        }
      }
    },
    "ContactBulkResultItem": {
      "type": "object",
      "description": "Per-item outcome from POST /contacts/bulk. `contact` is populated when status is 'created'; `error` is populated when status is 'failed'.",
      "properties": {
        "index":   {"type": "integer", "description": "Zero-based position of this item in the request's contacts array"},
        "status":  {"type": "string", "enum": ["created", "failed"]},
        "contact": {"$ref": "#/definitions/Contact"},
        "error":   {"type": "string", "description": "Reason this item failed (only present when status='failed')"}
      }
    },
    "ApiResponseContactBulk": {
      "type": "object",
      "properties": {
        "code":        {"type": "integer", "example": 201},
        "status_code": {"type": "integer", "example": 201},
        "metadata":    {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {"$ref": "#/definitions/ContactBulkResultItem"}
        }
      }
    },
    "ContactUpdateBody": {
      "type": "object",
      "properties": {
        "first_name":        {"type": "string"},
        "last_name":         {"type": "string"},
        "email":             {"type": "string", "format": "email"},
        "can_email":         {"type": "boolean"},
        "can_email_code":    {"type": "string", "description": "Machine-readable code indicating why can_email was changed"},
        "can_email_message": {"type": "string", "description": "Human-readable message describing why can_email was changed"},
        "options":           {"type": "object", "description": "Key-value attributes to merge into the contact's existing options. Input values take precedence; keys not in the request are preserved. Set a key to null to delete just that key. An empty object, null, or non-object value is a no-op — the stored options blob is never wholesale cleared."},
        "system":            {"type": "string", "description": "External system name (e.g. 'hubspot'). When provided with `identifier`, upserts an external identifier linking this contact to the external system."},
        "identifier":        {"type": "string", "description": "The contact's unique ID in the external system. Required when `system` is provided."},
        "system_identifier": {"type": "string", "description": "Optional qualifier within the external system (e.g. a list ID or portal ID)."}
      }
    },
    "SubscriptionCreateBody": {
      "type": "object",
      "required": ["contact_id", "subscription_type_id"],
      "properties": {
        "contact_id":           {"type": "string", "description": "Contact GUID or integer contact ID (both are returned by GET /contacts)"},
        "subscription_type_id": {"type": "integer"},
        "integration_id":       {"type": "integer", "description": "Optional integration scoping the subscription"},
        "can_email":            {"type": "boolean", "default": true},
        "is_active":            {"type": "boolean", "default": true},
        "synced_at":            {"type": "string", "format": "date-time", "description": "Timestamp of last sync from external system. Defaults to now."}
      }
    },
    "SubscriptionUpdateBody": {
      "type": "object",
      "properties": {
        "can_email":           {"type": "boolean"},
        "is_active":           {"type": "boolean"},
        "synced_at":           {"type": "string", "format": "date-time"},
        "unsubscribe_code":    {"type": "string", "description": "Machine-readable unsubscribe reason code"},
        "unsubscribe_message": {"type": "string", "description": "Human-readable unsubscribe reason message"}
      }
    },
    "SubscriptionTypeMapping": {
      "type": "object",
      "properties": {
        "id":                       {"type": "integer"},
        "integration_connection_id": {"type": "integer"},
        "external_type_id":         {"type": "string"},
        "external_type_name":       {"type": "string"},
        "is_active":                {"type": "boolean"},
        "created":                  {"type": "string", "format": "date-time"},
        "updated":                  {"type": "string", "format": "date-time"}
      }
    },
    "SubscriptionType": {
      "type": "object",
      "properties": {
        "id":         {"type": "integer"},
        "name":       {"type": "string"},
        "account_id": {"type": "integer"},
        "created":    {"type": "string", "format": "date-time"},
        "updated":    {"type": "string", "format": "date-time"},
        "mappings":   {"type": "array", "items": {"$ref": "#/definitions/SubscriptionTypeMapping"}}
      }
    },
    "ApiResponseSubscriptionType": {
      "type": "object",
      "properties": {
        "code":        {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata":    {"$ref": "#/definitions/ResponseMetadata"},
        "results":     {"type": "array", "items": {"$ref": "#/definitions/SubscriptionType"}}
      }
    },
    "ApiResponseSubscription": {
      "type": "object",
      "properties": {
        "code":        {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata":    {"$ref": "#/definitions/ResponseMetadata"},
        "results":     {"type": "array", "items": {"$ref": "#/definitions/SubscriptionFull"}}
      }
    },
    "ApiResponseContactArchive": {
      "type": "object",
      "properties": {
        "code":        {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata":    {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "contact_archived": {"type": "boolean"},
              "id":               {"type": "string", "description": "Contact GUID"}
            }
          }
        }
      }
    },
    "ApiResponseContactListArchive": {
      "type": "object",
      "properties": {
        "code":        {"type": "integer"},
        "status_code": {"type": "integer"},
        "metadata":    {"$ref": "#/definitions/ResponseMetadata"},
        "results": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "contact_list_archived": {"type": "boolean"}
            }
          }
        }
      }
    }
  }
}
