Ham anahtar yalnızca üretim anında bir kez gösterilir, geri okunamaz. Kaybederseniz yenisini üretip eskisini iptal edin. Anahtarı istemci tarafı koda koymayın — yalnızca kendi sunucunuzda tutun.
Geçersiz, iptal edilmiş ve süresi dolmuş anahtar aynı yanıtı alır:
401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya süresi dolmuş API anahtarı",
"field": null
}
}
İzinler
İzinler anahtar üretilirken seçilir ve sonradan değiştirilemez; yetkiyi değiştirmek için yeni anahtar üretip eskisini iptal edersiniz.
İzin
Ne yapar
orders:read
Sipariş listesi ve detayını okur.
orders:write
Siparişi onaylar (Hazırlanıyor).
deliveries:write
Dijital ürün kalemlerine teslimat işler.
invoices:write
Kesilen faturayı siparişe işler (yalnızca kurumsal satıcı).
403 Forbidden
{
"error": {
"code": "insufficient_scope",
"message": "Bu uç için gereken izin anahtarınızda yok: orders:write",
"field": null
}
}
Sayfalama
Liste uçları cursor ile sayfalanır. Yanıttaki nextCursor değerini bir sonraki isteğe ?cursor= olarak verin; null geldiğinde son sayfadasınız. limit varsayılan 25, en fazla 100.
Gövde doğrulaması, izin reddi, istek limiti ve iş kuralı hataları dahil tüm hatalar aynı yapıdadır. Koşullarınızı code alanına bağlayın; mesaj metni değişebilir. field hangi girdinin sorunlu olduğunu söyler.
Hata gövdesi
{
"error": {
"code": "invalid_status_transition",
"message": "Yalnızca ödemesi alınmış (PAID) sipariş onaylanabilir; bu sipariş PREPARING durumunda.",
"field": "status"
}
}
Kod
HTTP
Anlamı
unauthorized
401
Anahtar geçersiz, iptal edilmiş ya da süresi dolmuş.
insufficient_scope
403
Anahtarın bu uç için izni yok.
order_not_found
404
Sipariş bu mağazada yok.
item_not_found
404
Sipariş kalemi bulunamadı.
invalid_request
400
Parametre hatalı veya fazladan alan gönderildi.
invalid_status_transition
400
Sipariş bu durumdayken bu işlem yapılamaz.
not_digital_item
400
Kalem dijital değil.
order_not_paid
400
Ödeme alınmadan teslimat yapılamaz.
order_not_invoiceable
400
Sipariş faturalandırılabilir durumda değil.
seller_not_corporate
400
Fatura yalnızca kurumsal satıcıda işlenebilir.
rate_limited
429
İstek sınırı aşıldı.
internal_error
500
Beklenmeyen hata.
İstek limiti dakikada 600'dür ve anahtar başına sayılır.
Bu uç idempotent değildir. Zaten onaylanmış bir siparişi tekrar onaylarsanız 400 ve invalid_status_transition alırsınız. Yeniden deneme yazarken bu kodu "zaten onaylanmış" olarak ele alın.
Dijital ürün kalemine lisans kodu, indirme bağlantısı ya da serbest metin işler. itemId değerini sipariş detayındaki items[].id alanından alırsınız. Aynı kaleme birden çok teslimat eklenebilir.
Alan
Zorunlu
Açıklama
type
evet
CODE, LINK veya TEXT
content
evet
Kod / bağlantı / metin. LINK için http(s) ile başlamalı.
note
hayır
Alıcıya görünen not.
İstek
curl -X POST -H "Authorization: Bearer $SHOPHIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "CODE",
"content": "LISANS-ABC-123",
"note": "Kurulum sırasında bu kodu girin"
}' \
https://api.shophin.com/api/partner/v1/orders/SPH-MRXLHY0V-C528/items/cmrxlhy0v000dh37gtsibedq6/deliveries
Tüm dijital kalemler teslim edildiyse ve fiziksel kalem yoksa sipariş kendiliğinden DELIVERED olur ve alıcıya teslimat e-postası gider.
Dosya teslimatı bu uçtan yapılamaz; type: "FILE" gönderirseniz invalid_request alırsınız. Dosyayı kendi sunucunuzda barındırıp LINK olarak gönderebilirsiniz.
POST
Fatura gönder
POST/orders/{orderNumber}/invoice
Gerekli izin:invoices:write
Faturayı kime keseceğinizi siparişin invoiceInfo alanından okursunuz. Bu uç ise kestiğiniz faturanın bilgilerini siparişe işler ve alıcıya PDF bağlantılı "faturanız hazır" e-postası gönderir. PDF sizin sunucunuzda kalır; Shophin yalnızca bağlantıyı saklar.
İki ön koşul: sipariş PREPARING ya da sonraki bir durumda olmalı (önce /approve çağırın) ve satıcı kurumsal olmalıdır.
Gövde
Üç alan yeterlidir. Fazladan alan gönderilirse istek invalid_request ile reddedilir.
Alan
Açıklama
number
Fatura numarası, ör. ABC2026000000123
issuedAt
Düzenlenme tarihi (ISO-8601)
pdfUrl
Faturanın PDF adresi. Yalnızca https kabul edilir — bağlantı müşteriye e-posta ile gider.
İdempotenttir. Aynı fatura numarası ikinci kez gönderilirse yeni kayıt açılmaz, mevcut kayıt 200 ile döner ve müşteriye ikinci kez e-posta gitmez. İlk yazımda 201 döner.
Tutar göndermeye gerek yoktur: fatura tutarları ve KDV kırılımı sipariş gövdesinde hazırdır (taxSummary, shippingTax).
Bağlantının kalıcı ve herkese açık olması gerekir — müşteri e-postayı aylar sonra açtığında da faturasına ulaşabilmeli.
Ayrı bir liste ucu yoktur: işlenmiş faturalar sipariş gövdesindeki invoices alanında gelir. Aynı siparişe farklı number ile ikinci bir fatura gönderebilirsiniz; iade faturası bu uçtan gönderilmez.
KDV
Fiyatlar KDV dahildir. KDV tutarın üstüne eklenmez, içinden ayrıştırılır. lineTotal üzerine KDV eklerseniz müşteriden tahsil edilenden fazlasını faturalarsınız.
Hesabı sizin yapmanıza gerek yok: her kalemde taxBase ve taxTotal hazır gelir. Kuruş artığı KDV tarafına yazılır, böylece ikisinin toplamı brüt tutara birebir eşittir.
Kargo
Kargo her zaman %20 KDV'lidir ve satıcının tipinden etkilenmez. Tutar shippingTax alanında ayrıca verilir; ücretsiz kargoda null olur.
Oran bazında kırılım
taxSummary, e-Arşiv faturasının istediği oran bazlı satırları verir. Kargo da bu kırılıma dahildir; aynı orandaki ürün kalemleriyle tek satırda toplanır.
Kargoyu ayrı bir fatura satırı olarak göstermek isterseniz shippingTax alanını kullanın ve taxSummary'den çıkarın — iki kez saymayın.
Oranın null gelmesi
vatRate, satıcı bireysel (vergiden muaf) olduğunda null döner ve o kalem taxSummary'ye girmez. Kalemlerden herhangi biri için oran bilinmiyorsa sipariş düzeyindeki taxTotal de null olur — bu durumda faturayı otomatik kesmeyin. Kargo bu kuraldan etkilenmez.
Hem ürün hem kargo oranı sipariş anında dondurulur; geçmiş siparişler sonradan değişmez.
Sipariş Durumları
Durum
Anlamı
PENDING_PAYMENT
Ödeme bekleniyor. İşlem yapmayın.
PAID
Ödeme alındı, satıcı onayı bekleniyor.
PREPARING
Onaylandı. Faturalandırılabilir.
SHIPPED
Kargoya verildi.
DELIVERED
Teslim edildi.
CANCELLED
İptal edildi.
REFUNDED
İade tamamlandı.
API üzerinden yapabileceğiniz geçiş PAID → PREPARING'dir. Dijital-only siparişlerde teslimat sonrası DELIVERED otomatik atanır.
Örnek Akış
Ödenmiş siparişleri çek, onayla, dijital teslimatı yap ve faturayı işle. lisansUret ve faturaKes sizin tarafınızdaki işlevlerdir.
const BASE = "https://api.shophin.com/api/partner/v1";
const KEY = process.env.SHOPHIN_KEY;
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (!res.ok) throw Object.assign(new Error(data.error.message), data.error);
return data;
}
const { data: orders } = await call("GET", "/orders?status=PAID&limit=100");
for (const order of orders) {
// 1) Onayla — zaten onaylıysa hata sayma
try {
await call("POST", `/orders/${order.orderNumber}/approve`);
} catch (e) {
if (e.code !== "invalid_status_transition") throw e;
}
// 2) Bekleyen dijital kalemlere teslimat işle
for (const item of order.items) {
if (!item.awaitingDelivery) continue;
for (let i = 0; i < item.quantity; i++) {
await call("POST", `/orders/${order.orderNumber}/items/${item.id}/deliveries`, {
type: "CODE",
content: await lisansUret(item.title),
});
}
}
// 3) Faturayı kes ve siparişe işle
const fatura = await faturaKes(order);
await call("POST", `/orders/${order.orderNumber}/invoice`, {
number: fatura.number,
issuedAt: fatura.issuedAt,
pdfUrl: fatura.pdfUrl,
});
}
import os
import requests
BASE = "https://api.shophin.com/api/partner/v1"
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {os.environ['SHOPHIN_KEY']}",
"Content-Type": "application/json",
})
class ShophinError(Exception):
def __init__(self, code: str, message: str):
super().__init__(message)
self.code = code
def call(method: str, path: str, body: dict | None = None) -> dict:
res = session.request(method, BASE + path, json=body)
data = res.json()
if not res.ok:
raise ShophinError(data["error"]["code"], data["error"]["message"])
return data
orders = call("GET", "/orders?status=PAID&limit=100")["data"]
for order in orders:
no = order["orderNumber"]
# 1) Onayla — zaten onaylıysa hata sayma
try:
call("POST", f"/orders/{no}/approve")
except ShophinError as e:
if e.code != "invalid_status_transition":
raise
# 2) Bekleyen dijital kalemlere teslimat işle
for item in order["items"]:
if not item["awaitingDelivery"]:
continue
for _ in range(item["quantity"]):
call("POST", f"/orders/{no}/items/{item['id']}/deliveries", {
"type": "CODE",
"content": lisans_uret(item["title"]),
})
# 3) Faturayı kes ve siparişe işle
fatura = fatura_kes(order)
call("POST", f"/orders/{no}/invoice", {
"number": fatura["number"],
"issuedAt": fatura["issuedAt"],
"pdfUrl": fatura["pdfUrl"],
})
using System.Net.Http.Headers;
using System.Net.Http.Json;
const string Base = "https://api.shophin.com/api/partner/v1";
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("SHOPHIN_KEY"));
// System.Net.Http.Json web varsayılanlarını kullanır: orderNumber ↔ OrderNumber
async Task<T?> Call<T>(HttpMethod method, string path, object? body = null)
{
using var req = new HttpRequestMessage(method, Base + path);
if (body is not null) req.Content = JsonContent.Create(body);
using var res = await http.SendAsync(req);
if (!res.IsSuccessStatusCode)
{
var wrapper = await res.Content.ReadFromJsonAsync<ErrorWrapper>();
throw new ShophinException(wrapper!.Error.Code, wrapper.Error.Message);
}
return await res.Content.ReadFromJsonAsync<T>();
}
var page = await Call<Page>(HttpMethod.Get, "/orders?status=PAID&limit=100");
foreach (var order in page!.Data)
{
// 1) Onayla — zaten onaylıysa hata sayma
try
{
await Call<object>(HttpMethod.Post, $"/orders/{order.OrderNumber}/approve");
}
catch (ShophinException e) when (e.Code == "invalid_status_transition") { }
// 2) Bekleyen dijital kalemlere teslimat işle
foreach (var item in order.Items)
{
if (!item.AwaitingDelivery) continue;
for (var i = 0; i < item.Quantity; i++)
{
await Call<object>(
HttpMethod.Post,
$"/orders/{order.OrderNumber}/items/{item.Id}/deliveries",
new { type = "CODE", content = LisansUret(item.Title) });
}
}
// 3) Faturayı kes ve siparişe işle
var fatura = FaturaKes(order);
await Call<object>(HttpMethod.Post, $"/orders/{order.OrderNumber}/invoice", new
{
number = fatura.Number,
issuedAt = fatura.IssuedAt,
pdfUrl = fatura.PdfUrl,
});
}
record ErrorWrapper(ApiError Error);
record ApiError(string Code, string Message, string? Field);
record Page(List<Order> Data);
record Order(string OrderNumber, List<Item> Items);
record Item(string Id, string Title, int Quantity, bool AwaitingDelivery);
class ShophinException(string code, string message) : Exception(message)
{
public string Code { get; } = code;
}