diff --git a/restapi/README.md b/restapi/README.md
new file mode 100644
index 0000000..2928084
--- /dev/null
+++ b/restapi/README.md
@@ -0,0 +1,65 @@
+# restapi
+
+Plugin **restapi** provides a REST API to communicate with the *collectd*
+daemon.
+
+## Description
+
+The *restapi plugin* starts a webserver and waits for incoming REST API
+requests.
+
+## Building
+
+To build this plugin, the collectd header files are required.
+
+On Debian and Ubuntu, the collectd headers are available from the
+`collectd-dev` package. Once installed, add the import paths to the
+`CGI_CPPFLAGS`:
+
+```bash
+export CGO_CPPFLAGS="-I/usr/include/collectd/core/daemon \
+-I/usr/include/collectd/core -I/usr/include/collectd"
+```
+
+Alternatively, you can grab the collectd sources, run the `configure` script,
+and reference the header files from there:
+
+```bash
+TOP_SRCDIR="${HOME}/collectd"
+export CGO_CPPFLAGS="-I${TOP_SRCDIR}/src -I${TOP_SRCDIR}/src/daemon"
+```
+
+Then build the plugin with the "c-shared" buildmode:
+
+```bash
+go build -buildmode=c-shared -o restapi.so
+```
+
+## Configuration
+
+### Synopsis
+
+```
+LoadPlugin restapi
+
+ Addr "::"
+ Port "8443"
+ CertFile "/path/to/cert_file.pem"
+ KeyFile "/path/to/key_file.pem"
+
+```
+
+### Options
+
+* **Addr** *Network address*
+
+ Addredd to listen to. Defaults to `""` (any address).
+* **Port** *Port*
+
+ Post to listen to. Defaults to `8080` (`8443` if **CertFile** is specified).
+* **CertFile** *Path*
+ **KeyFile** *Path*
+
+ TLS certificate and key files. Refer to
+ [`"net/http".ListenAndServeTLS`](https://golang.org/pkg/net/http/#ListenAndServeTLS)
+ for more information on the TLS setup.
diff --git a/restapi/restapi.go b/restapi/restapi.go
new file mode 100644
index 0000000..63762e2
--- /dev/null
+++ b/restapi/restapi.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "time"
+
+ "collectd.org/api"
+ "collectd.org/config"
+ "collectd.org/plugin"
+ "go.uber.org/multierr"
+)
+
+const pluginName = "restapi"
+
+type restapi struct {
+ srv *http.Server
+}
+
+func init() {
+ ra := &restapi{}
+
+ plugin.RegisterConfig(pluginName, ra)
+ plugin.RegisterShutdown(pluginName, ra)
+}
+
+func valueListHandler(w http.ResponseWriter, req *http.Request) {
+ if req.Method != http.MethodPost {
+ w.WriteHeader(http.StatusNotImplemented)
+ fmt.Fprintln(w, "Only POST is currently supported.")
+ return
+ }
+
+ var vls []api.ValueList
+ if err := json.NewDecoder(req.Body).Decode(&vls); err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprintln(w, "parsing JSON failed:", err)
+ return
+ }
+
+ var errs error
+ for _, vl := range vls {
+ errs = multierr.Append(errs,
+ plugin.Write(req.Context(), &vl))
+ }
+
+ if errs != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintln(w, "plugin.Write():", errs)
+ return
+ }
+}
+
+type srvConfig struct {
+ Args string // unused
+ Addr string
+ Port string
+ CertFile string
+ KeyFile string
+}
+
+func (ra *restapi) Configure(_ context.Context, rawConfig config.Block) error {
+ cfg := srvConfig{}
+ if err := rawConfig.Unmarshal(&cfg); err != nil {
+ return err
+ }
+
+ if (cfg.CertFile == "") != (cfg.KeyFile == "") {
+ return fmt.Errorf("CertFile=%q, KeyFile=%q; need both for TLS setup",
+ cfg.CertFile, cfg.KeyFile)
+ }
+
+ if cfg.Port == "" {
+ cfg.Port = "8080"
+ if cfg.CertFile != "" {
+ cfg.Port = "8443"
+ }
+ }
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/valueList", valueListHandler)
+
+ ra.srv = &http.Server{
+ Addr: net.JoinHostPort(cfg.Addr, cfg.Port),
+ Handler: mux,
+ }
+
+ go func() {
+ var err error
+ if cfg.CertFile != "" {
+ err = ra.srv.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile)
+ } else {
+ err = ra.srv.ListenAndServe()
+ }
+ if !errors.Is(err, http.ErrServerClosed) {
+ plugin.Errorf("%s plugin: ListenAndServe(): %v", pluginName, err)
+ }
+ }()
+
+ return nil
+}
+
+func (ra *restapi) Shutdown(ctx context.Context) error {
+ if ra == nil || ra.srv == nil {
+ return nil
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+ defer cancel()
+
+ return ra.srv.Shutdown(ctx)
+}
+
+func main() {} // ignored