diff --git a/core/coreapi/interface/object.go b/core/coreapi/interface/object.go
index ea9aa5948..0a716dc97 100644
--- a/core/coreapi/interface/object.go
+++ b/core/coreapi/interface/object.go
@@ -31,6 +31,39 @@ type ObjectStat struct {
 	CumulativeSize int
 }
 
+
+const (
+	// DiffAdd is a Type of ObjectChange where a link was added to the graph
+	DiffAdd = iota
+
+	// DiffRemove is a Type of ObjectChange where a link was removed from the graph
+	DiffRemove
+
+	// DiffMod is a Type of ObjectChange where a link was changed in the graph
+	DiffMod
+)
+
+// ObjectChange represents a change ia a graph
+// TODO: do we want this to be an interface?
+type ObjectChange struct {
+	// Type of the change, either:
+	// * DiffAdd - Added a link
+	// * DiffRemove - Removed a link
+	// * DiffMod - Modified a link
+	Type int
+
+	// Path to the changed link
+	Path string
+
+	// Before holds the link path before the change. Note that when a link is
+	// added, this will be nil.
+	Before Path
+
+	// After holds the link path after the change. Note that when a link is
+	// removed, this will be nil.
+	After Path
+}
+
 // ObjectAPI specifies the interface to MerkleDAG and contains useful utilities
 // for manipulating MerkleDAG data structures.
 type ObjectAPI interface {
@@ -65,4 +98,8 @@ type ObjectAPI interface {
 
 	// SetData sets the data contained in the node
 	SetData(context.Context, Path, io.Reader) (ResolvedPath, error)
+
+	// Diff returns a set of changes needed to transform the first object into the
+	// second.
+	Diff(context.Context, Path, Path) ([]ObjectChange, error)
 }
diff --git a/core/coreapi/object.go b/core/coreapi/object.go
index 2dc018fb9..e6af7c1c9 100644
--- a/core/coreapi/object.go
+++ b/core/coreapi/object.go
@@ -297,6 +297,35 @@ func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.R
 	return coreiface.IpfsPath(pbnd.Cid()), nil
 }
 
+func (api *ObjectAPI) Diff(ctx context.Context, before coreiface.Path, after coreiface.Path) ([]coreiface.ObjectChange, error) {
+	beforeNd, err := api.core().ResolveNode(ctx, before)
+	if err != nil {
+		return nil, err
+	}
+
+	afterNd, err := api.core().ResolveNode(ctx, after)
+	if err != nil {
+		return nil, err
+	}
+
+	changes, err := dagutils.Diff(ctx, api.node.DAG, beforeNd, afterNd)
+	if err != nil {
+		return nil, err
+	}
+
+	out := make([]coreiface.ObjectChange, len(changes))
+	for i, change := range changes {
+		out[i] = coreiface.ObjectChange{
+			Type:   change.Type,
+			Path:   change.Path,
+			Before: coreiface.IpfsPath(change.Before),
+			After:  coreiface.IpfsPath(change.After),
+		}
+	}
+
+	return out, nil
+}
+
 func (api *ObjectAPI) core() coreiface.CoreAPI {
 	return (*CoreAPI)(api)
 }
diff --git a/core/coreapi/object_test.go b/core/coreapi/object_test.go
index 3d0a54349..8df9c04d2 100644
--- a/core/coreapi/object_test.go
+++ b/core/coreapi/object_test.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/ipfs/go-ipfs/core/coreapi/interface"
 	opt "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
 )
 
@@ -385,3 +386,42 @@ func TestObjectSetData(t *testing.T) {
 		t.Error("unexpected data")
 	}
 }
+
+func TestDiffTest(t *testing.T) {
+	ctx := context.Background()
+	_, api, err := makeAPI(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	p1, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"foo"}`))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	p2, err := api.Object().Put(ctx, strings.NewReader(`{"Data":"bar"}`))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	changes, err := api.Object().Diff(ctx, p1, p2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(changes) != 1 {
+		t.Fatal("unexpected changes len")
+	}
+
+	if changes[0].Type != iface.DiffMod {
+		t.Fatal("unexpected change type")
+	}
+
+	if changes[0].Before.String() != p1.String() {
+		t.Fatal("unexpected before path")
+	}
+
+	if changes[0].After.String() != p2.String() {
+		t.Fatal("unexpected before path")
+	}
+}