diff --git a/SVSim.EmulatedEntrypoint/Controllers/EmblemController.cs b/SVSim.EmulatedEntrypoint/Controllers/EmblemController.cs
new file mode 100644
index 00000000..e0fc4c50
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Controllers/EmblemController.cs
@@ -0,0 +1,39 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using SVSim.Database;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Common;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
+using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Emblem;
+
+namespace SVSim.EmulatedEntrypoint.Controllers;
+
+///
+/// /emblem/* — viewer-scoped emblem (lobby/profile badge) management. Read, mark-favorite,
+/// set-currently-displayed. Mirrors /sleeve/* and /degree/*.
+///
+[Route("emblem")]
+public class EmblemController : SVSimController
+{
+ private readonly SVSimDbContext _db;
+
+ public EmblemController(SVSimDbContext db) => _db = db;
+
+ [HttpPost("emblem_list")]
+ public async Task> EmblemList(BaseRequest _, CancellationToken ct)
+ {
+ if (!TryGetViewerId(out long viewerId)) return Unauthorized();
+
+ var viewer = await _db.Viewers
+ .Include(v => v.Emblems)
+ .FirstOrDefaultAsync(v => v.Id == viewerId, ct);
+ if (viewer is null) return Unauthorized();
+
+ return new EmblemListResponse
+ {
+ UserEmblemList = viewer.Emblems
+ .Select(e => new EmblemListEntry { EmblemId = e.Id })
+ .OrderBy(x => x.EmblemId)
+ .ToList(),
+ };
+ }
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListEntry.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListEntry.cs
new file mode 100644
index 00000000..8fc34f11
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListEntry.cs
@@ -0,0 +1,12 @@
+using MessagePack;
+using System.Text.Json.Serialization;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Emblem;
+
+[MessagePackObject]
+public class EmblemListEntry
+{
+ [JsonPropertyName("emblem_id")]
+ [Key("emblem_id")]
+ public int EmblemId { get; set; }
+}
diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListResponse.cs
new file mode 100644
index 00000000..bd1b0a2c
--- /dev/null
+++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/Emblem/EmblemListResponse.cs
@@ -0,0 +1,12 @@
+using MessagePack;
+using System.Text.Json.Serialization;
+
+namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Emblem;
+
+[MessagePackObject]
+public class EmblemListResponse
+{
+ [JsonPropertyName("user_emblem_list")]
+ [Key("user_emblem_list")]
+ public List UserEmblemList { get; set; } = new();
+}
diff --git a/SVSim.UnitTests/Controllers/EmblemControllerListTests.cs b/SVSim.UnitTests/Controllers/EmblemControllerListTests.cs
new file mode 100644
index 00000000..579893b1
--- /dev/null
+++ b/SVSim.UnitTests/Controllers/EmblemControllerListTests.cs
@@ -0,0 +1,49 @@
+using System.Net;
+using System.Text;
+using System.Text.Json;
+using NUnit.Framework;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using SVSim.Database;
+using SVSim.Database.Models;
+using SVSim.UnitTests.Infrastructure;
+
+namespace SVSim.UnitTests.Controllers;
+
+public class EmblemControllerListTests
+{
+ [Test]
+ public async Task EmblemList_returns_owned_ids_wrapped()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync(tutorialState: 0);
+
+ using (var scope = factory.Services.CreateScope())
+ {
+ var db = scope.ServiceProvider.GetRequiredService();
+ var viewer = await db.Viewers.Include(v => v.Emblems).FirstAsync(v => v.Id == viewerId);
+ var e100 = await db.Set().FirstOrDefaultAsync(e => e.Id == 100)
+ ?? db.Set().Add(new EmblemEntry { Id = 100 }).Entity;
+ var e101 = await db.Set().FirstOrDefaultAsync(e => e.Id == 101)
+ ?? db.Set().Add(new EmblemEntry { Id = 101 }).Entity;
+ viewer.Emblems.Add(e100);
+ viewer.Emblems.Add(e101);
+ await db.SaveChangesAsync();
+ }
+
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+ var requestJson = """{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
+ var response = await client.PostAsync("/emblem/emblem_list",
+ new StringContent(requestJson, Encoding.UTF8, "application/json"));
+
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
+
+ var body = await response.Content.ReadAsStringAsync();
+ using var doc = JsonDocument.Parse(body);
+ var ids = doc.RootElement.GetProperty("user_emblem_list").EnumerateArray()
+ .Select(e => e.GetProperty("emblem_id").GetInt32())
+ .ToList();
+ Assert.That(ids, Does.Contain(100));
+ Assert.That(ids, Does.Contain(101));
+ }
+}