diff --git a/internal/provider/device/data_source.go b/internal/provider/device/data_source.go
new file mode 100644
index 0000000000000000000000000000000000000000..18aefbb2eb8edf24cc4d847ce5fdfbe301bb6a7f
--- /dev/null
+++ b/internal/provider/device/data_source.go
@@ -0,0 +1,206 @@
+package device
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	v20250101 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20250101"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ datasource.DataSourceWithConfigure = (*DataSource)(nil)
+
+func NewDataSource() datasource.DataSource {
+	return &DataSource{}
+}
+
+type DataSource struct {
+	client *v20250101.Client
+}
+
+func (a *DataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the data source.
+func (ds *DataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+	// Prevent panic if the provider has not been configured.
+	if req.ProviderData == nil {
+		return
+	}
+
+	client, ok := req.ProviderData.(*v20250101.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20250101.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	ds.client = client
+}
+
+func (ds *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	device, props, err := utils.Describe("device")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI Device Schema",
+			err.Error(),
+		)
+		return
+	}
+
+	deviceUser, userProps, err := utils.Describe("deviceUser")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI Device User Schema",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: device,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Required:            true,
+			},
+			"permanent_identifier": schema.StringAttribute{
+				MarkdownDescription: props["permanentIdentifier"],
+				Computed:            true,
+			},
+			"serial": schema.StringAttribute{
+				MarkdownDescription: props["serial"],
+				Computed:            true,
+			},
+			"display_name": schema.StringAttribute{
+				MarkdownDescription: props["displayName"],
+				Computed:            true,
+			},
+			"display_id": schema.StringAttribute{
+				MarkdownDescription: props["displayId"],
+				Computed:            true,
+			},
+			"os": schema.StringAttribute{
+				MarkdownDescription: props["os"],
+				Computed:            true,
+			},
+			"ownership": schema.StringAttribute{
+				MarkdownDescription: props["ownership"],
+				Computed:            true,
+			},
+			"metadata": schema.MapAttribute{
+				MarkdownDescription: props["metadata"],
+				Computed:            true,
+				ElementType:         types.StringType,
+			},
+			"tags": schema.SetAttribute{
+				MarkdownDescription: props["tags"],
+				Computed:            true,
+				ElementType:         types.StringType,
+			},
+			"user": schema.SingleNestedAttribute{
+				MarkdownDescription: deviceUser,
+				Computed:            true,
+				Attributes: map[string]schema.Attribute{
+					"display_name": schema.StringAttribute{
+						MarkdownDescription: userProps["displayName"],
+						Computed:            true,
+					},
+					"email": schema.StringAttribute{
+						MarkdownDescription: userProps["email"],
+						Computed:            true,
+					},
+				},
+			},
+			"connected": schema.BoolAttribute{
+				MarkdownDescription: props["connected"],
+				Computed:            true,
+			},
+			"high_assurance": schema.BoolAttribute{
+				MarkdownDescription: props["highAssurance"],
+				Computed:            true,
+			},
+			"enrolled_at": schema.StringAttribute{
+				MarkdownDescription: props["enrolledAt"],
+				Computed:            true,
+			},
+			"approved_at": schema.StringAttribute{
+				MarkdownDescription: props["approvedAt"],
+				Computed:            true,
+			},
+			"last_seen": schema.StringAttribute{
+				MarkdownDescription: props["lastSeen"],
+				Computed:            true,
+			},
+		},
+	}
+}
+
+func (ds *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var config Model
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	deviceID := config.ID.ValueString()
+	if deviceID == "" {
+		resp.Diagnostics.AddError(
+			"Invalid Device ID",
+			"Device ID is required",
+		)
+		return
+	}
+
+	httpResp, err := ds.client.GetDevice(ctx, deviceID, &v20250101.GetDeviceParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read device %q: %v", config.ID.ValueString(), err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode == http.StatusNotFound {
+		resp.State.RemoveResource(ctx)
+		return
+	}
+	if httpResp.StatusCode != http.StatusOK {
+		reqID := httpResp.Header.Get("X-Request-Id")
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Request %q received status %d reading device %s: %s", reqID, httpResp.StatusCode, deviceID, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	device := &v20250101.Device{}
+	if err := json.NewDecoder(httpResp.Body).Decode(device); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal device %s: %v", deviceID, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, device, req.Config)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
diff --git a/internal/provider/device/data_source_test.go b/internal/provider/device/data_source_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..107e7dc90df130cf30c94f5ab621e178e87e75b6
--- /dev/null
+++ b/internal/provider/device/data_source_test.go
@@ -0,0 +1,44 @@
+package device
+
+import (
+	"fmt"
+	"testing"
+
+	helper "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+func TestAccProvisionerDataSource(t *testing.T) {
+	t.Parallel()
+	device := utils.NewDevice(t)
+
+	config := fmt.Sprintf(`
+data "smallstep_device" "test" {
+	id = %q
+}`, device.Id)
+
+	helper.Test(t, helper.TestCase{
+		ProtoV6ProviderFactories: providerFactories,
+		Steps: []helper.TestStep{
+			{
+				Config: config,
+				Check: helper.ComposeAggregateTestCheckFunc(
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "id", device.Id),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "permanent_identifier", device.PermanentIdentifier),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "display_id", utils.Deref(device.DisplayId)),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "display_name", utils.Deref(device.DisplayName)),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "serial", utils.Deref(device.Serial)),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "os", string(utils.Deref(device.Os))),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "ownership", string(utils.Deref(device.Ownership))),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "user.email", device.User.Email),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "tags.#", "1"),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "metadata.%", "1"),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "high_assurance", "false"),
+					helper.TestCheckResourceAttr("data.smallstep_device.test", "connected", "false"),
+					helper.TestCheckNoResourceAttr("data.smallstep_device.test", "enrolled_at"),
+					helper.TestCheckNoResourceAttr("data.smallstep_device.test", "last_seen"),
+				),
+			},
+		},
+	})
+}
diff --git a/internal/provider/device/resource_test.go b/internal/provider/device/resource_test.go
index d913b0d2ea814f64baf6f3ff25c2bdabf7b18d3a..d665a6f89748844ee4fc47301c253cb7ab554c79 100644
--- a/internal/provider/device/resource_test.go
+++ b/internal/provider/device/resource_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/google/uuid"
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
 	"github.com/hashicorp/terraform-plugin-framework/providerserver"
 	"github.com/hashicorp/terraform-plugin-framework/resource"
 	"github.com/hashicorp/terraform-plugin-go/tfprotov6"
