# Subscribe via API
You can create and subscribe to a topic either in the [web UI](web.md), via the [phone app](phone.md), or in your own 
app or script by subscribing the API. This page describes how to subscribe via API. You may also want to check out the 
page that describes how to [publish messages](../publish.md).

The subscription API relies on a simple HTTP GET request with a streaming HTTP response, i.e **you open a GET request and
the connection stays open forever**, sending messages back as they come in. There are three different API endpoints, which 
only differ in the response format:

* [JSON stream](#subscribe-as-json-stream): `<topic>/json` returns a JSON stream, with one JSON message object per line
* [SSE stream](#subscribe-as-sse-stream): `<topic>/sse` returns messages as [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events), which
  can be used with [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
* [Raw stream](#subscribe-as-raw-stream): `<topic>/raw` returns messages as raw text, with one line per message

## Subscribe as JSON stream
Here are a few examples of how to consume the JSON endpoint (`<topic>/json`). For almost all languages, **this is the 
recommended way to subscribe to a topic**. The notable exception is JavaScript, for which the 
[SSE/EventSource stream](#subscribe-as-sse-stream) is much easier to work with.

=== "Command line (curl)"
    ```
    $ curl -s ntfy.sh/disk-alerts/json
    {"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
    {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"}
    {"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"}
    ...
    ```

=== "HTTP"
    ``` http
    GET /disk-alerts/json HTTP/1.1
    Host: ntfy.sh

    HTTP/1.1 200 OK
    Content-Type: application/x-ndjson; charset=utf-8
    Transfer-Encoding: chunked
    
    {"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"}
    {"id":"hwQ2YpKdmg","time":1635528741,"event":"message","topic":"mytopic","message":"Disk full"}
    {"id":"DGUDShMCsc","time":1635528787,"event":"keepalive","topic":"mytopic"}
    ...
    ```

=== "Go"
    ``` go
    resp, err := http.Get("https://ntfy.sh/disk-alerts/json")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        println(scanner.Text())
    }
    ```

=== "PHP"
    ``` php-inline
    $fp = fopen('https://ntfy.sh/disk-alerts/json', 'r');
    if (!$fp) die('cannot open stream');
    while (!feof($fp)) {
        echo fgets($fp, 2048);
        flush();
    }
    fclose($fp);
    ```

## Subscribe as SSE stream
Using [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) in JavaScript, you can consume
notifications via a [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) stream. It's incredibly 
easy to use. Here's what it looks like. You may also want to check out the [live example](/example.html).

=== "Command line (curl)"
    ```
    $ curl -s ntfy.sh/mytopic/sse
    event: open
    data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"}
    
    data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"}
    
    event: keepalive
    data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"}
    ...
    ```

=== "HTTP"
    ``` http
    GET /mytopic/sse HTTP/1.1
    Host: ntfy.sh

    HTTP/1.1 200 OK
    Content-Type: text/event-stream; charset=utf-8
    Transfer-Encoding: chunked

    event: open
    data: {"id":"weSj9RtNkj","time":1635528898,"event":"open","topic":"mytopic"}
    
    data: {"id":"p0M5y6gcCY","time":1635528909,"event":"message","topic":"mytopic","message":"Hi!"}
    
    event: keepalive
    data: {"id":"VNxNIg5fpt","time":1635528928,"event":"keepalive","topic":"test"}
    ...
    ```

=== "JavaScript"
    ``` javascript
    const eventSource = new EventSource('https://ntfy.sh/mytopic/sse');
    eventSource.onmessage = (e) => {
      console.log(e.data);
    };
    ```

## Subscribe as raw stream
The `/raw` endpoint will output one line per message, and **will only include the message body**. It's useful for extremely
simple scripts, and doesn't include all the data. Additional fields such as [priority](../publish.md#message-priority), 
[tags](../publish.md#tags--emojis--) or [message title](../publish.md#message-title) are not included in this output 
format. Keepalive messages are sent as empty lines.

=== "Command line (curl)"
    ```
    $ curl -s ntfy.sh/disk-alerts/raw
    
    Disk full
    ...
    ```

=== "HTTP"
    ``` http
    GET /disk-alerts/raw HTTP/1.1
    Host: ntfy.sh

    HTTP/1.1 200 OK
    Content-Type: text/plain; charset=utf-8
    Transfer-Encoding: chunked

    Disk full
    ...
    ```

=== "Go"
    ``` go
    resp, err := http.Get("https://ntfy.sh/disk-alerts/raw")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        println(scanner.Text())
    }
    ```

=== "PHP"
    ``` php-inline
    $fp = fopen('https://ntfy.sh/disk-alerts/raw', 'r');
    if (!$fp) die('cannot open stream');
    while (!feof($fp)) {
        echo fgets($fp, 2048);
        flush();
    }
    fclose($fp);
    ```

## JSON message format
Both the [`/json` endpoint](#subscribe-as-json-stream) and the [`/sse` endpoint](#subscribe-as-sse-stream) return a JSON
format of the message. It's very straight forward:

| Field | Required | Type | Example | Description |
|---|---|---|---|---|
| `id` | ✔️ | *string* | `hwQ2YpKdmg` | Randomly chosen message identifier |
| `time` | ✔️ | *int* | `1635528741` | Message date time, as Unix time stamp |  
| `event` | ✔️ | `open`, `keepalive` or `message` | `message` | Message type, typically you'd be only interested in `message` |
| `topic` | ✔️ | *string* | `topic1,topic2` | Comma-separated list of topics the message is associated with; only one for all `message` events, but may be a list in `open` events |
| `message` | - | *string* | `Some message` | Message body; always present in `message` events |
| `title` | - | *string* | `Some title` | Message [title](../publish.md#message-title); if not set defaults to `ntfy.sh/<topic>` |
| `tags` | - | *string array* | `["tag1","tag2"]` | List of [tags](../publish.md#tags-emojis) that may or not map to emojis |
| `priority` | - | *1, 2, 3, 4, or 5* | `4` | Message [priority](../publish.md#message-priority) with 1=min, 3=default and 5=max |

Here's an example for each message type:

=== "Notification message"
    ``` json
    {
      "id": "wze9zgqK41",
      "time": 1638542110,
      "event": "message",
      "topic": "phil_alerts",
      "priority": 5,
      "tags": [
        "warning",
        "skull"
      ],
      "title": "Unauthorized access detected",
      "message": "Remote access to phils-laptop detected. Act right away."
    }
    ```


=== "Notification message (minimal)"
    ``` json
    {
      "id": "wze9zgqK41",
      "time": 1638542110,
      "event": "message",
      "topic": "phil_alerts",
      "message": "Remote access to phils-laptop detected. Act right away."
    }
    ```

=== "Open message"
    ``` json
    {
      "id": "2pgIAaGrQ8",
      "time": 1638542215,
      "event": "open",
      "topic": "phil_alerts"
    }
    ```

=== "Keepalive message" 
    ``` json
    {
      "id": "371sevb0pD",
      "time": 1638542275,
      "event": "keepalive",
      "topic": "phil_alerts"
    }
    ```    

## Advanced features

### Fetching cached messages
Messages may be cached for a couple of hours (see [message caching](../config.md#message-cache)) to account for network
interruptions of subscribers. If the server has configured message caching, you can read back what you missed by using 
the `since=` query parameter. It takes either a duration (e.g. `10m` or `30s`), a Unix timestamp (e.g. `1635528757`) 
or `all` (all cached messages).

```
curl -s "ntfy.sh/mytopic/json?since=10m"
```

### Polling
You can also just poll for messages if you don't like the long-standing connection using the `poll=1`
query parameter. The connection will end after all available messages have been read. This parameter can be
combined with `since=` (defaults to `since=all`).

```
curl -s "ntfy.sh/mytopic/json?poll=1"
```

### Subscribing to multiple topics
It's possible to subscribe to multiple topics in one HTTP call by providing a
comma-separated list of topics in the URL. This allows you to reduce the number of connections you have to maintain:

```
$ curl -s ntfy.sh/mytopic1,mytopic2/json
{"id":"0OkXIryH3H","time":1637182619,"event":"open","topic":"mytopic1,mytopic2,mytopic3"}
{"id":"dzJJm7BCWs","time":1637182634,"event":"message","topic":"mytopic1","message":"for topic 1"}
{"id":"Cm02DsxUHb","time":1637182643,"event":"message","topic":"mytopic2","message":"for topic 2"}
```