diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
index d5b0973d..b32ddf14 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
@@ -53,7 +53,7 @@ func Setup(
 ) {
 
 	apiMux.Handle("/_matrix/client/versions",
-		common.MakeAPI("versions", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("versions", func(req *http.Request) util.JSONResponse {
 			return util.JSONResponse{
 				Code: 200,
 				JSON: struct {
@@ -123,11 +123,11 @@ func Setup(
 		}),
 	).Methods("PUT", "OPTIONS")
 
-	r0mux.Handle("/register", common.MakeAPI("register", func(req *http.Request) util.JSONResponse {
+	r0mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
 		return writers.Register(req, accountDB, deviceDB, &cfg)
 	})).Methods("POST", "OPTIONS")
 
-	v1mux.Handle("/register", common.MakeAPI("register", func(req *http.Request) util.JSONResponse {
+	v1mux.Handle("/register", common.MakeExternalAPI("register", func(req *http.Request) util.JSONResponse {
 		return writers.LegacyRegister(req, accountDB, deviceDB, &cfg)
 	})).Methods("POST", "OPTIONS")
 
@@ -161,13 +161,13 @@ func Setup(
 	// Stub endpoints required by Riot
 
 	r0mux.Handle("/login",
-		common.MakeAPI("login", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("login", func(req *http.Request) util.JSONResponse {
 			return readers.Login(req, accountDB, deviceDB, cfg)
 		}),
 	).Methods("GET", "POST", "OPTIONS")
 
 	r0mux.Handle("/pushrules/",
-		common.MakeAPI("push_rules", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("push_rules", func(req *http.Request) util.JSONResponse {
 			// TODO: Implement push rules API
 			res := json.RawMessage(`{
 					"global": {
@@ -186,7 +186,7 @@ func Setup(
 	).Methods("GET")
 
 	r0mux.Handle("/user/{userID}/filter",
-		common.MakeAPI("make_filter", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("make_filter", func(req *http.Request) util.JSONResponse {
 			// TODO: Persist filter and return filter ID
 			return util.JSONResponse{
 				Code: 200,
@@ -196,7 +196,7 @@ func Setup(
 	).Methods("POST", "OPTIONS")
 
 	r0mux.Handle("/user/{userID}/filter/{filterID}",
-		common.MakeAPI("filter", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("filter", func(req *http.Request) util.JSONResponse {
 			// TODO: Retrieve filter based on ID
 			return util.JSONResponse{
 				Code: 200,
@@ -208,14 +208,14 @@ func Setup(
 	// Riot user settings
 
 	r0mux.Handle("/profile/{userID}",
-		common.MakeAPI("profile", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("profile", func(req *http.Request) util.JSONResponse {
 			vars := mux.Vars(req)
 			return readers.GetProfile(req, accountDB, vars["userID"])
 		}),
 	).Methods("GET")
 
 	r0mux.Handle("/profile/{userID}/avatar_url",
-		common.MakeAPI("profile_avatar_url", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("profile_avatar_url", func(req *http.Request) util.JSONResponse {
 			vars := mux.Vars(req)
 			return readers.GetAvatarURL(req, accountDB, vars["userID"])
 		}),
@@ -231,7 +231,7 @@ func Setup(
 	// PUT requests, so we need to allow this method
 
 	r0mux.Handle("/profile/{userID}/displayname",
-		common.MakeAPI("profile_displayname", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("profile_displayname", func(req *http.Request) util.JSONResponse {
 			vars := mux.Vars(req)
 			return readers.GetDisplayName(req, accountDB, vars["userID"])
 		}),
@@ -265,14 +265,14 @@ func Setup(
 	).Methods("POST", "OPTIONS")
 
 	r0mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken",
-		common.MakeAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse {
 			return readers.RequestEmailToken(req, accountDB, cfg)
 		}),
 	).Methods("POST", "OPTIONS")
 
 	// Riot logs get flooded unless this is handled
 	r0mux.Handle("/presence/{userID}/status",
-		common.MakeAPI("presence", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("presence", func(req *http.Request) util.JSONResponse {
 			// TODO: Set presence (probably the responsibility of a presence server not clientapi)
 			return util.JSONResponse{
 				Code: 200,
@@ -282,7 +282,7 @@ func Setup(
 	).Methods("PUT", "OPTIONS")
 
 	r0mux.Handle("/voip/turnServer",
-		common.MakeAPI("turn_server", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("turn_server", func(req *http.Request) util.JSONResponse {
 			// TODO: Return credentials for a turn server if one is configured.
 			return util.JSONResponse{
 				Code: 200,
@@ -292,7 +292,7 @@ func Setup(
 	).Methods("GET")
 
 	unstableMux.Handle("/thirdparty/protocols",
-		common.MakeAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("thirdparty_protocols", func(req *http.Request) util.JSONResponse {
 			// TODO: Return the third party protcols
 			return util.JSONResponse{
 				Code: 200,
@@ -302,7 +302,7 @@ func Setup(
 	).Methods("GET")
 
 	r0mux.Handle("/rooms/{roomID}/initialSync",
-		common.MakeAPI("rooms_initial_sync", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("rooms_initial_sync", func(req *http.Request) util.JSONResponse {
 			// TODO: Allow people to peek into rooms.
 			return util.JSONResponse{
 				Code: 403,
@@ -340,14 +340,14 @@ func Setup(
 	).Methods("GET")
 
 	r0mux.Handle("/rooms/{roomID}/read_markers",
-		common.MakeAPI("rooms_read_markers", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("rooms_read_markers", func(req *http.Request) util.JSONResponse {
 			// TODO: return the read_markers.
 			return util.JSONResponse{Code: 200, JSON: struct{}{}}
 		}),
 	).Methods("POST", "OPTIONS")
 
 	r0mux.Handle("/rooms/{roomID}/typing/{userID}",
-		common.MakeAPI("rooms_typing", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("rooms_typing", func(req *http.Request) util.JSONResponse {
 			// TODO: handling typing
 			return util.JSONResponse{Code: 200, JSON: struct{}{}}
 		}),
@@ -355,7 +355,7 @@ func Setup(
 
 	// Stub implementations for sytest
 	r0mux.Handle("/events",
-		common.MakeAPI("events", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {
 			return util.JSONResponse{Code: 200, JSON: map[string]interface{}{
 				"chunk": []interface{}{},
 				"start": "",
@@ -365,7 +365,7 @@ func Setup(
 	).Methods("GET")
 
 	r0mux.Handle("/initialSync",
-		common.MakeAPI("initial_sync", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("initial_sync", func(req *http.Request) util.JSONResponse {
 			return util.JSONResponse{Code: 200, JSON: map[string]interface{}{
 				"end": "",
 			}}
diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go
index 04674a2c..ccbc5681 100644
--- a/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go
+++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go
@@ -20,6 +20,7 @@ import (
 	"os"
 
 	"github.com/gorilla/mux"
+	"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
 	"github.com/matrix-org/dendrite/common"
 	"github.com/matrix-org/dendrite/common/config"
 	"github.com/matrix-org/dendrite/mediaapi/routing"
@@ -52,12 +53,17 @@ func main() {
 		log.WithError(err).Panic("Failed to open database")
 	}
 
+	deviceDB, err := devices.NewDatabase(string(cfg.Database.Device), cfg.Matrix.ServerName)
+	if err != nil {
+		log.WithError(err).Panicf("Failed to setup device database(%q)", cfg.Database.Device)
+	}
+
 	client := gomatrixserverlib.NewClient()
 
 	log.Info("Starting media API server on ", cfg.Listen.MediaAPI)
 
 	api := mux.NewRouter()
-	routing.Setup(api, cfg, db, client)
+	routing.Setup(api, cfg, db, deviceDB, client)
 	common.SetupHTTPAPI(http.DefaultServeMux, api)
 
 	log.Fatal(http.ListenAndServe(string(cfg.Listen.MediaAPI), nil))
diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go
index c1c19769..11920b8d 100644
--- a/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go
+++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-monolith-server/main.go
@@ -325,7 +325,7 @@ func (m *monolith) setupAPIs() {
 	)
 
 	mediaapi_routing.Setup(
-		m.api, m.cfg, m.mediaAPIDB, &m.federation.Client,
+		m.api, m.cfg, m.mediaAPIDB, m.deviceDB, &m.federation.Client,
 	)
 
 	syncapi_routing.Setup(m.api, syncapi_sync.NewRequestPool(
diff --git a/src/github.com/matrix-org/dendrite/common/httpapi.go b/src/github.com/matrix-org/dendrite/common/httpapi.go
index 0a1542b3..76182bf0 100644
--- a/src/github.com/matrix-org/dendrite/common/httpapi.go
+++ b/src/github.com/matrix-org/dendrite/common/httpapi.go
@@ -9,6 +9,8 @@ import (
 	"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
 	"github.com/matrix-org/gomatrixserverlib"
 	"github.com/matrix-org/util"
+	opentracing "github.com/opentracing/opentracing-go"
+	"github.com/opentracing/opentracing-go/ext"
 	"github.com/prometheus/client_golang/prometheus"
 )
 
@@ -21,13 +23,47 @@ func MakeAuthAPI(metricsName string, deviceDB auth.DeviceDatabase, f func(*http.
 		}
 		return f(req, device)
 	}
-	return MakeAPI(metricsName, h)
+	return MakeExternalAPI(metricsName, h)
 }
 
-// MakeAPI turns a util.JSONRequestHandler function into an http.Handler.
-func MakeAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler {
-	h := util.NewJSONRequestHandler(f)
-	return prometheus.InstrumentHandler(metricsName, util.MakeJSONAPI(h))
+// MakeExternalAPI turns a util.JSONRequestHandler function into an http.Handler.
+// This is used for APIs that are called from the internet.
+func MakeExternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler {
+	h := util.MakeJSONAPI(util.NewJSONRequestHandler(f))
+	withSpan := func(w http.ResponseWriter, req *http.Request) {
+		span := opentracing.StartSpan(metricsName)
+		defer span.Finish()
+		req = req.WithContext(opentracing.ContextWithSpan(req.Context(), span))
+		h.ServeHTTP(w, req)
+	}
+
+	return prometheus.InstrumentHandler(metricsName, http.HandlerFunc(withSpan))
+}
+
+// MakeInternalAPI turns a util.JSONRequestHandler function into an http.Handler.
+// This is used for APIs that are internal to dendrite.
+// If we are passed a tracing context in the request headers then we use that
+// as the parent of any tracing spans we create.
+func MakeInternalAPI(metricsName string, f func(*http.Request) util.JSONResponse) http.Handler {
+	h := util.MakeJSONAPI(util.NewJSONRequestHandler(f))
+	withSpan := func(w http.ResponseWriter, req *http.Request) {
+		carrier := opentracing.HTTPHeadersCarrier(req.Header)
+		tracer := opentracing.GlobalTracer()
+		clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
+		var span opentracing.Span
+		if err == nil {
+			// Default to a span without RPC context.
+			span = tracer.StartSpan(metricsName)
+		} else {
+			// Set the RPC context.
+			span = tracer.StartSpan(metricsName, ext.RPCServerOption(clientContext))
+		}
+		defer span.Finish()
+		req = req.WithContext(opentracing.ContextWithSpan(req.Context(), span))
+		h.ServeHTTP(w, req)
+	}
+
+	return prometheus.InstrumentHandler(metricsName, http.HandlerFunc(withSpan))
 }
 
 // MakeFedAPI makes an http.Handler that checks matrix federation authentication.
@@ -46,7 +82,7 @@ func MakeFedAPI(
 		}
 		return f(req, fedReq)
 	}
-	return MakeAPI(metricsName, h)
+	return MakeExternalAPI(metricsName, h)
 }
 
 // SetupHTTPAPI registers an HTTP API mux under /api and sets up a metrics
diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go
index b927133b..ec8fcd3b 100644
--- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go
+++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go
@@ -48,7 +48,7 @@ func Setup(
 	v2keysmux := apiMux.PathPrefix(pathPrefixV2Keys).Subrouter()
 	v1fedmux := apiMux.PathPrefix(pathPrefixV1Federation).Subrouter()
 
-	localKeys := common.MakeAPI("localkeys", func(req *http.Request) util.JSONResponse {
+	localKeys := common.MakeExternalAPI("localkeys", func(req *http.Request) util.JSONResponse {
 		return readers.LocalKeys(cfg)
 	})
 
@@ -81,7 +81,7 @@ func Setup(
 		},
 	)).Methods("PUT", "OPTIONS")
 
-	v1fedmux.Handle("/3pid/onbind", common.MakeAPI("3pid_onbind",
+	v1fedmux.Handle("/3pid/onbind", common.MakeExternalAPI("3pid_onbind",
 		func(req *http.Request) util.JSONResponse {
 			return writers.CreateInvitesFrom3PIDInvites(req, query, cfg, producer, federation, accountDB)
 		},
@@ -107,7 +107,7 @@ func Setup(
 		},
 	)).Methods("GET")
 
-	v1fedmux.Handle("/version", common.MakeAPI(
+	v1fedmux.Handle("/version", common.MakeExternalAPI(
 		"federation_version",
 		func(httpReq *http.Request) util.JSONResponse {
 			return readers.Version()
diff --git a/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go b/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go
index 6a01a65b..e43404d3 100644
--- a/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go
+++ b/src/github.com/matrix-org/dendrite/mediaapi/routing/routing.go
@@ -17,7 +17,10 @@ package routing
 import (
 	"net/http"
 
+	"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
+
 	"github.com/gorilla/mux"
+	"github.com/matrix-org/dendrite/clientapi/auth/storage/devices"
 	"github.com/matrix-org/dendrite/common"
 	"github.com/matrix-org/dendrite/common/config"
 	"github.com/matrix-org/dendrite/mediaapi/storage"
@@ -35,6 +38,7 @@ func Setup(
 	apiMux *mux.Router,
 	cfg *config.Dendrite,
 	db *storage.Database,
+	deviceDB *devices.Database,
 	client *gomatrixserverlib.Client,
 ) {
 	r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
@@ -43,10 +47,13 @@ func Setup(
 		PathToResult: map[string]*types.ThumbnailGenerationResult{},
 	}
 
-	// FIXME: /upload should use common.MakeAuthAPI()
-	r0mux.Handle("/upload", common.MakeAPI("upload", func(req *http.Request) util.JSONResponse {
-		return writers.Upload(req, cfg, db, activeThumbnailGeneration)
-	})).Methods("POST", "OPTIONS")
+	r0mux.Handle("/upload", common.MakeAuthAPI(
+		"upload",
+		deviceDB,
+		func(req *http.Request, _ *authtypes.Device) util.JSONResponse {
+			return writers.Upload(req, cfg, db, activeThumbnailGeneration)
+		},
+	)).Methods("POST", "OPTIONS")
 
 	activeRemoteRequests := &types.ActiveRemoteRequests{
 		MXCToResult: map[string]*types.RemoteRequestResult{},
diff --git a/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go b/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go
index 18b8cc57..851b49bf 100644
--- a/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go
+++ b/src/github.com/matrix-org/dendrite/publicroomsapi/routing/routing.go
@@ -32,7 +32,7 @@ const pathPrefixR0 = "/_matrix/client/r0"
 func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storage.PublicRoomsServerDatabase) {
 	r0mux := apiMux.PathPrefix(pathPrefixR0).Subrouter()
 	r0mux.Handle("/directory/list/room/{roomID}",
-		common.MakeAPI("directory_list", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("directory_list", func(req *http.Request) util.JSONResponse {
 			vars := mux.Vars(req)
 			return directory.GetVisibility(req, publicRoomsDB, vars["roomID"])
 		}),
@@ -44,7 +44,7 @@ func Setup(apiMux *mux.Router, deviceDB *devices.Database, publicRoomsDB *storag
 		}),
 	).Methods("PUT", "OPTIONS")
 	r0mux.Handle("/publicRooms",
-		common.MakeAPI("public_rooms", func(req *http.Request) util.JSONResponse {
+		common.MakeExternalAPI("public_rooms", func(req *http.Request) util.JSONResponse {
 			return directory.GetPublicRooms(req, publicRoomsDB)
 		}),
 	).Methods("GET", "POST", "OPTIONS")
diff --git a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go
index fed98dd8..5f5436eb 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/alias/alias.go
@@ -224,7 +224,7 @@ func (r *RoomserverAliasAPI) sendUpdatedAliasesEvent(
 func (r *RoomserverAliasAPI) SetupHTTP(servMux *http.ServeMux) {
 	servMux.Handle(
 		api.RoomserverSetRoomAliasPath,
-		common.MakeAPI("setRoomAlias", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("setRoomAlias", func(req *http.Request) util.JSONResponse {
 			var request api.SetRoomAliasRequest
 			var response api.SetRoomAliasResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -238,7 +238,7 @@ func (r *RoomserverAliasAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverGetAliasRoomIDPath,
-		common.MakeAPI("getAliasRoomID", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("getAliasRoomID", func(req *http.Request) util.JSONResponse {
 			var request api.GetAliasRoomIDRequest
 			var response api.GetAliasRoomIDResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -252,7 +252,7 @@ func (r *RoomserverAliasAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverRemoveRoomAliasPath,
-		common.MakeAPI("removeRoomAlias", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("removeRoomAlias", func(req *http.Request) util.JSONResponse {
 			var request api.RemoveRoomAliasRequest
 			var response api.RemoveRoomAliasResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/alias.go b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go
index 45a349c5..5099846d 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/api/alias.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/api/alias.go
@@ -17,6 +17,8 @@ package api
 import (
 	"context"
 	"net/http"
+
+	opentracing "github.com/opentracing/opentracing-go"
 )
 
 // SetRoomAliasRequest is a request to SetRoomAlias
@@ -111,8 +113,11 @@ func (h *httpRoomserverAliasAPI) SetRoomAlias(
 	request *SetRoomAliasRequest,
 	response *SetRoomAliasResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "SetRoomAlias")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverSetRoomAliasPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // GetAliasRoomID implements RoomserverAliasAPI
@@ -121,8 +126,11 @@ func (h *httpRoomserverAliasAPI) GetAliasRoomID(
 	request *GetAliasRoomIDRequest,
 	response *GetAliasRoomIDResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "GetAliasRoomID")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverGetAliasRoomIDPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // RemoveRoomAlias implements RoomserverAliasAPI
@@ -131,6 +139,9 @@ func (h *httpRoomserverAliasAPI) RemoveRoomAlias(
 	request *RemoveRoomAliasRequest,
 	response *RemoveRoomAliasResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "RemoveRoomAlias")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverRemoveRoomAliasPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/input.go b/src/github.com/matrix-org/dendrite/roomserver/api/input.go
index 82be45f3..fb4f7a61 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/api/input.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/api/input.go
@@ -20,6 +20,7 @@ import (
 	"net/http"
 
 	"github.com/matrix-org/gomatrixserverlib"
+	opentracing "github.com/opentracing/opentracing-go"
 )
 
 const (
@@ -117,6 +118,9 @@ func (h *httpRoomserverInputAPI) InputRoomEvents(
 	request *InputRoomEventsRequest,
 	response *InputRoomEventsResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "InputRoomEvents")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverInputRoomEventsPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/query.go b/src/github.com/matrix-org/dendrite/roomserver/api/query.go
index 9222b168..248850bf 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go
@@ -21,6 +21,10 @@ import (
 	"fmt"
 	"net/http"
 
+	"github.com/opentracing/opentracing-go/ext"
+
+	"github.com/opentracing/opentracing-go"
+
 	"github.com/matrix-org/gomatrixserverlib"
 )
 
@@ -234,8 +238,11 @@ func (h *httpRoomserverQueryAPI) QueryLatestEventsAndState(
 	request *QueryLatestEventsAndStateRequest,
 	response *QueryLatestEventsAndStateResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryLatestEventsAndState")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryLatestEventsAndStatePath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // QueryStateAfterEvents implements RoomserverQueryAPI
@@ -244,8 +251,11 @@ func (h *httpRoomserverQueryAPI) QueryStateAfterEvents(
 	request *QueryStateAfterEventsRequest,
 	response *QueryStateAfterEventsResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryStateAfterEvents")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryStateAfterEventsPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // QueryEventsByID implements RoomserverQueryAPI
@@ -254,8 +264,11 @@ func (h *httpRoomserverQueryAPI) QueryEventsByID(
 	request *QueryEventsByIDRequest,
 	response *QueryEventsByIDResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryEventsByID")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryEventsByIDPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // QueryMembershipsForRoom implements RoomserverQueryAPI
@@ -264,8 +277,11 @@ func (h *httpRoomserverQueryAPI) QueryMembershipsForRoom(
 	request *QueryMembershipsForRoomRequest,
 	response *QueryMembershipsForRoomResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryMembershipsForRoom")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryMembershipsForRoomPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // QueryInvitesForUser implements RoomserverQueryAPI
@@ -274,8 +290,11 @@ func (h *httpRoomserverQueryAPI) QueryInvitesForUser(
 	request *QueryInvitesForUserRequest,
 	response *QueryInvitesForUserResponse,
 ) error {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryInvitesForUser")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryInvitesForUserPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 // QueryServerAllowedToSeeEvent implements RoomserverQueryAPI
@@ -284,12 +303,15 @@ func (h *httpRoomserverQueryAPI) QueryServerAllowedToSeeEvent(
 	request *QueryServerAllowedToSeeEventRequest,
 	response *QueryServerAllowedToSeeEventResponse,
 ) (err error) {
+	span, ctx := opentracing.StartSpanFromContext(ctx, "QueryServerAllowedToSeeEvent")
+	defer span.Finish()
+
 	apiURL := h.roomserverURL + RoomserverQueryServerAllowedToSeeEventPath
-	return postJSON(ctx, h.httpClient, apiURL, request, response)
+	return postJSON(ctx, span, h.httpClient, apiURL, request, response)
 }
 
 func postJSON(
-	ctx context.Context, httpClient *http.Client,
+	ctx context.Context, span opentracing.Span, httpClient *http.Client,
 	apiURL string, request, response interface{},
 ) error {
 	jsonBytes, err := json.Marshal(request)
@@ -302,6 +324,15 @@ func postJSON(
 		return err
 	}
 
+	// Mark the span as being an RPC client.
+	ext.SpanKindRPCClient.Set(span)
+	carrier := opentracing.HTTPHeadersCarrier(req.Header)
+	tracer := opentracing.GlobalTracer()
+
+	if err = tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier); err != nil {
+		return err
+	}
+
 	req.Header.Set("Content-Type", "application/json")
 
 	res, err := httpClient.Do(req.WithContext(ctx))
diff --git a/src/github.com/matrix-org/dendrite/roomserver/input/input.go b/src/github.com/matrix-org/dendrite/roomserver/input/input.go
index 27797096..253ef9ff 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/input/input.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/input/input.go
@@ -74,7 +74,7 @@ func (r *RoomserverInputAPI) InputRoomEvents(
 // SetupHTTP adds the RoomserverInputAPI handlers to the http.ServeMux.
 func (r *RoomserverInputAPI) SetupHTTP(servMux *http.ServeMux) {
 	servMux.Handle(api.RoomserverInputRoomEventsPath,
-		common.MakeAPI("inputRoomEvents", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("inputRoomEvents", func(req *http.Request) util.JSONResponse {
 			var request api.InputRoomEventsRequest
 			var response api.InputRoomEventsResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
diff --git a/src/github.com/matrix-org/dendrite/roomserver/query/query.go b/src/github.com/matrix-org/dendrite/roomserver/query/query.go
index 902bf56a..265e4ad3 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go
@@ -423,7 +423,7 @@ func (r *RoomserverQueryAPI) QueryServerAllowedToSeeEvent(
 func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	servMux.Handle(
 		api.RoomserverQueryLatestEventsAndStatePath,
-		common.MakeAPI("queryLatestEventsAndState", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryLatestEventsAndState", func(req *http.Request) util.JSONResponse {
 			var request api.QueryLatestEventsAndStateRequest
 			var response api.QueryLatestEventsAndStateResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -437,7 +437,7 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverQueryStateAfterEventsPath,
-		common.MakeAPI("queryStateAfterEvents", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryStateAfterEvents", func(req *http.Request) util.JSONResponse {
 			var request api.QueryStateAfterEventsRequest
 			var response api.QueryStateAfterEventsResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -451,7 +451,7 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverQueryEventsByIDPath,
-		common.MakeAPI("queryEventsByID", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryEventsByID", func(req *http.Request) util.JSONResponse {
 			var request api.QueryEventsByIDRequest
 			var response api.QueryEventsByIDResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -465,7 +465,7 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverQueryMembershipsForRoomPath,
-		common.MakeAPI("queryMembershipsForRoom", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryMembershipsForRoom", func(req *http.Request) util.JSONResponse {
 			var request api.QueryMembershipsForRoomRequest
 			var response api.QueryMembershipsForRoomResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -479,7 +479,7 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverQueryInvitesForUserPath,
-		common.MakeAPI("queryInvitesForUser", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryInvitesForUser", func(req *http.Request) util.JSONResponse {
 			var request api.QueryInvitesForUserRequest
 			var response api.QueryInvitesForUserResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
@@ -493,7 +493,7 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	)
 	servMux.Handle(
 		api.RoomserverQueryServerAllowedToSeeEventPath,
-		common.MakeAPI("queryServerAllowedToSeeEvent", func(req *http.Request) util.JSONResponse {
+		common.MakeInternalAPI("queryServerAllowedToSeeEvent", func(req *http.Request) util.JSONResponse {
 			var request api.QueryServerAllowedToSeeEventRequest
 			var response api.QueryServerAllowedToSeeEventResponse
 			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {