{
  "openapi": "3.0.0",
  "info": {
    "title": "Pushello API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.pushello.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic auth carrying only the API key secret: Authorization: Basic base64(<key secret>)."
      },
      "accessToken": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Operator session token issued by the Pushello dashboard: Authorization: Token <access token>."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "message",
          "status"
        ],
        "properties": {
          "details": {
            "type": "object",
            "additionalProperties": true
          },
          "message": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Device": {
        "type": "object",
        "required": [
          "_id",
          "projectId",
          "platform"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "enum": [
              "APN",
              "FCM",
              "WebPush",
              "WNS"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ]
          },
          "interests": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "creationTime": {
            "type": "string",
            "format": "date-time"
          },
          "lastEditTime": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "DeviceRegistration": {
        "type": "object",
        "required": [
          "deviceId",
          "initialInterestSet",
          "registrationVersion"
        ],
        "properties": {
          "deviceId": {
            "type": "string"
          },
          "initialInterestSet": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "registrationVersion": {
            "type": "integer",
            "minimum": 1
          }
        },
        "additionalProperties": false
      },
      "Notification": {
        "type": "object",
        "required": [
          "_id",
          "projectId"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "creationTime": {
            "type": "string",
            "format": "date-time"
          },
          "lastEditTime": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "PublishResult": {
        "type": "object",
        "required": [
          "publishId",
          "targetCount",
          "matchedDeviceCount",
          "attemptCount",
          "deliveryCount",
          "successCount",
          "failureCount",
          "ambiguousCount",
          "skippedCount",
          "outcome"
        ],
        "properties": {
          "publishId": {
            "type": "string"
          },
          "targetCount": {
            "type": "integer",
            "minimum": 0
          },
          "matchedDeviceCount": {
            "type": "integer",
            "minimum": 0
          },
          "attemptCount": {
            "type": "integer",
            "minimum": 0
          },
          "deliveryCount": {
            "type": "integer",
            "minimum": 0
          },
          "successCount": {
            "type": "integer",
            "minimum": 0
          },
          "failureCount": {
            "type": "integer",
            "minimum": 0
          },
          "ambiguousCount": {
            "type": "integer",
            "minimum": 0
          },
          "skippedCount": {
            "type": "integer",
            "minimum": 0
          },
          "outcome": {
            "type": "string",
            "enum": [
              "DELIVERED",
              "PARTIAL_SUCCESS",
              "AMBIGUOUS",
              "NO_MATCHING_DEVICES",
              "NO_DELIVERIES",
              "ALL_FAILED"
            ]
          }
        },
        "additionalProperties": false
      },
      "Project": {
        "type": "object",
        "required": [
          "_id"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "webPushPublicKey": {
            "type": "string"
          },
          "creationTime": {
            "type": "string",
            "format": "date-time"
          },
          "lastEditTime": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "WebhookSubscription": {
        "type": "object",
        "required": [
          "_id",
          "projectId",
          "url",
          "active"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "consecutiveFailures": {
            "type": "integer",
            "minimum": 0
          },
          "creationTime": {
            "type": "string",
            "format": "date-time"
          },
          "lastEditTime": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "WebhookSubscriptionWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookSubscription"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "readOnly": true,
                "description": "Signing secret returned exactly once on creation."
              }
            }
          }
        ]
      },
      "DeletedResource": {
        "type": "object",
        "required": [
          "_id"
        ],
        "properties": {
          "_id": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    }
  },
  "paths": {
    "/api/devices": {
      "get": {
        "tags": [
          "Devices"
        ],
        "summary": "List registered devices",
        "description": "Lists the devices registered in your organization. Requires the `devices:read` scope.\n",
        "security": [
          {
            "apiKey": []
          },
          {
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "projectId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "platform",
            "schema": {
              "type": "string",
              "enum": [
                "APN",
                "FCM",
                "WebPush",
                "WNS"
              ]
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of devices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Device"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key is missing the `devices:read` scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API key rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getApiDevices"
      }
    },
    "/api/projects/{projectId}/devices": {
      "post": {
        "tags": [
          "Devices"
        ],
        "summary": "Register a device",
        "description": "Registers a device token so it can receive push notifications. Called by the client SDKs; no API key is required.\n",
        "parameters": [
          {
            "in": "path",
            "name": "projectId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "platform"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "APN",
                      "FCM",
                      "WebPush",
                      "WNS"
                    ]
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "sandbox",
                      "production"
                    ]
                  },
                  "metadata": {
                    "type": "object"
                  },
                  "previousToken": {
                    "type": "string"
                  },
                  "wnsAuthenticationMode": {
                    "type": "string",
                    "enum": [
                      "legacy",
                      "entra"
                    ],
                    "description": "Required as `entra` for Windows App SDK channels. Omitted historical WNS registrations default to Partner Center `legacy`; invalid on other platforms.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The registered device id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRegistration"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "postApiProjectsProjectIdDevices"
      }
    },
    "/api/notifications": {
      "get": {
        "tags": [
          "Notifications"
        ],
        "summary": "List published notifications",
        "description": "Lists the notifications published in your organization, newest first. Requires the `notifications:read` scope.\n",
        "security": [
          {
            "apiKey": []
          },
          {
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "projectId",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "skip",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "in": "query",
            "name": "sortDirection",
            "schema": {
              "type": "string",
              "enum": [
                "ASC",
                "DESC"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of notifications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Notification"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key is missing the `notifications:read` scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API key rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getApiNotifications"
      }
    },
    "/api/projects/{projectId}/notifications/publish": {
      "post": {
        "tags": [
          "Notifications"
        ],
        "summary": "Publish a notification to interests or users",
        "description": "Fans a push notification out to every device subscribed to the given interests, or associated with the given user ids. `verifiedUsers` targets only cryptographically verified device-user bindings. Authenticate with a marketplace API key (Basic auth) carrying the `notifications:write` scope, or with the project's legacy publish token (Bearer auth). Bearer publishes that include `users` or `verifiedUsers` require the private project secret; the legacy project key remains valid only for interest broadcasts on projects that have not disabled that legacy credential. DodoDentist has disabled it and requires its private project secret for every Bearer publish, including interest broadcasts.\n",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "projectId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "interests": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "users": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "verifiedUsers": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "apns": {
                    "type": "object"
                  },
                  "fcm": {
                    "type": "object"
                  },
                  "webpush": {
                    "type": "object"
                  },
                  "wns": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The publish id and target/attempt/outcome counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublishResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key is missing the `notifications:write` scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Project not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API key rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "postApiProjectsProjectIdNotificationsPublish"
      }
    },
    "/api/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Lists the projects in your organization (push provider credentials stripped). Requires the `projects:read` scope.\n",
        "security": [
          {
            "apiKey": []
          },
          {
            "accessToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Array of projects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key is missing the `projects:read` scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API key rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getApiProjects"
      }
    },
    "/api/webhooksubscriptions": {
      "get": {
        "tags": [
          "Webhook subscriptions"
        ],
        "summary": "List webhook subscriptions",
        "description": "Webhook subscriptions deliver `notification.published`, `device.created` and `device.deleted` events to your server as signed POST requests (`X-Pushello-Signature: t=<timestamp>,v1=<hex HMAC-SHA256 of \"timestamp.body\">`). An endpoint failing 20 times in a row is disabled automatically. Subscriptions are managed with an operator access token; the `secret` is only returned once, on create.\n",
        "security": [
          {
            "accessToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Array of webhook subscriptions (without secrets)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookSubscription"
                  }
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "getApiWebhooksubscriptions"
      },
      "post": {
        "tags": [
          "Webhook subscriptions"
        ],
        "summary": "Create a webhook subscription",
        "description": "The response includes the signing `secret` exactly once — store it; it cannot be retrieved again.\n",
        "security": [
          {
            "accessToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "projectId",
                  "url"
                ],
                "properties": {
                  "projectId": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "example": "https://example.com/pushello-webhook"
                  },
                  "events": {
                    "type": "array",
                    "description": "Empty array subscribes to all events",
                    "items": {
                      "type": "string",
                      "enum": [
                        "notification.published",
                        "device.created",
                        "device.deleted"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created subscription, including its secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionWithSecret"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "postApiWebhooksubscriptions"
      }
    },
    "/api/webhooksubscriptions/{webhookSubscriptionId}": {
      "put": {
        "tags": [
          "Webhook subscriptions"
        ],
        "summary": "Update a webhook subscription",
        "description": "`url`, `events` and `active` are editable; the secret and project are immutable. Re-enabling an auto-disabled endpoint is done by setting `active` back to true.\n",
        "security": [
          {
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "webhookSubscriptionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subscription (without secret)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "putApiWebhooksubscriptionsWebhookSubscriptionId"
      },
      "delete": {
        "tags": [
          "Webhook subscriptions"
        ],
        "summary": "Delete a webhook subscription",
        "security": [
          {
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "in": "path",
            "name": "webhookSubscriptionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResource"
                }
              }
            }
          },
          "default": {
            "description": "Request failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "deleteApiWebhooksubscriptionsWebhookSubscriptionId",
        "description": "Delete a webhook subscription"
      }
    }
  },
  "tags": []
}
