diff --git a/jobs/csharp/sdk/README.md b/jobs/csharp/sdk/README.md
new file mode 100644
index 000000000..5a2d635c2
--- /dev/null
+++ b/jobs/csharp/sdk/README.md
@@ -0,0 +1,185 @@
+# Dapr Jobs API (SDK)
+
+In this quickstart, you'll schedule, get, and delete a job using Dapr's Job API. This API is responsible for scheduling and running jobs at a specific time or interval.
+
+Visit [this](https://docs.dapr.io/developing-applications/building-blocks/jobs/) link for more information about Dapr and the Jobs API.
+
+> **Note:** This example leverages the Dotnet SDK. If you are looking for the example using only HTTP requests, [click here](../http/).
+
+This quickstart includes two apps:
+
+- Jobs Scheduler, responsible for scheduling, retrieving and deleting jobs.
+- Jobs Service, responsible for handling the triggered jobs.
+
+## Run all apps with multi-app run template file
+
+This section shows how to run both applications at once using [multi-app run template files](https://docs.dapr.io/developing-applications/local-development/multi-app-dapr-run/multi-app-overview/) with `dapr run -f .`. This enables to you test the interactions between multiple applications and will `schedule`, `run`, `get`, and `delete` jobs within a single process.
+
+1. Build the apps:
+
+
+
+```bash
+cd ./job-service
+dotnet build
+```
+
+
+
+
+
+```bash
+cd ./job-scheduler
+dotnet build
+```
+
+
+
+2. Run the multi app run template:
+
+
+
+```bash
+dapr run -f .
+```
+
+The terminal console output should look similar to this, where:
+
+- The `R2-D2` job is being scheduled.
+- The `R2-D2` job is being retrieved.
+- The `C-3PO` job is being scheduled.
+- The `C-3PO` job is being retrieved.
+- The `R2-D2` job is being executed after 15 seconds.
+- The `C-3PO` job is being executed after 20 seconds.
+
+```text
+== APP - job-scheduler-sdk == Scheduling job...
+== APP - job-service-sdk == Job Scheduled: R2-D2
+== APP - job-scheduler-sdk == Job scheduled: {"name":"R2-D2","job":"Oil Change","dueTime":15}
+== APP - job-scheduler-sdk == Getting job: R2-D2
+== APP - job-service-sdk == Getting job...
+== APP - job-scheduler-sdk == Job details: {"schedule":"@every 15s","repeatCount":1,"dueTime":null,"ttl":null,"payload":"ChtkYXByLmlvL3NjaGVkdWxlL2pvYnBheWxvYWQSJXsiZHJvaWQiOiJSMi1EMiIsInRhc2siOiJPaWwgQ2hhbmdlIn0="}
+== APP - job-scheduler-sdk == Scheduling job...
+== APP - job-service-sdk == Job Scheduled: C-3PO
+== APP - job-scheduler-sdk == Job scheduled: {"name":"C-3PO","job":"Limb Calibration","dueTime":20}
+== APP - job-scheduler-sdk == Getting job: C-3PO
+== APP - job-service-sdk == Getting job...
+== APP - job-scheduler-sdk == Job details: {"schedule":"@every 20s","repeatCount":1,"dueTime":null,"ttl":null,"payload":"ChtkYXByLmlvL3NjaGVkdWxlL2pvYnBheWxvYWQSK3siZHJvaWQiOiJDLTNQTyIsInRhc2siOiJMaW1iIENhbGlicmF0aW9uIn0="}
+== APP - job-service-sdk == Handling job...
+== APP - job-service-sdk == Starting droid: R2-D2
+== APP - job-service-sdk == Executing maintenance job: Oil Change
+```
+
+After 20 seconds, the terminal output should present the `C-3PO` job being processed:
+
+```text
+== APP - job-service-sdk == Handling job...
+== APP - job-service-sdk == Starting droid: C-3PO
+== APP - job-service-sdk == Executing maintenance job: Limb Calibration
+```
+
+
+
+## Run apps individually
+
+### Schedule Jobs
+
+1. Open a terminal and run the `job-service` app. Build the dependencies if you haven't already.
+
+```bash
+cd ./job-service
+dotnet build
+```
+
+```bash
+dapr run --app-id job-service-sdk --app-port 6200 --dapr-http-port 6280 -- dotnet run
+```
+
+2. In a new terminal window, schedule the `R2-D2` Job using the Jobs API.
+
+```bash
+curl -X POST \
+ http://localhost:6200/scheduleJob \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "R2-D2",
+ "job": "Oil Change",
+ "dueTime": 2
+ }'
+```
+
+In the `job-service` terminal window, the output should be:
+
+```text
+== APP - job-app == Received job request...
+== APP - job-app == Starting droid: R2-D2
+== APP - job-app == Executing maintenance job: Oil Change
+```
+
+3. On the same terminal window, schedule the `C-3PO` Job using the Jobs API.
+
+```bash
+curl -X POST \
+ http://localhost:6200/scheduleJob \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "C-3PO",
+ "job": "Limb Calibration",
+ "dueTime": 30
+ }'
+```
+
+### Get a scheduled job
+
+1. On the same terminal window, run the command below to get the recently scheduled `C-3PO` job.
+
+```bash
+curl -X GET http://localhost:6200/getJob/C-3PO -H "Content-Type: application/json"
+```
+
+You should see the following:
+
+```text
+{"name":"c-3po", "dueTime":"30s", "data":{"@type":"type.googleapis.com/google.protobuf.Value", "value":{"Value":"C-3PO:Limb Calibration"}}}
+```
+
+### Delete a scheduled job
+
+1. On the same terminal window, run the command below to deleted the recently scheduled `C-3PO` job.
+
+```bash
+curl -X DELETE http://localhost:6200/deleteJob/C-3PO -H "Content-Type: application/json"
+```
+
+2. Run the command below to attempt to retrieve the deleted job:
+
+```bash
+curl -X GET http://localhost:6200/getJob/C-3PO -H "Content-Type: application/json"
+```
+
+In the `job-service` terminal window, the output should be similar to the following:
+
+```text
+ERRO[0157] Error getting job C-3PO due to: rpc error: code = NotFound desc = job not found: C-3PO instance=local scope=dapr.api type=log ver=1.15.0
+```
diff --git a/jobs/csharp/sdk/dapr.yaml b/jobs/csharp/sdk/dapr.yaml
new file mode 100644
index 000000000..e96c92ba2
--- /dev/null
+++ b/jobs/csharp/sdk/dapr.yaml
@@ -0,0 +1,16 @@
+version: 1
+apps:
+ - appDirPath: ./job-service/
+ appID: job-service-sdk
+ appPort: 6200
+ daprHTTPPort: 6280
+ command: ["dotnet", "run"]
+ appLogDestination: console
+ daprdLogDestination: console
+ - appDirPath: ./job-scheduler/
+ appID: job-scheduler-sdk
+ appPort: 6300
+ daprHTTPPort: 6380
+ command: ["dotnet", "run"]
+ appLogDestination: console
+ daprdLogDestination: console
\ No newline at end of file
diff --git a/jobs/csharp/sdk/job-scheduler/Program.cs b/jobs/csharp/sdk/job-scheduler/Program.cs
new file mode 100644
index 000000000..79df5842d
--- /dev/null
+++ b/jobs/csharp/sdk/job-scheduler/Program.cs
@@ -0,0 +1,103 @@
+#pragma warning disable CS0618 // Type or member is obsolete
+
+using System.Text.Json.Serialization;
+using Dapr.Client;
+
+var builder = WebApplication.CreateBuilder(args);
+var app = builder.Build();
+
+await Task.Delay(5000); // Allow time for the job-service-sdk to start
+
+// Instantiate an HTTP client for invoking the job-service-sdk application
+var httpClient = DaprClient.CreateInvokeHttpClient(appId: "job-service-sdk");
+
+// Job details
+var r2d2Job = new DroidJob
+{
+ Name = "R2-D2",
+ Job = "Oil Change",
+ DueTime = 15
+};
+
+var c3poJob = new DroidJob
+{
+ Name = "C-3PO",
+ Job = "Limb Calibration",
+ DueTime = 20
+};
+
+await Task.Delay(50);
+
+try
+{
+ // Schedule R2-D2 job
+ await ScheduleJob(r2d2Job);
+ await Task.Delay(5000);
+ // Get R2-D2 job details
+ await GetJobDetails(r2d2Job);
+
+ // Schedule C-3PO job
+ await ScheduleJob(c3poJob);
+ await Task.Delay(5000);
+ // Get C-3PO job details
+ await GetJobDetails(c3poJob);
+
+ await Task.Delay(30000); // Allow time for jobs to complete
+}
+catch (Exception ex)
+{
+ Console.Error.WriteLine($"Error: {ex.Message}");
+ Environment.Exit(1);
+}
+
+async Task ScheduleJob(DroidJob job)
+{
+ Console.WriteLine($"Scheduling job...");
+
+ try
+ {
+ var response = await httpClient.PostAsJsonAsync("/scheduleJob", job);
+ var result = await response.Content.ReadAsStringAsync();
+
+ response.EnsureSuccessStatusCode();
+ Console.WriteLine($"Job scheduled: {result}");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error scheduling job: " + e);
+ }
+}
+
+async Task GetJobDetails(DroidJob job)
+{
+ Console.WriteLine($"Getting job: " + job.Name);
+
+ try
+ {
+ var response = await httpClient.GetAsync($"/getJob/{job.Name}");
+ var jobDetails = await response.Content.ReadAsStringAsync();
+
+ response.EnsureSuccessStatusCode();
+ Console.WriteLine($"Job details: {jobDetails}");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error getting job: " + e);
+ }
+}
+
+await app.RunAsync();
+
+public class DroidJob
+{
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("job")]
+ public string? Job { get; set; }
+
+ [JsonPropertyName("dueTime")]
+ public int DueTime { get; set; }
+}
+
+#pragma warning restore CS0618 // Type or member is obsolete
\ No newline at end of file
diff --git a/jobs/csharp/sdk/job-scheduler/jobs-scheduler.csproj b/jobs/csharp/sdk/job-scheduler/jobs-scheduler.csproj
new file mode 100644
index 000000000..3bfa2adc2
--- /dev/null
+++ b/jobs/csharp/sdk/job-scheduler/jobs-scheduler.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net8.0
+ jobs_scheduler
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/jobs/csharp/sdk/job-service/Program.cs b/jobs/csharp/sdk/job-service/Program.cs
new file mode 100644
index 000000000..82301d5d8
--- /dev/null
+++ b/jobs/csharp/sdk/job-service/Program.cs
@@ -0,0 +1,163 @@
+#pragma warning disable CS0618 // Type or member is obsolete
+using Dapr.Jobs;
+using Dapr.Jobs.Extensions;
+using Dapr.Jobs.Models;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+// The jobs host is a background service that connects to the sidecar over gRPC
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.AddDaprJobsClient();
+var app = builder.Build();
+
+var appPort = Environment.GetEnvironmentVariable("APP_PORT") ?? "6200";
+var jobsClient = app.Services.GetRequiredService();
+
+app.MapPost("/scheduleJob", async (HttpContext context) =>
+{
+ var droidJob = await JsonSerializer.DeserializeAsync(context.Request.Body);
+ if (droidJob?.Name is null || droidJob?.Job is null)
+ {
+ context.Response.StatusCode = 400;
+ await context.Response.WriteAsync("Job must contain a name and a task " + context.Request.Body);
+ return;
+ }
+
+ try
+ {
+ var jobData = new JobData
+ {
+ Droid = droidJob.Name,
+ Task = droidJob.Job
+ };
+
+ await jobsClient.ScheduleJobWithPayloadAsync(droidJob.Name, DaprJobSchedule.FromDuration(TimeSpan.FromSeconds(droidJob.DueTime)), payload: jobData, repeats: 1); //Schedule cron job that repeats once
+
+ Console.WriteLine($"Job Scheduled: {droidJob.Name}");
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsJsonAsync(droidJob);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error scheduling job: " + e);
+ }
+ return;
+});
+
+app.MapGet("/getJob/{name}", async (HttpContext context) =>
+{
+ var jobName = context.Request.RouteValues["name"]?.ToString();
+ Console.WriteLine($"Getting job...");
+
+ if (string.IsNullOrEmpty(jobName))
+ {
+ context.Response.StatusCode = 400;
+ await context.Response.WriteAsync("Job name required");
+ return;
+ }
+
+ try
+ {
+ var jobDetails = await jobsClient.GetJobAsync(jobName);
+
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsJsonAsync(jobDetails);
+
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error getting job: " + e);
+ context.Response.StatusCode = 400;
+ await context.Response.WriteAsync($"Error getting job");
+ }
+ return;
+});
+
+app.MapDelete("/deleteJob/{name}", async (HttpContext context) =>
+{
+ var jobName = context.Request.RouteValues["name"]?.ToString();
+ Console.WriteLine($"Deleting job: " + jobName);
+
+ if (string.IsNullOrEmpty(jobName))
+ {
+ context.Response.StatusCode = 400;
+ await context.Response.WriteAsync("Job name required");
+ return;
+ }
+
+ try
+ {
+ await jobsClient.DeleteJobAsync(jobName);
+ Console.WriteLine($"Job deleted: {jobName}");
+
+ context.Response.StatusCode = 200;
+ await context.Response.WriteAsync("Job deleted");
+
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Error deleting job: " + e);
+ context.Response.StatusCode = 400;
+ await context.Response.WriteAsync($"Error deleting job");
+ }
+ return;
+});
+
+// Job handler route to capture incoming jobs
+app.MapDaprScheduledJobHandler((string jobName, ReadOnlyMemory jobPayload) =>
+{
+ Console.WriteLine("Handling job...");
+ var deserializedPayload = Encoding.UTF8.GetString(jobPayload.Span);
+
+ try
+ {
+ if (deserializedPayload is null)
+ {
+ throw new Exception("Payload is null");
+ }
+
+ var jobData = JsonSerializer.Deserialize(deserializedPayload);
+ if (jobData?.Droid is null || jobData?.Task is null)
+ {
+ throw new Exception("Invalid format of job data.");
+ }
+
+ // Handling Droid Job from decoded value
+ Console.WriteLine($"Starting droid: {jobData.Droid}");
+ Console.WriteLine($"Executing maintenance job: {jobData.Task}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to handle job {jobName}");
+ Console.Error.WriteLine($"Error handling job: {ex.Message}");
+ }
+ return Task.CompletedTask;
+});
+
+app.UseRouting();
+app.Run($"http://*:{appPort}");
+
+// Classes for request and response models
+public class JobData
+{
+ [JsonPropertyName("droid")]
+ public string? Droid { get; set; }
+
+ [JsonPropertyName("task")]
+ public string? Task { get; set; }
+}
+
+public class DroidJob
+{
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("job")]
+ public string? Job { get; set; }
+
+ [JsonPropertyName("dueTime")]
+ public int DueTime { get; set; }
+}
+
+#pragma warning restore CS0618 // Type or member is obsolete
\ No newline at end of file
diff --git a/jobs/csharp/sdk/job-service/Properties/launchSettings.json b/jobs/csharp/sdk/job-service/Properties/launchSettings.json
new file mode 100644
index 000000000..2b151427e
--- /dev/null
+++ b/jobs/csharp/sdk/job-service/Properties/launchSettings.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:5305",
+ "sslPort": 44346
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5023",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7073;http://localhost:5023",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/jobs/csharp/sdk/job-service/appsettings.Development.json b/jobs/csharp/sdk/job-service/appsettings.Development.json
new file mode 100644
index 000000000..0c208ae91
--- /dev/null
+++ b/jobs/csharp/sdk/job-service/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/jobs/csharp/sdk/job-service/appsettings.json b/jobs/csharp/sdk/job-service/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/jobs/csharp/sdk/job-service/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/jobs/csharp/sdk/job-service/job-service.csproj b/jobs/csharp/sdk/job-service/job-service.csproj
new file mode 100644
index 000000000..77287559f
--- /dev/null
+++ b/jobs/csharp/sdk/job-service/job-service.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jobs/csharp/sdk/makefile b/jobs/csharp/sdk/makefile
new file mode 100644
index 000000000..e7a8826bf
--- /dev/null
+++ b/jobs/csharp/sdk/makefile
@@ -0,0 +1,2 @@
+include ../../../docker.mk
+include ../../../validate.mk
\ No newline at end of file