diff --git a/client/client.go b/client/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbceea16cd9fd25e2b0e925101115df83c546c6b
--- /dev/null
+++ b/client/client.go
@@ -0,0 +1,78 @@
+package client
+
+import (
+	"context"
+	"crypto/tls"
+	"net/http"
+	"net/url"
+	"time"
+
+	"git.autistici.org/ai3/go-common/clientutil"
+
+	"git.autistici.org/id/keystore"
+)
+
+// Client for the keystore API.
+type Client struct {
+	*http.Client
+	backendURL string
+}
+
+// Config for a keystore client.
+type Config struct {
+	BackendURL string                      `yaml:"backend_url"`
+	TLSConfig  *clientutil.TLSClientConfig `yaml:"tls_config"`
+}
+
+// New returns a new Client with the given Config.
+func New(config *Config) (*Client, error) {
+	u, err := url.Parse(config.BackendURL)
+	if err != nil {
+		return nil, err
+	}
+
+	var tlsConfig *tls.Config
+	if config.TLSConfig != nil {
+		tlsConfig, err = config.TLSConfig.TLSConfig()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	c := &http.Client{
+		Transport: clientutil.NewTransport([]string{u.Host}, tlsConfig, nil),
+		Timeout:   20 * time.Second,
+	}
+	return &Client{
+		Client:     c,
+		backendURL: config.BackendURL,
+	}, nil
+}
+
+func (c *Client) Open(ctx context.Context, username, password string, ttl int) error {
+	req := keystore.OpenRequest{
+		Username: username,
+		Password: password,
+		TTL:      ttl,
+	}
+	var resp keystore.OpenResponse
+	return clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/open", &req, &resp)
+}
+
+func (c *Client) Get(ctx context.Context, username, ssoTicket string) ([]byte, error) {
+	req := keystore.GetRequest{
+		Username:  username,
+		SSOTicket: ssoTicket,
+	}
+	var resp keystore.GetResponse
+	err := clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/get", &req, &resp)
+	return resp.Key, err
+}
+
+func (c *Client) Close(ctx context.Context, username string) error {
+	req := keystore.CloseRequest{
+		Username: username,
+	}
+	var resp keystore.CloseResponse
+	return clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/close", &req, &resp)
+}