C# API Documentation & Wrapper

.NET 6+ client for validation, ZIP packs, and account downloads

Unshared.shop / Blog / C# API Documentation & Wrapper
Cyanokit March 31, 2026 16 min read API Documentation

.NET against the current router

This is the 2026 C# reference for https://api.unshared.shop/api/. Use HttpClient (or the wrapper below). Do not call EnsureSuccessStatusCode() before reading JSON — validation failures are often HTTP 400 with a useful message body.

Base URL: https://api.unshared.shop/api/
Token: My Profile for purchase/download routes. validate, validate_url, info, rarity, and products are public.

Endpoints

  • POST ?action=validate — multipart. Fields: file, kv_file, or kv_files[]. Raw 16 KB KV or ZIP pack.
  • POST ?action=validate_url — form url, optional product name for expected pack size.
  • GET ?action=info&id={12-digit serial} — UnsharedSHOP share log. Optional status=1|2 to record a check.
  • GET ?action=rarity&date=MM-DD-YY
  • GET ?action=products — public catalog with credit_cost.
  • GET ?action=profile / GET ?action=downloads / POST ?action=purchase — require X-API-Token.
  • GET ?action=download&token=&txn= — JSON redirect, or add stream=1 to receive bytes (extract=1 by default).

Auth header

http.DefaultRequestHeaders.Add("X-API-Token", token);
// or: Authorization: Bearer {token}
// or: ?api_token= on the query string (checked first by the server)

Minimal .NET 6+ client

using System.Net.Http.Headers;
using System.Text.Json;

public sealed class UnsharedApiClient : IDisposable
{
    private readonly HttpClient _http;
    private readonly string _base;

    public UnsharedApiClient(string token, string baseUrl = "https://api.unshared.shop/api/")
    {
        _base = baseUrl.TrimEnd('/') + "/";
        _http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
        if (!string.IsNullOrWhiteSpace(token))
            _http.DefaultRequestHeaders.Add("X-API-Token", token);
    }

    public async Task<JsonElement> ValidateFileAsync(string filePath)
    {
        using var form = new MultipartFormDataContent();
        await using var fs = File.OpenRead(filePath);
        using var sc = new StreamContent(fs);
        sc.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        form.Add(sc, "file", Path.GetFileName(filePath));
        using var res = await _http.PostAsync(_base + "?action=validate", form);
        var raw = await res.Content.ReadAsStringAsync();
        return JsonDocument.Parse(raw).RootElement.Clone();
    }

    public async Task<JsonElement> CheckSerialAsync(string serial)
    {
        using var res = await _http.GetAsync(_base + "?action=info&id=" + Uri.EscapeDataString(serial));
        var raw = await res.Content.ReadAsStringAsync();
        return JsonDocument.Parse(raw).RootElement.Clone();
    }

    public void Dispose() => _http.Dispose();
}

Reading a result

using var api = new UnsharedApiClient("YOUR_TOKEN");
var doc = await api.ValidateFileAsync("kv.bin");

if (doc.TryGetProperty("multi", out var multi) && multi.GetBoolean())
{
    Console.WriteLine(doc.GetProperty("message").GetString());
    foreach (var kv in doc.GetProperty("results").EnumerateArray())
        Console.WriteLine($"{kv.GetProperty("serial").GetString()}: {kv.GetProperty("status").GetString()}");
    return;
}

if (doc.TryGetProperty("success", out var ok) && ok.GetBoolean())
{
    Console.WriteLine(doc.GetProperty("status").GetString());
    var serial = doc.TryGetProperty("serial", out var s) ? s.GetString() : null;
    if (!string.IsNullOrWhiteSpace(serial))
    {
        var info = await api.CheckSerialAsync(serial);
        Console.WriteLine("UnsharedSHOP shared log: " + info.GetProperty("shared"));
    }

    if (doc.TryGetProperty("sharing", out var sharing) &&
        sharing.TryGetProperty("is_shared", out var partnerShared) && partnerShared.GetBoolean())
    {
        Console.WriteLine("Listed on a partner database — inspect sharing.shared_partners");
    }
}
else
{
    Console.WriteLine(doc.TryGetProperty("message", out var m) ? m.GetString() : "Validation failed");
}

ZIP and KVChecker fields

  • Multiple 16 KB files in one ZIP are valid. You get results[], not an error.
  • partner_checks is keyed by partner name. first_seen set means a hit; checked: true with a null date means they looked and found nothing.
  • kvchecker.verify_link / signature (or kvchecker.verifications[] on packs) are the signed proof from KVChecker.com.
  • Timeouts: live Xbox Live lookups often take several seconds. Set HttpClient.Timeout to 90s.

Downloads from C#

After purchase or downloads, call ?action=download&stream=1&token={download_token}&txn={transaction_id} if you want bytes in-process. Without stream=1 the JSON redirect points at dl.unshared.shop/kv.bin?id=…&dl=true, which consumes a link use.

Support ticket if a field changed on your integration.