{
  "openapi": "3.1.0",
  "info": {
    "title": "Hailbell Push API",
    "version": "1.0.0",
    "description": "Send push notifications to iOS (APNs), Android (FCM) and web browsers. Every endpoint takes an API key as `Authorization: Bearer hbk_...`, made on the Push page of the dashboard. Keys have scopes (read, send, manage), a daily limit on notifications they create, and belong to one app or to all apps. AI agents can use the same operations through the MCP server at /mcp, with a key or by signing in with OAuth (see /.well-known/oauth-authorization-server). See /llms.txt for a short guide."
  },
  "servers": [
    {
      "url": "https://api.hailbell.com"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/apps": {
      "get": {
        "summary": "List apps",
        "operationId": "listApps",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apps": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/App"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      }
    },
    "/v1/notifications": {
      "post": {
        "summary": "Send or schedule a notification",
        "operationId": "createNotification",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Repeating a request with the same key returns the first notification (200) instead of sending again"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already created with this idempotency key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `send`."
      },
      "get": {
        "summary": "List recent notifications",
        "operationId": "listNotifications",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "notifications": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      }
    },
    "/v1/notifications/preview": {
      "post": {
        "summary": "Preview a notification without sending",
        "operationId": "previewNotification",
        "description": "Returns the cleaned content, the audience size per platform, warnings and the exact payload each platform receives.\n\nScope: `read`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Preview"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/notifications/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Get a notification and its numbers",
        "operationId": "getNotification",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      },
      "delete": {
        "summary": "Cancel a notification",
        "operationId": "cancelNotification",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "const": "canceled"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `send`."
      }
    },
    "/v1/notifications/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Cancel a notification",
        "operationId": "cancelNotificationPost",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "const": "canceled"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `send`."
      }
    },
    "/v1/audience/estimate": {
      "post": {
        "summary": "Count the devices an audience reaches",
        "operationId": "estimateAudience",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appId": {
                    "type": "string",
                    "description": "Needed only with an all-apps key"
                  },
                  "audience": {
                    "$ref": "#/components/schemas/Audience"
                  },
                  "kind": {
                    "$ref": "#/components/schemas/Kind"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "estimate": {
                      "$ref": "#/components/schemas/Estimate"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      }
    },
    "/v1/segments": {
      "get": {
        "summary": "List segments",
        "operationId": "listSegments",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "segments": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Segment"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      },
      "post": {
        "summary": "Create a segment",
        "operationId": "createSegment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SegmentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "segment": {
                      "$ref": "#/components/schemas/Segment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `manage`."
      }
    },
    "/v1/segments/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "summary": "Replace a segment",
        "operationId": "updateSegment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SegmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `manage`."
      },
      "delete": {
        "summary": "Delete a segment",
        "operationId": "deleteSegment",
        "description": "Scope: `manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/context": {
      "get": {
        "summary": "What to know before writing a message",
        "operationId": "getContext",
        "description": "The local time where the audience is, their countries, the hours they usually use the app, upcoming occasions (festivals, holidays, shopping days and the app's own dates), the app's limits and the template variables.\n\nScope: `read`.",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 14
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Context"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/tag-keys": {
      "get": {
        "summary": "Tag keys in use",
        "operationId": "listTagKeys",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "n": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      }
    },
    "/v1/users/{externalId}": {
      "parameters": [
        {
          "name": "externalId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "appId",
          "in": "query",
          "schema": {
            "type": "string"
          },
          "description": "Needed only with an all-apps key"
        }
      ],
      "get": {
        "summary": "Get a user by your own user id",
        "operationId": "getUser",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Tags, devices and recent events"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        },
        "description": "Scope: `read`."
      }
    },
    "/v1/users/{externalId}/tags": {
      "parameters": [
        {
          "name": "externalId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Set tags on a user",
        "operationId": "setUserTags",
        "description": "An empty string removes a tag. Up to 100 tags per user.\n\nScope: `manage`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tags"
                ],
                "properties": {
                  "appId": {
                    "type": "string",
                    "description": "Needed only with an all-apps key"
                  },
                  "tags": {
                    "type": "object",
                    "additionalProperties": {
                      "type": [
                        "string",
                        "number",
                        "boolean"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/coming-up": {
      "get": {
        "summary": "What is coming: scheduled sends and occasions",
        "operationId": "comingUp",
        "description": "Notifications scheduled or still sending, and upcoming festivals, holidays and the app's own dates, in time order. Without appId (on an all-apps key) it covers every app.\n\nScope: `read`.",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "One app, or omit for every app the key covers"
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/settings": {
      "get": {
        "summary": "Push setup of an app",
        "operationId": "getSettings",
        "description": "Readiness per platform, subscriber counts and settings (limits, occasions, web prompt, Android settings).\n\nScope: `read`.",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "patch": {
        "summary": "Change push setup",
        "operationId": "updateSettings",
        "description": "Any of `limits`, `occasions`, `web` (`prompt`, `welcome`, `subject`), `firebaseProject`, `androidOffersChannel`. Each part given replaces that part.\n\nScope: `manage`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appId": {
                    "type": "string"
                  },
                  "limits": {
                    "type": "object"
                  },
                  "occasions": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "web": {
                    "type": "object"
                  },
                  "firebaseProject": {
                    "type": "string"
                  },
                  "androidOffersChannel": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/subscribers": {
      "get": {
        "summary": "Devices of an app",
        "operationId": "listSubscribers",
        "description": "Newest 100 devices, or those matching `q` (name, email, external id or subscription id).\n\nScope: `read`.",
        "parameters": [
          {
            "name": "appId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Needed only with an all-apps key"
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/subscribers/{id}": {
      "patch": {
        "summary": "Mark a test device",
        "operationId": "setTestDevice",
        "description": "Scope: `manage`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "test"
                ],
                "properties": {
                  "test": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/images": {
      "post": {
        "summary": "Store a notification image",
        "operationId": "uploadImage",
        "description": "PNG, JPEG, WebP or GIF up to 1 MB, from an https `url` or base64 `data`. Returns the URL for `content.image`.\n\nScope: `send`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appId": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  },
                  "data": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "201": {
            "description": "Stored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "hbk_..."
      },
      "oauth": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth/authorize",
            "tokenUrl": "/oauth/token",
            "refreshUrl": "/oauth/token",
            "scopes": {
              "read": "Apps, stats, audiences, calendar",
              "send": "Send, schedule, cancel, images",
              "manage": "Segments, tags, test devices, setup"
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error, with a message that says what to change",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Kind": {
        "type": "string",
        "enum": [
          "marketing",
          "transactional"
        ],
        "default": "marketing",
        "description": "transactional also reaches users who opted out of marketing. Use it only for messages the user needs."
      },
      "App": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "apnsTopic": {
            "type": [
              "string",
              "null"
            ]
          },
          "subscribers": {
            "$ref": "#/components/schemas/Estimate"
          },
          "ready": {
            "type": "object",
            "properties": {
              "apns": {
                "type": "boolean"
              },
              "fcm": {
                "type": "boolean"
              },
              "web": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Estimate": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "apns": {
            "type": "integer"
          },
          "fcm": {
            "type": "integer"
          },
          "web": {
            "type": "integer"
          }
        }
      },
      "Content": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "subtitle": {
            "type": "string",
            "maxLength": 200,
            "description": "iOS only"
          },
          "body": {
            "type": "string",
            "maxLength": 2000,
            "description": "Not needed when silent"
          },
          "image": {
            "type": "string",
            "format": "uri",
            "description": "https only"
          },
          "icon": {
            "type": "string",
            "format": "uri"
          },
          "url": {
            "type": "string",
            "description": "Opened on tap: https://… or a deep link"
          },
          "appUrl": {
            "type": "string"
          },
          "webUrl": {
            "type": "string"
          },
          "buttons": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "object",
              "required": [
                "text"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "text": {
                  "type": "string",
                  "maxLength": 40
                },
                "url": {
                  "type": "string"
                }
              }
            }
          },
          "sound": {
            "type": "string",
            "description": "\"none\" or a bundled sound name"
          },
          "badge": {
            "type": "object",
            "properties": {
              "type": {
                "enum": [
                  "set",
                  "increase",
                  "none"
                ]
              },
              "count": {
                "type": "integer"
              }
            }
          },
          "priority": {
            "enum": [
              "high",
              "normal"
            ]
          },
          "ttl": {
            "type": "integer",
            "maximum": 2419200
          },
          "interruptionLevel": {
            "enum": [
              "passive",
              "active",
              "time-sensitive",
              "critical"
            ]
          },
          "relevanceScore": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "accentColor": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "group": {
            "type": "string"
          },
          "collapseId": {
            "type": "string",
            "maxLength": 32
          },
          "androidChannelId": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "Up to 2 KB, passed to the app"
          },
          "silent": {
            "type": "boolean"
          },
          "requireInteraction": {
            "type": "boolean"
          },
          "versions": {
            "type": "array",
            "minItems": 2,
            "maxItems": 5,
            "description": "Per-person learning: versions about different topics; each person gets the topic their own taps favour (Thompson sampling, starting from the app-wide rate).",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "topic": {
                  "type": "string",
                  "maxLength": 32
                },
                "title": {
                  "type": "string"
                },
                "subtitle": {
                  "type": "string"
                },
                "body": {
                  "type": "string"
                },
                "image": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "buttons": {
                  "type": "array"
                }
              }
            }
          },
          "variants": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/Variant"
            },
            "description": "Other versions for some people; the first whose when matches replaces the fields it lists"
          }
        },
        "description": "Text fields and links can use {{variables}} with an optional fallback, e.g. {{first_name|there}}. Variables: first_name, name, external_id, app_name, city, country, time_of_day, greeting, weekday, weather, temp, occasion, tag.<key>."
      },
      "Variant": {
        "type": "object",
        "required": [
          "when"
        ],
        "properties": {
          "when": {
            "type": "object",
            "properties": {
              "weather": {
                "type": "array",
                "items": {
                  "enum": [
                    "sunny",
                    "clear",
                    "cloudy",
                    "rainy",
                    "stormy",
                    "snowy",
                    "foggy"
                  ]
                }
              },
              "timeOfDay": {
                "type": "array",
                "items": {
                  "enum": [
                    "morning",
                    "afternoon",
                    "evening",
                    "night"
                  ]
                }
              },
              "country": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "occasion": {
                "oneOf": [
                  {
                    "const": true
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                ]
              },
              "tag": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string"
                  },
                  "value": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "title": {
            "type": "string"
          },
          "subtitle": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "image": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "buttons": {
            "type": "array"
          }
        }
      },
      "Delivery": {
        "type": "object",
        "description": "now: everyone at sendAfter (promotional pushes wait out quiet hours). local_time: at time in each person's time zone. best_hour: at the hour each person usually uses the app, else fallback. Waves run over up to 24 hours.",
        "properties": {
          "mode": {
            "enum": [
              "now",
              "local_time",
              "best_hour"
            ],
            "default": "now"
          },
          "time": {
            "type": "string",
            "pattern": "^\\d{1,2}:\\d{2}$"
          },
          "fallback": {
            "type": "string",
            "default": "19:00"
          }
        }
      },
      "Context": {
        "type": "object",
        "properties": {
          "now": {
            "type": "string"
          },
          "audienceTime": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "utcOffsetMinutes": {
                  "type": "integer"
                },
                "devices": {
                  "type": "integer"
                },
                "localTime": {
                  "type": "string"
                }
              }
            }
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "country": {
                  "type": "string"
                },
                "n": {
                  "type": "integer"
                }
              }
            }
          },
          "usualHours": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "localHour": {
                  "type": "integer"
                },
                "devices": {
                  "type": "integer"
                }
              }
            }
          },
          "upcomingOccasions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "date": {
                  "type": "string"
                },
                "countries": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "string"
                  }
                },
                "source": {
                  "enum": [
                    "calendar",
                    "app"
                  ]
                }
              }
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "marketingPerDay": {
                "type": "integer"
              },
              "restAfterIgnored": {
                "type": "integer"
              },
              "quietHours": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string"
                  },
                  "end": {
                    "type": "string"
                  }
                }
              },
              "defaultTimezone": {
                "type": "string"
              }
            }
          },
          "templateVariables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Filter": {
        "type": "object",
        "description": "A condition, or {\"operator\":\"OR\"} to start a new group. Conditions are ANDed within a group; groups are ORed.",
        "properties": {
          "operator": {
            "const": "OR"
          },
          "field": {
            "enum": [
              "last_session",
              "first_session",
              "session_count",
              "language",
              "app_version",
              "country",
              "platform",
              "tag",
              "event",
              "external_id",
              "test_users"
            ]
          },
          "relation": {
            "enum": [
              ">",
              "<",
              "=",
              "!=",
              "exists",
              "not_exists",
              "happened",
              "not_happened"
            ]
          },
          "key": {
            "type": "string"
          },
          "value": {},
          "name": {
            "type": "string"
          },
          "hours_ago": {
            "type": "number"
          }
        }
      },
      "Audience": {
        "type": "object",
        "description": "Empty means everyone subscribed.",
        "properties": {
          "segments": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "excludeSegments": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Filter"
            }
          },
          "externalIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "subscriptionIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "testUsers": {
            "type": "boolean"
          },
          "platforms": {
            "type": "array",
            "items": {
              "enum": [
                "apns",
                "fcm",
                "web"
              ]
            }
          }
        }
      },
      "NotificationRequest": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "appId": {
            "type": "string",
            "description": "Needed only with an all-apps key"
          },
          "name": {
            "type": "string",
            "maxLength": 128
          },
          "kind": {
            "$ref": "#/components/schemas/Kind"
          },
          "content": {
            "$ref": "#/components/schemas/Content"
          },
          "audience": {
            "$ref": "#/components/schemas/Audience"
          },
          "delivery": {
            "$ref": "#/components/schemas/Delivery"
          },
          "sendAfter": {
            "type": "string",
            "format": "date-time",
            "description": "Up to 30 days ahead"
          },
          "idempotencyKey": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "appId": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/Kind"
          },
          "status": {
            "enum": [
              "scheduled",
              "sending",
              "sent",
              "canceled",
              "failed"
            ]
          },
          "source": {
            "enum": [
              "dashboard",
              "api",
              "mcp"
            ]
          },
          "content": {
            "$ref": "#/components/schemas/Content"
          },
          "audience": {
            "$ref": "#/components/schemas/Audience"
          },
          "delivery": {
            "$ref": "#/components/schemas/Delivery"
          },
          "sendAfter": {
            "type": "integer"
          },
          "startedAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "finishedAt": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "integer"
          },
          "stats": {
            "type": "object",
            "properties": {
              "recipients": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "clicked": {
                "type": "integer"
              }
            }
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "estimate": {
            "$ref": "#/components/schemas/Estimate"
          },
          "versions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "topic": {
                  "type": "string"
                },
                "sent": {
                  "type": "integer"
                },
                "clicked": {
                  "type": "integer"
                },
                "clickRate": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "Preview": {
        "type": "object",
        "properties": {
          "content": {
            "$ref": "#/components/schemas/Content"
          },
          "audience": {
            "$ref": "#/components/schemas/Audience"
          },
          "kind": {
            "$ref": "#/components/schemas/Kind"
          },
          "delivery": {
            "$ref": "#/components/schemas/Delivery"
          },
          "estimate": {
            "$ref": "#/components/schemas/Estimate"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "samples": {
            "type": "array",
            "description": "How it reads for a few real recipients",
            "items": {
              "type": "object"
            }
          },
          "waves": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "at": {
                  "type": "string"
                },
                "devices": {
                  "type": "integer"
                }
              }
            }
          },
          "cappedToday": {
            "type": "integer"
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "unknownVariables": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "payloads": {
            "type": "object"
          },
          "confirmToken": {
            "type": "string",
            "description": "Used by the MCP send tool"
          },
          "confirmExpiresIn": {
            "type": "integer"
          }
        }
      },
      "Segment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Filter"
            }
          },
          "size": {
            "type": "integer"
          },
          "createdAt": {
            "type": "integer"
          }
        }
      },
      "SegmentRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "appId": {
            "type": "string",
            "description": "Needed only with an all-apps key"
          },
          "name": {
            "type": "string",
            "maxLength": 80
          },
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Filter"
            }
          }
        }
      }
    }
  }
}
