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 6f38da39a..14afb3e87 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go
@@ -24,7 +24,7 @@ import (
 
 // QueryLatestEventsAndStateRequest is a request to QueryLatestEventsAndState
 type QueryLatestEventsAndStateRequest struct {
-	// The roomID to query the latest events for.
+	// The room ID to query the latest events for.
 	RoomID string
 	// The state key tuples to fetch from the room current state.
 	// If this list is empty or nil then no state events are returned.
@@ -44,6 +44,30 @@ type QueryLatestEventsAndStateResponse struct {
 	StateEvents []gomatrixserverlib.Event
 }
 
+// QueryStateAfterEventsRequest is a request to QueryStateAfterEvents
+type QueryStateAfterEventsRequest struct {
+	// The room ID to query the state in.
+	RoomID string
+	// The list of previous events to return the events after.
+	PrevEventIDs []string
+	// The state key tuples to fetch from the state
+	StateToFetch []gomatrixserverlib.StateKeyTuple
+}
+
+// QueryStateAfterEventsResponse is a response to QueryStateAfterEvents
+type QueryStateAfterEventsResponse struct {
+	// Copy of the request for debugging.
+	QueryStateAfterEventsRequest
+	// Does the room exist on this roomserver?
+	// If the room doesn't exist this will be false and StateEvents will be empty.
+	RoomExists bool
+	// Do all the previous events exist on this roomserver?
+	// If some of previous events do not exist this will be false and StateEvents will be empty.
+	PrevEventsExist bool
+	// The state events requested.
+	StateEvents []gomatrixserverlib.Event
+}
+
 // RoomserverQueryAPI is used to query information from the room server.
 type RoomserverQueryAPI interface {
 	// Query the latest events and state for a room from the room server.
@@ -51,11 +75,20 @@ type RoomserverQueryAPI interface {
 		request *QueryLatestEventsAndStateRequest,
 		response *QueryLatestEventsAndStateResponse,
 	) error
+
+	// Query the state after a list of events in a room from the room server.
+	QueryStateAfterEvents(
+		request *QueryStateAfterEventsRequest,
+		response *QueryStateAfterEventsResponse,
+	) error
 }
 
 // RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API.
 const RoomserverQueryLatestEventsAndStatePath = "/api/roomserver/QueryLatestEventsAndState"
 
+// RoomserverQueryStateAfterEventsPath is the HTTP path for the QueryStateAfterEvents API.
+const RoomserverQueryStateAfterEventsPath = "/api/roomserver/QueryStateAfterEvents"
+
 // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API.
 // If httpClient is nil then it uses the http.DefaultClient
 func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI {
@@ -79,6 +112,15 @@ func (h *httpRoomserverQueryAPI) QueryLatestEventsAndState(
 	return postJSON(h.httpClient, apiURL, request, response)
 }
 
+// QueryStateAfterEvents implements RoomserverQueryAPI
+func (h *httpRoomserverQueryAPI) QueryStateAfterEvents(
+	request *QueryStateAfterEventsRequest,
+	response *QueryStateAfterEventsResponse,
+) error {
+	apiURL := h.roomserverURL + RoomserverQueryStateAfterEventsPath
+	return postJSON(h.httpClient, apiURL, request, response)
+}
+
 func postJSON(httpClient http.Client, apiURL string, request, response interface{}) error {
 	jsonBytes, err := json.Marshal(request)
 	if 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 bf15e6b97..c4a743300 100644
--- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go
+++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go
@@ -38,6 +38,8 @@ type RoomserverQueryAPIDatabase interface {
 	// Lookup the Events for a list of numeric event IDs.
 	// Returns a list of events sorted by numeric event ID.
 	Events(eventNIDs []types.EventNID) ([]types.Event, error)
+	// Lookup the state at a list of string event IDs.
+	StateAtEventIDs(eventIDs []string) ([]types.StateAtEvent, error)
 }
 
 // RoomserverQueryAPI is an implementation of RoomserverQueryAPI
@@ -88,6 +90,33 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState(
 	return nil
 }
 
+// QueryStateAfterEvents implements api.RoomserverQueryAPI
+func (r *RoomserverQueryAPI) QueryStateAfterEvents(
+	request *api.QueryStateAfterEventsRequest,
+	response *api.QueryStateAfterEventsResponse,
+) (err error) {
+	response.QueryStateAfterEventsRequest = *request
+	roomNID, err := r.DB.RoomNID(request.RoomID)
+	if err != nil {
+		return err
+	}
+	if roomNID == 0 {
+		return nil
+	}
+	response.RoomExists = true
+
+	_, err = r.DB.StateAtEventIDs(request.PrevEventIDs)
+	if err != nil {
+		// TODO: Check if the error was because we are missing events from the
+		// database or are missing state at events from the database.
+		return err
+	}
+	response.PrevEventsExist = true
+
+	// TODO: Calculate the state and return it.
+	return nil
+}
+
 // SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux.
 func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 	servMux.Handle(
@@ -104,6 +133,20 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
 			return util.JSONResponse{Code: 200, JSON: &response}
 		}),
 	)
+	servMux.Handle(
+		api.RoomserverQueryStateAfterEventsPath,
+		makeAPI("query_state_after_events", func(req *http.Request) util.JSONResponse {
+			var request api.QueryStateAfterEventsRequest
+			var response api.QueryStateAfterEventsResponse
+			if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
+				return util.ErrorResponse(err)
+			}
+			if err := r.QueryStateAfterEvents(&request, &response); err != nil {
+				return util.ErrorResponse(err)
+			}
+			return util.JSONResponse{Code: 200, JSON: &response}
+		}),
+	)
 }
 
 func makeAPI(metric string, apiFunc func(req *http.Request) util.JSONResponse) http.Handler {