2 · Token Exchange (/plugin-api/oauth/token)

Connect'ten gelen tek-kullanımlık code'u, sunucu-sunucu bir istekle kalıcı credential'lara çevirirsin: apiKey, webhookSecret, tenantId ve gerçekten verilen scopes. Bu credential'ları tenant başına saklarsın.

İstek

POST {RESTOMENUM_BASE}/plugin-api/oauth/token
// {RESTOMENUM_BASE} = ortam tabanı (kanonik liste /docs/api):
//   Dev / Sandbox : https://sandbox.plugins.restomenum.app
//   Production    : https://plugins.restomenum.app
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "<connect ile gelen tek-kullanımlık code>",
  "client_id": "<pluginId — eklenti UUID'si; SLUG DEĞİL (slug → 401 invalid_client)>",
  "client_secret": "<portalda üretilen cs_...>"
}

{RESTOMENUM_BASE} ortam tabanıdır. Sabitleme — /connect'e gelen environment parametresiyle seç:

environmentOrtamBase URL
sandboxDev / Sandboxhttps://sandbox.plugins.restomenum.app
productionProductionhttps://plugins.restomenum.app
Kökü kurulum başına seç. Tek bir ortama sabitlenmiş kod, production kurulumunda takası yanlış köke gönderir. Ayrıntı: Ortamlar.

Yanıt

200 OK
// ZARF (teyitli): BAŞARI yanıtı DAİMA zarflıdır — alanlar `data` İÇİNDEDİR, kökte DEĞİL.
//    Kökten okuyan kod alanları undefined görür → canlıda patlar.
//    ⚠️ HATA yanıtı bilinçli olarak FARKLIDIR — aşağıdaki TOKEN_ERROR'a bak.
{
  "success": true,
  "data": {
    "tokenType": "...",       // token tipi
    "environment": "sandbox", // "sandbox" | "production" — KOŞULSUZ gelir; credential'ı tenant + ORTAM başına sakla
    "tenantId": "...",        // tenant kimliği (kurulumu bununla eşle)
    "pluginId": "...",        // eklenti (client) id — client_id ile AYNI UUID
    "version": "...",         // kurulum manifest sürümü
    "scopes": ["orders:read", "events:subscribe", "..."],  // gerçekten verilen yetkiler
    "apiKey": "...",          // bu tenant için Callback API çağrılarında kullan
    "webhookSecret": "..."    // bu tenant'ın webhook + session token imzası
  }
}
Başarı yanıtı DAİMA zarflıdır — alanlar data içindedir, kökte değil. Kökten okuyan parse kodu (body.apiKey) undefined görür ve entegrasyonun canlıda hata verir; doğrusu body.data.apiKey. Resmi SDK exchangeCode bunu zaten çözer.

⚠️ Hata yanıtı bilinçli olarak FARKLIDIR: error ve error_description kökte döner, çünkü RFC 6749 §5.2 standart OAuth istemcilerinin beklediği şekil budur. { success:false, message } yanına geri-uyum aynası olarak konur. Sözleşmedeki tek asimetri budur — başarıda zarf, hatada RFC şekli.
Hata yanıtı — alanlar KÖKTE (RFC 6749 §5.2)
4xx
{
  "error": "invalid_request",          // RFC 6749 §5.2 — KÖKTE
  "error_description": "...",          // RFC 6749 §5.2 — KÖKTE
  "success": false,                    // geri-uyum aynası
  "message": "..."                     // geri-uyum aynası
}
  • apiKey — bu tenant için Callback API çağrılarında kullanılır. Üç parçalıdır: serverId.pluginId.secret. Buradaki serverId, zarftaki (envelope) tenantId ile aynı kimliktir (tenantId = serverId'nin dış adı).
  • webhookSecret — webhook + hook imza doğrulaması ve session token doğrulaması.
  • tenantId — tenant kimliği; kurulumu ve gelen event'leri bununla eşle. (apiKey'in içindeki serverId'nin dış adı — aynı kimlik.)
  • scopes — kurulumda fiilen verilen yetkiler (manifest'te istediğinin alt kümesi olabilir).

Adımlar

  1. /connect'te aldığın code + client_id + client_secret ile POST et. client_id = eklentinin pluginId'si (UUID) — eklenti detay sayfasında client_id olarak gösterilir. Slug değildir; slug gönderirsen 401 invalid_client alırsın.
  2. Kalıcı dört değeri (tenantId, apiKey, webhookSecret, scopes) tenantId başına güvenli sakla.
  3. scopes'a göre özelliklerini koşullu çalıştır (verilmeyen yetkiyi varsayma).

apiKey rotasyonu (sızıntı / periyodik)

  • Kurulumun apiKey'ini portaldan yenileyebilirsin: eklentinin Teslim logları → Teslim Sağlığı tablosunda kurulum satırındaki "API key yenile" butonu.
  • Eski key anında geçersiz olur — yeni key yalnız bir kez gösterilir; entegrasyonun yeni key'i alana kadar API çağrıların 401 alır. Kurulumu kaldırıp yeniden kurmana gerek yok.
  • Kullanım: sızıntı şüphesi veya periyodik rotasyon. Henüz bağlanmamış (exchange tamamlanmamış) kurulumda apiKey olmadığından rotate çalışmaz.
  • apiKey'in başka bir son kullanma süresi yoktur — rotate edilene (veya kurulum kaldırılana) kadar geçerlidir.

Hata yanıtı (RFC 6749)

Token ucu artık standart OAuth 2.0 hatası döndürür — başarı yanıtı değişmedi (Connect akışını bozmaz), yalnız hata şekli standartlaştı:

HTTPerrorAnlam
401invalid_clientclient_id/client_secret hatalı — en sık sebep: client_id yerine slug göndermek (doğrusu eklenti UUID'si)
400invalid_grantcode geçersiz/kullanılmış/süresi dolmuş → yeniden Connect
400invalid_requestEksik/yanlış parametre
400unsupported_grant_typegrant_type desteklenmiyor
500server_errorGeçici sunucu hatası → tekrar dene
  • Gövde: { error, error_description } (standart OAuth lib'leri bunu bekler).
  • Geri uyum için { success:false, message } alanları da gönderilir (kademeli geçiş) — ama HTTP status'a göre dallan.
code tek kullanımlıktır; exchange invalid_grant ile başarısız olursa kullanıcıyı yeniden Connect'e yönlendir. client_secret yalnız bu sunucu çağrısında kullanılır.