@@ -17,11 +18,9 @@ var provider = &testprovider.SmallstepTestProvider{
 	ResourceFactories: []func() resource.Resource{
 		NewResource,
 	},
-	/*
-		DataSourceFactories: []func() datasource.DataSource{
-			NewDataSource,
-		},
-	*/
+	DataSourceFactories: []func() datasource.DataSource{
+		NewDataSource,
+	},
 }
 
 var providerFactories = map[string]func() (tfprotov6.ProviderServer, error){
diff --git a/internal/provider/utils/testutils.go b/internal/provider/utils/testutils.go
index 0b39783ebf7d1529cb5a44e18ae17e532bc7d8d6..5146e04be29d4a63de10879a043b0e1b122afb16 100644
--- a/internal/provider/utils/testutils.go
+++ b/internal/provider/utils/testutils.go
@@ -174,3 +174,50 @@ func Slug(t *testing.T) string {
 	require.NoError(t, err)
 	return "tfprovider" + slug
 }
+
+func NewDevice(t *testing.T) *v20250101.Device {
+	t.Helper()
+
+	deviceName, err := randutil.Alphanumeric(12)
+	require.NoError(t, err)
+	permanentID, err := randutil.Alphanumeric(12)
+	require.NoError(t, err)
+	displayID, err := randutil.Alphanumeric(12)
+	require.NoError(t, err)
+	serial, err := randutil.Alphanumeric(12)
+	require.NoError(t, err)
+
+	req := v20250101.DeviceRequest{
+		PermanentIdentifier: permanentID,
+		DisplayId:           Ref(displayID),
+		DisplayName:         Ref(deviceName),
+		Metadata: &v20250101.DeviceMetadata{
+			"k1": "v1",
+		},
+		Tags:      Ref([]string{"ubuntu"}),
+		Os:        Ref(v20250101.Linux),
+		Ownership: Ref(v20250101.User),
+		Serial:    Ref(serial),
+		User: &v20250101.DeviceUser{
+			Email: "employee@example.com",
+		},
+	}
+
+	client, err := SmallstepAPIClientFromEnv()
+	require.NoError(t, err)
+
+	resp, err := client.PostDevices(context.Background(), &v20250101.PostDevicesParams{}, req)
+	require.NoError(t, err)
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	require.NoError(t, err)
+
+	require.Equal(t, 201, resp.StatusCode, string(body))
+
+	device := &v20250101.Device{}
+	err = json.Unmarshal(body, device)
+	require.NoError(t, err)
+
+	return device
+}
diff --git a/internal/provider/utils/utils.go b/internal/provider/utils/utils.go
index 1f76f57556186b4946fe71a4ff8f2490c75c3920..99e95ad99630cc8d63deadd378816428aefcccd8 100644
--- a/internal/provider/utils/utils.go
+++ b/internal/provider/utils/utils.go
@@ -31,7 +31,7 @@ type dereferencable interface {
 
 // Deref gets the default value for a pointer type. This makes it easier to work
 // with the generated API client code, which uses pointers for optional fields.
-func Deref[T dereferencable](v *T) (r T) {
+func Deref[T any](v *T) (r T) {
 	if v != nil {
 		r = *v
 	}