{
  "asyncapi": "2.6.0",
  "info": {
    "title": "Apinator Realtime WebSocket Protocol",
    "version": "1.0.0-breaking",
    "description": "Canonical WebSocket protocol contract for client SDK developers."
  },
  "defaultContentType": "application/json",
  "servers": {
    "production": {
      "url": "wss://ws-{region}.apinator.io/app/{app_key}",
      "protocol": "wss",
      "variables": {
        "region": {
          "default": "us"
        },
        "app_key": {
          "default": "app_xxx"
        }
      }
    },
    "local": {
      "url": "ws://localhost:8081/app/{app_key}",
      "protocol": "ws",
      "variables": {
        "app_key": {
          "default": "app_xxx"
        }
      }
    }
  },
  "channels": {
    "/app/{app_key}": {
      "parameters": {
        "app_key": {
          "description": "Public app key",
          "schema": {
            "type": "string"
          }
        }
      },
      "subscribe": {
        "operationId": "receiveFromServer",
        "message": {
          "oneOf": [
            {
              "$ref": "#/components/messages/ConnectionEstablished"
            },
            {
              "$ref": "#/components/messages/SubscriptionSucceeded"
            },
            {
              "$ref": "#/components/messages/SubscriptionError"
            },
            {
              "$ref": "#/components/messages/MemberAdded"
            },
            {
              "$ref": "#/components/messages/MemberRemoved"
            },
            {
              "$ref": "#/components/messages/Pong"
            },
            {
              "$ref": "#/components/messages/Error"
            },
            {
              "$ref": "#/components/messages/ClientEventBroadcast"
            }
          ]
        }
      },
      "publish": {
        "operationId": "sendFromClient",
        "message": {
          "oneOf": [
            {
              "$ref": "#/components/messages/Subscribe"
            },
            {
              "$ref": "#/components/messages/Unsubscribe"
            },
            {
              "$ref": "#/components/messages/Ping"
            },
            {
              "$ref": "#/components/messages/Pong"
            },
            {
              "$ref": "#/components/messages/ClientEvent"
            }
          ]
        }
      }
    }
  },
  "components": {
    "schemas": {
      "StringifiedJSON": {
        "type": "string",
        "contentMediaType": "application/json",
        "description": "JSON payload serialized as a string."
      },
      "ConnectionEstablishedData": {
        "type": "object",
        "required": [
          "socket_id",
          "activity_timeout"
        ],
        "properties": {
          "socket_id": {
            "type": "string"
          },
          "activity_timeout": {
            "type": "integer",
            "minimum": 1
          }
        },
        "additionalProperties": false
      },
      "SubscriptionSucceededData": {
        "description": "Usually {} for public/private channels, or a presence snapshot for presence channels.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/EmptyObject"
          },
          {
            "$ref": "#/components/schemas/PresenceSubscriptionData"
          }
        ]
      },
      "PresenceSubscriptionData": {
        "type": "object",
        "required": [
          "presence"
        ],
        "properties": {
          "presence": {
            "type": "object",
            "required": [
              "count",
              "ids",
              "hash"
            ],
            "properties": {
              "count": {
                "type": "integer",
                "minimum": 0
              },
              "ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "hash": {
                "type": "object",
                "additionalProperties": true,
                "description": "Map of user_id to user_info JSON values."
              }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "SubscriptionErrorData": {
        "type": "object",
        "required": [
          "type",
          "error",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          }
        },
        "additionalProperties": false
      },
      "PresenceMember": {
        "type": "object",
        "required": [
          "user_id",
          "user_info"
        ],
        "properties": {
          "user_id": {
            "type": "string"
          },
          "user_info": {
            "description": "Arbitrary user payload provided via channel_data.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": true
              },
              {
                "type": "array"
              },
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "integer"
              },
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "PresenceMemberRemoved": {
        "type": "object",
        "required": [
          "user_id"
        ],
        "properties": {
          "user_id": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ErrorData": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "SubscribeData": {
        "type": "object",
        "required": [
          "channel"
        ],
        "properties": {
          "channel": {
            "type": "string"
          },
          "auth": {
            "type": "string",
            "description": "Required for private/presence channels. Format: app_key:signature"
          },
          "channel_data": {
            "type": "string",
            "description": "JSON string required for presence channels containing user_id and user_info."
          }
        },
        "additionalProperties": false
      },
      "UnsubscribeData": {
        "type": "object",
        "required": [
          "channel"
        ],
        "properties": {
          "channel": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "EmptyObject": {
        "type": "object",
        "additionalProperties": false
      },
      "MessageConnectionEstablished": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:connection_established"
          },
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/StringifiedJSON"
              }
            ],
            "contentSchema": {
              "$ref": "#/components/schemas/ConnectionEstablishedData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageSubscriptionSucceeded": {
        "type": "object",
        "required": [
          "event",
          "channel",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:subscription_succeeded"
          },
          "channel": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/SubscriptionSucceededData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageSubscriptionError": {
        "type": "object",
        "required": [
          "event",
          "channel",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:subscription_error"
          },
          "channel": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/SubscriptionErrorData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageMemberAdded": {
        "type": "object",
        "required": [
          "event",
          "channel",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:member_added"
          },
          "channel": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/PresenceMember"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageMemberRemoved": {
        "type": "object",
        "required": [
          "event",
          "channel",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:member_removed"
          },
          "channel": {
            "type": "string"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/PresenceMemberRemoved"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageError": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:error"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/ErrorData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessagePing": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:ping"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/EmptyObject"
            }
          }
        },
        "additionalProperties": false
      },
      "MessagePong": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:pong"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "description": "Currently empty string in server responses.",
            "contentSchema": {
              "$ref": "#/components/schemas/EmptyObject"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageSubscribe": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:subscribe"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/SubscribeData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageUnsubscribe": {
        "type": "object",
        "required": [
          "event",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "const": "realtime:unsubscribe"
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON",
            "contentSchema": {
              "$ref": "#/components/schemas/UnsubscribeData"
            }
          }
        },
        "additionalProperties": false
      },
      "MessageClientEvent": {
        "type": "object",
        "required": [
          "event",
          "channel",
          "data"
        ],
        "properties": {
          "event": {
            "type": "string",
            "pattern": "^client-"
          },
          "channel": {
            "type": "string",
            "description": "Required for client events. Must be a non-public channel."
          },
          "data": {
            "$ref": "#/components/schemas/StringifiedJSON"
          }
        },
        "additionalProperties": false
      }
    },
    "messages": {
      "ConnectionEstablished": {
        "name": "ConnectionEstablished",
        "summary": "Server announces successful connection and socket id.",
        "payload": {
          "$ref": "#/components/schemas/MessageConnectionEstablished"
        }
      },
      "SubscriptionSucceeded": {
        "name": "SubscriptionSucceeded",
        "summary": "Server confirms subscription (presence channels include member snapshot).",
        "payload": {
          "$ref": "#/components/schemas/MessageSubscriptionSucceeded"
        }
      },
      "SubscriptionError": {
        "name": "SubscriptionError",
        "summary": "Server rejects a subscription attempt.",
        "payload": {
          "$ref": "#/components/schemas/MessageSubscriptionError"
        }
      },
      "MemberAdded": {
        "name": "MemberAdded",
        "summary": "Presence member joined.",
        "payload": {
          "$ref": "#/components/schemas/MessageMemberAdded"
        }
      },
      "MemberRemoved": {
        "name": "MemberRemoved",
        "summary": "Presence member left.",
        "payload": {
          "$ref": "#/components/schemas/MessageMemberRemoved"
        }
      },
      "Error": {
        "name": "Error",
        "summary": "General server error message.",
        "payload": {
          "$ref": "#/components/schemas/MessageError"
        }
      },
      "Ping": {
        "name": "Ping",
        "summary": "Client heartbeat ping.",
        "payload": {
          "$ref": "#/components/schemas/MessagePing"
        }
      },
      "Pong": {
        "name": "Pong",
        "summary": "Heartbeat pong response.",
        "payload": {
          "$ref": "#/components/schemas/MessagePong"
        }
      },
      "Subscribe": {
        "name": "Subscribe",
        "summary": "Client subscribe request.",
        "payload": {
          "$ref": "#/components/schemas/MessageSubscribe"
        }
      },
      "Unsubscribe": {
        "name": "Unsubscribe",
        "summary": "Client unsubscribe request.",
        "payload": {
          "$ref": "#/components/schemas/MessageUnsubscribe"
        }
      },
      "ClientEvent": {
        "name": "ClientEvent",
        "summary": "Client-originated event published on private/presence channels.",
        "payload": {
          "$ref": "#/components/schemas/MessageClientEvent"
        }
      },
      "ClientEventBroadcast": {
        "name": "ClientEventBroadcast",
        "summary": "Server fan-out of another client's event.",
        "payload": {
          "$ref": "#/components/schemas/MessageClientEvent"
        }
      }
    }
  }
}
