Shopify in C#, part 1: the Admin API, a custom app, and your first customers
Part 1from the Shopify in C# series · 6 parts in all
Stripe and Shopify are the two API surfaces of MapleCart's stack, and they could not be more different: Stripe is a payments ledger you call with intents; Shopify is a whole store — customers, products, orders, fulfillments — that you extend. This series teaches the Admin API to a .NET developer in six parts. Part 1: get a credential, make a call, and be the person who "knows Shopify".
Where the token comes from (2018 edition)
Two kinds of apps talk to a store's Admin API. Public apps are distributed to many merchants and use OAuth (the authorization-code redirect dance). A private app is your own credential for your own store: you create it in the admin, and it authenticates with plain HTTP basic auth — the API key and password joined by a colon. That is the simplest possible start:
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
// 2018: private app = basic auth. No OAuth, no tokens to refresh.
var http = new HttpClient();
var shop = "maplecart.myshopify.com";
http.BaseAddress = new System.Uri($"https://{shop}/admin/");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(
$"{apiKey}:{password}"))); // from Shopify admin: Apps -> Manage private apps
// The resource grid that organizes the whole REST API:
// /admin/customers.json customers
// /admin/orders.json orders
// /admin/draft_orders.json "invoices": orders you create for the customer
// /admin/products.json catalog
var resp = await http.GetAsync("customers.json?limit=5");
var json = await resp.Content.ReadAsStringAsync(); // Shopify's JSON, verbatim
Note the shape: everything is {resource}.json and Shopify's JSON comes back
exactly as documented — a thin C# layer over raw HttpClient and
System.Text.Json keeps you closest to the docs (and is precisely how our
generic integration library is built).
The first real query: customers
// customers.json wraps the list in a root property named after the resource.
// { "customers": [ { "id": 8822423351, "first_name": "Beth", ... } ] }
using System.Text.Json;
var doc = JsonDocument.Parse(json);
foreach (var c in doc.RootElement.GetProperty("customers").EnumerateArray())
{
var id = c.GetProperty("id").GetInt64();
var email = c.GetProperty("email").GetString();
var orders = c.GetProperty("orders_count").GetInt32(); // rollup field
Console.WriteLine($"{id} {email} orders={orders}");
}
// Get one: /admin/customers/{id}.json -> { "customer": {...} }
// Search: /admin/customers/search.json?query=email:[email protected]
Pagination, 2018 style
REST pagination this year is the simple kind: ?page=2&limit=50 and you
keep asking for pages until an empty array returns. It works, it is easy to script — and
it is also the thing the API team will spend 2019 replacing (part 2 shows the cursor
scheme that replaces it).
Writing: create and update
Create POSTs the resource wrapped in its root property; send exactly the fields you have and Shopify fills in the rest. One habit from the start: build request bodies with the serializer, never with hand-concatenated JSON strings — quoting accidents are the classic integration bug, and the serializer is what this whole series uses:
var body = JsonSerializer.Serialize(new {
customer = new {
email = "[email protected]",
first_name = "Beth",
last_name = "Carpenter",
tags = "vip,wholesale"
}
});
var resp = await http.PostAsync("customers.json",
new StringContent(body, Encoding.UTF8, "application/json"));
// Update: PUT to the id URL. Note PUT - Shopify's REST API has no PATCH verb;
// if your client library exposes "Patch", it is translating to PUT under the hood.
var upd = JsonSerializer.Serialize(new { customer = new { tags = "vip" } });
var resp2 = await http.PutAsync($"customers/{id}.json",
new StringContent(upd, Encoding.UTF8, "application/json"));
// Errors come back as { "errors": { "email": ["is not valid"] } } - check
// IsSuccessStatusCode before parsing, then surface that structure verbatim.
That is the whole loop: authenticate, GET/POST/PUT
{resource}.json, inspect the root property. Parts 2–6 go deep — customers
done properly (search, addresses, metafields), the GraphQL pivot, orders and draft-order
invoices, Shopify Payments transactions and refunds, and fulfillments in the 2025
GraphQL-first world.