İçeriğe atla
openplate

Bu sayfada

Çekirdek sunucu

openplate eşitleme protokolü

Kablo ve anahtar protokolü, sürüm 2

Bu sayfa, İngilizce belgeden makine çevirisiyle çevrildi.

Protokol sürümü: 2 · Zarf sürümü: 1 · Durum: 1.0 öncesi, henüz yayınlanmadı

Bu, bir openplate istemcisi ile bir çekirdek sunucu arasındaki kablo protokolünün normatif şartnamesidir. Üçüncü bir tarafın kodumuzu okumadan iki taraftan biri uygulayabilmesi için yazılmıştır: barındırdığımız hizmete karşı senkronize olan alternatif bir istemci veya bir openplate istemcisinin CORE_URL ile yönlendirilebileceği alternatif bir sunucu.

Makine tarafından okunabilir karşılığı, birbirinin elle korunan kopyası olan iki dosyada bulunur:

DepoDosya
openplate-coresrc/protocol.ts
openplateapp/lib/sync/engine/protocol.ts

Her deponun sabitlerini aktarılan sabit değerlere (tests/unit/protocol.test.ts ve tests/unit/sync-engine/protocol.test.ts) karşı doğrulayan bir birim testi vardır. Depolar arasında paylaşılan bir CI yoktur, bu nedenle bu testler bizimle sessiz bir protokol bölünmesi arasında duran tek şeydir. Bu belge normatiftir; TypeScript onun aktarımıdır.

1. Tek paragraflık özet

Tüm anahtarlar istemcidedir. Tüm yerel deposunu serileştirir, gzip ile sıkıştırır, sunucunun hiç görmediği bir anahtarla AES-256-GCM kullanarak şifreler ve sonucu tek bir opak ikili nesne (blob) olarak gönderir. Sunucu baytları saklar, sürümlerini tutar ve başka bir cihazın verilerini ezecek yazma işlemlerini reddeder. İkinci bir cihazın ilk kurulumu yapabilmesi için iki küçük anahtar kayıtları (biri kullanıcının parola cümlesinden, diğeri ise bir kurtarma kodundan türetilmiş iki farklı anahtar şifreleme anahtarıyla sarılmış aynı veri şifreleme anahtarı) da saklar. Sunucudaki hiçbir kod yolu bunların hiçbirinin şifresini çözmez. Sunucu, yönetilen bir bulut kopyasının işletmecisi bir günlüğü açmak için gerekeni elinde tutabilsin diye her hesabın kurtarma kodunu kendi gizli anahtarı altında mühürlü olarak saklar (§3.1). Sunucunun tam olarak ne bildiğini §9 belirtir.

Aşağıdaki resim bütün bir oturumu gösterir. İlk olarak sürüm el sıkışması çalışır ve bu işlem tavsiye niteliğinde değildir: Uyuşmazlık durumunda veya ulaşılamayan bir serviste istemci, karşı tarafın farklı çerçeveleyebileceği bir zarfı göndermek yerine orada durur. §6 bu kuralı belirtir, §5.7 ila §5.9 koruduğu oturum açma işlemidir ve §5.1 ise bir istemcinin telafi etmesi gereken karşılaştır ve değiştir kaybını da içeren gönderme işlemidir.

Sırasıyla tek bir oturum: herhangi bir uyuşmazlıkta eşitlemeyi reddeden sürüm el sıkışması, ardından oturum açma, ardından tek bir karşılaştır ve değiştir göndermesi.
Diyagram kaynağı
sequenceDiagram
    participant C as Client
    participant S as Core server
    C->>S: GET /health
    S-->>C: protocolVersion, envelopeVersion
    alt versions differ, or unreachable
        C->>C: refuse to sync
        Note over C: no push, no pull, no retry
    else versions equal
        C->>S: POST /v1/auth/kdf
        S-->>C: salt, Argon2id params
        C->>C: derive authHash, derive KEK
        C->>S: POST /v1/auth/login
        S-->>C: access token, refresh token
        C->>C: encrypt snapshot under DEK
        C->>S: POST /blob, baseVersion 3
        alt baseVersion matches
            S-->>C: 200, newVersion 4
        else another device wrote first
            S-->>C: 409, currentVersion 5
            C->>S: GET /blob
            C->>C: decrypt, merge, re-encrypt
            C->>S: POST /blob, baseVersion 5
            S-->>C: 200, newVersion 6
        end
    end

2. Terminoloji

TerimAnlamı
DEKVeri şifreleme anahtarı. Rastgele 32 bayt. Veri yığınını şifreler. İstemciden hiçbir zaman sarmalanmadan çıkmaz.
KEKAnahtar şifreleme anahtarı. DEK'i sarmalar. İki tanedir: parola cümlesinden türetilen ve kurtarma kodundan türetilen.
ZarfŞifrelenmiş veri yığınının iletim biçimi: iv ‖ AES-256-GCM(gzip(JSON(payload))).
Anahtar kaydıSarmalanmış bir DEK ile birlikte (yalnızca parola cümlesi türü için) KEK'ini yeniden türetmek için gereken KDF parametreleri.
blobVersionHesap başına monotonik sayaç. Karşılaştır ve değiştir belirteci.
HesapYalıtım birimi. Bir hesabın en fazla bir geçerli veri yığını ve en fazla iki anahtar kaydı bulunur.
E-postaHesap tanımlayıcısı: standartlaştırılmış bir adres (NFKC, kırpılmış, küçük harfe dönüştürülmüş). Sunucu başına benzersizdir.
DavetBir operatör tarafından üretilen, tek bir e-postaya ADANMIŞ tek kullanımlık yetki. İçeri girmenin tek yolu.
EmanetHesabın sunucuda SERVER_SECRET alt anahtarıyla mühürlenmiş kurtarma kodu.
Roladmin veya member. Bir yöneticinin kendi erişim belirteci /v1/admin için kimlik doğrular.

Protokol 2, kullanıcı adının yerini bir e-posta ile değiştirdi (ADR-0005). Sürüm 1'in Handle değeri, yani @ içeremeyen sunucuya özel mat tanımlayıcı artık yok: sütun, ayrıştırıcı ve kural kaldırıldı. Sürüm 1 konuşan bir istemci, yarım yamalak çalışmak yerine sürüm 2 servisiyle konuşmayı reddetmelidir, bkz. §6.

3. Kriptografi (istemci tarafı, sunucu bunların hiçbirini uygulamaz)

Uyumlu bir sunucu bu bölüme ihtiyaç duymaz, bu bölüm alternatif bir istemci birlikte çalışabilsin ve bir denetçi iddiaları kontrol edebilsin diye buradadır.

3.1 Anahtar türetme

                          ┌─HKDF-SHA-256(salt, info=PASSPHRASE_KEK)──► KEK_p   (never sent)
passphrase ─Argon2id(salt, m, t, p)─► hash ─┤
                          └─HKDF-SHA-256(salt, info=AUTH)───────────► authHash (sent to the server)

                                     ┌─HKDF-SHA-256(salt="", info=RECOVERY_KEK)──► KEK_r            (never sent)
recovery code ───────────────────────┤
                                     └─HKDF-SHA-256(salt="", info=RECOVERY_AUTH)─► recoveryAuthHash (sent)
  • Argon2id parametreleri (mevcut hesapları bozmadan daha sonra artırılabilmeleri için parola anahtarı kaydının kdfDescriptor alanında ve hesabın kendi KDF tanımlayıcısında hesap başına kaydedilir): memorySizeKib: 65536 (64 MiB), iterations: 3, parallelism: 1, hashLength: 32. Salt: 16 rastgele bayt.
  • HKDF info etiketleri dondurulmuş, UTF-8 ile kodlanmış bayt dizileridir. Türetilen değerlerin kriptografik olarak bağımsız olması için alan ayrımı sağlarlar: - openplate-sync:passphrase-kek:v1 - openplate-sync:recovery-kek:v1 - openplate-sync:auth:v1 - openplate-sync:recovery-auth:v1
  • İstemcinin parolası olarak gönderdiği şey auth dalıdır. KEK_p değerinin kardeşidir, ebeveyni veya çocuğu değildir: her ikisi de farklı info etiketleri altında aynı Argon2id özeti üzerindeki HKDF çıktılarıdır, bu yüzden birine sahip olmak diğeri hakkında hiçbir bilgi vermez. Sunucunun şifresini çözemediği bir kullanıcının kimliğini doğrulayabilmesinin tüm nedeni budur. authHash 32 bayttır, hatta base64 olarak iletilir.
  • İstemcinin kurtarma koduna sahip olduğunu kanıtlamak için gönderdiği şey recovery-auth dalıdır (§5.14). Tam olarak authHash değerinin KEK_p değerinin kardeşi olması anlamında KEK_r değerinin kardeşidir ve 32 bayttır, hatta base64 olarak iletilir.
  • recovery-auth etiketi hiçbir zaman recovery-kek etiketi değildir. Bu alan ayrımı bir tertip değil, mimari bir zorunluluktur. KEK kolu, günlüğü açan anahtarı türetir; aynı çıktı sunucuya da gönderilseydi bu hizmet, bir DEK'in şifresini çözen malzemenin HMAC'ini saklar ve "işletmeci verilerini okuyamaz" iddiası (yalnızca işletmeci emanet edilmiş kurtarma kodundan yoksunken geçerli olan bir iddia, §9.1), işletmecinin bu değeri hiçbir zaman elinde tutmamış olmasına değil, SHA-256'nın tek yönlü olmasına dayanırdı. Her iki etiket de dondurulmuştur, hiçbiri diğerinden türetilmez ve gelecekte ikisinden birinde yapılacak bir değişiklik, yeniden tanımlama yerine yeni bir :v2 etiketi olur (ADR-0004).
  • Sunucu authHash veya recoveryAuthHash değerini de asla saklamaz. Pepper veritabanı dışında tutulacak şekilde, her birinin HMAC-SHA-256(serverPepper, ...) değerini saklar. Bkz. §5.8.
  • Kurtarma yolu bilerek Argon2id adımını atlar ve bir boş HKDF salt değeri kullanır. Bu bir gözetim hatası değil, doğrudur: RFC 5869 §3.1, girdi anahtar materyali zaten yüksek entropili olduğunda buna izin verir ki 160 bitlik rastgele bir kod yapısı gereği öyledir. Yalnızca düşük entropili insan parolaları belleği zorlayan bir genişletmeye ve gerçek bir salt değerine ihtiyaç duyar.
  • Kurtarma kodu: 5'li gruplar halinde, Crockford tarzı base32 alfabesiyle (0123456789ABCDEFGHJKMNPQRSTVWXYZ, kopyalama hatalarını önlemek için O, I, L yok) sunulan 20 rastgele bayt (160 bit). Kurallı olarak, gruplandırması kaldırılmış ve büyük harfe dönüştürülmüş 32 karakterdir; sunucunun mühürlediği biçim budur.
  • Kurtarma kodu sunucuda EMANETE ALINIR (protokol 2, ADR-0005). İstemci bunu artık kişiye göstermez: ham kodu kayıt gövdesinde bir kez gönderir ve sunucu iv(12) ‖ AES-256-GCM(escrowKey, code) ‖ tag(16) değerini accounts.recovery_code_escrow içinde saklar; burada escrowKey, SERVER_SECRET anahtarının üçüncü dondurulmuş HMAC alt anahtarıdır (openplate-sync:escrow-key:v1, doğrulayıcı pepper'ı ve kukla tanımlayıcı anahtarının yanında). E-posta ile gönderilen bir sıfırlama (§5.12), kodu hesap sahibine geri verir, o da daha sonra bununla sıradan §5.14 rotasyonunu çalıştırır. Yönetilen bir örneğin işletmecisi, bu nedenle bir günlüğü açmak için gerekenleri elinde tutar. Bu, bu servisin ne olduğuna dair gerçek bir değişikliktir, gizlenmek yerine burada belirtilmiştir ve tüm gerekçeleri docs/adr/0005-organization-accounts-and-escrowed-recovery.md içinde tartışılmıştır.
  • Emanet KEK_r üzerinde veya DEK üzerinde değil, KOD üzerindedir. Sunucudaki hiçbir şey bir KEK türetmez, bir DEK'in paketini açmaz veya elinde tutmaz; kod, yalnızca bir istemci üzerinde HKDF çalıştırdıktan sonra bir anahtar haline gelir. Bu durum, kendisi de HKDF çalıştırabilen işletmeciye karşı bir gizlilik sağlamaz; kod yolunda kullanıcı verilerinin şifresinin çözülmesini içermeyen bir sunucu sağlar, bu da iddiayı vaat edilen değil, denetlenebilir kılan şeydir.
  • KEK'ler, dışa aktarılamaz olarak içe aktarılan 256 bitlik AES-GCM anahtarlarıdır.

3.2 Zarf

build:  payload ─► JSON ─► UTF-8 ─► gzip ─► AES-256-GCM(key=DEK, iv=random 12B, aad=AAD) ─► iv ‖ ciphertext‖tag
parse:  split(iv, rest) ─► AES-256-GCM decrypt ─► gunzip ─► UTF-8 ─► JSON ─► payload
  • IV: şifreleme başına yeni, ciphertext değerinin başındaki baytlar olarak paketlenmiş 12 rastgele bayt. Bu protokolün hiçbir yerinde ayrı bir IV alanı yoktur.
  • Etiket: 16 baytlık GCM kimlik doğrulama etiketi şifreli metne eklenir (WebCrypto kuralı).
  • AAD, kurallı ve sabit anahtar sıralı bir JSON nesnesinin UTF-8 kodlamasıdır:
    json
    {"accountId":<int>,"blobVersion":<int>,"payloadSchemaVersion":<int>}

    Bunları bağlamak kes-yapıştır (bir blobu farklı bir hesapta yeniden oynatma) ve geri alma (daha eski bir sürümü veya uyumsuz bir yerel depolama şemasından bir yükü yeniden oynatma) saldırılarını engeller. Bir istemci şifreyi çözerken aynı üçlüyü sunmalıdır, aksi takdirde etiket denetimi başarısız olur; bu da etrafından dolaşılacak bir hata değil, hedeflenen davranıştır.

  • Düz metne şifrelemeden önce Sıkıştırma (gzip, RFC 1952) uygulanır. Şifreli metin sıkıştırılamaz, bu yüzden ya önce sıkıştırırsın ya da hiç sıkıştıramazsın. Bunun neden önemli olduğunu görmek için §8'e, neyi sızdırdığına dair dürüst açıklama için §9.2'ye bak.
  • Yük biçimi (bu protokol için snapshot içindeki her şey opaktır):
    json
    {
      "snapshot": { "...": "the client's local-store snapshot, protocol-opaque" },
      "syncMeta": {
        "perEntity": { "<entityId>": { "lamport": 3, "deviceId": "abc" } },
        "tombstones": [{ "entityId": "x", "entityType": "foodLog", "lamport": 4, "deviceId": "abc" }]
      }
    }
  • Sarmalanmış DEK: iv ‖ AES-256-GCM(key=KEK, plaintext=DEK), AAD yok: sarmalanmış bir DEK belirli bir blob sürümüne bağlı değildir. Uzunluk her zaman 12 + 32 + 16 = 60 bayttır.

3.3 Birleştirme semantiği (istemci tarafı)

Çakışmalar varlık başına (lamport, deviceId) ile çözülür: daha yüksek Lamport sayacı kazanır; eşitlik durumunda sözlükbilimsel deviceId belirleyicidir. Cihazın sistem saati kesinlikle bir sıralama mercii okumaz; sapma gösterir ve cihazlar arasında kolayca hatalı olur. Silme işareti (tombstone) de canlı bir değerle aynı karşılaştırmaya girer. Kabul edilen v1 ödünü: tüm kayıt düzeyinde son yazan kazanır kuralı geçerlidir, bu nedenle iki cihazda aynı varlığına yapılan eşzamanlı bir çevrimdışı düzenlemede daha eski yazım sessizce kaybolur. Alan düzeyinde birleştirme veya çakışma arayüzü yoktur.

3.4 Paylaşım sarmalama (ADR-0002)

Bir paylaşım, aynı DEK'in başka bir hesabın ortak anahtarına hedeflenmiş üçüncü bir sarmalamasıdır. Sunucu bunu depolar, hedeflendiği tek hesaba sunar ve buna ait hiçbir anahtar tutmaz; bu özellik §9.1'i değiştirmez.

sender (grantor, holding recipientPub):
  (ephPriv, ephPub) ← ECDH P-256, fresh per wrap, discarded after
  Z         ← ECDH(ephPriv, recipientPub)
  KEK_share ← HKDF-SHA-256(salt = empty, IKM = Z,
                           info = "openplate-sync:share-kek:p256:v1")
  AAD       ← UTF-8 of canonical fixed-key-order JSON:
              {"grantorAccountId":<int>,"recipientKeyFingerprint":"<base64>"}
  wrap      ← ephPub(65, uncompressed SEC1) ‖ iv(12) ‖ AES-256-GCM(KEK_share, DEK, aad=AAD)
  • Her zaman Uzunluk 125 bayttır. Bunun, §3.2'deki 60 baytlık sarmalanmış DEK'ten farklı bir değişmez olduğuna dikkat et: anahtar kaydı için 60, paylaşım için 125 bayttır. Farklı tablolarda yer alırlar ve uzunluğa göre dallanan ortak bir doğrulama yolu yoktur.
  • P-256 kullanılır ve yalnızca sürüm yerine eğrinin adı etikette belirtilir; böylece gelecekteki bir yapı, :v1 hakkında belirsizlik yaratmak yerine yeni bir etiket olur.
  • Kurtarma kodu için §3.1'de halihazırda belirtilen aynı RFC 5869 §3.1 gerekçeleriyle Boş HKDF tuzu doğrudur: IKM, belleği zorlayan bir genişletmeye ihtiyaç duyan bir insan parolası değil, taze ve yüksek entropili bir ECDH çıktısıdır.
  • Bu sarmalama AAD taşır; §3.2'deki sarmalanmış DEK'ler taşımaz. Bir anahtar kaydı sarmalaması yalnızca sahibine özel bir satırla kapsamlandırılır ve başkasınınkiyle karıştırılamaz. Paylaşım sarmalaması ise sunucu denetimindeki bir ilişkilendirme tablosunda durur ve orada karıştırılabilir: bunu bağlamak, araya eklenmiş bir satırın yanlış günlüğe çözülmek yerine etiket denetiminde başarısız olması anlamına gelir.
  • AAD, yetkilendirilenin hesap kimliğini değil, alıcının anahtar parmak izini bağlar. Değiştirme saldırısı anahtarı hedef alır, bu yüzden bağlama anahtarın adını belirtir ve yetkilendirilen taraf yerel olarak hesaplanan bir parmak izinden AAD'yi yeniden oluşturur, böylece güven yoluna sunucu kaynaklı hiçbir değer girmez.
  • recipientKeyFingerprint, ham sıkıştırılmamış ortak anahtarın SHA-256 değeridir. Sunucu bunu sabitleme meta verisi olarak depolar ve bir ortak anahtarı asla onaylamaz, sunmaz veya üretmez; asıl sabitlenmiş anahtar, yetki verenin kendi şifreli anlık görüntüsünün içinde yer alır.

Yetkilendirilen deneme şifre çözmesi yapmalıdır. §3.2'deki blob AAD'si, §7'nin iletim hattında asla görünmeyen opak bir tamsayı olarak tanımladığı payloadSchemaVersion değerini bağlar. Sahip kendisininkini bilir; yetkilendirilen ise yetki verininkini bilmez. Bu nedenle yetkilendirilen, kendi derlemesinin desteklediği şema sürümleri genelinde şifre çözmeyi dener ve GCM etiketi doğrulananı alır. Bu maliyetsizdir ve hedeflenen davranış budur; bunu çözmek için düz metin bir şema sürümü alanı ekleme.

3.5 Araştırma katkı zarfı (ADR-0003)

Bir katkı, günlüğün bir araştırmanın ortak anahtarıyla şifrelenmiş, küçültülmüş ve tarihle sınırlandırılmış bir dilimidir. Paylaşımdan farklı bir yapıdır, onun daha dar bir hali değildir: farklı yük, farklı anahtar, farklı yaşam döngüsü ve hiçbir DEK kullanılmaz; sarmalama doğrudan yükün üzerindedir.

Rumuz. Hesap başına rastgele bir 256 bitlik kök, sahibe özel bölmede durur, böylece kurtarma işleminden sonra da kalır ve ikinci bir cihaza ulaşır.

pid = HMAC-SHA-256(root, "openplate-sync:study-pseudonym:v1" ‖ uint64be(studyAccountId))
      truncated to the leading 128 bits, Crockford base32, 26 characters

Baytlar sabittir, çünkü tam tanımlanmamış bir birleştirme, tek bir dağıtımda birbiriyle uyuşmayan iki farklı uygulama demektir. Etiket, sonlandırıcı içermeyen UTF-8 baytlarıdır, studyAccountId ise onluk metni veya en küçük uzunluktaki kodlaması değil, her zaman 8 bayt, işaretsiz, big-endian, her zaman sekiz değeridir. Çıktı, MAC'in Crockford base32 alfabesindeki 0123456789ABCDEFGHJKMNPQRSTVWXYZ ilk 16 baytıdır (denetim sembolü yok, kısa çizgi yok), bu da tam olarak 26 büyük harfe denk gelir. Kimliğin ASCII basamakları üzerinden türetme yapan bir istemci, hiçbir şeyle eşleşmeyen kurallara uygun bir rumuz üretir.

Katkıda bulunanın gönderimleri boyunca kararlıdır, çalışmalar arasında bağlantı kurulamaz (farklı iletiler altındaki HMAC çıktıları bağımsızdır) ve hem hesap tablosuna hem de bir gruba sahip olan hiç kimse tarafından türetilemez. H(accountId ‖ studyId) son özelliğe okumaz sahip olurdu: herkese açık girdilerle numaralandırma yoluyla tersine çevrilir.

Rumuz sunucuya değil, araştırmacı tarafına karşı koruma sağlar. Sunucu, gönderme işlemini taşıyıcı belirteçle doğrular ve bu nedenle her satırın arkasındaki hesabı her halükarda bilir, bkz. §9.2.

Zarf.

  (ephPriv, ephPub) ← ECDH P-256, fresh per contribution
  Z         ← ECDH(ephPriv, studyPub)
  KEK       ← HKDF-SHA-256(salt = empty, IKM = Z,
                           info = "openplate-sync:research-kek:p256:v1")
  AAD       ← UTF-8 of canonical fixed-key-order JSON:
              {"studyAccountId":<int>,"pseudonym":"<string>",
               "contributionVersion":<int>,"schemaTier":"<string>",
               "studyKeyFingerprint":"<base64>"}
  body      ← ephPub(65) ‖ iv(12) ‖ AES-256-GCM(KEK, payload, aad = AAD)

Paylaşım etiketinin bir sürümü yerine dondurulmuş yeni bir etiket: farklı amaç, eğriyi ada yerleştiren mantığın aynısı.

AAD hiçbir hesap kimliği taşımaz ve çalışma tarafındaki hiçbir yanıt da taşımaz. Bu durum §5.16'nın bilinçli bir tersine çevrilmiş halidir, orada §3.2'nin AAD'si bağladığı için grantorAccountId zorunludur. Buradaki her AAD alanı şifre çözme işleminden önce araştırmacı tarafından yeniden oluşturulabilir: dördü yanıtta taşınır ve parmak izini araştırmacı kendi anahtarından yerel olarak hesaplar.

Yük sabit bir katmandır, ada göre seçilir. Bir çalışma bir katman ve bir zaman aralığı seçer, asla bir alan listesi sağlamaz. v1 bir tane tanımlar:

daily-intake:v1: zaman aralığındaki her takvim günü için bir satır, date (gün ayrıntısı düzeyinde, zaman damgası yok), energyKcal, proteinG, carbsG, fatG, fiberG, loggedEntryCount ile birlikte. Sayım alanı araştırmacının aksi halde "hiçbir şey yemedi" ile "kayıt girmedi" durumunu ayırt edememesi nedeniyle vardır, girdilerin kendisi değil, yalnızca bir sayıdır.

Yeni bir alan bir yapılandırma değil, protokol revizyonudur. Bkz. ADR-0003.

4. Aktarım kuralları

  • Tüm istek ve yanıt gövdeleri application/json biçimindedir.
  • İkili alanlar (ciphertext, wrappedDek), base64 dizgileridir (standart alfabe, dolgulu). Bilinçli olarak ikili içerik türü olarak gönderilmezler: her isteğin her alanı, kendi kurulumunda hata ayıklayan bir sunucu yöneticisi tarafından okunabilir olmalıdır.
  • Zaman damgaları ISO-8601 UTC dizgileridir, örneğin 2026-08-04T10:11:12.000Z.
  • Nerede görünürse görünsün Ağdaki bir kurtarma kodu Crockford base32 METNİDİR (signup.recoveryCode, recover-rotate.recoveryCode, rotate-dek.recoveryCode ve reset/open yanıtı). Bir sunucu bunu gruplanmış veya gruplanmamış olarak kabul ETMEK ZORUNDADIR ve her iki durumda da boşluklar ile kısa çizgiler kaldırılmış halde 32 büyük harf karakteri olarak standartlaştırmalı, ONU mühürlemeli ve bu aynı standart biçimi reset/open üzerinden döndürmelidir. Dolayısıyla bir kod tek bir mühürlü biçime sahiptir, bu sayede bir rotasyondan sonra yeniden emanete alma işlemi daha önce orada olanla karşılaştırılabilir ve kodu beşli gruplar halinde işleyen bir istemci işlediği veriyi geri gönderebilir. Uyumlu bir istemci de her iki biçimi kabul eder.
  • 2xx dışındaki her yanıt gövdesi {"error": "<human-readable text>"} biçimindedir. Metin yalnızca tanılama amaçlıdır, istemciler iletiye göre değil, her zaman durum kodu değerine göre dallanmalıdır.
  • Gövde sınırını aşan istekler 413 ile reddedilir. /v1/sync altındaki her rota ailesinin kendi sınırı vardır ve hiçbir aile diğerininkini miras almaz: blob ve anahtar kayıtları base64 cinsinden blob sınırına ek olarak 4 KiB, rotate-dek base64 cinsinden blob sınırına ek olarak 64 KiB, paylaşım ailesi 8 KiB ve araştırma ailesi 512 KiB alır.
  • Kimliği doğrulanmış bir rota, gövdeyi okumadan önce bearer belirtecini denetler. Geçerli bir belirteci olmayan bir arayan, gövde ne kadar büyük olursa olsun asla 413 almaz, 401 alır.
  • Kaynak adresi, bir IPv4 adresi veya bir IPv6 /64 bloğudur. Bu belgenin IP başına veya kaynak adresi başına olarak adlandırdığı her sınırlama, tek bir ev bağlantısı koca bir /64 tuttuğu için, IPv6 çağıranını adresinin ilk 64 bitine göre sayar. IPv4 ile eşlenmiş bir IPv6 adresi (::ffff:a.b.c.d), taşıdığı IPv4 adresi olarak sayılır. Bir IPv4 adresi kendisi olarak sayılır. Sunucunun adresini belirleyemediği bir istek, bu türdeki diğer tüm isteklerle aynı sepeti paylaşır.

4.1 Kimlik doğrulama

Bir Authorization: Bearer <token> üstbilgisinde taşıyıcı belirteç. Hiçbir yönde çerez yok.

  • Access-Control-Allow-Origin: * ve Access-Control-Allow-Credentials hiçbir zaman gönderilmez. Dolayısıyla herhangi bir openplate istemcisi (bizimki, kendi alan adındaki bir sunucu barındırıcısınınki veya bir üçüncü taraf uygulaması), kökene bakılmaksızın bu hizmetin herhangi bir örneğiyle iletişim kurabilir.
  • Bu birleşim, ortam kimlik bilgisi bulunmadığı için tam olarak güvenlidir. Saldırgan bir sayfa kökenler arası istek gönderebilir ve tarayıcının otomatik olarak ekleyeceği hiçbir şey olmadığı için 401 alır. Çerezlerde bulunmayan CSRF özelliği budur ve tamamen açık kökenin bir kestirme yol değil, bilinçli bir tercih olmasının nedeni de budur.
  • Kimliği doğrulanmamış çağıranlar 401 alır. Kimliği doğrulanmış ama yetkilendirilmemiş çağıranlar 403 alır. Uyumlu bir sunucu bunları birbirine karıştırmamalıdır.
  • İki 403, bir istemcinin dallanma yapacağı bir sabit makine kodu taşır: her taşıyıcı rotasında account-suspended ve §5.15.1'in açık olarak listelemediği her veri rotasında health-consent-required. Bunlar "asla mesaja göre değil, duruma göre dallanma yap" kuralının istisnasıdır, çünkü tek başına 403, onları birbirinden veya sıradan bir ret yanıtından ayırt edemez:

    | Durum | Gövde | Anlamı | İstemcinin yaptığı | | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | 401 | herhangi biri | Geçerli erişim belirteci yok | Bir kez yeniler, ardından kişinin oturum açmasını ister | | 403 | {"error":"account-suspended"} | Bir operatör hesabı askıya aldı (§5) | Bunu belirtir; tekrar oturum açmak işe yaramaz | | 403 | {"error":"health-consent-required"} | Örnek bir sağlık verisi onayı istiyor ve hesap bunun güncel sürümüne sahip değil (§5.15.1) | Onayı ister, ardından tekrar dener | | 403 | başka herhangi bir şey | Kimlik doğrulandı, bu tek istek için izin verilmedi | Uç noktanın kendi tablosunu okur |

  • Bir rotanın okuduğu her özel istek başlığı Access-Control-Allow-Headers içinde listelenir: Authorization, Content-Type, Idempotency-Key (§5.23) ve X-Intake-Id (§5.19). Bir istemcinin okuduğu her özel yanıt başlığı ise Access-Control-Expose-Headers içinde listelenir: Retry-After, X-Trial-Scans-Left, X-Quota-Used ve X-Quota-Limit. Bir tarayıcı, ilk listenin atladığı bir başlığı göndermeyi reddeder, ikinci listenin atladığı başlığı ise hiçbir günlüğe yazmadan gizler.

Bu, işleyici çekirdekleri openplate uygulaması içine bağlıyken var olan aynı kökenden oturum çerezinin yerini aldı. Bu değişiklik ve eşitleme yollarının /api/sync yerine /v1/sync altına taşınması 1.0 öncesidir ve PROTOCOL_VERSION değerini artırmaz: üretimde hiçbir blob bulunmuyor, üçüncü taraf uygulama yok ve dağıtılmış hiçbir istemci bunlardan dolayı bozulamaz. Bu belge herkese açık bir sürümle birlikte yayımlandığında bu serbestlik sona erer, bkz. §7.

4.2 Belirteç yaşam döngüsü

İki belirteç türü, ikisi de opak rastgele dizgiler, ikisi de yalnızca SHA-256 özetleri olarak saklanır. Dökümü alınan bir belirteç tablosu yeniden oynatılabilecek hiçbir şey vermez ve esnetilmemiş SHA-256 burada doğrudur, çünkü ön görüntü 256 bitlik rastgeleliktir; çalıştırılacak bir sözlük yoktur.

BelirteçÖmürAmaç
access15 dkHer istekte gönderilir. Kısa sürelidir, çünkü sızan bir belirteç yalnızca yaşadığı sürece işe yarar.
refresh30 günYeni bir çiftle takas edilir. Döndürmelidir: her kullanım onu tüketir.

Neden JWT değil de opak bir çift. Yürürlükten kaldırma bu protokolde kritik bir dayanaktır: parola ifadesi değişikliği ve kurtarma kodu döndürme işlemi bekleyen tüm oturumları hemen geçersiz kılmalıdır; şüphe altında parola ifadesini değiştiren bir kullanıcı da tam olarak bunu bekler. Durumsuz bir belirteç, veritabanı destekli opak bir belirtecin zaten olduğu sunucu tarafı engelleme listesini eklemeden yalnızca zaman aşımına uğratılabilir, hiçbir zaman çalışması durdurulamaz.

Neden en başta bir çift. İstemci parolayı asla kalıcı olarak saklamamalıdır, dolayısıyla yeniden giriş yapmak için sessizce bir kimlik doğrulama özeti türetemez. Sunucunun parolayı hiçbir zaman görmediği bir tasarımda, sessiz yeniden kimlik doğrulamayı mümkün kılan tek şey, uzun ömürlü ve döndürülen bir yenileme belirtecidir.

Döndürme ve yeniden kullanım tespiti. Her çift, döndürme sonrasında da korunan bir family tanımlayıcısı taşır.

  • Geçerli bir yenileme belirteci ile POST /v1/auth/refresh, onu yürürlükten kaldırır ve aynı ailede yeni bir çift döndürür.
  • zaten yürürlükten kaldırılmış bir yenileme belirteci sunmak yeniden kullanım sinyalidir: yasal istemci belirteci döndürdü, bu nedenle onu şu anda sunan kimse sahip olmaması gereken bir kopyayı elinde tutuyor demektir. Bütün aile yürürlükten kaldırılır. Bu durum hem saldırganın hem de gerçek kullanıcının oturumunu kapatır ve doğru sonuç da budur; diğer seçenek bir hırsızı çalışan bir oturumla bırakır.
  • Okuma değil, harcama belirler. Bir yenileme belirteci taşıyan iki istek de onu etkin bulabilir. Yalnızca biri onu harcayabilir ("etkin" durumundan "iptal edildi" durumuna koşullu bir güncelleme) ve diğeri yeniden kullanım olarak yanıtlanır: 401 ve aile iptal edilir. Dolayısıyla tek bir belirteçle yapılan eşzamanlı iki yenilemeden tam olarak biri 200 alır ve bu çift diğerinin yeniden kullanım yanıtından sağ çıkamaz. Bir istemci kendi yenilemelerini sıralamalıdır (§11); birincisiyle yarışan ikinci bir sahip, tam olarak yeniden kullanım tespitinin var olma nedenidir.
  • Daha önceki yenilemelerde üretilen erişim belirteçlerine bilerek dokunulmaz; birkaç dakika içinde kendiliğinden geçerliliklerini yitirirler ve bunları yenileme anında iptal etmek, yolda olan meşru bir isteği kesintiye uğratır.

İptal tetikleyicileri. Bunların her biri, hesap için bekleyen tüm access ve refresh belirteçlerini iptal eder:

  • POST /v1/auth/change-passphrase
  • POST /v1/auth/recover-rotate
  • Arayanın kendi ailesi hariç (§5.17), POST /v1/sync/rotate-dek
  • bir operatör tarafından askıya alma
  • hesap silme (satır basamağıyla)

POST /v1/auth/logout tek bir aileyi (o cihazı) iptal eder ve hesabın diğer oturumlarına dokunmaz.

account_tokens içindeki tek tür oturum belirteçleridir. 0.5.0 sürümüne kadar bu tablo, bir iletiye yerleştirilmek üzere üretilen tek kullanımlık iki LINK türünü de tutuyordu: biri adresi doğruluyor, diğeri postalanan kurtarma bağlantısını kullanıyordu. İkisi de posta göndericiyle birlikte gitti ve hiçbiri geri gelmedi. Protokol 2'de adres doğrulaması hiç yoktur (davet doğrulamanın kendisidir, §5.8) ve sıfırlama bağlantısı hiçbir kimlik bilgisinin yerini almaz (§5.12).

Bu tablonun dışında iki yetki belirteci bulunur ve biri diğerinin ait olduğu yere gönderilemesin diye ikisi de bir önek taşır:

BelirteçÖnekÖmürSaklandığı yerNe sağlar
Kayıt davetisi_7 günsignup_invitesDavette belirtilen adreste BİR hesap oluşturur.
Parola sıfırlamasr_60 dkpassword_resetsHesabın emanetteki kurtarma kodunu bir defaya mahsus döndürür.

İkisi de 256 bitlik rastgeleliktir, ikisi de yalnızca SHA-256 özeti olarak saklanır ve ikisi de tek kullanımlıktır. Hiçbiri asla bir Authorization: Bearer kimlik bilgisi olarak kabul edilmez ve yerlerine asla bir oturum belirteci kabul edilmez: önek, herhangi bir aramadan önce uygulanan bir biçim kapısıdır ve reddedilmesi yanlış bir belirtecin aldığı genel hatayla aynıdır, bu nedenle hiçbir kehanet eklemez.

Askıya alma da iptal eder. accounts.suspended_at değerinin ayarlanması, bekleyen her access ve refresh belirtecini aynı işlemde iptal eder, böylece askıya alma işlemi geçerli erişim belirtecinin süresi dolduğunda değil, hemen yürürlüğe girer.

5. Uç noktalar

Sürümlendirilmiş tek bir ad alanı altında iki aile:

AileÖnekKimlik doğrulama
Eşitleme (§5.1 ile §5.5 arası)/v1/sync (SYNC_API_PREFIX)Her zaman Bearer
El sıkışma (§5.6)/healthYok
Hesap (§5.7 - §5.15)/v1/authKarışık: uç nokta başına belirtilir

Askıya alınmış bir hesap her yerde reddedilir. POST /login, POST /refresh, POST /recover, POST /recover-rotate, bearer korumalı her rota ve yönetici ağacı, istemcinin durumu tanıyıp ne olduğunu bildirebilmesi için tam olarak bu dize olan 403 {"error":"account-suspended"} yanıtını verir. login ve kurtarma yollarında bu denetim kimlik bilgisi doğrulandıktan SONRA çalışır, bu sayede bilinmeyen bir adres yine de ayırt edilemeyen sıradan 401 yanıtını alır.

Örneğin onayına sahip olmayan bir hesaba her veri rotası reddedilir. instance.healthConsent değerinin null olmadığı durumlarda (§5.6), tam olarak bu sürüme sahip olmayan bir hesap, kendisi için bir şey depolayan, gönderen veya harcayan her rotada 403 {"error":"health-consent-required"} alır; kabul etmek, ayrılmak ve kendi kopyasını geri okumak için ihtiyaç duyduğu rotaları ise korur. §5.15.1 her ikisini de listeler. Önce askıya alma durumu denetlenir, bu nedenle askıya alınmış bir hesap account-suspended yanıtını alır.

§5.1 ile §5.5 arasındaki yollar SYNC_API_PREFIX yoluna göreli yazılmıştır; diğer her şey mutlaktır.

5.1 POST /blob: push (karşılaştır ve değiştir)

İstek:

json
{ "baseVersion": 3, "envelopeVersion": 1, "ciphertext": "<base64>", "shrinkAcknowledged": false }
  • baseVersion: istemcinin şu anda saklandığını düşündüğü blobVersion. 0 değeri "bu hesabın henüz bir blob'u yok" anlamına gelir.
  • Yazma işlemi, yalnızca baseVersion hesabın geçerli sürümüne eşit olduğunda kabul edilir. Bütün eşzamanlılık modeli bundan ibarettir. Zorla gönderme (force-push) ve If-Match içermeyen yazma yoktur.
  • shrinkAcknowledged: İSTEĞE BAĞLI, yokluğu ise false anlamına gelir. Aşağıdaki küçülme koruması bölümüne bak.

Yanıtlar:

DurumGövdeAnlamı
200{"newVersion": 4}Kabul edildi. Blob artık newVersion sürümünde.
409{"currentVersion": 5}Yarış kaybedildi. Önce başka bir cihaz yazdı.
400{"error": "..."}baseVersion negatif olmayan bir tamsayı değil, envelopeVersion pozitif bir tamsayı değil, ciphertext eksik/base64 değil veya boş, ya da shrinkAcknowledged mevcut ve bir boolean değil.
400{"error": "...", "currentSizeBytes": 5310, "nextSizeBytes": 1588}Onaylanmamış büyük bir küçülme. Hiçbir şey yazılmadı. Aşağıya bak.
413{"error": "..."}Blob MAX_BLOB_BYTES sınırını aşıyor.
401/403{"error": "..."}Kimlik doğrulanmadı / izin verilmedi.

Küçülme koruması (M224). Kodu çözülmüş ciphertext boyutu saklanan sürümün size_bytes boyutunun (BLOB_SHRINK_ACK_RATIO) kesinlikle yarısının altında altında olan bir push, istekte "shrinkAcknowledged": true bulunmadığı sürece 400 ile REDDEDİLİR. Henüz blob'u olmayan bir hesap asla reddedilmez; ilk push bir silme işlemi değildir.

Bu bir ONAYDIR, bir hüküm değildir. İstemci, bu alanı yalnızca kesinlikle güvendiği bir durumdan silme işlemleri yaydığında true olarak ayarlar; bu iddiada bulunamayan bir istemci ise alanı atlar ve reddi kabul eder. Hizmet şifreli metin tutar, bu yüzden yerel deposunu kaybedip her şeyin silindiğini sanan bir istemci ile kasıtlı bir silme işlemini birbirinden ayırt edemez; ikisi de aynı baytlardır. Bu nedenle hizmet sorar ve hiçbir şey söylemeyen bir istemci, silinme yerine ret yanıtı alır.

Onaylanmış bir küçülme kabul EDİLDİĞİNDE, hemen önceki sürüm BLOB_PRE_SHRINK_PIN_DAYS boyunca budanmaya karşı tutulur (§8).

ÖNCE CAS kontrol edilir: boyutu ne olursa olsun eski bir baseVersion üzerinden yapılan push sıradan bir 409 durumudur, çünkü o istemcinin görevi pull edip birleştirmektir ve bunu yaptıktan sonra genelde artık küçülmez. Koruma, yalnızca bu olmasa kabul edilecek olan bir push için geçerlidir.

Reddetme yanıtı 400 şeklindedir ve kasıtlı olarak 409 DEĞİLDİR: bu rotada bir 409, "önce başka bir cihaz yazdı" anlamına gelir ve aşağıdaki kurtarma döngüsünü zorunlu kılar, bu da aynı baytları tekrar push eder. 413 da kullanılmadı: istek çok büyük değil.

shrinkAcknowledged bir GÖVDE ALANIDIR ve asla bir başlığa dönüşmemelidir. Yeni bir özel istek başlığının servisin CORS Access-Control-Allow-Headers listesinde belirtilmesi gerekir, aksi halde tarayıcı preflight kontrolünü okur, gönderemeyeceği bir başlık görür ve isteği hiçbir yere log satırı düşmeden ve tarayıcı dışı bir testin gözlemleyemeyeceği şekilde hiç göndermez. Gerekçe: docs/adr/0009-a-shrinking-blob-is-acknowledged-or-refused.md.

Bir optimizasyon değil, 409 kurtarma döngüsü zorunlu istemci davranışıdır: currentVersion bilgisini pull et, şifresini çöz, yerel durumla birleştir (§3.3), yeni blobVersion değerine bağlı AAD ile yeniden şifrele ve baseVersion: currentVersion ile tekrar push et. 409 durumunu ölümcül bir hata sayan bir istemci, kullanıcının cihazını kalıcı olarak senkronizasyon dışı bırakır.

5.2 GET /blob: pull

DurumGövde
200{"blobVersion": 4, "envelopeVersion": 1, "ciphertext": "<base64>", "createdAt": "<iso>"}
404{"error": "..."}: bu hesap hiç blob push etmedi. Bu bir hata durumu değildir, yeni bir hesap böyle görünür.

5.3 GET /key-records: list

json
{
  "records": [
    {
      "kind": "passphrase",
      "kdfDescriptor": { "salt": "<base64>", "params": { "memorySizeKib": 65536, "iterations": 3, "parallelism": 1 } },
      "wrappedDek": "<base64>",
      "updatedAt": "<iso>"
    },
    { "kind": "recovery", "kdfDescriptor": null, "wrappedDek": "<base64>", "updatedAt": "<iso>" }
  ]
}

Kurulumu tamamlamamış bir hesap için {"records": []} döner. kind başına en fazla bir kayıt.

5.4 PUT /key-records/:kind: oluştur veya döndür (karşılaştır ve değiştir)

:kind, passphrase veya recovery değeridir, bunun dışındaki her şey 400 olur.

İstek:

json
{
  "kdfDescriptor": { "...": "..." } | null,
  "wrappedDek": "<base64>",
  "expectedUpdatedAt": "<iso>" | null,
  "currentAuthHash": "<base64, 32 bytes>"
}
  • expectedUpdatedAt: null, "bu türde bir kayıt henüz yok" olduğunu iddia eder (ilk kurulum).
  • Başka herhangi bir değer "en son okuduğum kayıt tam olarak bu updatedAt değerine sahipti" olduğunu iddia eder (döndürme).
  • Anahtar bulunmalıdır. expectedUpdatedAt alanının bulunmaması kasıtlı olarak bir 400 hatasıdır: çağıran taraf bir alanı unutarak eşzamanlılık kontrolünü atlayamamalıdır.
  • Üzerine yazma işlemi parolayı kanıtlar. expectedUpdatedAt, null olmadığında, currentAuthHash (mevcut parolanın kimlik doğrulama dalı, §3.1) ZORUNLUDUR: eksik veya hatalı biçimlendirilmiş olması onu adlandıran bir 400 sonucunu verir ve hesapla eşleşmeyen bir tanesi hiçbir şey yazılmadan 401 {"error":"current passphrase is incorrect"}, yani change-passphrase tarafından gönderilen gövdedir. Bir oluşturma işlemi (null) yalnızca taşıyıcı belirteçle kalır ve alanı yok sayar: kurulum sırasında boş bir yuvayı doldurur ve bir kayıt var olduğunda CAS bunu reddeder. Bir sarmanın değiştirilmesi hesabı açan şeyi değiştirir ve tek başına bir taşıyıcı belirteç bunu yapamamalıdır.
  • change-passphrase, delete ve rotate-dek ile aynı havuzda Tahminler hesap başına sınırlandırılır: kilitli bir hesap, herhangi bir adresten dördünde de Retry-After ile 429 alır. Bir eşleşme havuzu temizler.

Doğrulama, tümü 400:

  • boş wrappedDek
  • null olmayan bir kdfDescriptor ile kind: "recovery" (kurtarma yolu yalnızca HKDF kullanır, kaydedilecek parametre yoktur)
  • null olan bir kdfDescriptor ile kind: "passphrase"

Yanıtlar:

DurumGövde
200Depolanan kayıt, bir GET /key-records girdisiyle aynı yapıda.
400{"error": "..."}: yukarıdaki doğrulama veya düzgün biçimlendirilmiş bir currentAuthHash olmadan üzerine yazma.
401{"error": "current passphrase is incorrect"}: currentAuthHash değeri eşleşmeyen bir üzerine yazma.
409`{"currentUpdatedAt": "<iso>" \null}`: CAS iddiası tutmadı.
429Retry-After ile {"error": "..."}: bu hesabın parola tahminleri kilitlendi.

5.5 DELETE /key-records/:kind: kaldırıldı ve geri yüklenmedi

2026-09 döneminde kaldırıldı. Bu yol artık önek altındaki herhangi bir bilinmeyen yolun verdiği yanıtı verir: belirteç olmadan 401, hesabın örnek onayına sahip olmadığı durumlarda 403 ve aksi takdirde olağan 404. Hiçbir istemci bunu çağırmadı ve kalan tek anahtar kaydını silmek, depolanan her blobu tek başına bir taşıyıcı belirteçle kalıcı olarak çözülemez hale getirdi.

Bir anahtar kaydı parolayı kanıtlayan §5.4 yoluyla veya bir döndürme yoluyla (§5.14, §5.17) değiştirilir ve yalnızca hesapla birlikte kaldırılır (§5.15).

Bir paylaşım (§5.16) anahtar kaydı sayılmaz. Kriptografik açıdan aynı DEK'in üçüncü bir sarmalanmış halidir, ancak başka bir kişinin yetkisidir; onlar tarafından iptal edilebilir, senin tarafından doğrulanamaz ve onların süregelen iş birliğine ve dürüstlüğüne bağlıdır. Hiçbir istemci, bir kurtarma yolu olarak asla "verilerini diyetisyenin aracılığıyla kurtar" seçeneği sunamaz.

5.6 GET /health: sürüm el sıkışması

Kasıtlı olarak kimlik doğrulamasızdır: bir istemci kimlik bilgilerine sahip önce uyumsuz olduğunu tespit edebilmelidir ve belirteç gerektiren bir sağlık kontrolü belirtecin durumunu bildirirdi.

json
{
  "protocolVersion": 2,
  "envelopeVersion": 1,
  "serviceVersion": "0.20.0",
  "instance": {
    "name": "openplate",
    "language": "de",
    "mail": true,
    "memberInvites": true,
    "openSignup": true,
    "signupCaptcha": { "provider": "turnstile", "siteKey": "0x4AAAAAAAexample" },
    "trial": { "scans": 10, "days": 14 },
    "plans": true,
    "push": false,
    "healthConsent": { "version": "2026-09-28" },
    "nutrientReferenceBasis": "dge",
    "ai": { "model": "vendor/model-name" },
    "defaultCapabilities": null
  }
}

instance, bu dağıtımın ne olduğunu ve neler yapabildiğini açıklar ve isteğe bağlı niteliğindedir: bu alandan daha eski bir servis onu atlar, onu zorunlu tutan bir istemci ise bu türdeki her örnekle konuşmayı reddeder. name işletmecinin bu örnek için belirlediği etikettir; language ise en, de, fr, it, es, tr dillerinden biridir (e-postalarının yazıldığı altı dil; istemci bunu gösterir ve asla buna göre dallanma yapmaz, dolayısıyla yedinci bir dil protokol değişikliği sayılmaz). mail bir mektup gönderip gönderemeyeceğini, memberInvites sıradan bir üyenin buraya kişi davet edip edemeyeceğini (§5.21), openSignup bir kişinin burada hesap talep edip edemeyeceğini (§5.8.3), signupCaptcha bu talebin nelere ihtiyaç duyduğunu, trial yeni bir hesabın alacağı ücretsiz tarama sözünü (§5.19), plans bu örneğin arkasında bir faturalandırıcının durup durmadığını ve böylece /v1/plans/* var olup olmadığını (§5.22), push bu örneğin web push gönderip gönderemeyeceğini ve böylece /v1/push/* var olup olmadığını (§5.24), healthConsent her hesaptan istediği sağlık verisi onayının adını (§5.15.1), nutrientReferenceBasis kimin mikro besin referans değerlerini gösterdiğini belirtir; ai ise yapılandırılmış bir yukarı akış anahtarı olmadığında null değerini alır. ai.model, örneğin varsayılan katmanının modelidir (§5.19): işletmeci bu isteğin şemasını başka bir katmana yönlendirmediği sürece vekilin isteği gönderdiği modeldir. İşletmeci hiçbirini belirtmediğinde ve arayanın kendi modeli gönderildiğinde null olur.

defaultCapabilities, bir hesabın kendine ait bir kaydı olmadığında sahip olduğu yeteneklerin (§5.19, "Yetenekler") listesidir, örneğin ["scan", "recipes"]. Değeri her zaman mevcut olur: null, bu kurulumun hiçbir yeteneği kontrol etmediği, dolayısıyla hiçbir şey ayarlamayan bir kendi sunucunda barındırma kurulumunun sahip olduğu gibi her özelliğin açık olduğu anlamına gelir; [] ise bir kayıt aksini belirtmedikçe hesabın hiçbir yeteneğe sahip olmadığı anlamına gelir. Hiçbir anahtar bulamayan bir istemci (bu alandan daha eski olan her hizmet) bunu null olarak okur. Tanımlayıcıdır, asla bir yetkilendirme değildir: vekil, hesabın kendi kaydına ve bu varsayılana göre istek başına karar verir.

healthConsent, bu örneğin her hesaptan istediği açık sağlık verisi onayını, {"version": "<v>"} değerini ya da hiçbir onay istemediğinde (ki bu kendi sunucunda barındırma için varsayılandır) null değerini belirtir. ai gibi yok olmak yerine null şeklindedir ve hiçbir anahtar bulamayan bir istemci (alandan daha eski olan her servis) bunu null olarak okur. Bu bloğun geri kalanından farklı olarak servis bunu zorunlu kılar: null olmadığı sürece, hesap oluşturma eşleşen onayı gerektirir (§5.8) ve §5.15.1 üzerinde anlaşana kadar her veri rotası, tam olarak bu sürüme sahip olmayan bir hesabı reddeder ile 403 {"error":"health-consent-required"}. Bir sürüm bulan istemci eşitlemeden önce sorar ve bu 403 yanıtını geç sorulmuş aynı soru olarak ele alır. null bulan bir istemci onay onay kutusu çizmez ve servis bunun için hiçbir şeyi reddetmez.

push alanı tıpkı plans gibidir: yalnızca bir kapının var olup olmadığını söyleyen bir boole değeridir. false değeri tüm /v1/push alt ağacının sıradan bilinmeyen yol 404 yanıtı verdiği anlamına gelir, böylece istemci hiçbir bildirim ayarı çizmez. Bir push bildiriminin ne içerdiği hakkında hiçbir şey söylemez, çünkü bir push bildirimi yalnızca bir tür içerir ve başka hiçbir şey içermez (§5.24).

plans bir isteğe bağlı bir vaat değil, boole değeridir; bu da instance.feedback alanının aşağıda yaptığı tercihin tam tersidir ve bu kasıtlıdır. O alan bir fotoğrafa ne olacağına dair bir taahhüttür ve vaat edecek hiçbir şeyi olmayan bir örnek onu atlar. Bu alan ise hiçbir şey vaat etmez: yalnızca bir kapının var olup olmadığını söyler; bu da mail ve memberInvites alanlarının yaptığı türden bir ifadedir, dolayısıyla false hem faturalandırıcısı olmayan bir örnek hem de alan var olmadan önce oluşturulmuş bir servis için dürüst yanıttır.

openSignup, tıpkı memberInvites ve plans gibi bir boolean değeridir: yalnızca bir kapının var olup olmadığını belirtir. true, POST /v1/auth/signup-request bir adres kabul ettiği (§5.8.3) anlamına gelir; false ve bu alandan önce derlenmiş bir servis ise bu yolun sıradan bilinmeyen yol yanıtı olan 404 döndürdüğü ve bir istemcinin kayıt formu yerine davet metnini gösterdiği anlamına gelir. Bu değer tanımlayıcıdır, asla bir yetki değildir: hız sınırları, captcha, reddedilen alan adları ve posta kutusu başına günde bir mektup kuralı serviste kalmaya devam eder.

signupCaptcha, yalnızca openSignup değeri true olduğunda ve işletmeci bir captcha çalıştırdığında mevcuttur. provider, bugün turnstile değerindedir; siteKey, Cloudflare Turnstile'ın istemcinin widget'ı oluşturmakta kullandığı ve hiçbir yetki sağlamayan genel site anahtarıdır. Widget'ın ürettiği belirteç, kayıt isteğinde captchaToken olarak iletilir. Bulunmaması, isteğin belirtece ihtiyaç duymadığı anlamına gelir.

Tarama denemesi çalıştırmayan bir örnekte trial, bir aşağıdaki feedback gibi bir taahhüt olduğundan, null yerine hiç bulunmaz değeridir. scans, yeni bir hesabın orada alacağı ücretsiz yapay zeka taramalarının sayısıdır (§5.19, "Tarama denemesi"). days, hangisi önce gelirse gelsin, taramalar kalmış olsa bile o denemenin sona erdiği kayıt gününden sonraki gün sayısıdır (§5.8 bu sonun nereye düştüğünü belirtir); denemesinin bitiş tarihi olmayan bir örnekte yoktur, asla null olmaz olur ve hiçbir days bulamayan bir istemci, taramaları tam olarak daha önceki gibi belirtir ve bir gün sayısı BELİRTMEMELİDİR. Hiçbir trial bulamayan bir istemci ücretsiz tarama sayısı BİLDİRMEMELİDİR. Her iki sayı da her deneme kapısının yazdığı, aynı ayarlardan yayımlanan sayılardır (TRIAL_SCANS, TRIAL_DAYS); bu sayede bir kişinin kaydolmadan önce okuduğu cümle ile proxy'nin tuttuğu sınırlar birbirinden farklılaşamaz.

Bu bloktaki diğer her şey gibi memberInvites da tanımlayıcıdır, asla yetki vermez niteliğindedir. Bir istemci bunu yalnızca bir davet kartı çizip çizmeyeceğine karar vermek için okur; asla basım yapıp yapamayacağına karar vermek için okumaz. false değeri POST /v1/auth/invites yolunun sıradan bilinmeyen yol 404 yanıtı verdiği anlamına gelir ve true değeri ömür boyu sınırı, yeniden davet kuralını ve hız sınırlamasını yine servise bırakır.

tanımlayıcıdır, asla bağlayıcı değildir niteliğindedir. mail: true bir mektubun ulaşacağını vaat etmez ve ai herhangi bir yetki vermek yerine operatörün ne yapılandırdığını bildirir; bu ne derse desin dailyAiLimit: 0 sahibi bir hesap 403 alır.

nutrientReferenceBasis, dge, efsa veya us değerlerinden biridir: bu örnekteki her istemcinin hangi kurumun mikro besin referans değerlerini gösterdiğini, yani Alman DGE, AB'nin EFSA veya ABD'nin NASEM verilerini belirtir. isteğe bağlı olduğundan, alandan önce derlenmiş bir servis bu alanı atlarken alanı hiç tanımayan bir istemci ise görmezden gelir. örnek başına tek temel, asla dile göre değil ve asla kişi başına değil: dil ile referans kurumu birbirinden bağımsızdır ve yerel ayarı takip eden bir varsayılan, gizliden gizliye kişi başına dayatılmış bir temel olurdu.

Ayrıca bu blokta yöneticinin servis çalışırken değiştirebileceği tek alan alanıdır. Buradaki diğer her şey işletmecinin ortamıdır ve yeniden dağıtıma kadar sabittir; bu alan ise saklanır ve PATCH /v1/admin/settings (§5.20) tarafından yazılır. Bu yüzden istemci, bir kurulumun ömrü boyunca bunu önbelleğe almak yerine her bağlantıda yeniden okur. Bir servis bu yolu değerin işlem içi kopyasından sunMAK ZORUNDADIR ve /health isteğini yanıtlamak için depolama alanını okuMAMALIDIR: burası sürekli yoklanan konteyner sağlık denetimi yoludur ve orada depolamayı okumak, veritabanındaki ufak bir aksamayı yeniden başlatmaya dönüştürür.

instance.feedback buradaki tanım değil, vaattir niteliğinde olan tek alandır ve yukarıdaki paragrafın istisnasıdır. Bildirilen tahminleri kabul eden bir örnek, birinin yiyeceğine ait bir fotoğrafı saklar ve bunu ne kadar süreyle yapacağını yayınlar:

json
{
  "protocolVersion": 2,
  "envelopeVersion": 1,
  "serviceVersion": "0.6.0",
  "instance": { "name": "openplate", "language": "en", "mail": true, "ai": null, "feedback": { "retentionDays": 30 } }
}

retentionDays, servisin kendi saklama temizliğinin silme işleminde kullandığı sayıdır ve aynı bağlamdan yayınlanır; böylece bir istemcinin bir kişiye fotoğrafı teslim etmeden önce gösterdiği cümle ile ardından gelen silme işlemi birbirinden farklılaşamaz.

Rapor kabul etmeyen bir örnekte bu alan yoktur, asla null olmaz durumundadır. ai: null her örneğin yaptığı bir açıklamadır; bu ise bir vaattir ve özelliği kapalı olan bir örneğin verecek vaadi yoktur, bu nedenle hiçbir anahtar eklemez ve alan var olmadan önce oluşturulmuş bir örnekten ayırt edilemez kalır, tıpkı /v1/feedback ağacının özelliğin hiç yazılmadığı bir ağaçtan ayırt edilemez kalması gibi.

İstemci hiçbir zaman penceresi bildirilmediğini görürse ASLA bir süre belirtmemelidir. Hiçbir rapor sunmaz veya bir süre belirtmeyen ifadeler kullanır; yerel bir varsayılan değerden bir sayı yazdırmak, fotoğraf gönderip göndermeyeceğine karar veren bir kişiye servisin hiç vermediği bir vaadi duyurmaktır.

signupMode, açıkladığı ayarla birlikte protokol 2'de kaldırılmıştır durumuna gelmiştir: bir hesap yalnızca bir daveti kullanarak oluşturulur ve openSignup, bir kişinin davet isteyip isteyemeyeceğini belirtir (§5.8). Hâlâ signupMode yayımlayan bir servis, sürüm 1 konuşuyor demektir.

notice operatörün tüm istemcilere yönelik mesajıdır ve tam olarak instance ile aynı anlamda isteğe bağlı niteliğindedir: söyleyecek hiçbir şeyi olmayan bir örnek bu alanı atlar ve bunu hiç duymamış bir istemci de alanı yok sayar.

json
{
  "protocolVersion": 2,
  "envelopeVersion": 1,
  "serviceVersion": "0.6.0",
  "notice": { "text": "This instance moves to a new address on 1 March.", "url": "https://example.org/moving" }
}

Alan mevcut olduğunda text zorunludur; url isteğe bağlıdır ve mevcut olduğunda mutlak bir https:/http: URL'sidir. Servis text değerini 280 karakterle sınırlar ve daha uzun bir değerde önyükleme yapmayı reddeder, çünkü /health aynı zamanda konteynerin HEALTHCHECK yoludur ve sürekli olarak yoklanır.

Bu bir çekme kanalıdır ve daha fazlası değildir. Bir bildirimi kimin okuduğunu bilemez: uygulamayı açan kişi onu görür, açmayan ise görmez. Bir bildirim mekanizması değildir ve öyleymiş gibi güvenilmemelidir. Protokol 2 servise gönderebileceği iki mektup verir (bir davet ve bir parola sıfırlama, §5.8 ve §5.12) ve bunların hiçbiri başka bir şey için kanal değildir: kullanıcılarına bir şey duyurması gereken bir operatör, bu iletişim listesini bu servisin dışında, kendisi tutar.

Bir istemci text ve url alanlarını zararlı girdi olarak ele ALMALIDIR. Bunlar kullanıcının işaret ettiği sunucu hangisiyse oradan gelir. text değerini asla işaretleme olarak değil, metin olarak işle ve url değerini yalnızca şemasını açıkça kontrol ettikten sonra takip et.

5.7 POST /v1/auth/kdf: giriş öncesi KDF tanımlayıcısı

Kimlik doğrulamasız, IP hız sınırlamalı. Bir cihazın giriş yapabilmeden önce authHash türetmek için ihtiyaç duyduğu Argon2id tuzunu ve parametrelerini döndürür.

Bir okuma işlemi için GET yerine POST kullanılır: GET işlemi adresi istek satırına koyar ve oradan da erişim günlüklerine, vekil sunucu günlüklerine, Referer üstbilgilerine ve tarayıcı geçmişine aktarır. Tüm amacı kimin hesabı olduğunu ifşa etmemek olan bir uç nokta, kendisine sorulan tanımlayıcıyı etrafa saçmamalıdır. Bu argüman zaten bir kullanıcı adı için de geçerliydi; bir adresin yeniden hatta aktarılmasıyla bu durum bir sızıntı ile bir e-posta listesi arasındaki fark haline gelir.

İstek: {"email": "anna@example.org"} · Yanıt 200:

json
{
  "kdfDescriptor": {
    "salt": "<base64, 16 bytes>",
    "params": { "memorySizeKib": 65536, "iterations": 3, "parallelism": 1 }
  }
}

Bilinmeyen bir adres de tanımlayıcı alır. Kurallara uygun adres üzerinden (§5.8) HMAC(serverSecret, email) olarak deterministik biçimde türetilir; dolayısıyla istekler arasında kararlıdır, aynı biçimdedir ve aynı kod yoluyla üretilir. 400 yalnızca hiçbir şekilde adres olamayacak girdiler için döndürülür. Ne M181 ile rumuzlara geçiş ne de M192 ile adreslere geri dönüş türetmenin tek bir satırını bile değiştirmedi: opak bir dize üzerinde çalışır ve ikisi de birdir.

Bu göründüğünden daha önemlidir. Sunucunun parolayı hiçbir zaman görmediği bir oturum açma işlemi, kimlik doğrulamasından önce yanıt veren kimlik doğrulamasız, tanımlayıcı anahtarlı bir uç nokta gerektirir; safça yapıldığında bu, hangi adreslerin hesaba sahip olduğunun ücretsiz, sessiz ve sınırlandırılamayan bir listesidir. Kararlılık da biçim kadar mimari bir zorunluluktur: rastgele bir sahte değer, iki kez sorularak ayırt edilebilir.

Uyumlu bir sunucu, bilinmeyen bir adres için 404, boş bir gövde ya da farklı bir biçim DÖNDÜRMEMELİDİR. Ayrıca şunları yapmalıdır:

  • Her iki kolda da aynı işi yap. Sahte değeri, var olan ve bunu asla kullanmayacak hesaplar da dahil olmak üzere koşulsuz türet; böylece bir eşleşme ile ıskalama aynı sorgu ve aynı HMAC maliyetine sahip olur. Tembel türetmek bir zamanlama farkı bırakır: yanıt hiçbir şey söylemez ama üretilmesinin ne kadar sürdüğü söyler.
  • Kurallara uygun adres üzerinden türet, böylece tek bir bilinmeyen adresin iki farklı yazımı tanımlayıcılarından ayırt edilemez.
  • Kaynak adrese göre hız sınırla, Retry-After ile birlikte 429 döndürerek. Bu, aynı savunmanın diğer yarısıdır: artık zamanlama sinyali istatistikseldir ve yalnızca adres başına çok sayıda örneklem alındığında ortaya çıkar. Bunu kapatan şey örneklemleri reddetmektir. Sınırı gönderilen adrese göre anahtarlamak hiçbir şey yapmamaktan daha kötü olurdu, çünkü çok sayıda adresi yoklamak saldırının kendisidir, dolayısıyla adres başına bir havuz, saldırganın test etmek istediği her adres için taze bir kota sunar.

5.8 POST /v1/auth/signup

Kimlik doğrulaması yok, IP hız sınırlandırması var. Her örnekte Hesap oluşturan tek şey hâlâ bir davettir. instance.openSignup: true bulunan bir örnekte kişi, kendisine hitap eden bir davet İSTEYEBİLİR (§5.8.3); eline geçen, tıpkı bir işletmecinin bastığı gibi burada kullanılan sıradan bir davettir. SIGNUP_MODE bir önyükleme hatasıdır, çünkü ayarlanacak bir mod yoktur: tek anahtar, §5.8.3'teki istek kapısının var olup olmadığıdır.

json
{
  "inviteToken": "si_…",
  "authHash": "<base64, 32 bytes>",
  "kdfDescriptor": { "...": "..." },
  "displayName": "optional or null",
  "recoveryAuthHash": "<base64, 32 bytes>",
  "recoveryCode": "ABCDE-FGHJK-MNPQR-STVWX-YZ012-3456",
  "keyRecords": [
    { "kind": "passphrase", "kdfDescriptor": { "...": "..." }, "wrappedDek": "<base64>" },
    { "kind": "recovery", "kdfDescriptor": null, "wrappedDek": "<base64>" }
  ],
  "healthConsent": { "version": "2026-09-28" }
}

healthConsent, instance.healthConsent değeri null olmadığında gereklidir şeklindedir ve diğer her yerde yok sayılır (§5.15.1). Bunun version değeri, örneğinkiyle baytına kadar eşit olmalıdır. Bu olmadan yanıt 400 {"error":"health-consent-required"} ve hiçbir şey oluşturulmaz veya harcanmaz olur: davet kullanılabilir kalır, böylece kişi kutuyu işaretler ve tekrar gönderir. Denetim diğer her alandan sonra çalışır, bu nedenle hatalı biçimlendirilmiş bir davet yine de önce aşağıdaki 403 yanıtını verir. Bununla oluşturulan bir hesap, ilk isteğinden itibaren onaya sahip olur, bu yüzden hiçbir veri rotası onu reddetmez; böyle bir örnekte hesap oluşturan bir istemci (bir çalışma konsolu, bir tohumlama aracı) alanı da gönderir, aksi takdirde hesabı her veri rotasında reddedilir (§5.15.1).

Hiçbir email alanı yoktur ve asıl mesele de budur. Adres, işlemin içindeki davet satırından gelir. Bir istek gövdesi, işletmecinin yazmadığı bir posta kutusunu talep edemez; davetin kendisini adres doğrulaması yapan da budur: mektubu alan kişi onu kullanan kişidir, bu yüzden onay bağlantısı yoktur ve sonrasında onaylanacak bir şey kalmaz. role ve dailyAiLimit aynı nedenden dolayı davetten gelir: bir hesap asla kendi durumunu talep etmez.

recoveryAuthHash, recoveryCode ve HER İKİ anahtar kaydı da zorunludur. Protokol 1'de her biri isteğe bağlıydı, artık hiçbiri öyle değil:

  • İstemci artık kurtarma kodunu kişiye göstermez (§3.1), bu nedenle emanet olmadan oluşturulan bir hesap hiçbir sıfırlamanın geri getiremeyeceği bir hesaptır ve sahibine hiçbir zaman uyarı yapılmamıştır.
  • Parolanın herhangi bir şeyin şifresini çözmesini sağlayan şey bir passphrase kaydıdır; bu olmadan hesap giriş yapar ama hiçbir şeyi okuyamaz ve istemci bunu fark edene kadar parolayı elden çıkarmıştır.
  • Emanete alınmış kodun açılmasını sağlayan şey bir recovery kaydıdır; bu olmadan postalanan bir sıfırlama, kimlik doğrulayan ama hiçbir şeyi açmayan bir kimlik bilgisi sunar ve bu ancak ihtiyaç duyulan gün fark edilir.

recoveryCode, 20 baytlık Crockford base32 olarak doğrulanır (boşluklar ve tireler kaldırılıp değer büyük harfe çevrildikten sonra 32 karakter) ve mühürlenmeden önce bu biçime kurallaştırılır. Hiçbir biçimde, hiçbir yolda asla günlüğe kaydedilmez.

DurumAnlamı
201{"account": AccountView, "tokens": {...}} (§5.15). Her zaman bir oturum açılır; onaylanacak hiçbir şey kalmamıştır.
400Yanlış biçimde bir authHash, recoveryAuthHash veya recoveryCode; 16 baytlık tuz ve pozitif Argon2id parametreleri içermeyen bir tanımlayıcı; ya da türü eksik bir keyRecords. {"error":"health-consent-required"}: örnek bir onay istiyor ve gövdede hiç onay yok veya başka bir sürüm var; davet TÜKETİLMEZ.
403{"error":"invite-invalid"}: davet eksik, bozuk, başka bir hizmete ait, bilinmiyor, süresi dolmuş, iptal edilmiş veya zaten kullanılmış. Yedisi de tek bir yanıt.
409Davetin adresi için zaten bir hesap var. Davet TÜKETİLMEZ.
429Hız sınırına takıldı. Saniye cinsinden Retry-After.

Sunucu HMAC-SHA-256(serverPepper, authHash) ve recoveryAuthHash üzerindeki aynı yapıyı saklar, bunların hiçbiri üzerinde ikinci bir yavaş KDF okumaz. İstemci bellek açısından zorlu maliyeti zaten ödemiştir; sunucu tarafında yeniden özetlemek, kaba kuvvet direncini artırmazken (kimlik doğrulama özetini elinde tutan bir saldırgan Argon2id adımını zaten atlamıştır) her denemenin 64 MiB bağladığı bir giriş taşkını DoS'u yaratır. Biberleme, varoluş amacını yine de yerine getirir: biber veritabanının dışındayken dökümü alınmış bir tablo, canlı bir kuruluma karşı yeniden oynatılamaz veya tahminlere karşı çevrimdışı denetlenemez.

Gönderimin tamamı tek bir işlemde işlenir: davetin kullanılması, hesap satırı (örneğin istediği durumlarda onayla birlikte), mühürlü emanet ve her iki anahtar kaydı. Her yarım durum, kullanıcının kendi günlüğünü okumaya çalışana kadar göremediği ayrı bir felakettir.

409 bu protokoldeki tek listeleme kahinidir ve protokol 2 bunu neredeyse yok etmiştir. Yalnızca, kullanımda olduğunu bildirdiği adresin TAM OLARAK kendisine GÖNDERİLMİŞ geçerli bir davetiyeye sahip biri tarafından erişilebilir, dolayısıyla sadece yöneticinin mektuba yazdığı şeyi doğrular. Protokol 1'de davetiye sahibi tek bir davetiye ile rastgele kullanıcı adlarını yoklayabilirdi, artık bunu yapamaz çünkü adresi seçmek onun elinde değildir. Davetiyeyi tüketmez, bu sayede birini yanlışlıkla iki kez davet eden bir yönetici geçerli davetiyeyi yok etmiş olmaz. Gerekçenin tamamı: SECURITY.md.

5.8.1 Davetiyeler

Davetiye tek kullanımlık, süresi dolan bir yetki belgesidir tek bir kişiye gönderilmiş. Hesabın oluşturulacağı adresi, yöneticinin isim tahminini, rolü ve günlük yapay zeka kotasını taşır. Bilinmeyen, bozuk biçimli, eksik, yanlış servisli, süresi dolmuş, iptal edilmiş ve daha önce kullanılmış belirteçlerin tümü AYNI 403 ve aynı gövdeyi, {"error":"invite-invalid"} üretir: bunları birbirinden ayırmak, arayanın hangi belirteçlerin var olduğunu yoklamasına ve bir belirtecin bir zamanlar gerçek olduğunu öğrenmesine izin verirdi.

Kullanmanın sağladıkları (2026-09-30), davet satırına göre şu sırayla kararlaştırılır: tarama denemesi içeren bir satır bu denemeyi verir (aşağıda); bir üyenin vesile olduğu bir satır, davet eden kişi hesabını o zamandan bu yana silmiş olsa bile ve hiç yapay zeka yok mektup gönderildikten sonra örnek üye davetlerini kapatmış olsa dahi üye kapısının hibesini (§5.21), tarama denemesini veya gün çiftini verir; diğer her satır, deneme içermeyen bir işletmeci basımı veya hiç deneme çalıştırmayan bir örnekteki açık kayıt, ücretli 0 değeri dailyAiLimit olacak biçimde hesabın kalıcı ücretsiz hibe'si (freeDailyAiLimit, §5.15) olarak günlük kotasını verir. Eskiden bu son durum, §5.19'un artık karşılığında hiçbir şey tanımlamadığı bir biçim olan tarihsiz bir dailyAiLimit yazardı.

Bir davetiye belirteci si_ ile başlar ve servis, başlamayan her şeyi reddeder. Ön ek, belirteci bu servise ve bu uç noktaya bağlar. Bir kişiye e-postada, sr_ ile başlayan bir parola sıfırlama belirtecinin yanında bir davetiye teslim edilir, ön ekler olmadan bu ikisi birbiriyle değiştirilebilir dizgilerdir ve biri yanlış uç noktaya gönderilebilir. Denetim bir sorgulama öncesi biçim geçidi niteliğindedir, diğer tüm geçersiz davetiyelerle aynı durum ve aynı gövdeyle reddedilir, bu yüzden geçit yeni bir kahin eklemez. Oturum belirteçleri ön ek taşımaz ve değiştirilmemiştir.

Basım işlemi POST /v1/admin/invites niteliğindedir. Aynı adres için bekleyen daha eski bir davet yenisiyle iptal edilir; böylece adres başına birden fazla etkin yetki asla bulunmaz; zaten bir hesabı olan adresler ise hiçbir şekilde davet edilemez (409). Tek istisna, yabancı birinin sözüyle geri çekmek yerine işletmeciden veya bir üyeden bekleyen davete dokunmayan §5.8.3'teki istek kapısıdır.

Bir davet tarama denemesi taşıyabilir (trialScans, §5.19): açık kayıt, "trial": true ile bir yönetici oluşturması ve örneğin bunu çalıştırdığı durumlarda bir üye daveti satıra örneğin sayısını yazar ve kullanım bunu hesaba kopyalar. Örneğin ayrıca bir gün sınırı belirlediği durumlarda (instance.trial.days), satır bunu da taşır ve kullanım saati başlatır: kullanım günü hesaba katılmaz ve hesabın trialEndsAt değeri, örneğin saat diliminde (TRIAL_TIME_ZONE, işletici bir tane belirlemedikçe UTC) bu günden sonraki days. günün sonundaki yerel gece yarısıdır. Europe/Berlin içinde 2026-09-29 tarihinde, 10:00 veya 23:30'da, on dört gün ile yapılan bir kullanım, 2026-10-13T22:00:00.000Z, Berlin saatiyle 2026-10-14 00:00'da sona erer. Bu bir süre değil, bir takvim kuralıdır: saatlerin değişmesi durumunda bile son gün yine yerel gece yarısında biter. Saat dilimi kullanım anında okunur ve satıra yazılmaz, çünkü iki gün arasındaki sınırı kaydırır, gün sayısını asla değiştirmez. Gün sınırı var olmadan önce oluşturulan bir satır bunu taşımaz ve oluşturduğu hesabın bitiş tarihi olmaz.

Bir örnek, sıradan bir üyenin de örneğin koşullarına göre ve bu paragrafın 409 içeriğinin yaptığı ifşaların hiçbiri olmadan bir tane üretmesine izin verebilir. Bu POST /v1/auth/invites, §5.21 konusudur.

5.8.2 POST /v1/auth/invite-lookup

Kimlik doğrulaması yok, IP ile sınırlandırılmıştır. İstek {"inviteToken": "si_…"}.

json
{ "email": "anna@example.org", "displayName": "Anna", "expiresAt": "2026-09-11T10:00:00.000Z" }

Kullanıcı e-postadaki bağlantıyı açtığında istemci bunu çağırır, böylece kayıt formu mektubun gittiği adresi yazmasını istemek yerine GÖSTEREBİLİR. Adrese gönderilmiş bir davetiyenin asıl amacı budur: kimsenin ulaşamayacağı bir hesaba kendi adreslerini yanlış yazamazlar.

Başka hiçbir şey göstermez. Davetiyenin sağladığı rol ve kota kasıtlı olarak yer almaz: kaydolmamış birinin, yöneticinin kendisini yönetici yaptığını öğrenmesi gerekmez, yabancı birinin bağlantısını elinde tutan bir arayanın ise hiç gerekmez.

Bilinmeyen, bozuk biçimli, yanlış servisli, süresi dolmuş, iptal edilmiş ve harcanmış belirteçler, birebir aynı çalışma sonrasında TEK BİR 404 {"error":"invite-invalid"} döndürür: belirtecin özeti alınır ve her dallanmada tablo sorgulanır. Geçerli bir sorgulama hiçbir şey tüketmez, böylece bağlantıyı iki kez açan birinin davetiyesi geçerliliğini korur.

5.8.3 POST /v1/auth/signup-request: kişi hesap ister

Kimlik doğrulaması yok. Yalnızca instance.openSignup değerinin true olduğu durumlarda mevcuttur; diğer her yerde yol, sıradan bilinmeyen yol yanıtı olan 404 döndürür. Bir örneğin bunu açabilmesi için e-posta yapılandırmasına ihtiyacı vardır, çünkü mektup adresin kontrolüdür.

İstek: {"email": "anna@example.org", "captchaToken": "…", "plan": "yearly", "tier": "tier-a", "locale": "de"}. captchaToken, instance.signupCaptcha varken zorunludur, aksi durumda yoksayılır. plan, tier ve locale isteğe bağlıdır ve kişinin talep etmeden önce kayıt ekranında ne seçtiğini belirtir, gövdedeki başka hiçbir şey okunmaz.

  • plan, "monthly" veya "yearly" değeridir. Bunlardan biri olduğunda, postalanan bağlantı davetten sonra &plan=<key> taşır.
  • tier, planın ait olduğu faturalandırıcının kademesinin kimliğidir (§5.22). Servis kademelerin bir listesini bilmez, bu yüzden yalnızca biçim değerini değerlendirir: 1 ila 32 karakterlik küçük harfli bir etiket, önce bir harf, ardından harfler, rakamlar ve tireler (^[a-z][a-z0-9-]{0,31}$), kırpma ve büyük/küçük harf dönüştürme olmadan birebir eşleştirilir. Uygun olduğunda, postalanan bağlantı &plan= sonrasında (veya bir plan olmadığında davetten sonra) &tier=<id> taşır. Servis, faturalandırıcının bu kademeyi sattığını veya bununla birlikte bir plan geldiğini denetlemez, bağlantıyı okuduğunda buna bir istemci karar verir.
  • locale, örnek dilinden (en, de, fr, it, es, tr) biridir; bu liste bildirim locale (§5.24) tarafından kabul edilenle aynıdır. Bunlardan biri olduğunda, e-postayla gönderilen bağlantı &lang=<code> taşır ve mektup ya da hesap sahibi notu o dilde yazılır. Geçerli bir locale bulunmadığında her ikisi de örneğin dilinde (instance.language) yazılır.
  • Eksik bir değer, null, başka türde bir değer ve diğer tüm dizgeler sessizce göz ardı edilir sayılır: asla bir 400 değildir ve aşağıdaki yanıt değişmez. tier için bu; Alpha (büyük/küçük harf), alpha (boşluk dolgusu), 33 karakterlik bir etiket, 42, bir nesne, bir dizi ve a&plan=monthly (parçayı sunacak & veya = içermeyen) durumlarını kapsar. Hiçbir alan depolanmaz, her biri bağlantıda taşınır, böylece başka bir cihazda açılan bağlantı yine de planı ve kademeyi bilir. Hesap sahibine giden not bağlantı taşımaz, bu nedenle bunların hiçbirini içermez, yalnızca dili locale kuralını izler.

Her üçünü de içeren bir bağlantı <client>/join#server=…&invite=si_…&plan=yearly&tier=tier-a&lang=de şeklinde görünür.

json
{}

→ bu gövdeyle 202, boş ve sabit.

Servis yeni bir adres için sıradan bir adrese tanımlı davet basar ve postalar: member rolü, varsayılan davet geçerlilik süresi, davet eden yok ve örneğin koşulları (çalıştırdığı durumlarda tarama denemesi (§5.19), aksi halde yapay zeka yok). Postalanan bağlantı değişmeden §5.8.2 ve §5.8'e yönlendirir. §5.21'deki gibi Yanıt, adresle ilgili neyin doğru olduğuna göre DEĞİŞMEMELİDİR: yeni bir adres, bir hesabı olan adres, işletmeciden veya üyeden bekleyen bir mektubu zaten bulunan adres ve bugün zaten mektup almış bir posta kutusu, tek bir gövdeye sahip tek bir 202 sonucudur. Mektuplar kapının kendisine aittir; okuyucuyu birinin davet ettiğini söyleyen §5.21'deki davet veya not kesinlikle değildir: yeni bir adres; kendisinin veya onu kullanan birinin hesap açma isteğinde bulunduğunu, tek bağlantıyı, son kullanma süresini ve postayı yok saymanın hiçbir şeyi değiştirmeyeceğini bildiren bir mektup alır; bir hesap sahibi ise aynı şeyleri söyleyen, ikinci bir hesabın açılmadığını belirten ve bağlantı içermeyen bir not alır. Başka bir kapıdan bekleyen bir mektuba dokunulmaz, böylece bir yabancı adresi girerek işletmecinin davetini geri çekemez. Yalnızca mektuplar farklıdır.

DurumAnlamı
202{}. Adres hakkında ne doğru olursa olsun kabul edildi
400{"error":"email-invalid"}: adres değil. {"error":"email-domain-refused"}: bilinen tek kullanımlık bir e-posta servisindeki bir adres; alan adı ve onun tüm üst alan adlarıyla eşleştirilir. {"error":"captcha-failed"}: captcha belirteci eksik veya reddedildi; yeniden çöz
404Örnek açık kayıt çalıştırmıyor
429Bir kaynak adresinden bir saatte beşten fazla istek. Saniye cinsinden Retry-After
503{"error":"captcha-unavailable"}: captcha sağlayıcısına erişilemedi. Daha sonra tekrar dene

400 yanıtları isteği tanımlar, asla örneğin hesaplarını değil: bir alan adı, kimin hesap sahibi olduğu hakkında hiçbir şey söylemez, bu nedenle birini reddetmek bir kahin işlevi görmez.

İki hız sınırı. Kaynak adresi başına, her denemenin sayıldığı saatte beş istek; bu bir betiği sınırlandırır. Posta kutusu başına günde bir mektup: sonraki istekler yine 202 yanıtını verir ve hiçbir şey göndermez, böylece bu sınır başkasının hangi adresleri sorguladığını belli edemez. Posta kutusu anahtarı deneme anahtarı değeridir: yerel kısmından +tag çıkarılmış kurallı adres (§5.8); gmail.com ve googlemail.com için ise her noktanın kaldırıldığı ve alan adının gmail.com olarak yazıldığı halidir. anna+x@gmail.com, a.n.n.a@gmail.com ve anna@gmail.com tek bir anahtarı paylaşır; a.nna@example.org ile anna@example.org paylaşmaz.

Tek bir posta kutusu, tek bir deneme, her zaman. Anahtarı, herhangi bir yazımla ücretsiz tarama içeren bir daveti zaten kullanmış olan veya hesabı bir deneme süresine sahip olup silinmiş olan bir posta kutusu, deneme süresi 0 olan bir davet alır: kişi yine de bir hesap edinir ve ilk tarama 403 trial-scans-spent yanıtını verir. Servis, posta kutusunu saklanan bir adresle değil, deneme anahtarının anahtarlı bir özetiyle tanır (§9.2).

Hiçbir dalda hiçbir adres günlüğe kaydedilmez. Bir sunucu, gönderilen adresi veya captcha belirtecini GÜNLÜĞE KAYDETMEMELİDİR.

5.9 POST /v1/auth/login

Kimlik doğrulamasız, iki hız sınırlayıcı ile. Her ikisi de sadece bir 401 sayar, başka hiçbir şeyi saymaz ve başarılı bir işlem ikisini de temizler.

  • IP ve e-posta başına. Beş başarısızlık serbesttir. Bu, bir kurbanın hesabını başka bir adresten kilitlemeye kimseye izin vermeden, tek kaynaklı bir kaba kuvvet saldırısını yavaşlatır.
  • Herhangi bir adresten, e-posta başına. Yirmi başarısızlık yanıtlanır; yirmi birinci istek bir dakika boyunca reddedilir ve sonraki her başarısızlık kilit süresini on beş dakikaya kadar ikiye katlar. On beş dakika boyunca başarısızlık görmeyen bir sepet yeniden başlar. Bu, adres değiştiren bir tahminciyi sınırlar. Hesabı olmayan bir adres de aynı şekilde sayılır, böylece ret yanıtı hesabın var olup olmadığını ele vermez. Adres, hesap aramasının katladığı gibi katlanır (§2), bu nedenle farklı bir yazımı aynı sepete düşer.

İki kilit de, iki bekleme süresinden daha uzunu olan Retry-After ile aynı 429 yanıtıdır.

İstek {"email": "...", "authHash": "..."} → 200 {"account": AccountView, "tokens": {...}}.

email makul bir adres olmadığında veya authHash base64 ile kodu çözülmüş 32 bayt olmadığında 400: istek hiçbir zaman kimlik bilgisi denetimine ulaşmaz, dolayısıyla bu durum hesabın var olup olmadığı hakkında hiçbir bilgi taşımaz. Bilinmeyen bir hesap ve yanlış kimlik doğrulama özeti için özdeş gövde metniyle ve özdeş çalışma sonrasında 401, çünkü doğrulayıcı karşılaştırması her iki kolda da tam genişlikte bir vekile karşı çalışır. Hesap askıya alındığında 403 {"error":"account-suspended"}, kimlik bilgisinden SONRA denetlenir, böylece yalnızca hesabın sahibi olduğunu kanıtlamış birine kapının neden kapalı olduğu söylenir. Sınırlandırıldığında 429.

5.10 POST /v1/auth/refresh

Kimlik doğrulaması yok (yenileme belirteci kimlik bilgisidir). İstek {"refreshToken": "..."} → 200 {"tokens": {...}}. Döndürme ve yeniden kullanım tespiti için §4.2 bölümüne bak. Askıya alınmış bir hesap dışındaki her hata 401 döndürür, askıya alınmış hesap ise 403 {"error":"account-suspended"} döndürür ve sunulan belirteci HARCAMAZ: bir askıya alma işlemi kaldırılabilir ve belirteci yakmak, kişiyi geri alacağı bir cihazdan çıkarmış olur. Ayrı durum kodu, bir istemcinin bu uç noktada sonsuza kadar döngüye girmesini engelleyen şeydir.

5.11 POST /v1/auth/logout

Bearer. 204. Arayanın belirteç ailesini iptal eder: yalnızca bu cihaz.

5.12 POST /v1/auth/reset/request ve POST /v1/auth/reset/open: e-postayla gönderilen sıfırlama

Bu numaralar, verify-email ve request-reset e-posta göndericiyle birlikte gittiğinde 0.5.0 sürümünde kullanımdan kaldırıldı. Protokol 2 bunları yeniden kullanır ve iki yeni numara almak yerine bunları yeniden kullanmak kasıtlıdır: burada şimdi duran şey daha önce duran şeyin yanıtıdır ve kaynak yorumundaki bir §5.12 başvurusunu izleyen bir okuyucu, mezar taşı yerine çözüme ulaşmalıdır.

§5.12.1 POST /v1/auth/reset/request: kimlik doğrulaması yok, (IP, e-posta) başına sınırlandırılmıştır, başarılı olduğunda ASLA temizlenmez.

İstek {"email": "anna@example.org"} → 202 {}, her zaman.

Bilinen bir adres, bilinmeyen bir adres ve hatalı biçimlendirilmiş bir adres için farksız olarak 202. Uyumlu bir sunucu, yanıt vermeden önce her iki kolda da aynı işi YAPMALIDIR: adresi aramak, belirteci basmak, özetini çıkarmak. Yalnızca bilinen bir adresin aldığı depoya yazma ve gönderme işlemleri yanıtı GECİKTİRMEMELİDİR: referans sunucu bunları 202 gönderildikten sonra çalıştırır (2026-09'dan beri) ve oradaki bir hata asla döndürülmez, günlüğe kaydedilir. Bu simetri, numaralandırma karşıtı savın tamamıdır ve bu belgenin daha önce EKSİK olarak kaydettiği durumdur: eski request-reset masraflı işi yalnızca var olan adresler için yapıyordu, bu nedenle gövdesinin söylemediğini zamanlaması söylüyordu. 2026-09 öncesinde bu sunucu, yalnızca bilinen kolda hâlâ bir yazma ve bir gönderme işlemini bekliyordu.

Açıkça bir adres olmayan bir değer için bile bir 400 asla döndürülmez: durum kodu bu kurulumun tuttuğu adreslerin biçimine dair ücretsiz bir kehanet aracına dönüşürdü ve çağıranın bu ayrımla işine yarar şekilde yapabileceği hiçbir şey yoktur.

Belirteç, sr_ önekli, base64url formatında 32 rastgele bayttır. Yalnızca SHA-256 özeti, password_resets içinde, 60 dakikalık TTL ile saklanır. Hesap başına tek bir etkin belirteç: yeni bir istek, aynı işlem içinde daha eski ve tüketilmemiş her satırı tüketildi olarak işaretler, böylece gelen kutusunda yukarı kaydıran biri dünkü mektubu kullanamaz. Bu işlemler hesap başına sıralanır (hesap üzerinde bir satır kilidi), böylece çakışan istekler geriye yine tam olarak bir geçerli belirteç bırakır.

E-posta yapılandırılmadığında gönderme işlemi etkisizdir ve uç nokta yine de 202 yanıtı verir. Bu durumda kendi sunucusunu barındıran birinin kullanıcıları sıfırlama yapamaz; işletmecinin çözümü bağlantıyı döndüren POST /v1/admin/accounts/:id/reset-mail olur.

§5.12.2 POST /v1/auth/reset/open: kimlik doğrulanmamış, IP bazlı hız sınırlandırmalı.

İstek {"resetToken": "sr_…"} → 200:

json
{ "email": "anna@example.org", "recoveryCode": "ABCDEFGHJKMNPQRSTVWXYZ0123456789" }

Belirteç, kendisini okuyan AYNI ifade içinde tüketilir (UPDATE … WHERE consumed_at IS NULL AND expires_at > now RETURNING), bu sayede tek bir belirteç taşıyan iki istek birden yanıtlanamaz. Bilinmeyen, harcanmış ve süresi dolmuş belirteçler, tamamen aynı iş yapıldıktan sonra TEK BİR 404 {"error":"reset-invalid"} üretir.

BU UÇ NOKTA HESABA HİÇBİR ŞEY YAZMAZ, ve bu cümle §5.13 bölümünün eskiden belgelediği akıştan olan tüm farkı özetler. Sunucunun emanette zaten tuttuğu kurtarma kodunu geri verir (§3.1); ardından istemci bununla SIRADAN §5.14 recover-rotate merasimini çalıştırır: kodu kanıtla, yeni bir parola belirle, DEK anahtarını yeniden sar, yeni bir kod üret, onu yeniden emanete ver, tek bir işlem. Anahtar kayıtları olmadan bu ucun döndürdüğü şey yalnızca bir dizgidir. Bu yolun bir doğrulayıcıya veya bir anahtar kaydına dokunmasına izin veren gelecekteki bir değişiklik, adı ne olursa olsun ADR-0004 belgesinin sildiği hesap ele geçirme akışını yeniden inşa etmiş olurdu.

İma edilmek yerine açıkça belirtilen bedeli. Sıfırlama işlemi işletmeci kurtarma kodunu elinde tuttuğu için çalışır. Barındırılan bir kuruluma güvenmeye karar vermeden önce §3.1 ve docs/adr/0005-organization-accounts-and-escrowed-recovery.md bölümlerini oku; bu karar kriptografiyle değil, işletmeciyle ilgilidir.

5.13 POST /v1/auth/verify-email: 0.5.0 sürümünde kaldırıldı ve geri getirilmedi

0.5.0 sürümünde e-posta göndericiyle birlikte gitti ve bu servis yeniden e-posta gönderiyor olsa bile protokol 2 onu geri getirmez.

Artık onaylanacak hiçbir şey kalmadı: bir hesap, bir posta kutusuna ADRESLEŞTİRİLMİŞ bir davetin kullanılmasıyla oluşturulur (§5.8), dolayısıyla mektubu alan kişi kaydolan kişidir. Davetiyenin kendisi doğrulamadır ve ikinci bir bağlantı yalnızca birinden halihazırda kanıtlanabilir biçimde bir kez yaptığı şeyi iki kez kanıtlamasını ister.

5.14 POST /v1/auth/recover, POST /v1/auth/recover-rotate ve POST /v1/auth/change-passphrase

Kurtarma kodu kimlik doğrulayıcısı ve iki kimlik bilgisi rotasyonu. recover-rotate ve change-passphrase aynı gönderim biçimini alır çünkü aynı şeyi yaparlar; yalnızca kanıt farklıdır.

POST /v1/auth/recover: kimlik doğrulanmamış, IP başına hem de e-posta sınırlandırmalı. İstek {"email": "...", "recoveryAuthHash": "<base64, 32 bytes>"} → 200 {"account": AccountView, "tokens": {...}}.

Geri gelen şey sıradan bir oturumdur, kasten daha düşük yetkili bir oturum değildir: kurtarma kodunun sahibi tasarım gereği hesabın sahibidir ve kısıtlanmış bir "kurtarma modu" belirteci, kodun zaten taşımadığı hiçbir özelliği taşımayan ikinci bir yetkilendirme yüzeyi eklerdi.

jsonc
// POST /v1/auth/recover-rotate: unauthenticated, proof is the recovery code
{
  "email": "...",
  "recoveryAuthHash": "<the current recovery proof>",
  "newAuthHash": "<new>",
  "kdfDescriptor": {...},
  "keyRecords": [ ... ],
  "newRecoveryAuthHash": "<a new recovery proof>" | null,  // optional: rotate the code too
  "recoveryCode": "<the new code, in the clear>"           // REQUIRED whenever newRecoveryAuthHash is present
}

// POST /v1/auth/change-passphrase: bearer, proof is the current passphrase
{ "currentAuthHash": "...", "newAuthHash": "...", "kdfDescriptor": {...}, "keyRecords": [ ... ] }

keyRecords girdileri {"kind": "passphrase" | "recovery", "kdfDescriptor": {...} | null, "wrappedDek": "<base64>"} biçimindedir, tür başına en fazla bir tanedir ve §5.4 ile aynı kurallara uyar (bir recovery kaydının tanımlayıcısı null olmalıdır; bir passphrase kaydınınki ise olmamalıdır).

change-passphrase, 200 {"tokens": {...}} döndürür. recover-rotate ise 200 {"account": AccountView, "tokens": {...}} döndürür, çünkü çağıran bir oturum olmadan gelmiştir ve az önce hangi hesaba yeniden girdiğini bilmesi gerekir. Her ikisi de taze bir çift sunar.

Gönderimin tamamı atomik olarak uygulanır. Yeni doğrulayıcı, yeni hesap KDF tanımlayıcısı, isteğe bağlı yeni bir kurtarma doğrulayıcısı, yeniden mühürlenmiş emanet, güncellenerek eklenen anahtar kayıtları, bekleyen tüm oturumların iptali ve çağıranın yeni çifti ya hep birlikte işlenir ya da hiçbiri işlenmez. Bu bir uygulama ayrıntısı değildir. Her yarım durum, kullanıcının kendi günlüğünü okumaya çalışana kadar göremeyeceği ayrı bir felakettir: yeniden sarılmış kaydı olmayan bir doğrulayıcı oturum açar ama hiçbir şeyin şifresini çözemez, doğrulayıcısı olmayan bir kayıt hiç oturum açamaz ve kaydı olmayan döndürülmüş bir kurtarma doğrulayıcısı ise kimliği doğrulayan ama ardından hiçbir şeyin sarmasını açamayan bir kod bırakır.

keyRecords bulunmalıdır, [] olarak bile. Eksik bir anahtar bir 400 hatasıdır, §5.4 bölümünde expectedUpdatedAt değerinin zorunlu tutulmasıyla aynı nedenden ötürü: veriyi yüzüstü bırakabilecek bir yolda sessizlik asla rıza olarak yorumlanmamalıdır.

Gönderilmeyen türlere (okumaz) dokunulmaz. Bir parola değişikliği DEK anahtarını yeni bir KEK_p altında yeniden sarar; recovery kaydı ise aynı, değiştirilmemiş DEK anahtarını sarmayı sürdürür ve geçerli kalır.

Dört kural yalnızca recover-rotate için geçerlidir:

  • Bir passphrase anahtar kaydı gereklidir ve [] bir 400 değeridir. Parola değişiminden farklı olarak bu yol zorunlu şekilde KEK_p değerini değiştirdi, bu yüzden yeniden sarmalama olmadan bir gönderimi kabul etmek, sorunsuz oturum açan fakat hiçbir şifreyi çözemeyen bir hesap oluşturur.
  • Kurtarma kodunu yenilemek ya hep ya hiç kuralına tabidir ve protokol 2 kapsamında bu kural ÜÇ yönlüdür. newRecoveryAuthHash, bir recovery anahtar kaydı ve recoveryCode birlikte gelmelidir ya da hiç gelmemelidir; bunların herhangi bir alt kümesi bir 400 oluşturur. Eksik her bir parça ayrı bir felakettir: kayıt olmadan gelen doğrulayıcı, kimlik doğrulayan fakat hiçbir sarmalamayı açamayan bir kod bırakır; doğrulayıcı olmadan gelen kayıt, sarmalamayı açan fakat oturum açamayan bir kod bırakır; ve eski kodu tutmayı sürdüren bir ESCROW, postayla gelen sonraki sıfırlamayı (§5.12), hesabın artık kabul etmediği bir kimlik bilgisini taşıyan ve tam ihtiyaç duyulduğu gün fark edilen bir mektuba dönüştürür.
  • İşlemin içinde yeniden öne sürülen Yazma işlemi, kanıtın eşleştiği kurtarma doğrulayıcısı üzerinde bir karşılaştır ve takas et işlemidir. Bu işlem daha önce tamamlanmış olan kimlik doğrulama değildir; aynı anda çalışan iki kurtarma işleminin, kullanıcıya zaten kendisine ait olduğu söylenmiş bir kimlik bilgisinin üzerine yazmasını engelleyen şeydir.
  • Bir başarısızlık, dört neden. Bilinmeyen bir adres, hiç kurtarma kodu belirlememiş bir hesap, yanlış bir kod ve bu karşılaştır ve takas et yarışını kaybeden bir yenileme işlemi, aynı çalışmanın ardından 401 yanıtını birebir aynı metinle verir. Bir yarışın hatalı bir tahminden ayırt edilememesi, eksik bir ikinci kimlik doğrulayıcının da eksik bir hesaptan ayırt edilememesi gerekir. SUSPENDED bir hesap tek istisnadır: yalnızca kanıt başarılı olduktan sonra 403 {"error":"account-suspended"} yanıtını verir.

change-passphrase, herhangi bir adresten gelen çağrılar için delete sepetinde, hesap başına hız sınırına tabidir; rotate-dek ve bir anahtar kaydı üzerine yazma paylaşımı (§5.4) da bu sepeti paylaşır: çağıran zaten bir belirtece sahiptir ve currentAuthHash, o belirtecin kanıtlayamayacağı bir tahmindir. Kilitli bir hesap Retry-After ile 429 alır; başarılı bir işlem sepeti temizler.

Her iki kurtarma uç noktası (IP, e-posta) başına bir kısma havuzunu paylaşır ve hiçbiri başarı durumunda bunu temizlemez. İkisi de aynı gizli bilgiyi doğrular, bu yüzden her biri için ayrı bir kota tahmin etme maliyetini yarıya indirirdi; meşru bir kurtarma ise bir kez gerçekleşir, dolayısıyla dürüst hiçbir istemcinin kotasını geri alması gerekmez. POST /v1/auth/reset/request de aynı kurala göre kısılır.

Bir yenilemenin yapabilecekleri ve yapamayacakları. girişi durumunu geri yükler. verileri durumunu geri yükleyemez, çünkü sunucu hiçbir zaman bir anahtar tutmadı. keyRecords: [] gönderen bir change-passphrase, verisi kalıcı olarak çözülemez çalışan bir hesap bırakır; recover-rotate çağrısının bu gönderimi doğrudan reddetmesinin tam nedeni de budur. Uyumlu bir istemci, kullanıcı akışı onaylamadan önce durumu aynen bu ifadelerle belirtmelidir.

Parola kaybolursa, dönüş yolu §5.12 olur ve işletmeci kodu emanette (§3.1) tuttuğu için çalışır. Protokol 1 burada, kayıp bir parola ile kayıp bir kodun birlikte bir hesabı kimsenin açamayacağı şekilde kalıcı olarak sonlandırdığını belirtirdi. Bu cümle artık yalnızca SERVER_SECRET bilgisi de kaybolmuş bir örnek için geçerlidir; bu gizli bilginin veritabanı İLE BİRLİKTE yedeklenmesinin ve bunu kaybetmenin göründüğünden daha kötü olmasının nedeni budur.

Eski uyarının dürüst biçimi matematikle değil, işletmeciyle ilgilidir. Yönetilen bir örnek, üzerindeki herhangi bir hesabı açabilir. Kendi sunucunu barındırdığın bir örnek kendi işletmecisidir, bu nedenle eski taahhüt kişisel durum için geçerliliğini korur. Uyumlu bir istemci, bir kişi içine bir günlük koymadan önce bu ikisinden hangisiyle konuştuğunu belirtir.

5.15 GET /v1/auth/account, PATCH /v1/auth/account ve POST /v1/auth/delete

Üçü de taşıyıcıdır.

AccountView, bu protokoldeki TEK hesap yapısıdır. POST /signup, POST /login, GET /account, PATCH /account, POST /recover, POST /recover-rotate ve yönetici hesap uç noktalarından geri döner, bu yüzden bir istemcinin tam olarak tek bir hesap kod çözücüsü vardır:

json
{
  "id": 1,
  "email": "anna@example.org",
  "displayName": null,
  "role": "member",
  "dailyAiLimit": 200,
  "aiUsedToday": 3,
  "allowanceExpiresAt": null,
  "freeDailyAiLimit": 0,
  "capabilities": null,
  "trialScans": { "granted": 10, "left": 7 },
  "trialEndsAt": "2026-09-19T00:00:00.000Z",
  "suspendedAt": null,
  "invitesLeft": 5,
  "invitesNeedAPlan": false,
  "healthConsent": { "version": "2026-09-28", "at": "2026-09-04T10:11:12.000Z" },
  "createdAt": "2026-09-04T10:11:12.000Z"
}

İçinde gizli hiçbir şey yoktur ve olamaz: doğrulayıcı yok, KDF tanımlayıcısı yok, sarmalanmış DEK yok, emanet yok, belirteç yok. Her alan, ya kişinin kendi bilgisidir ya da bir işletmecinin ona tanıdığı konumdur. aiUsedToday, geçerli UTC gününde dailyAiLimit kotasından düşer; kimliği doğrulanmış her çağrı 403 account-suspended yanıtı verirken suspendedAt değeri null dışı bir değerdir.

invitesLeft, bu hesabın POST /v1/auth/invites üzerinden (§5.21) hâlâ kaç davet gönderebileceğini gösterir ya da bu sınır onunla ilgili olmadığında null olur. Bir yönetici için 0 değil, her zaman null: 0, "hepsini kullandın" anlamına gelir; bir yönetici ise hiçbirini kullanmamıştır, çünkü sınır ve yeniden davet kuralından muaf olan yönetici API üzerinden üretim yapar. instance.memberInvites: false içeren bir örnek de aynı nedenle null gönderir: orada sınır yoktur, çünkü rota yoktur ve bir 0, hiç var olmamış harcanmış bir kotayı bildirir. Bir istemci bunu ekrana çizebilir ve buna dayanarak yetkilendirme YAPMAMALIDIR; istemci neye inanırsa inansın, servis altıncı bir üretimi reddeder.

invitesNeedAPlan, yalnızca hesap henüz kimsenin ödeme yapmadığı bir tarama denemesi olduğu için (§5.21) invitesLeft değeri 0 olduğunda true değerini alır, yönetici ile instance.memberInvites: false içeren bir örnek dahil diğer tüm durumlarda ise false olur. Bir hesap trialScans taşıdığında ve allowanceExpiresAt değeri null veya geçmiş bir tarih olduğunda böyle bir deneme sayılır; ödeme yapıldığında faturalandırıcının yazdığı ileri bir allowanceExpiresAt tarihi, davetleri yeniden açar ve trialScans değerini korur. Bu alan ek niteliğindedir: alanı yok sayan bir istemci yine de doğru olan invitesLeft: 0 değerini okur, alanı okuyan bir istemci ise davetlerin tümünün tükendiğini söylemek yerine bir planla açılacağını belirtebilir.

allowanceExpiresAt bir ISO anı ya da null değeridir; null ise yapay zekâ kotasının bir bitiş tarihi olmadığı anlamına gelir ki kendi sunucunu barındırdığın bir örneğin tuttuğu da budur. O andan itibaren, §5.19 içindeki vekil 403 allowance-expired yanıtını verir. Yalnızca yapay zekâyı kısıtlar, başka hiçbir şeyi değil: eşitleme bu tarihten sonra da çalışmayı sürdürür, çünkü günlük hesaba aittir ve yeni bir cihazın onu çekebilmesi gerekir. Bir istemci tarihi ekrana çizebilir ve buna dayanarak yetkilendirme yapmamalıdır; kural vekil üzerinde yer alır.

freeDailyAiLimit, hesabın kalıcı ücretsiz hibe değeridir: aktif bir ücretli aralık bulunmadığında geçerli olan, bitiş tarihi ve tarama sınırı olmayan UTC günü başına Yapay Zeka birimleri (§5.19). 0 hiç yok demektir. Vekilin öncelik sırası (§5.19) önce aktif bir ücretli aralık (dailyAiLimit anında, gelecekteki allowanceExpiresAt), ardından bu tanımlama, sonra da tarama denemesidir; bu sayede ücretsiz tanımlaması olan bir hesap, ücretli bir aralık sona erdiğinde Yapay Zekayı kaybetmek yerine buna geri döner. Günlük sınır gösteren bir istemci, aktif bir ücretli aralık bulunmadığında bunu gösterir. Görünümdeki değer, vekilin zorunlu kıldığı değerdir: 0 üzerinde olduğunda hesabın kendi sınırı, aksi halde kurulum varsayılanı (§5.19), aksi halde 0. Bunu yalnızca bir işletmeci yazar (§5.20); faturalandırıcının kimlik bilgisi yazamaz. Bir istemci bunu işleyebilir ve buna göre yetkilendirme YAPILMAMALIDIR. Alan eklemelidir: bunu yoksayan bir istemci görünümü değiştirmeden çözer.

capabilities, vekilin bu hesabı denetlediği yeteneklerin listesidir (§5.19, "Yetenekler"): hesabın kendi kaydı, aksi halde kurulumun defaultCapabilities değeri (§5.6), aksi halde null. null, "hiçbir şey" değil, denetim yok anlamına gelir: her özellik açıktır; bu da hiçbir varsayılan ve hiçbir kayıt ayarlamayan bir kurulumdaki her hesaptır. [] hiçbir özelliğin olmaması demektir. null bulan bir istemci, her özelliği kullanılabilir kabul ETMELİDİR. Bir istemci bunu işleyebilir ve buna göre yetkilendirme YAPILMAMALIDIR: vekil 403 capability-required yanıtı verir. Alan eklemelidir: bunu yoksayan bir istemci görünümü değiştirmeden çözer. Yönetici ve faturalandırıcı görünümleri ise hesabın kendi kaydını taşır (§5.20); burada null kayıt yok anlamına gelir.

trialScans, tarama denemesi olan bir hesap için {"granted": n, "left": n}, olmayan bir hesap için null değerindedir; bu da hiç deneme çalıştırmayan bir örnekteki her hesaptır. left, kullanılan taramalar çıkarılmış granted değeridir, asla 0 değerinin altına düşmez. Bir istemci bunu ve buna göre yetkilendirme YAPILMAMALIDIR değerini ekrana çizebilir: vekil sunucu sayar (§5.19), left bu görünüm oluşturulduğunda alınan bir anlık görüntüdür ve vekil sunucudan geçen her yanıt X-Trial-Scans-Left içinde güncel sayıyı taşır. Gelecekteki bir allowanceExpiresAt tarama kapısını kaldırır, bu nedenle ödeme yapan bir hesap bu alanı taşımaya devam edebilir.

trialEndsAt, bir ISO anıdır veya bitiş tarihi olmayan bir tarama denemesi ve hiç denemesi olmayan bir hesap için null değeridir. Gün sınırı belirleyen bir örnekte, deneme kullanım sırasında başladığında (§5.8) bir kez yazılır ve her zaman bu örneğin saat dilimindeki yerel bir gece yarısıdır; örneği bir tane ayarlamadan önce oluşturulmuş bir hesap null değerini korur ve denemesi sonradan asla kısaltılmaz. Ücretsiz taramalar daha önce tükenmediyse, o andan itibaren proxy 403 trial-expired yanıtını verir (§5.19). Bir istemci bunu işleyebilir ve buna göre yetkilendirme YAPILMAMALIDIR. Gelecekteki bir allowanceExpiresAt, tarama sayısını kaldırdığı gibi bunu da kaldırır. Alan eklemelidir: bunu yok sayan bir istemci görünümü değiştirmeden çözer.

healthConsent, kayıtlarda sağlık verisi onayı bulunan bir hesap için {"version": "<v>", "at": "<ISO instant>"}, bulunmayan bir hesap için null olur: örneği bunu sormadan önce oluşturulan her hesap ve hiçbir onay istemeyen bir örnekteki her hesap buna dahildir. version, kişinin kabul ettiği metindir ve at, servisin o andaki kendi saatidir. Bir istemci version ile instance.healthConsent.version değerini karşılaştırır (§5.6) ve bunlar farklı olduğunda ya da bunu isteyen bir örnekte bu değer null olduğunda bir kez sorar (§5.15.1). Bu alan eklemelidir: alanı yok sayan bir istemci görünümü değiştirmeden çözer.

Yönetici hesap uç noktaları aynı yapıyı ve ek olarak iki işletmeci alanını, yani blob ve keyRecordKinds alanlarını döndürür (ADR-0001). Dolayısıyla bir yönetici yanıtından AccountView kodunu çözen bir istemci değişmeden çalışır ve istemediği iki alanı okur.

GET /v1/auth/account → 200 {"account": AccountView}.

PATCH /v1/auth/account, {"displayName": string | null} → 200 {"account": AccountView} alır. Anahtar, null olarak bile olsa BULUNMALIDIR: eksik bir anahtar 400 sonucunu doğurur; bu kural keyRecords ve expectedUpdatedAt tarafından da izlenir, çünkü yanlış yazılmış bir alan adı yüzünden sessizce hiçbir şey yapmayan bir PATCH, istemcinin yaptığını sandığı bir değişikliktir.

Bir hesabın kendisiyle ilgili değiştirebileceği tek alan budur. email kimliktir ve yalnızca bir işletmeci aracılığıyla değişir; role ve dailyAiLimit, bir hesabın kendi başına yükseltememesi gereken konumlardır; kimlik doğrulama biçimindeki her şey §5.14 üzerinden ilerler.

POST /v1/auth/delete, {"authHash": "..."} alır ve 204 döndürür. İstekte bulunanın elinde zaten geçerli bir belirteç bulunsa bile yeniden kimlik doğrulaması gerekir: paylaşılan bir cihazda geride bırakılan bir oturum, birinin verilerini geri alınamaz şekilde yok etmek için yeterli olmamalıdır. Yanlış bir authHash, 401 sonucunu verir; tahminler §5.4'ün açıkladığı havuzda hesap başına sınırlandırılır ve kilitli bir hesap Retry-After ile 429 alır. Faturalandırıcısı olan bir kurulumda (§5.22), her iki denetim de geçildiğinde hizmet, faturalandırıcı hesap silinmeden önce hesabın aboneliklerini iptal etsin diye X-Plans-Secret ve X-Account-Id ile gövdesiz bir POST <PLANS_UPSTREAM_URL>/erase gönderir. En fazla beş saniye bekler ve faturalandırıcı ne yanıt verirse versin siler; DELETE /v1/admin/accounts/:id de aynısını yapar. Posta API'si Pigeon olan bir kurulumda (MAIL_API_URL, /v1/emails ile biter), hesap silindikten sonra hizmet, Pigeon adresin elinde tuttuğu her kopyasını silsin diye {"email": "<address>"} ve posta API'sinin Bearer anahtarı ile POST <base>/v1/recipients/erase de gönderir. Bu çağrı yanıtı asla değiştirmez: en fazla üç deneme yapar, 204 süresini en fazla iki saniye geciktirir, yanıttan sonra hâlâ yapılması gereken denemelere devam eder ve son bir başarısızlık durumunda bir sayaç ve bir durum veya hata kodu içeren, adres içermeyen tek bir satır günlük kaydı düşer. Daha sonra tekrar denemek için adresi hiçbir şey saklamaz; Pigeon'ın saklama sınırı son güvenlik ağıdır. DELETE /v1/admin/accounts/:id de aynısını yapar.

Silme işlemi hesabı ve kademeli olarak hesaba ait tüm ikili nesneleri, anahtar kayıtlarını, sıfırlama belirteçlerini ve kullanım satırlarını kaldırır. Geçici silme ve ek süre yoktur. Bu, self servis silme yoludur ve birinin çalıştırmayı hatırlamak zorunda olduğu bir temizleme göreviyle değil, yapısı gereği eksiksiz şekilde tamamlanır.

Aynı işlem ayrıca hesabın gönderdiği ve hâlâ beklemede olan her daveti geri çeker (§5.21). Bir kişinin gönderdiği davet, o kişinin kapısının koşullarını taşır; o kişi ayrıldıktan sonra beklemede kalan bir davet yine de kullanılabilir ve gün denemeli bir kapıda bu davetin kotası yalnızca kullanıldığında başlar.

Tarama denemesi çalıştıran bir kurulumda aynı işlem o posta kutusuna ait her davet satırından adresi ve adı kaldırır da yapar ve hesabın bir deneme hakkı varsa, §5.8.3'teki posta kutusu başına tek deneme kuralı silme işleminden sonra da geçerliliğini korusun diye posta kutusunun anahtarlı tek yönlü bir özetini tutar işlemini gerçekleştirir. Özet, silinmeden sonra TRIAL_HASH_RETENTION_DAYS boyunca (varsayılan olarak 365) saklanır ve ardından saatlik bir taramayla silinir; bundan sonra aynı posta kutusu yeniden deneme hakkı alabilir. Tarama denemesi vermeyen bir kurulum hiçbir özeti saklamaz. Kişiyle ilgili başka hiçbir şey saklanmaz (§9.2). Dayanak ve süre docs/adr/0010-the-mailbox-hash-has-a-basis-and-an-end.md içinde yazılıdır.

5.15.1 POST /v1/auth/account/health-consent: sağlık verilerine açık rıza

Bearer. Yalnızca instance.healthConsent değeri null olmadığında mevcuttur; bunun dışındaki her yerde yol, oturum açmış olsun ya da olmasın herkese sıradan bilinmeyen yol yanıtı olan 404 döndürür.

Bir örnek neden onay ister? Bir günlük sağlık verisidir: yiyecekler, kilo, açlık. Yönetilen bir örnekte işletmeci emanete alınmış kurtarma kodunu tutar (§3.1, ADR-0005) ve bu sayede günlüğü açabilir; gizlilik bildirimi de yasal dayanak olarak GDPR Madde 9(2)(a) kapsamındaki açık rızayı gösterir. İşletmeci onayın verildiğini, ne zaman verildiğini ve hangi metne verildiğini gösterebilmelidir. İşletmecisi kişinin kendisi olan Kendi sunucunda barındırma örneği ise kimseye bir şey sormaz ve HEALTH_CONSENT_VERSION ayarını boş bırakır.

Bir onayın bir hesaba ulaşmasının iki yolu, tek sürüm. İşletmeci, 1 ila 32 harf, rakam, ., _ veya - (2026-09-28 gibi bir tarih) içeren kısa bir dize olan HEALTH_CONSENT_VERSION değerini ayarlar ve /health bunu instance.healthConsent.version olarak yayımlar.

  • Yeni bir hesap hesap oluşturma adımında kabul eder: POST /v1/auth/signup değeri "healthConsent": {"version": "<v>"} taşır ve bunu hesapla aynı ifadede kaydeder (§5.8).
  • Buna sahip olmayan ya da daha eski bir sürüme sahip Mevcut bir hesap için bir kez sorulur ve burada kabul edilir.

Her veri rotasında zorunludur. Kabul edene kadar, örneğin geçerli sürümüne sahip olmayan bir hesap 403 {"error":"health-consent-required"} ile reddedilir; bu, taşıyıcı denetiminden sonra ve herhangi bir şey depolanmadan, sayılmadan veya gönderilmeden önce her rotada aynı gövdedir:

Onayı olmayan bir hesaba reddedilirNeden
/v1/sync altındaki her rota, ancak aşağıdaki iki okuma hariç: blob yükleme, anahtar kaydı yazma ve silme işlemleri, rotate-dek, paylaşımlar, araştırmaGünlüğü depolarlar veya devrederler
Askıya almadan sonra ve kotadan önce, POST /v1/chat/completions (§5.19)Gövde bir tabak fotoğrafıdır
POST /v1/feedback (§5.25), /v1/pulse/* (§5.23), /v1/push/* (§5.24)Her biri günlükten alınan bir şeyi depolar
Anonim GET /v1/plans/prices hariç /v1/plans/* (§5.22)Kabul etmek veya ayrılmak değil, hesabı kullanmak
PATCH /v1/auth/account, POST /v1/auth/invites (§5.21)Kabul etmek veya ayrılmak değil, hesabı kullanmak
POST /v1/auth/change-passphrase (§5.14)İkinci yarısı blob üzerindeki bölmeyi yeniden yazar ve bu reddedilir, bu yüzden tüm değişiklik bekler
Onayı olmayan bir hesaba açıkNeden
POST /v1/auth/login, POST /v1/auth/refresh, POST /v1/auth/logoutOturum açma ve kapatma
GET /v1/auth/accountİstemci sorması gerektiğini öğrenmek için bunu okur
POST /v1/auth/account/health-consent (bu rota)Hesabın kabul ettiği yer
POST /v1/auth/delete (§5.15)Silme, bir onayın nasıl reddedileceği veya geri çekileceğidir
GET /v1/sync/blob (§5.2), GET /v1/sync/key-records (§5.3)Kendi kopyası. Yeni bir cihazda oturum açmak istemcinin talepte bulunabilmesi için her ikisine de ihtiyaç duyar, yeni bir cihazdaki dışa aktarma ise çekme işlemidir. Hiçbir şey saklanmaz
/health, GET /v1/plans/prices, POST /v1/legal/declarations, kimlik doğrulaması yapılmayan /v1/auth/* rotaları (§5.7 - §5.14)Oturum yok, bu yüzden sorulacak bir hesap da yok
/v1/admin/* (§5.20)İşletmecinin kendi kimlik bilgisi; bir yöneticinin günlük rotaları herkesinki gibi reddedilir

Daha eski bir metne verilen onay, hiç verilmemiş gibi reddedilir. Bu rota 200 yanıtı verdikten hemen sonraki ilk istekte, aynı erişim belirteci üzerinden bu ret kalkar: askıya alma durumunda olduğu gibi, servis kimliği doğrulanmış her istekte hesap satırını ve bununla birlikte onayı okur. instance.healthConsent değerinin null olduğu bir kurulumda buradaki hiçbir şey hiçbir şeyi reddetmez.

Anlık bildirim göndericisi bir rota yerine abonelik tablosunu okur, dolayısıyla aynı kuralı kendi başına uygular: kurulum talep etmeden önce abone olan bir cihaz kendi satırını korur ve hesap onaylayana kadar hiçbir şey almaz (§5.24).

İstek: healthConsent ayarlanmış olarak {"version": "2026-09-28"} → 200 {"account": AccountView} (§5.15).

DurumAnlamı
200{"account": AccountView}. Onay, şu anda veya aynı sürümle yapılan daha önceki bir çağrıdan kaydedilmiş durumda
400{"error":"health-consent-required"}: gövdede version dizesi yok veya /health tarafından yayımlanan dize değil; hiçbir şey kaydedilmez
401Geçerli erişim belirteci yok
403{"error":"account-suspended"}
404Örnek hiçbir onay istemiyor

Uyumlu bir sunucunun uyması ZORUNLU olan dört kural:

  1. Depolanan sürüm çağıranın değil, her zaman örneğinkidir. Gövdedeki version, örneğinkiyle bayt bayt karşılaştırılır, kırpma ve büyük/küçük harf dönüştürme yapılmaz ve yazılan şey örneğin dizesidir.
  2. An, sunucunun saatidir. İstemci bir zaman göndermez ve gönderilse de okunmaz.
  3. Eşgüçlüdür ve ilk an geçerli olur. Zaten kayıtlı olan sürümle yapılan ikinci bir çağrı hiçbir şeyi değiştirmez ve aynı 200 yanıtını verir; at, kişinin ilk kabul ettiği an olarak kalır. Farklı bir sürüm her ikisinin de yerini alır, böylece yeni bir metin kendi anını taşır.
  4. Geri çekme, silme işlemidir. Hiçbir rota bir onayı temizlemez. Onayı geri çeken kişi hesabı siler (POST /v1/auth/delete, §5.15), bu da satırla birlikte günlüğü ve onayı da kaldırır. İşletmeci onayı yönetici hesap görünümünde okur ve hiçbir yönetici rotası bunu yazmaz: bir işletmecinin birisi adına verebileceği bir onay hiçbir şeyi kanıtlamaz.

health-consent-required, her onay denetiminin tek ret yanıtıdır ve iki durumda döner: hesap oluşturulurken ve istemcinin onay kutusunu tekrar gösterdiği bu rotada 400, istemcinin kişiyi bu rotaya yönlendirdiği bir veri rotasında ise 403. HEALTH_CONSENT_VERSION değerini değiştirmek her hesaptan yeniden onay ister ve o andan itibaren her veri rotası, eski metni onaylamış hesapları yenisini onaylayana kadar reddeder; bir işletmeci bunu yalnızca metin değiştiğinde değiştirir.

5.16 Paylaşımlar: /v1/sync/shares ve /v1/sync/shared (ADR-0002)

Yalnızca dağıtım SYNC_SHARING değerini ayarladığında mevcuttur. Bu olmadan aşağıdaki her yol, kimlik bilgisi olsun veya olmasın her çağırana sıradan bilinmeyen rota yanıtı olan 404 değerini döner; sonlandırıcı, kimlik doğrulamanın öncesinde bağlandığından, yapılandırılmamış bir kurulumla bu özelliğin hiç yazılmadığı bir kurulum birbirinden ayırt edilemez.

Her iki taraf da bir paylaşıma yapay bir paylaşım kimliğiyle değil, karşı tarafın hesap kimliği ile başvurur: bir paylaşımın değişmeyen kimliği (veren, alan) çiftidir ve DEK döndürme işleminden sonra da bu korunur.

Yetki veren tarafı.

EylemYolNotlar
PUT/shares/:granteeAccountId`{"wrappedDek": "<base64>", "recipientKeyFingerprint": "<string>", "expectedUpdatedAt": "<iso>" \null}. CAS exactly as §5.4: null asserts no share exists yet, any other value asserts the row last read had this updatedAt, and an **absent** key is a 400. 409 returns {"currentUpdatedAt": "<iso>" \null}`.
GET/sharesYetki verenin kendi tanımladığı yetkiler. Asla wrappedDek döndürmez: başkasının anahtarına hitap eden bir ikili nesnenin burada hiçbir işlevi yoktur, bu yüzden kimsenin ihtiyaç duymadığı bir yere iletilmez.
DELETE/shares/:granteeAccountId204, birim etkilidir. Bir kalıcı silme; mezar taşı kaydı yoktur.

Yetki alan tarafı.

EylemYolNotlar
GET/sharedHer biri kendi wrappedDek değerini içeren ve bu çağırana hitap eden paylaşımlar; bunu yalnızca bu çağıran açabilir.
GET/shared/:grantorAccountId/blob{"grantorAccountId": <int>, "blobVersion": <int>, "envelopeVersion": <int>, "ciphertext": "<base64>", "createdAt": "<iso>"}. grantorAccountId zorunludur: §3.2 kapsamındaki AAD bunu bağlar, bu nedenle buna sahip olmayan bir yetki alan hiçbir şekilde şifre çözemez.
DELETE/shared/:grantorAccountId204, birim etkilidir. Yetki alanın, kendisine yöneltilen bir paylaşımı bırakmasını sağlar.
  • Yetki alan yüzeyinde yetki verene karşı yazma eylemleri bulunmaz ve yalnızca çağıranın kendi paylaşım satırını, yetki verenin geçerli ikili nesnesini ve grantorAccountId sunar. Asla yetki verenin anahtar kayıtlarını, KDF tanımlayıcısını, doğrulayıcısını, emanetini, e-postasını veya görünen adını sunmaz. Yetki verenin recovery ile sarmalanmış DEK verisini çekebilen bir yetki alan, o hesap üzerinde döndürme yetkisi elde etmeye yalnızca kaba kuvvetle çözülecek bir kurtarma kodu kadar uzakta olurdu.
  • Yalnızca geçerli ikili nesne. Saklanan sürüm halkası, yetki alana yönelik bir zaman çizelgesi değil, sahibin kurtarma mekanizmasıdır.
  • Yetkilendirme her istekte doğrudan satır üzerinden okunur, asla önbelleğe alınmaz. Bir DELETE işlemini hemen bir sonraki çağrıda geçerli kılan da budur.
  • Bilinmeyen, yabancı ve hiç itilmemiş durumların tümü aynı 404 yanıtını verir. Bir paylaşımın bulunmaması, hesabın var olduğunu doğrulamamalıdır.

5.17 POST /v1/sync/rotate-dek: atomik DEK rotasyonu (ADR-0002)

Hesap sahip olarak Bearer. Tek gönderim, tek işlem:

json
{
  "blob": { "baseVersion": 3, "envelopeVersion": 1, "ciphertext": "<base64>" },
  "keyRecords": [{ "kind": "passphrase", "kdfDescriptor": { "...": "..." }, "wrappedDek": "<base64>" }],
  "newRecoveryAuthHash": "<base64, 32 bytes>",
  "recoveryCode": "ABCDE-FGHJK-MNPQR-STVWX-YZ012-3456",
  "currentAuthHash": "<base64, 32 bytes>",
  "shares": [{ "granteeAccountId": 7, "wrappedDek": "<base64>", "recipientKeyFingerprint": "<string>" }]
}

İstemci yeni bir DEK üretir, tüm anlık görüntüsünü bu anahtarla yeniden şifreler, her iki KEK altında yeniden sarmalar ve sakladığı her paylaşıma yeniden sarar. Servis sonucu ya hep ya hiç depolar.

§5.16'nın aksine Her dağıtımda mevcuttur. Rotasyon, paylaşım yüzeyinin bir parçası değildir: çağıranın kendi blob'unu ve kendi iki anahtar kaydını, yani her yerdeki her hesapta bulunan satırları yeniden yazar ve hiçbir şey paylaşmamış bir örnekte bir DEK'in sızdırıldığına (geri yüklenen bir yedek, kaybolan bir cihaz) dair her türlü şüphenin cevabıdır. Tehlikeye girmiş bir DEK'i kullanımdan kaldırabilecek tek mekanizmayı ilgisiz bir bayrağın arkasına gizlemek, böyle bir işletmeciyi DEK'i kullanımdan kaldıramaz halde bırakırdı.

  • currentAuthHash ZORUNLUDUR ve tek başına bir taşıyıcı belirteç asla rotasyon yapmaz. Geçerli parolanın yetkilendirme koludur (§3.1), change-passphrase ile nasıl eşleşiyorsa öyle eşleşir. Eksik veya hatalı biçimlendirilmiş olması, adını belirten bir 400 sonucunu doğurur; eşleşmeyen bir tanesi 401 {"error":"current passphrase is incorrect"} sonucunu verir ve hiçbir şey yazılmaz. Bir rotasyon, POST /v1/auth/recover'in kabul ettiği kurtarma doğrulayıcısını yazar; dolayısıyla bu alandan önce, çalınan bir belirteç kendisine ait bir kod yerleştirebilir ve sonrasında asıl sahip parolasına ne yaparsa yapsın bu kodla kalıcı olarak oturum açabilirdi. Tahminler, §5.4'te açıklanan sepette hesap başına sınırlandırılır (Retry-After ile 429). İşlem, hesabın parola doğrulayıcısının hâlâ eşleşen doğrulayıcı olup olmadığını yeniden denetler; bu sırada işlenen bir parola değişikliği rotasyonu 401 yapar ve hiçbir şey yazılmaz.
  • Diğer her oturum aynı işlem içinde iptal edilir. Çağıranın kendi belirteç ailesi korunur, böylece rotasyonu yapan cihazın oturumu açık kalır; hesabın diğer tüm access ve refresh belirteçleri çalışmayı durdurur. Bir anahtarın sızdırıldığı düşünüldüğünde rotasyon çalıştırılır ve rotasyondan sonra yaşamaya devam eden bir oturum sızıntının ta kendisi olurdu.
  • Tek bir veritabanı işleminde, ya hep ya hiç. ADR-0002 yasaklama 8: bir rotasyon ya atomiktir ya da yoktur ve tek tek commit edilen hiçbir uç nokta dizisi rotasyon olarak belgelenemez veya kullanılamaz. Kısmi bir uygulama, §5.14'ün izin vermeyi zaten reddettiği "sorunsuz giriş yapar, hiçbir şeyin şifresini çözemez" açmazıdır ve burada bir katılımcı daha vardır: blob yazma işlemi CAS'ını kaybederken yeniden sarılan bir anahtar kaydı sahibini ortada bırakır, blob yazma işlemi CAS'ını kaybederken yeniden sarılan bir paylaşım ise klinisyeni ortada bırakır.
  • Tam olarak §5.1'deki gibi blob, baseVersion üzerinde karşılaştır ve değiştir (CAS) yapılır. Güncel olmayan bir değer 409 {"currentVersion": n} hatasıdır ve hiçbir şey yazılmaz.
  • newRecoveryAuthHash VE recoveryCode ZORUNLUDUR ve ikisinden birinin eksik olduğu bir gönderim, ilgili alanı belirten bir 400 hatasıdır. Bir rotasyon her zaman yeni bir kurtarma kodu üretir, çünkü yeniden sardığı recovery anahtar kaydı bu koddan türetilen bir KEK altında mühürlenmiştir; bu nedenle sunucu accounts.recovery_verifier ve emaneti (§3.1) blob, anahtar kayıtları ve paylaşımlarla aynı işlem içinde değiştirir. Bu ikisini ESKİ kodda bırakan bir rotasyon, emanet edilen kodun kimlik doğrulayıp ardından hiçbir şeyi açamadığı bir hesap üretirdi; bu durum kurtarma kodu ikinci kimlik doğrulayıcı haline geldiği andan itibaren gizli kalır ve postalanan bir sıfırlama (§5.12) bu kodu insanlara dağıtmaya başladığında ölümcül olurdu. İstemci yeni kodu kişiye göstermez; kod emanete gider ve orada kalır.
  • Sunucu, kurtarma kanıtını kendisi türetir. Kanonik recoveryCode üzerinden §3.1'in kurtarma yetkilendirme kolunu çalıştırır ve yeni doğrulayıcıyı BUNDAN hesaplar; böylece doğrulayıcı ve emanet her zaman aynı kodu tanımlar. newRecoveryAuthHash hâlâ zorunludur ve türetilen kanıta eşit olmalıdır; bir uyuşmazlık, adını belirten bir 400 sonucunu verir ve hiçbir şey yazılmaz.
  • keyRecords HER İKİ türü de taşımalıdır. Eksik bir tür sessiz bir kısmi rotasyon değil, bir 400 hatasıdır: yalnızca passphrase sarmasını göndermek, recovery kaydının artık hiçbir şeyi açmayan bir DEK'i sarmalamasına yol açar, böylece kurtarma kodu hesaba giriş yapmaya devam eder ancak şifresini bir daha asla çözemez. Her girdi §5.4 kurallarına uyar (bir recovery tanımlayıcısı null olmalıdır, bir passphrase tanımlayıcısı olmamalıdır). Kayıt başına expectedUpdatedAt yoktur: eşzamanlılık birimi gönderimin kendisidir.
  • shares SAKLAMA listesidir ve bu listede adı geçmeyen her paylaşım satırı aynı işlemde silinir. Bu durum, dokunulmamış bir anahtar kaydının kasıtlı olarak saklandığı §5.14'ü tersine çevirir, çünkü bu satırlar çağıranın günlüğü üzerinde başkasının yetkisidir ve sessizlik güvenli varsayılan olmalıdır. Bu nedenle shares: [] her şeyi iptal eder ve geçerlidir; bir yok shares anahtarı, §5.4'ün expectedUpdatedAt değerinin açıkça yazılmasını gerektirme gerekçesiyle bir 400 hatasıdır. SYNC_SHARING bulunmayan bir dağıtımda liste boş olmalıdır; boş olmayan bir liste, o örneğin tutamayacağı bir durumu ileri sürdüğü için bir 400 hatasıdır.
  • Var olmayan adlandırılmış bir paylaşım bir 400 hatasıdır, tamamen geri alınır, hiçbir zaman bir yetki verme olarak değerlendirilmez. Yetki verilen taraf kendi tarafını silmiş olabilir; GET /v1/sync/shares değerini yeniden oku ve tekrar gönder.
  • Saklanan eski blob sürümleri (§8) ESKİ DEK altında mühürlü kalır ve bir rotasyon commit edildiği anda ölü ağırlık haline gelir, sahibi dahil herkes için okunamaz olur. Burada silinmezler: budama işlemi sonraki beş itme içinde bunları temizler ve bunları bir rotasyon sırasında silmek, aynı işlemde sahibin hatalı bir istemci yazmasına karşı tek savunmasını çöpe atar.
DurumGövde
200{"newVersion": 4, "keptShares": 1, "revokedShares": 2}
400{"error": "..."}: eksik bir anahtar kaydı türü, hatalı biçimlendirilmiş veya eksik bir alan, kodun kanıtı olmayan bir newRecoveryAuthHash, orada bulunmayan bir paylaşıma işaret eden bir saklama listesi.
401{"error": "current passphrase is incorrect"}: currentAuthHash eşleşmedi veya rotasyon sırasında parola değişti. Hiçbir şey yazılmadı.
409{"currentVersion": 5}: blob CAS doğrulaması başarısız oldu. Hiçbir şey yazılmadı.
413{"error": "..."}: yeni blob MAX_BLOB_BYTES sınırını aşıyor.
429Retry-After ile {"error": "..."}: bu hesabın parola tahminleri kilitlendi.

Rotasyon Seviye 2 iptaldir ve §5.16'nın ifade kuralları bağlayıcılığını korur. Bir paylaşım satırını silmek sunucunun servis vermesini durdurur; rotasyon ise gelecekteki girdilerin yetkisi iptal edilen tarafın hiç sahip olmadığı bir anahtarla mühürlenmesini sağlar. İkisi de önceden indirilmiş olanı geri almaz ve hiçbir istemci aksini söyleyemez.

5.18 Araştırma katkıları: /v1/sync/contributions ve /v1/sync/study (ADR-0003)

Yalnızca dağıtım SYNC_RESEARCH değerini ayarladığında mevcuttur. Yoksa, sonlandırıcı kimlik doğrulamasının önüne bağlandığı için aşağıdaki her yol kimlik bilgisi olsun veya olmasın her arayana sıradan bilinmeyen rota 404 yanıtını verir. SYNC_SHARING değerinden bağımsızdır, iki bayrak da diğerini ima etmez.

Katkıda bulunan tarafı, katkıda bulunan olarak kimliği doğrulanmış:

EylemYolNotlar
PUT/contributions/:studyAccountId{"pseudonym","schemaTier","body","contributionVersion"}. Monotonik bir contributionVersion üzerinde CAS. Katkı, ilgili aralık için yeniden hesaplanıp bütünüyle tekrar itilen kümülatif veri kümesidir, istemci kaynağı her zaman elinde tutar, bu yüzden bu satır bir projeksiyondur, asla birincil kopya değildir.
GET/contributionsKatkıda bulunanın kendi kayıtları. Asla body döndürmez.
DELETE/contributions/:studyAccountIdGeri çekilme. Tek işlem: satırı kalıcı olarak sil, takma ad anahtarlı bir mezar taşı ekle. 204, birim etkilidir.

Araştırma tarafı, araştırma hesabı olarak kimliği doğrulanmış:

EylemYolNotlar
GET/study/contributionsSatır başına {"pseudonym","contributionVersion","schemaTier","body","createdAt"}. Hiçbir zaman hesap kimliği içermez.
GET/study/withdrawalsZaman damgalarıyla birlikte çekilen takma adlar. Araştırma istemcisi herhangi bir şeyi sunmadan veya dışa aktarmadan önce bunları temizlemelidir.

GET /study/contributions, her satırda değil, zarfın en üst düzeyinde bir kez studyAccountId değerini yankılar: arayanın kendi kimliğidir, bu kimlikle doğrulanmıştır, her satır için aynıdır ve katkıda bulunan tanımlayıcısı değildir. Araştırmacının Bölüm 3.5'in AAD'sini yeniden oluşturması için buna ihtiyacı vardır, satır başına eklenmesi ise gereksiz gürültü olurdu.

contributionVersion karşılaştır ve takas et işlemi. Gönderilen değer yeni sürümün olmalıdır, bir taban değil, AAD'ye bağlandığı için şifreli metin mühürlenirken kullanılan değer olmalıdır. Kural saklanan sürümden kesinlikle daha büyük olması şeklindedir: tüm projeksiyonu yeniden hesaplayıp tekrar iten bir istemci, cihazdan hiç çıkmamış bir sürüm yüzünden asla kilitlenmemelidir. Kaybeden bir yazma işlemi, Bölüm 5.1'in biçimiyle eşleşen 409 {"currentVersion": <int>} sonucunu verir.

Sunucu, schemaTier değerini bu protokolün tanımladığı katmanlara göre doğrular. Katman adı içerik değil meta veridir (şifresiz iletilir ve sunucu zaten saklar), bu denetim olmadan ADR-0003'ün 1 numaralı yasağı istemci dışında hiçbir yerde geçerlilik kazanamaz. Bilinmeyen bir katman 400 sonucunu verir.

Sunucu, takma adın biçimini doğrulamaz, yalnızca var olduğunu ve sınırlı olduğunu doğrular. Bir tanesini doğrulayamaz (bunun için katkıda bulunanın kök bilgisi gerekir) ve yapısal bir denetim, sahip olmadığı bir yetkiyi ima eder.

DurumŞu durumlarda
400hatalı gövde, bilinmeyen schemaTier, eksik contributionVersion
404bilinmeyen araştırma, bilinmeyen katkı ve diğer tüm bulunamadı durumları: tek bir kod yolu
409contributionVersion saklanan değerden kesinlikle daha büyük değil
413katkı MAX_CONTRIBUTION_BYTES (256 KiB) sınırını aşıyor

Veritabanı tarafından zorunlu kılınan, araştırma başına bir takma ad. Aynı takma adı gönderen iki katılımcı sessizce tek bir katılımcı serisinde birleşir ve araştırmacı hiçbir şey hata vermeden iki kişiyi tek bir kişi olarak analiz eder. Yanlışlıkla çakışma olasılığı yaklaşık 2^-128 kadardır, yani kısıtlamanın asla tetiklenmemesi gerekir, asıl amaç da budur: bozulmayı ihtimal dışı bırakmak yerine imkansız kılar.

Geri çekme işlemi bu tarafta verileri gerçekten siler. Araştırmanın henüz çekmediği bir katkı kimseye ulaşmaz. Araştırmanın zaten çektiği veriler ise geri alınamaz: mezar taşı bu talimatı taşır, buna uymak bu sistemin belirttiği ancak zorlayamayacağı bir etik yükümlülüktür.

5.19 POST /v1/chat/completions: yapay zeka vekili

Yalnızca işletici bir yukarı akış anahtarı yapılandırdığında mevcuttur. Bu olmadan yol, kimlik bilgisi olsun veya olmasın herkese sıradan bilinmeyen yol yanıtı olan 404 kodunu döndürür ve el sıkışmada (§5.6) instance.ai değeri null olur. Bu protokolün bir uygulaması bu rotayı tamamen hariç TUTABİLİR; bir istemci yolu yoklamak yerine bir tarama önermeden önce instance.ai okUMALIDIR.

Hesabın normal erişim belirteci belirteciyle kimlik doğrulaması yapılır (§4.1), gövde okunmadan önce denetlenir: geçerli bir belirteci olmayan bir istek, boyutu veya biçimi ne olursa olsun 401 sonucunu alır ve servis bu isteği ne arabelleğe alır ne de ayrıştırır. Gövde, OpenAI uyumlu bir sohbet tamamlama isteğidir. Servis bunun bir JSON nesnesi olduğunu denetler, yalnızca izin verilenler listesindeki (aşağıda) alanları iletir, bir isteğin maliyetini belirleyen az sayıdaki alanı yeniden yazar, bir isteğin içeri alabileceği veriyi sınırlar ve bilmediği bir alan yüzünden hiçbir şeyi reddetmez: o alanı atar. Yanıt, sağlayıcının yanıtıdır ve durumuyla birlikte aktarılır.

Tek bir isteğin neye mal olabileceğine örnek karar verir. Bir üst kaynak anahtarı bir örnekteki her hesaba hizmet verebilir ve günlük istek sayısı tek bir isteğin maliyeti hakkında hiçbir şey söylemez. Bu nedenle servis, her hesap için iletmeden önce bu alanları yeniden yazar ve bunlar yüzünden bir isteği asla reddetmez.

Yalnızca bu üst düzey alanlar iletilir: model, messages, stream, stream_options, temperature, top_p, response_format, max_tokens, max_completion_tokens, reasoning ve n. Diğer tüm alanlar atılır ve servisin bilmediği bir alanı gönderen istemcinin çalışmaya devam edebilmesi için adı (asla değeri değil) günlüğe kaydedilir. messages içinde bir ileti role, content ve name alanlarını korur; bir içerik parçası text (text ile) veya image_url (yalnızca url ile, bu yüzden detail atılır) şeklindedir ve url değeri bir data:image/...;base64, URI'si olmayan bir image_url atılır; çünkü uzak bir URL veya bir veri URI'sinin arkasındaki belge, kimsenin ölçmediği bir girdidir. Diğer tüm parça türleri atılır.

AlanSağlayıcının aldığı
modelisteğin katmanının modeli, işletmecinin seçimi: rotası isteğin response_format.json_schema.name değeriyle eşleşen katman, aksi halde örneğin varsayılan katmanı. Varsayılan katmanın modeli, instance.ai.model tarafından yayınlanan modeldir (§5.6). Katman dosyası olmayan bir örneğin tek bir katmanı vardır ve modeli AI_ADVERTISED_MODEL olur. İşletmeci hiçbir şey belirtmediğinde, arayanın model değeri değiştirilmeden gönderilir.
max_tokens, max_completion_tokensen fazla AI_MAX_OUTPUT_TOKENS (varsayılan 8192) ya da varsa isteğin katmanının kendi daha düşük üst sınırı. Bunun üzerindeki bir değer veya sayı olmayan bir değer, bu üst sınır haline gelir. İkisini de içermeyen bir gövdeye max_tokens yazılır.
reasoning.max_tokensen fazla aynı üst sınır. İsteğin katmanı kendi eforunu belirlemedikçe reasoning.effort korunur: katman belirlerse servis bu eforu yazar ve arayanın reasoning.max_tokens değerini atar, çünkü bir sağlayıcı bu ikisinden yalnızca birini kabul eder.
nVarsa 1.
usage, bir OpenRouter yukarı akışında{"include":true} olarak yazılmıştır, böylece yanıt kendi belirteç sayılarını ve fiyatını bildirir (aşağıda). Diğer kaynakların hiçbiri almaz. Arayanın kendi usage değeri kaldırılır.
usage, bir OpenRouter yukarı akışında{"include":true} olarak yazılmıştır, böylece yanıt kendi belirteç sayılarını ve fiyatını bildirir ("Bir tamamlamanın maliyeti nedir", aşağıda). Diğer kaynakların hiçbiri almaz. Arayanın kendi usage değeri kaldırılır.
yukarıdaki izin verilenler listesinde olmayan herhangi bir alankaldırıldı, örneğin models, route, plugins, web_search_options, prediction, tools.
provider, bir OpenRouter yukarı akışında{"data_collection":"deny"} olarak geri yazılır: yalnızca isteği saklamayan veya istekle eğitilmeyen uç noktalar. İşletmeci, isteğin katmanı üzerinden (routing) veya UPSTREAM_ZDR ve UPSTREAM_PROVIDER_ONLY üzerinden "allow_fallbacks":false ile "zdr":true ve "only":[...] ekleyebilir. Arayanın kendi provider değeri asla iletilmez. Diğer tüm yukarı akışlar, ne ayarlanmış olursa olsun provider alanını almaz.

Katman işletmecinindir, asla arayanın değil. İşletmeci katmanları bir dosyaya yazar (AI_TIERS_FILE, README dosyasına bak): her biri bir model, onun sağlayıcı yönlendirmesini ve isteğe bağlı olarak daha düşük bir çıktı üst sınırını ve bir akıl yürütme eforunu barındırır; bir routes eşlemesi ise yapılandırılmış çıktı şeması adını bir katmana gönderir. Şeması yönlendirilen bir istek o katmanı alır, diğer tüm istekler ise varsayılan katmanı alır. Arayan kişi yalnızca bir şema adı belirtebilir, dolayısıyla işletmecinin tanımladığı bir katmana erişebilir ve başkasına erişemez; ayrıca asla bir model veya sağlayıcı belirlemez. El sıkışma (instance.ai.model, §5.6) varsayılan katmanın modelini adlandırır.

Üst sınır bir model olsa da olmasa da geçerlidir. Üst sınırın izin verdiğinden daha uzun bir yanıta ihtiyaç duyan bir istemci kesilmiş bir yanıt alır ve işletmeci AI_MAX_OUTPUT_TOKENS değerini artırır. İnsanlarının modeli seçmesini isteyen, kendi sunucunda barındırılan bir örnek AI_ADVERTISED_MODEL ve AI_TIERS_FILE değerlerini ayarlanmamış olarak bırakır.

POST /v1/chat/completions
Authorization: Bearer <accessToken>
Content-Type: application/json
X-Intake-Id: 2f9d0b416c3a4e579f10a1b2c3d4e5f6

{ "model": "…", "messages": [ … ], "stream": true }

Uyumlu bir uygulamanın SAĞLAMASI GEREKEN üç özellik vardır ve istek gövdesi birinin yemeğinin fotoğrafı olduğu için bunların her biri mevcuttur:

  1. Arayanın kimlik bilgisi değiştirilir, asla birleştirilmez. Yukarı akış isteğinin başlıkları, gelen istekten kopyalanıp üzerine yazılmak yerine SIFIRDAN OLUŞTURULUR. Önce kopyalayıp sonra üzerine yazma yöntemi çerezleri, x-api-key değerini ve bir sonraki sağlayıcının okumaya karar vereceği diğer her şeyi iletir.
  2. Hiçbir yönde hiçbir gövde günlüğe kaydedilmez. Bir ön ek değil, kodu çözülmüş bir arabellek değil, bir hata belgesi değil. Günlüğe kaydedilebilecekler: bir hesap kimliği, yukarı akış durumu, bayt sayıları, bir süre ve başarılı bir yanıttan okunan, sağlayıcının bildirdiği belirteç sayıları ve fiyat ile bir model adına benzeyen bir model adı (aşağıda).
  3. Yukarı akış hattından gelen her dize temizlenir bir günlük satırına veya bir yanıta ulaşmadan önce. Bir isteği reddeden sağlayıcı, isteği hata gövdesinin içinde, görseliyle birlikte rutin olarak geri yansıtır.

Bir tamamlamanın maliyeti

Kullanımı bildiren bir sağlayıcı bunu yanıta koyar. Bir OpenRouter yukarı akışında servis, iletilen gövdeye "usage": {"include": true} yazar (kendi usage alanı izin verilenler listesi dışındaki her alan gibi atılan arayandan asla alınmaz) ve başka hiçbir yukarı akış böyle bir alan almaz, bu yüzden gövdeleri olduğu gibi kalır. Yanıt iletildi edildikten sonra servis, Proxied a completion satırında model, promptTokens, completionTokens ve costMicroUsd (sağlayıcının usage.cost değeri, dolar cinsinden bir fiyat, bir doların milyonda birinin tam sayı katı olarak) bilgilerini, yanıt belirtmediğinde her birini null olarak günlüğe kaydeder. Ayrıca costMicroUsd değerini, örneğin UTC günü için olan toplamına ekler, ai_instance_days.cost_micro_usd, içinde hesap bulunmayan ve fiyat bildirmeyen bir sağlayıcı için 0 kalan bir toplam. Sayılar, tek bir baytı geciktirmeden veya değiştirmeden, yanıt geçerken JSON veya sunucu tarafından gönderilen bir akış olarak okunur. 1 MiB'den büyük bir gövde, 64 KiB'den büyük bir akış satırı ve makul bir sayı olmayan herhangi bir alan okunmaz ve null sonucunu verir. Yanıtın metni asla okunmaz. Maliyetin kaydedilememesi günlüğe kaydedilir ve sunulmuş bir isteği asla başarısızlığa uğratmaz.

Bir isteğin içeri alabileceği veri miktarı

Çıktı yukarıda sınırlandırılmıştır, girdi ise burada sınırlandırılır. Sağlayıcının aldığı gövde üzerinden (izin verilenler listesinden sonra) ölçüldüğünde, bir istek şunları taşıdığında herhangi bir tarama talebinden, herhangi bir rezervasyondan ve herhangi bir yukarı akış çağrısından önce 400 ile reddedilir, böylece hiçbir şey harcamaz:

SınırVarsayılanlimit
image_url parça1image-parts
UTF-8 metin baytı49152text-bytes
ileti4messages

Metin; content dizgeleri, her text parçası, her ileti name ve serileştirilmiş response_format anlamına gelir: bir şema, modelin okuduğu girdidir. Görselin kendi baytları metin değildir. İşletmeci sınırları AI_MAX_IMAGE_PARTS, AI_MAX_TEXT_BYTES ve AI_MAX_MESSAGES ile belirler; ret yanıtı da sınırı ve değerini belirtir:

json
{ "error": "ai-request-too-large", "limit": "text-bytes", "max": 49152 }

Varsayılan değerler, openplate'in fazlasıyla pay bırakan en büyük gerçek isteğidir: bir fotoğraf, iki ileti ve yaklaşık 12 KB metin.

Gövde sınırı

İstek gövdesi bir fotoğraf taşır, bu nedenle sınır bir tanesine göre boyutlandırılmıştır: AI_MAX_REQUEST_BYTES, varsayılan 8.000.000 bayt. Base64 bir görseli 4/3 oranında şişirir, bu yüzden bu sınır yaklaşık 5,7 MiB boyutunda bir JPEG taşır; bu da istemcinin ölçek küçülttükten sonra gönderdiği, varsayılan kalitedeki modern bir telefon kamerasıdır.

Bunun MAX_BLOB_BYTES ile ilgisizdir (§8) ile kasıtlı olarak bir ilgisi yoktur. O, bu servisin sakladığı bir günlüğü sınırlar; bu ise yalnızca ilettiği bir görseli sınırlar ve birini diğerinden türetmek her gerçek fotoğrafı reddeder.

Sınırı aşan bir gövde, geçerli bir belirteci olan bir çağırıcı için 413 olur; kimliği doğrulanmamış bir gövde ise okunmadan önce 401 olur. Bu rotadaki hata gövdesi OpenAI biçimlidir, §4 içindeki {"error": "<sentence>"} değildir, çünkü çağırıcı bir nesneden error.message okuyan OpenAI uyumlu bir istemcidir:

json
{
  "error": {
    "message": "Request body exceeds the maximum accepted size of 8000000 bytes. The operator can raise AI_MAX_REQUEST_BYTES.",
    "type": "invalid_request_error",
    "code": "request_too_large"
  }
}

Geçerli bir JSON olmayan bir gövde, "code": "invalid_json" ile aynı zarf içinde 400 alır. Katı kural 2'nin belirttiği nedenden ötürü İkisi de girdiyi geri alıntılamaz. Bir uygulama bunun yerine §4 biçiminde yanıt VEREBİLİR, ancak OpenAI sağlayıcısına göre yazılmış bir istemci bu durumda hata yerine hiçbir şey görüntülemez.

Akış doğrudan aktarımdır. İstek bunu talep ettiğinde, yanıt gövdesi Cache-Control: no-cache, no-transform ile ve Content-Length olmadan, ulaştığı anda iletilir. Arabelleğe alan bir servis de her baytı teslim ederdi, bu yüzden bir istemci aradaki farkı yalnızca kaçınmaya çalıştığı gecikme süresinden anlayabilir.

Kota

Her hesap, her biri UTC günü başına birim cinsinden olan ve her biri varsayılan olarak 0 değerini alan iki günlük sınır taşır: ücretli aralığınki dailyAiLimit ve kalıcı ücretsiz hakkınki freeDailyAiLimit (§5.15). Hangisi geçerlidir istek başına, şu sırayla kararlaştırılır:

Hesabın sahip olduğuHakKarşılığında rezervasyon yapılan sınır
isteğin anından sonra allowanceExpiresAt, dailyAiLimit > 0ücretli aralıkdailyAiLimit
aksi halde freeDailyAiLimit > 0ücretsiz hakfreeDailyAiLimit
aksi takdirde örneğin DEFAULT_FREE_DAILY_AI_LIMIT > 0ücretsiz hakbu varsayılan
aksi halde dailyAiLimit, 0 oluryok403 ai-not-allowed
aksi halde allowanceExpiresAt ayarlanmıştır (yani geçmiştir)yok403 allowance-expired
aksi halde trialScans ayarlanmıştırtarama denemesidailyAiLimit
aksi halde (bir sınır, tarih yok, deneme yok, ücretsiz hak yok)yok403 ai-not-allowed

Örnek varsayılanı (2026-10-05). Bir işletmeci DEFAULT_FREE_DAILY_AI_LIMIT ayarlayabilir. Kendi freeDailyAiLimit değeri 0 olan her hesabın ücretsiz hakkıdır: sıranın aynı yerindeki aynı hak; böylece canlı bir ücretli aralık yine kazanır, varsayılan ne olursa olsun kendi sınırı korunur ve tarama denemesine sahip bir hesap tarama harcamak yerine varsayılanın kapsamına girer. Asla sona ermez ve tarama kapısı yoktur. Hiçbir satıra yazılmaz, bu nedenle onu düşüren veya kaldıran bir işletmeci tüm hesapları aynı anda değiştirir. Tüketilmiş bir gün, Retry-After ile birlikte aşağıdaki 429 durumudur ve hiçbir hakkı olmayan bir hesabın 403 ai-not-allowed durumu asla değildir. Varsayılanı olmayan bir servis tam olarak eskisi gibi davranır. Varsayılan, tarama denemesinin yanında ayarlanamaz: o örnek açılmayı reddeder.

Son satır 2026-09-30 tarihinde değişti. Bu yapı eskiden sonu olmayan kalıcı bir haktı; bu yapıya sahip olan her hesap bir geçişle freeDailyAiLimit konumuna taşındı ve artık hiçbir şey bunu yazmıyor. Ücretsiz hak hiçbir zaman tarama koşullu değildir ve asla bitmez, bu nedenle ücretsiz hakka sahip bir tarama denemesi hesaba katılmaz.

Bir istek, yukarıdaki sıranın seçtiği sınıra karşı max(1, ceil(estimated input tokens / AI_UNIT_INPUT_TOKENS)) birim rezerve eder; burada çağrıdan önce yapılan tahmin, yukarıdaki metin baytlarının 4'e bölünmesi ve görsel başına AI_IMAGE_INPUT_TOKENS (varsayılan 1500) eklenmesidir. 8192 olan varsayılan AI_UNIT_INPUT_TOKENS ile openplate'in tabak taraması 1 birim ağırlığındadır ve metin sınırına yakın bir istek 2 birim ağırlığındadır, bu yüzden uygulama için bir birim bir istektir. Aynı ağırlık aşağıdaki bulut sunucusu tavanlarından düşülür ve geri verildiğinde eksiksiz olarak geri verilir. Vekillik edilen her yanıt, hesabın uygulanan sınırdaki konumunu taşır:

BaşlıkAnlamı
X-Quota-UsedBu işlemden sonra bugün harcanan birimler
X-Quota-LimitYukarıdaki sıranın seçtiği sınır: dailyAiLimit, freeDailyAiLimit veya örnek varsayılanı
X-Trial-Scans-LeftTarama kapısının geçerli olduğu bir hesapta bu istekten sonra kalan ücretsiz taramalar (aşağıda). Aksi takdirde bulunmaz
DurumerrorŞu durumlarda
401authentication requiredErişim belirteci yok veya süresi dolmuş ya da iptal edilmiş
403ai-not-allowedHesabın hiçbir hakkı yoktur (yukarıdaki sıra). Sunucudan herhangi bir şey ayrılmadan önce reddedilir
403allowance-expiredallowanceExpiresAt ayarlanmış ve isteğin ulaştığı andan sonra değil ve ücretsiz bir hak yok. Sunucudan herhangi bir şey ayrılmadan önce ve bir kullanım satırı yazılmadan önce reddedilir
403trial-scans-spentHesabın ücretsiz taramaları tükendi ve bir kota tarihi yok. Sunucudan herhangi bir şey çıkmadan önce ve bir kullanım satırı yazılmadan önce reddedildi. X-Trial-Scans-Left: 0. Gövde "endedBy": "scans" taşır
403trial-expiredHesabın trialEndsAt değeri ayarlanmış ve isteğin ulaştığı andan sonra değil, taramaları tükenmemiş ve bir kota tarihi yok. Herhangi bir satır yazılmadan önce reddedildi. Gövde "endedBy": "days" taşır
403capability-requiredİstek, hesabın sahip olmadığı bir özelliği belirtiyor (aşağıda). Gövde, eksik etiket olan capability taşır. Herhangi bir şey sayılmadan ve ana bilgisayardan herhangi bir şey çıkmadan önce reddedilir
403account-suspendedHesap askıya alınmış (§5.9 aynı kodu kullanır)
403health-consent-requiredKurulum bir sağlık verisi onayı ister ve hesap bunun güncel sürümüne sahip değildir (§5.15.1). Ana makineden herhangi bir şey ayrılmadan önce ve bir kullanım satırı yazılmadan önce reddedilir
400request body must be a JSON objectGövde bir nesne değil. Girdi hiçbir zaman aynen geri aktarılmaz
400ai-request-too-largeGövde, bulut sunucusunun izin verdiğinden daha fazla görsel parçası, metin baytı veya ileti taşımaktadır (yukarıda). Gövde limit ve max belirtir. Herhangi bir satır yazılmadan önce reddedilir
400feature-header-invalidDenetlenen bir hesapta X-Openplate-Feature mevcut ve bir etiket değil (aşağıda). Herhangi bir satır yazılmadan önce reddedilir
400intake-id-invalidX-Intake-Id mevcut ve 16 ila 64 karakter uzunluğunda A-Z a-z 0-9 _ - değil. Herhangi bir satır yazılmadan önce reddedildi
409intake-in-flightAynı X-Intake-Id değerine sahip daha önceki bir istek, tarama kapısının geçerli olduğu bir hesapta (aşağıda) hâlâ işleniyor. Hiçbir şey harcanmaz ve hiçbir satır yazılmaz
429sıfırlanma anını belirten bir cümleKota bu isteğin birimlerini karşılayamaz. Retry-After, bir sonraki UTC gece yarısına kalan saniyedir
429dakika başına sınırı belirten bir cümleSon 60 saniye içinde AI_RATE_LIMIT_PER_MINUTE adetten fazla istek
503ai-instance-ceilingTüm örnek günlük tavanını harcadı veya tarama denemesi hesapları kendilerininkini harcadı. Retry-After, bir sonraki UTC gece yarısına kalan saniyedir

403 ai-not-allowed bir makine kodudur çünkü istemci buna göre MUTLAKA dallanmalıdır; "bir operatör bir şeyi değiştirene kadar bu hesap burada hiçbir zaman başarılı olamayacak" anlamına gelir ve bu da "yarın tekrar gel" ifadesinden farklı bir iletidir. İki 429 birer cümledir çünkü dallanacak bir durum yoktur: bunları bir insan okur.

403 allowance-expired bir ayrı makine kodudur ve bu iki cümle aynı cümle olmadığı için ayrı tutulmuştur: "operatörün sana hiçbir zaman yapay zeka tanımlamadı" ile "süren doldu" ifadeleri farklı kelimeler ve farklı sonraki adımlar gerektirir. Bunları birbirine katan bir istemci, deneme süresi bitmiş birine zaten sahip olduğu bir kotayı yöneticiden istemesini söylerdi. Her iki ret de rezervasyondan önce gerçekleşir, böylece yanıt alamayan bir hesabın aleyhine işlenmiş bir kullanım satırı olmaz. Tarih "sonrasına ait değil" şeklinde karşılaştırılır: sınır anı izin vermek yerine reddeder. Süresi dolmuş bir hesapta eşitleme bundan etkilenmez (§5.15).

403 trial-scans-spent, üçüncü bir cümle için bir üçüncü makine kodudur: "ücretsiz taramalarını kullandın". Bir istemci bunun için "yöneticine danış" (ai-not-allowed) veya "süren doldu" (allowance-expired) uyarısını değil, plan teklifini gösterir. Koddan daha eski bir istemci bilinmeyen bir 403 okur, bu yüzden ikisinden birine dahil edilmek yerine ayrı tutulmuştur.

403 trial-expired, tarama denemesinin diğer sınırı için bir dördüncü durumudur: "ücretsiz günleriniz bitti". Süresi dolan ücretli veya tanımlanmış bir aralık olan allowance-expired değildir; birini diğeri olarak okuyan bir istemci, ödeme yapmış bir kişiye deneme süresinin bittiğini söyler. Her iki deneme reddi de endedBy, "scans" veya "days" taşır, böylece bir istemci tek bir alandan denemeyi hangi sınırın bitirdiğini belirtebilir:

json
{ "error": "trial-expired", "endedBy": "days" }

Uyumlu bir sunucunun koruması ZORUNLU olan Retlerin sırası: kimlik ve askıya alma; sağlık verisi izni (health-consent-required, §5.15.1); tanımlanan hak (yukarıdaki tablo: ai-not-allowed veya allowance-expired); gövde okunur ve ardından yetenek (capability-required, feature-header-invalid, aşağıda); gövdenin içeri taşıdığı şey (ai-request-too-large); X-Intake-Id biçimi; ardından, yalnızca tarama denemesi hakkı için (ücretsiz taramalar, olmayan kota tarihi ve ücretsiz hak yok), gün sınırı (trial-expired, yalnızca taramalar tükenmediğinde sorulur, böylece harcanan taramalar kendi kodunu korur) ve tarama talebi (intake-in-flight, trial-scans-spent). Gelecekteki bir tarih her ikisini de kaldırır: bu ücretli veya tanımlanmış bir aralıktır ve denemenin sınırları yalnızca hiçbir tarih olmadığında karar verir. Ücretsiz bir hak da her ikisini birden kaldırır. Ardından, aşağıda olduğu gibi, tarama denemesi hesaplarının tavanı, örnek tavanı ve günlük kota.

Yetenekler

Bir yetenek, bir tür yapay zeka isteği için scan veya recipes gibi kısa bir etikettir. Bir hesap bunların bir listesini tutar ve vekil sunucu, hesabın sahip olmadığı bir özelliğe sahip isteği reddeder. Servis, bir hesabın bir etiketi neden tuttuğunu bilmez: listeyi bir işletmeci yazar (§5.20) ve faturalandırma kimlik bilgisi de aynısını yapar; bu kimlik bilgisi diğer iki alanının ötesinde hiçbir yetki olmadan capabilities belirtebilir.

Bir etiket, küçük bir harf ve ardından en fazla 31 küçük harf, rakam veya kısa çizgiden oluşur (^[a-z][a-z0-9-]{0,31}$). Bir liste en fazla 32 tane tutar, tekilleştirilmiş ve sıralanmış olarak saklanır ve ayrılmış olan none değerini içeremez.

Hesabın kendi kaydının üç durumu vardır ve bunlar üç olgudur. null hiçbir kayıt olmamasıdır, bu yüzden örnek varsayılanı karar verir. [] hiçbir şey tanımlamayan bir kayıttır. Bir liste tam olarak bu etiketleri tanımlar. Geçerli değer kendi kaydıdır, yoksa örneğin DEFAULT_CAPABILITIES değeridir (instance.defaultCapabilities olarak yayımlanır, §5.6), yoksa null değeridir ve geçerli bir null hiçbir denetim olmaması demektir: her istek geçer ve hatalı biçimlendirilmiş bir başlık bile reddedilmez. Hiçbir şey yapılandırmayan bir örneğin her zaman sahip olduğu şey budur. Ayarlanmamış veya boş DEFAULT_CAPABILITIES değeri null olur. none kelimesi boş listedir, çünkü compose dosyaları ayarlanmamış bir değişkeni boş bir dize olarak iletir.

Bir isteğin özelliğini nasıl belirttiği.

  • X-Openplate-Feature: <label> istek başlığı. Bir etiket olmayan başlık 400 feature-header-invalid olur. Başlık istemcinin kendi ifadesidir, bu yüzden tek başına hiçbir şeyi korumaz.
  • Gövdenin yapılandırılmış çıktısı, response_format.json_schema.name. İşletmeci, CAPABILITY_SCHEMA_MAP (schemaName:label çiftleri) ile bir şema adını bir etikete bağlayabilir. Listelenen bir şemayı isteyen bir gövde, başlık ne derse desin etiketine ihtiyaç duyar; bu sayede başlıkta yalan söyleyen bir istemci hiçbir şey elde edemez. Her iki etiket de eksik olduğunda, bildirilen şemanın etiketidir.

Hiçbir özellik ve listelenen hiçbir şema belirtmeyen bir istek, denetimin karşılaştırabileceği hiçbir şey istemez ve geçer. Başlık göndermeyen bir istemciye karşı bir özelliğin uygulanabilir olmasını sağlayan şey şema eşlemesidir.

Ret, 403 {"error": "capability-required", "capability": "<label>"} durumudur. Kota ve gövdeden sonra ve günlük sayımdan, tarama talebinden, örnek tavanlarından ve sağlayıcıdan önce belirlenir: reddedilen bir istek hiçbir kullanım satırı yazmaz, tarama harcamaz ve yukarı akışa hiçbir şey göndermez. Bu yüzden bir özelliği eksik olan bir hesaba 403 denir ve asla 429 denmez; hiç yapay zekası olmayan bir hesaba bile ilk olarak ai-not-allowed denir. Bir istemci koda göre dallanır: bu "yetenekleri değişene kadar bu hesap burada başarılı olamayacaktır" demektir, "yarın tekrar gel" değil. Bir tarayıcı istemcisi başlığı kökenler arası gönderebilir: CORS izin verilenler listesindedir.

Tarama denemesi

Bir hesap, örneğin deneme süresi (instance.trial, §5.6) tarafından tanımlanan ücretsiz yapay zeka taramaları (AccountView.trialScans, §5.15) ve bir bitiş tarihi (AccountView.trialEndsAt) içerebilir: hangisi önce gelirse, o kadar tarama veya o kadar gün. Tarama, kişinin başlattığı tek bir yapay zeka eylemidir ve bir eylem birden fazla yukarı akış isteği olabilir: bir istemci, sağlayıcı reddinden sonra response_format olmadan bir kez yeniden deneyebilir. (Eski bir taşıyıcıdan sonraki bir yeniden deneme, herhangi bir talep olmadan önce taşıyıcı denetimi tarafından reddedilir, §4.1.) Bir tarama, teslim edilen bir yanıt satın alır.

X-Intake-Id, bir istemcinin hangi isteklerin tek bir eylem olduğunu bildirme yöntemidir. isteğe bağlı, 16 ila 64 karakter A-Z a-z 0-9 _ - (kısa çizgileri olan veya olmayan bir UUID uygundur), kişi eylemi başına yeni bir kimlik oluşturur, bu eylemin her yeniden denemesinde aynı kimlik kullanılır şeklindedir ve kişinin kendisinin yapılandırdığı bir sağlayıcıya asla gönderilmez, yalnızca bu vekil sunucuya gönderilir. Servis:

  • sınırın WHERE olduğu tek bir ifadede, yukarı akış çağrısından önce, daha önce görmediği bir kimlik için bir tarama talep eder; böylece üç tarama varken gelen on paralel istek üç tarama talep eder;
  • daha önceki isteği 409 intake-in-flight ile hâlâ işleniyor durumunda olan bir kimliğe sahip isteği, hiçbir şey harcamadan reddeder: tek bir kimlik üzerindeki çakışan istekler tek bir tarama için iki yanıt alır. Bir kimlik, isteği sonuçlandıktan sonra yeniden kullanılabilir. Başarısız bir istek taramasını geri vermiştir, bu yüzden yeniden deneme net bir maliyet olmadan onu tekrar talep eder; iletilmiş bir yanıttan sonraki bir istek yeni bir taramaya sahip yeni bir eylemdir ve hiç kalmadığında 403 trial-scans-spent ile reddedilir;
  • 30 dakikadan sonra hâlâ işlenmekte olan bir isteğe sonuçlanmadan çökmüş gibi davranır ve yeni bir taramaya gerek kalmadan bu kimlikteki bir sonraki isteğin onun taramasını devralmasına izin verir, böylece hiçbir kimlik bundan daha uzun süre kilitli kalmaz;
  • her geri vermeyi ve her teslimatı ait olduğu hak talebine bağlar, böylece geç başarısız olan bir istek, aynı kimlik üzerindeki daha yeni bir isteğin talep ettiği taramayı asla geri döndürmez;
  • tek bir yeni kimliğe sahip paralel istekleri sıraya koyar, böylece bunlardan tam olarak biri tarama talep eder ve diğerleri 409 intake-in-flight olur;
  • olmayan kimliğine sahip bir isteği kendi eylemi olarak değerlendirir, böylece hiçbir zaman kimlik göndermeyen bir istemci, tek istekli her eylem için doğru şekilde sayılır.

Kimlikler 24 saat boyunca saklanır ve ardından silinir (§9.2). Asla günlüğe kaydedilmezler.

yanıt alamayan istek taramasını geri verir alan bir istek: geri verme işlemi, iletilen bir 2xx hariç aşağıdaki tablonun her satırında ve talep sonrasındaki her rette (tavanlar ve günlük kota) çalışır. Bu, satır satır, kasıtlı olarak günlük birimden farklıdır:

SonuçGünlük birimTaramaTaramanın nerede ve neden farklılaştığı
Bağlantı reddedildi / üstbilgi zaman aşımıserbest bırakıldıserbest bırakıldı
Yukarı akış 4xxserbest bırakıldıserbest bırakıldı
Yukarı akış 5xxharcananserbest bırakıldıBirim faturayı korur: üretim çalışmış olabilir. Tarama ise başarısız bir denemenin hiçbir maliyet getirmeyeceği ve kişinin yanıt alamadığı vaadini korur. Güvenilmez bir sağlayıcıdaki yeniden deneme döngüsü yine de günlük birimle sınırlandırılır
Gövde zaman aşımı / akış sağlayıcı tarafından iptal edildiharcananserbest bırakıldıÜstbilgiler ulaştı, bu yüzden sağlayıcı faturalandırabilir; kişi yine de yanıt alamadı
Yukarı akışta 2xx, ardından arayan taraf bağlantıyı keserharcananharcananYanıt yoldaydı
Yukarı akış 2xxharcananharcanan
Talepten sonra bir tavan veya günlük kota isteği reddederalınmadı veya serbest bırakıldıserbest bırakıldıİstek kimseye ulaşmadı

Kurulum üst sınırı

Bir işletmeci, yukarıdaki kota ile aynı birimde olmak üzere tüm örnek için bir tavan belirleyebilir: tüm hesaplar genelinde birlikte UTC günü başına birim (AI_INSTANCE_DAILY_LIMIT). Ayarlanmamış olması hiç olmadığı anlamına gelir; bu da kendi sunucunda barındırılan bir örneğin ve mevcut her dağıtımın koruduğu durumdur.

Bunun var olma nedeni, buradaki diğer her sınırın hesap başına olmasıdır. Günde 200 istek hakkı olan on hesap, operatörün sağlayıcı anahtarına karşı günde 2000 istek demektir; dolayısıyla davetler sınırı katlamadan hesapları katlar.

Üst sınıra ulaşıldığında, kendi kotasını hiç harcamamış olanlar da dahil olmak üzere, bir sonraki UTC gününe kadar her hesap reddedilir. Bu ret, saniye cinsinden Retry-After ile birlikte 503 ai-instance-ceiling şeklindedir. Bir 429 veya 403 yerine 503 olmasının nedeni, durumun ne çağıranın hatası ne de çağıranın kotası olmasıdır: servis, operatörünün ödediği kapasiteyi tüketmiştir. İstemci buna göre MUTLAKA dallanmalıdır, çünkü "operatörün bugünkü kapasitesi tükendi" ekranı "bugünkü istek hakkın tükendi" ekranından farklıdır ve yalnızca ikincisi onu okuyan kişiyle ilgilidir.

Örneğin birimleri hesabınkinden önce alınır, böylece reddedilen bir örnek asla kimseye fatura çıkarmaz ve hesabın birimleri her iade edildiğinde onlar da iade edilir (aşağıdaki tablo satır satır her ikisi için de geçerlidir).

Üst sınır /health üzerinde okumaz yayımlanır: bu operatörün bütçesidir ve bu el sıkışma kimlik doğrulamasızdır. GET /v1/admin/stats bunu, sınırlandırdığı aiRequestsToday yanında aiInstanceDailyLimit olarak bildirir.

Tarama denemesi hesaplarının kendilerine ait bir tavanı olabilir (AI_TRIAL_INSTANCE_DAILY_LIMIT): tarama kapısının geçerli olduğu tüm hesaplar genelinde UTC günü başına birim. Bu hesapları ve yalnızca bu hesapları aynı 503 ai-instance-ceiling ile reddeder. Ardından diğer tüm hesapları sınırlandıran Ayarlandığı takdirde, bir tarama denemesi isteği yalnızca buna sayılır ve asla AI_INSTANCE_DAILY_LIMIT için sayılmaz, böylece deneme trafiği ücretli hesapların ihtiyaç duyduğu kapasiteyi asla tüketemez. Bir günde ulaşılabilecek sağlayıcı faturası bu ikisinin toplamıdır. Ayarlanmadığı durumlarda tarama denemesi istekleri, diğer herkesinki gibi örnek tavanına sayılır. Ayrıca yayınlanmaz; GET /v1/admin/stats bunu signup.trialRequestsToday yanında aiTrialInstanceDailyLimit olarak bildirir.

Deneme tavanının belirlendiği yerlerde, çağıran tek bir ağ bundan bir pay alır (AI_TRIAL_NETWORK_DAILY_LIMIT, varsayılan olarak deneme tavanının onda biri, aşağı yuvarlanır, en az 1): tek bir ağdan gelen tarama denemesi isteklerinin UTC günü başına harcayabileceği birim miktarı. Bir ağ, oturum açma sınırlamalarının saydığı gibi, bir IPv6 /64 veya bir IPv4 adresidir. Payını tüketmiş bir ağdan gelen tarama denemesi isteği, aynı Retry-After ile aynı 503 ai-instance-ceiling yanıtını alır, bu sayede istemcinin yeni bir mantık dalına ihtiyacı kalmaz; hiçbir tarama ve birim harcanmaz ve sağlayıcı çağrılmaz. Ücretli bir zaman aralığı veya kalıcı bir ücretsiz hak kapsamındaki istekler hiçbir zaman bunun tarafından sayılmaz veya reddedilmez. Birimleri, deneme tavanının birimleri iade edildiğinde geri verilir. Tek bir IPv4 operatör NAT'ı arkasındaki birçok kişi tek bir sepeti paylaşır; bir IPv6 çağıranının ise kendine ait bir /64 bloğu vardır. Servis bunun için hiçbir adres saklamaz: gün ve ağ başına tek bir satır, ağın ve günün anahtarlı bir özetini (TRIAL_ADDRESS_PEPPER altında HMAC-SHA256) tutar ve satır ertesi gün silinir.

Neyin harcandığı ve neyin geri verildiği

Bir birim, yukarı akış çağrısından önceden rezerve edilir, asla çağrıdan sonra sayılmaz. Sonradan saymak, N adet paralel isteğin hepsinin eski sayımı okuduğu ve hepsinin geçtiği bir aralık yaratır; hata durumunda yeniden deneyen bir istemci de tam olarak bu istekleri bir arada tetikleyen istemcidir.

SonuçBirimNeden
Bağlantı reddedildi / DNS hatasıserbest bırakıldıİstek bu ana makineden hiç çıkmadı
Başlık zaman aşımı (henüz bayt yok)serbest bırakıldıBize hiçbir şey sunulmadı; sağlayıcı yanıt vermeden önce kendi sınırımız pes etti
Yukarı akış 4xxserbest bırakıldıSağlayıcı isteği REDDETTİ. Hiçbir modele ulaşmadı, bu yüzden kimse faturalandırmadı ve işletmecinin kendi yapılandırma hatası yüzünden hesaptan ücret almak, bozuk bir vekil sunucunun bir kuruluşun tüm kotasını bir dakikada tüketmesine yol açardı
Yukarı akış 5xxharcananSağlayıcı isteği kabul etti ve yanıt verirken başarısız oldu. Üretim çalışmış olabilir. Burada serbest bırakmak, tam da aksayan sağlayıcıya karşı ücretsiz ve sonsuz bir yeniden deneme döngüsüne yol açar
Gövde zaman aşımı / akış iptal edildiharcananBaşlıklar zaten ulaştı, yani sağlayıcı isteği çalıştırdı. Yanıtı okuyamamış olmamız bizim sorunumuzdur, bir para iadesi değildir
Yukarı akış 2xxharcananAçıkça

Hizmet yalnızca UTC günü başına hesap başına bir tam sayı kaydeder ve başka hiçbir şeyi kaydetmez: istem yok, yanıt yok, model adı yok, günden daha ayrıntılı zaman damgası yok (§9.2).

5.20 Yönetici API'si: /v1/admin

İstemci yüzeyi değil, işletmeci yüzeyi. Bir openplate istemcisi, yalnızca oturum açmış hesap bir yönetici olduğunda bu uç noktalardan tam olarak birini kullanır: uygulamanın /admin üzerinde oluşturduğu konsol. Alternatif bir istemci bu bölümü tamamen yok sayabilir.

Ona iki kimlik bilgisi ulaşır ve her ikisi de sıradan bir Authorization: Bearer olarak gelir:

  1. Tüm hesaplar kilitlendiğinde bile çalışmaya devam eden Statik işletmeci belirteci (ADMIN_TOKEN).
  2. Kendi erişim belirtecini kullanan role değeri admin olan bir hesap. Konsolu bir kabuk yerine uygulamanın içine yerleştiren şey budur.
  3. Kapsamı sınırlandırılmış bir hizmet belirteci (BILLING_TOKEN). İlkinin ikinci bir kopyası değil, ÜÇÜNCÜ bir öznedir: üç rotaya ve üç alana ulaşır ve diğer her yerde reddedilir. Aşağıdaki "Faturalandırma öznesi" bölümüne bak.

yok ne yapılandırılmış ne de eşleşiyorken, tüm alt ağaç bilinmeyen herhangi bir yolun herkese verdiği 404 yanıtının aynısını döndürür. İki belirteci de yapılandırmamış bir örnek, bu özellik var olmadan önce oluşturulmuş bir örnekten ayırt edilemez. Oradaki bir 401, bir kimlik bilgisinin var olduğunu ve yalnızca kilitlendiğini ele verirdi. Belirteçlerden birini ayarlamak, bu 404 yanıtını yanlış bir değerin alacağı 401 yanıtına dönüştürür.

Uç noktaYapar
GET /v1/admin/statsToplam sayımlar: hesaplar, bloblar, baytlar, anahtar kayıtları, pendingInvites, admins, aiRequestsToday ve bunu sınırlayan aiInstanceDailyLimit (tavan olmaması durumunda null); aiTrialInstanceDailyLimit; ve signup: §5.8.3'teki istek kapısının bugün ve son yedi günde bastığı davetler, son yedi günde tanımlanan denemeler ve bugünün tarama denemesi istekleri
GET /v1/admin/ai/budgetSağlayıcı anahtarının bütçesi ve bugünün yapay zeka kapasitesi, aşağıdaki "Yapay zeka bütçesi" bölümüne bak. Yapay zekası olmayan bir örnekte 404. BILLING_TOKEN ile erişilemez
GET /v1/admin/accountsBir sayfa dolusu AccountView ve total
GET /v1/admin/accounts/expiringKotası gelecekte sona eren hesaplar için bir sayfa dolusu { id, allowanceExpiresAt } ve total
GET /v1/admin/accounts/:idBir AccountView
GET /v1/admin/accounts/:id/activitySon oturum açma ve sınırlı bir pencere boyunca UTC günü başına bir girdi
GET /v1/admin/activityListedeki sırayla, koca bir SAYFA dolusu hesap için aynı gün be gün şerit
PATCH /v1/admin/accounts/:idrole, dailyAiLimit, allowanceExpiresAt (bir ISO anı veya temizlemek için null), freeDailyAiLimit (kalıcı ücretsiz hibe, 0 ile 10000 arasında bir tam sayı; BILLING_TOKEN ile yazılamaz), capabilities (hesabın kendi yetenek listesi, bir etiket dizisi, hiçbir şey vermeyen bir kayıt için [] veya kaydı kaldırıp örnek varsayılanının belirlemesi için null; BILLING_TOKEN ile yazılabilir, §5.19), trialScans (verilen ücretsiz taramalar, 0 ile 100 arasında bir tam sayı veya tarama denemesini geri almak için null; kaç tanesinin kullanıldığına asla dokunmaz), suspended, displayName, label (işletmecinin notu, aşağıya bak veya temizlemek için null). En az biri zorunludur
POST /v1/admin/accounts/:id/reset-mailİşleticinin inisiyatifiyle §5.12 sıfırlamasını başlatır
DELETE /v1/admin/accounts/:idHesabı ve ona bağlı her şeyi siler
GET /v1/admin/accounts/:id/blob/versionsTutulan her blob sürümü: numara, zarf sürümü, bayt sayısı, zaman ve varsa sabitleme. Asla şifreli metin değil
POST /v1/admin/accounts/:id/blob/rollback{"targetVersion": n}. Üstündeki her sürümü SİLEREK o sürümü yeniden geçerli yapar (§5.1 küçülme koruması, ADR-0009). Bilinmeyen bir sürümü, geçerli sürümü, bu derlemenin kabul etmediği bir zarf sürümünü ve sıfır baytlık bir satırı reddeder. Yeniden yükleme yerine bir geri alma işlemidir, çünkü §3.2 AAD'si blobVersion bağlar: eski baytları yeni bir sürüm gibi yeniden eklemek, hiçbir istemcinin şifresini çözemeyeceği bir şey üretir
GET /v1/admin/invitesBekleyen davetlerin bir sayfası, artı total
POST /v1/admin/invitesBir tane üretir (§5.8). Belirteç bir kez döndürülür. "trial": true, bir kota yerine örneğin tarama denemesini yazar: hiçbirini çalıştırmayan bir örnekte 400 ve bir dailyAiLimit yanında 400. Alan olmadan üretilenin dailyAiLimit değeri, kullanım sırasında hesabın kalıcı ücretsiz hibesi (freeDailyAiLimit) haline gelir
POST /v1/admin/trials/grant-lapsed{"trialDays": n, "apply": false, "excludeAccountIds": []}. trialDays günlük denemesi sona eren ve hiçbir zaman taşınmayan her üyenin listesini çıkarır veya apply: true ile örneğin tarama denemesini tanımlar: kota tarihi, yalnızca bir ödemenin veya bir operatörün değiştirebileceği şekilde, milisaniyesine kadar kullanım artı trialDays değerine eşit kalır. Tarihi temizler ve denemenin günlük sınırını belirler. Eşgüçlüdür: hak tanımlanmış bir hesap bir daha asla listelenmez. {"accountIds": [...], "applied": bool} yanıtını verir
POST /v1/admin/invites/:id/resendAYNI satırda YENİ bir belirteç ve yeni bir son kullanma tarihi
DELETE /v1/admin/invites/:idBekleyen bir daveti geri çeker
PATCH /v1/admin/settings`{"nutrientReferenceBasis": "dge" \"efsa" \"us"}. The instance-wide reference basis (§5.6). Required; anything else is 400 and NOTHING is written. Answers {"settings": {...}}`, örneğin şu anda tuttuklarıyla birlikte
GET /v1/admin/feedbackBildirilen tahminlerin bulunduğu bir sayfa (§5.25), en yeniden başlar: her biri { id, accountId, hasImage, consentWordingVersion, createdAt }, artı total, limit ve offset. Sayısal veriler ve fotoğraf içermez
GET /v1/admin/feedback/:idTek bir bildirim: liste alanları, cihazın gönderdiği haliyle birebir measurements ve consent: { agreedAt, wordingVersion }
GET /v1/admin/feedback/:id/imageFotoğrafın saklanan Content-Type altındaki baytları, Cache-Control: no-store ve X-Content-Type-Options: nosniff ile birlikte. Bildirimde fotoğraf olmadığında 404. Her okuma işlemi, bildirim kimliği ve hangi kimlik bilgisinin istediğiyle birlikte günlüğe kaydedilir
DELETE /v1/admin/feedback/:idÖnce fotoğrafı, ardından bildirimi siler. Bilinmeyen bir kimlik için 204 veya 404

GET /v1/admin/ai/budget, işletmecinin yapay zeka bütçesidir: sağlayıcı anahtarında ne kadar kaldığı ve bugünün örnek kapasitesinin ne kadarının kullanıldığı.

json
{
  "day": "2026-09-30",
  "capacity": {
    "paid": { "used": 412, "limit": 2000 },
    "trial": { "used": 37, "limit": 500 }
  },
  "upstream": {
    "status": "ok",
    "limitUsd": 5,
    "remainingUsd": 3.94,
    "reset": "monthly",
    "usageDailyUsd": 0.12,
    "usageWeeklyUsd": 0.4,
    "usageMonthlyUsd": 1.06,
    "checkedAt": "2026-09-30T10:00:00.000Z"
  }
}
  • day, tavanların sayıldığı UTC günüdür. capacity, vekil sunucunun ayırdığı boyut ağırlıklı sayımlar olan birim cinsindendir. paid.used, AI_INSTANCE_DAILY_LIMIT için sayılan miktardır ve trial.used, tarama denemesi hesaplarının harcadığı miktardır. Her limit, yapılandırılmış tavandır veya hiç yoksa null değeridir. Bir deneme tavanı olmadığında deneme istekleri de paid.used içinde sayılır.
  • Kaynak OpenRouter olmadığında upstream, null olur. Aksi takdirde, OpenRouter'ın GET /key değerinin dolar cinsinden anahtar okumasıdır: sınırı olmayan bir anahtar için limitUsd ve remainingUsd, null olur ve reset, asla sıfırlanmayan bir sınır için "daily", "weekly", "monthly" veya null olur. Başarısız bir okuma {"status": "unavailable", "checkedAt": ...} olur ve capacity yine de bildirilir.
  • Anahtar okuma sunucuda 5 saniyelik zaman aşımıyla çalışır ve bellekten 60 saniye boyunca sunulur, başarısız olan ise 15 saniye sunulur. Gövde hiçbir anahtar, hiçbir anahtar etiketi ve sağlayıcının gönderdiği başka hiçbir şeyi taşımaz.
  • Aynı okumada remainingUsd, limitUsd değerinin AI_BUDGET_ALERT_FRACTION (varsayılan 0.2) oranının altına düştüğünde, işletmeci sıfırlama dönemi başına MAIL_OPERATOR_EMAIL adresine bir e-posta alır. Servis ayrıca anahtarı her 15 dakikada bir okur, böylece e-posta birinin konsolu açmasını beklemez.

PATCH, bir işleticinin sahip olduğu tek kimlik doğrulama bağlantılı yazma işlemidir ve kasıtlı olarak sınırlandırılmıştır. Bir parola belirleyemez ve bunu yapabilecek bir uç nokta da yoktur: parola istemcideki veri anahtarını sarmalar, bu yüzden sunucu tarafında bir kimlik bilgisi değişikliği, oturum açan fakat hiçbir şeyin şifresini çözemeyen bir hesap yaratır. Bir hesabın email değerini değiştiremez, çünkü adres davetin doğruladığı şeydir. Kurtarma kodu yazdıramaz.

Askıya alma, aynı işlem kapsamında her oturumu iptal eder. Tek başına bir suspended_at, birinin cebindeki telefonun bir çeyrek saat daha eşitlemeye devam etmesine neden olur; işleticinin bu kelimeden kastı bu değildir. Yeniden etkinleştirmek hiçbir oturumu geri getirmez; kişi yeniden oturum açar.

Bir yönetici HESABI kendini askıya alamaz, yetkisini düşüremez veya kendini silemez: {"error": "self-change"} ile 400. Bunu yapan tek yöneticili bir kuruluş, herkesi bu dizinin dışına kilitlemiştir ve tek çare konteynerde bir kabuk açmaktır. Statik belirteç muaftır, çünkü kendi benliği yoktur ve tam olarak bu durum için var olan kimlik bilgisidir.

label, bir hesap üzerinde işletmecinin kendi notudur, örneğin "Beta supporter" veya hiçbiri için null. GET /v1/admin/accounts ve GET /v1/admin/accounts/:id içindeki her hesap anahtarı taşır.

  • {"label": "Beta supporter"} ile PATCH bunu ayarlar ve {"label": null} temizler. Değer kırpılır ve kırpıldığında boş kalan bir dize de onu temizler, böylece boş bir etiket asla saklanmaz.
  • Unicode kod noktaları olarak sayılan en fazla 40 karakter, Postgres char_length fonksiyonunun saydığı birimdir. Daha uzun bir etiket, satır sonu, sekme veya başka herhangi bir denetim karakteri içeren bir etiket ve bir dize ya da null olmayan herhangi bir şey 400 olur ve gövdedeki hiçbir şey yazılmaz. Sütundaki bir denetim kısıtlaması (check constraint) aynı sınırı zorunlu kılar, böylece doğrudan yazan bir araç da bu sınıra uyar.
  • Bir işletmeci verisidir, asla bir yetkilendirme girdisi değildir. Hiçbir rota herhangi bir şeye karar vermek için bunu okumaz. Hesabın kendi GET /v1/auth/account bunu taşımaz, hesap bunu ayarlayamaz (PATCH /v1/auth/account yalnızca displayName okur) ve faturalandırma sorumlusu bunu ne okuyabilir ne de yazabilir.
  • pnpm core-api accounts set-label <id> "Beta supporter" bunu ayarlar ve pnpm core-api accounts clear-label <id> temizler.

GET /v1/admin/accounts/:id/activity, bir işleticinin konsolu açarken aklındaki soruyu yanıtlar: bu kişi örneği hâlâ kullanıyor mu. Servisin zaten depoladığı veriyi okur ve yeni hiçbir şey toplamaz.

json
{
  "accountId": 7,
  "lastSeenAt": "2026-09-06T18:30:00.000Z",
  "window": { "days": 90, "fromDay": "2026-06-10", "toDay": "2026-09-07" },
  "days": [
    { "day": "2026-06-10", "count": 0 },
    { "day": "2026-06-11", "count": 3 }
  ]
}
  • lastSeenAt, hiç oturum açmamış bir hesap için null değerindedir; yalnızca bir oturum açma ve bir vekil tamamlama işlemi tarafından yazılır, asla bir belirteç yenileme veya eşitleme yoklaması (§9.2) ile yazılmaz. Ağdan bir zaman damgası olarak geçer; göreli bir ifade bir görselleştirme kararıdır ve istemciye aittir.
  • days, aralıktaki tüm günü, sırayla taşır; satırı olmayan bir gün için count: 0 içerir. Eksik bir gün ile sessiz bir gün, şeridi okuyan kişiye aynı görünmemelidir.
  • ?days=N aralığı daraltır. N en az 1 olan bir tamsayı olmalıdır, aksi halde yanıt 400 olur. 90 günden uzun bir aralığa 90 ile yanıt verilir ve window gerçekte neyin çizildiğini bildirir. Doksan, aşağıdaki saklama aralığıdır, bu yüzden daha uzun bir şerit yalnızca silinmiş satırlar için sıfırlardan ibaret olabilir.
  • Bilinmeyen bir kimlik, diğer tüm hesap yollarıyla aynı 404 sonucunu verir ve tüm ağaç yukarıdaki kimlik bilgilerinin arkasındadır.

GET /v1/admin/activity, koca bir sayfa için aynı soruyu tek seferde yanıtlar, çünkü bir kişi listesi her satırın yanına bir şerit çizer ve satır başına bir kez sormak N+1 problemidir.

json
{
  "window": { "days": 7, "fromDay": "2026-09-02", "toDay": "2026-09-08" },
  "accounts": [{ "accountId": 2, "days": [{ "day": "2026-09-02", "count": 0 }] }],
  "total": 4
}
  • ?limit= ve ?offset=, GET /v1/admin/accounts üzerinde çalıştıkları gibi tam olarak davranır: aynı varsayılanlar, aynı üst sınır, aynı cümleyle aynı 400. Bu bir tesadüf değil, sözleşmedir: çağıran taraf iki uç noktayı birbiriyle uyumlu şekilde sayfalar ve n kişisinin yanına n şeridini çizer, bu nedenle buradaki accounts, o listenin aynı sayfa için döndürdüğü sıradadır ve total de o listenin total değeridir.
  • ?days=N, yukarıdaki uç noktanın aynı şekilde kırpılmış aralığıdır: en az 1 olan bir tamsayı veya bir 400, 90'dan fazlasına 90 ile yanıt verilir ve window neyin çizildiğini bildirir.
  • days değeri sıfırlardan oluşan bir dizi olan ve hiç istek yapmamış hesaplar da dahil olmak üzere Sayfadaki her hesap görünür. Bir hesabın atlanması, "bu kişi hiçbir şey yapmadı" ile "bu kişi yanıtta yoktu" ifadelerini aynı olgu haline getirirdi; bu da bir üst seviyedeki günlük sıfırla doldurmanın önlemek için var olduğu hatadır.
  • Her girdi accountId ve days içerir, başka bir şey içermez. Adres, ad ve kota, çağıranın zaten okumakta olduğu GET /v1/admin/accounts kaynağına aittir.

Saklama: kullanım sayaçları 90 gün boyunca saklanır. ai_usage_days, UTC günü başına her hesap için bir tamsayı tutar (§9.2). Hizmet içindeki saatlik bir süpürme işlemi, bir operatör eylemine veya cron girdisine gerek kalmadan, her örnekte bugün de sayılarak 90 günden eski her satırı siler. Bir hesabın silinmesi, ON DELETE CASCADE aracılığıyla, silme işleminin geri kalanıyla aynı ifadede sayaçlarını ve lastSeenAt bilgisini kaldırır. Doksan, tek bir yerdeki tek bir sayıdır: süpürmenin temizlik yaptığı sınırdır ve yukarıdaki uç noktanın yanıtlayabileceği en uzun zaman aralığıdır.

Faturalandırma sorumlusu (BILLING_TOKEN). Bir ödeme servisinin tek bir hesapta iki sayıyı ve bir listeyi değiştirmesi gerekir: bir kotanın bitişi, satın aldığı günlük yapay zeka isteklerinin sayısı ve etkinleştirdiği yapay zeka özelliklerinin etiketleri. Servise işletmeci belirtecini vermek, ona örnekteki her adresi, silme düğmesini ve bildirilen fotoğrafları verirdi; bu nedenle kimlik bilgisi kapıda sınırlandırılır. İsteğe bağlıdır, varsayılan olarak ayarlanmamıştır ve işletmeci belirteciyle aynı olan 24 karakterlik alt sınırı taşır.

Uç noktaFaturalandırma sorumlusu şunları yapabilir
GET /v1/admin/accounts/expiringBitiş tarihi gelecekte olan hesaplar için { id, allowanceExpiresAt } oku, buradaki diğer tüm sayfalanmış uç noktalarla aynı limit, offset ve 400 cümlesiyle sayfalanır
GET /v1/admin/accounts/:idO tek hesap için { id, allowanceExpiresAt, dailyAiLimit, capabilities } oku; burada capabilities hesabın kendi kaydıdır (null kayıt olmamasıdır)
PATCH /v1/admin/accounts/:idallowanceExpiresAt, dailyAiLimit ve capabilities yaz, başka hiçbir şey yazma. trialScans diğer tüm alanlar gibi reddedilir: bir kota için ödeme yapan bir kimlik bilgisi ücretsiz taramalar dağıtmaz
  • Dört geri bildirim rotası ve bu metin yazıldıktan sonra eklenen tüm rotalar da dahil olmak üzere Bu bölümdeki diğer her rota 403 isteğine {"error": "service-scope"} ile yanıt verir. Reddetme işlemi bağlama noktasında, hiçbir işleyici çalışmadan ve hiçbir satır okunmadan önce gerçekleşir, bu nedenle bir hesabın var olup olmadığını anlama yolu değildir.
  • Yanındaki izin verilen alanlar bile değil, Başka bir alanı belirten bir PATCH gövdesi {"error": "service-scope-field"} ile 403 olur ve hiçbir şey yazılmaz. Sessizce göz ardı etmek, faturalandırma hizmetindeki bir hatanın başarılı gibi algılanmasına yol açardı.
  • Değerlerin kapsamı da belirlenmiştir. allowanceExpiresAt: null (sonu olmayan bir kota) ve örneğin BILLING_MAX_DAILY_AI_LIMIT değerinin (varsayılan 1000) üzerindeki bir dailyAiLimit, {"error": "service-scope-value"} ile 403 hatası verir ve hiçbir şey yazılmaz. İşletmecinin kimlik bilgileri her ikisini de yazabilir. capabilities geçerli herhangi bir listeyi kabul eder; ayrıca kaydı kaldıran ve bu sayede işletmecinin seçtiği örnek varsayılanından fazlasını asla vermeyen null değerini de kabul eder. Hatalı biçimlendirilmiş bir liste, bir işletmecide olduğu gibi bu kimlik bilgisinde de sıradan 400 hatasıdır.
  • Bunun ötesinde dailyAiLimit, bir işletmeci için olduğu gibi doğrulanır. Kimlik bilgisi hiçbir doğrulamayı esnetmez.
  • Bu iki okuma birer izdüşümdür ve asla bir AccountView değildir. Adres yok, görünen ad yok, rol yok, askıya alma yok, kullanım yok, blob yok. `GET

/v1/admin/accounts/expiring`, sonradan bir satırı filtrelemek yerine sorguda iki sütun seçer.

  • Silinmiş bir hesap ile bilinmeyen bir kimlik aynı 404 anlamına gelir. Buradaki silme işlemi bir işaretçi değil ardışık bir silme işlemidir (§9), bu nedenle onları birbirinden ayırt edecek hiçbir şey kalmaz; bu rotanın bildirebileceği bir deletedAt, onları kaldıran silme işleminden sonra saklanan bir kişiye ait kayıt olurdu. Her ikisi de "ücretlendirmeyi durdur" anlamına gelir.
  • Sorumlunun bir kendisi yoktur, bu nedenle yukarıdaki kendi kendini değiştirme kuralı ona uygulanamaz: kendisi de dahil olmak üzere hiç kimseyi askıya alamaz, rütbesini düşüremez veya silemez, çünkü bu rotaların hiçbirine ulaşılamaz.

AccountView, hesabın kendi GET /v1/auth/account uç noktasının döndürdüğüyle aynı yapıdadır (§5.15), invitesLeft dahildir ve aynı şekilde hesaplanır, ayrıca aiUsedToday ve yönetici yüzeyinde ek olarak lastSeenAt, label, blob ve keyRecordKinds bulunur. Hesabın kendi görünümünün geçerli değeri bildirdiği yerde Yönetici yüzeyindeki capabilities ve freeDailyAiLimit, hesabın KENDİ kaydıdır (capabilities için, null kayıt olmamasıdır). healthConsent de bunun üzerindedir ve burada salt okunur şeklindedir: PATCH /v1/admin/accounts/:id bunu okumaz, çünkü bir işletmecinin birisi adına verebileceği bir onay hiçbir şeyi kanıtlamaz (§5.15.1). doğrulayıcı yok, KDF tanımlayıcısı yok, emanet yok ve şifreli metin yok taşır. Bir blob, bir bayt sayısı ve bir zaman damgası olarak bildirilir. Gerekçe docs/adr/0001-an-admin-api-for-a-zero-knowledge-service.md belgesidir; ADR-0005 belgesi bu belgenin 1, 2, 3, 5 ve 8 numaralı yasaklarının yerine geçer, yanıtlardaki gizli bilgiler yasağının ise yerine geçmez.

5.21 POST /v1/auth/invites: bir üye birini davet eder

Bearer, her deneme sayılır ile kaynak adres başına kısıtlanır. Yalnızca dağıtım hem MEMBER_INVITE_DAILY_AI_LIMIT hem de MEMBER_INVITE_ALLOWANCE_DAYS belirlediğinde veya bunun yerine örneğin tarama denemesinin yanında MEMBER_INVITE_TRIAL belirlediğinde mevcuttur; ikisi de yoksa bu yol, oturum açmış olsun ya da olmasın her arayana sıradan bilinmeyen yol yanıtı olan 404 verir ve instance.memberInvites, false olur (§5.6).

İstek: {"email": "boris@example.org"}, başka hiçbir şey yok.

json
{}

→ bu gövdeyle 202, boş ve sabit.

Koşullar örneğe aittir, asla çağıran kişiye ait değildir. Davet edilen hesap role: "member", yönetici basımının varsayılan olarak kullandığı davet ömrünü ve iki haktan YALNIZCA birini alır, asla ikisini birden değil: gün çifti altında, MEMBER_INVITE_DAILY_AI_LIMIT üzerinden dailyAiLimit ve kayıt sırasında yazılan kullanım artı MEMBER_INVITE_ALLOWANCE_DAYS şeklinde bir allowanceExpiresAt; MEMBER_INVITE_TRIAL altında, tarihsiz olarak örneğin tarama denemesi (§5.19). Gün çifti altında basılan ve örnek geçiş yaptıktan sonra kullanılan bir üye daveti, tarih değil tarama denemesi alır. Gövdedeki bir dailyAiLimit, role veya expiresInDays reddedilmez, yalnızca okunmaz. Bu üçü, bir üye ile bir operatör arasındaki farkı oluşturan POST /v1/admin/invites (§5.20) üzerindeki gövde alanlarıdır.

Yanıt, adres hakkında neyin doğru olduğuna göre DEĞİŞMEMELİDİR. Yeni bir adres, bekleyen bir daveti zaten tutan bir adres ve bir hesabı zaten tutan bir adres, tek bir gövdeye sahip tek bir 202 durumundadır. Bu, §5.7 ve §5.12'deki numaralandırma önleme özelliğinin, bir üyenin başka birinin posta kutusunu işaret ettiği tek uç noktaya uygulanmış halidir: İş arkadaşının adresini yazan bir kişi, iş arkadaşının zaten burada olduğunu bir durum kodundan, gövdeden veya başlıktan öğrenmemelidir. Yönetici üretiminin 409 {"error":"an account already exists for this email"} değeri bundan muaftır, bunun tek nedeni de işletmecinin kendi kimlik bilgisinin arkasında yer almasıdır.

Adres zaten bir hesap tuttuğunda, servis davet yerine o kişiye kısa bir not postalar. Hiçbir bağlantı taşımaz: Bir katılma bağlantısı hesabı olan biri için ikinci bir hesap üretir, bir sıfırlama bağlantısı ise kimsenin istemediği bir parola sıfırlama olurdu. Bu mektup olmasaydı davet sessizce kaybolur ve iki kişi de bunu beklerdi.

Üye kaynaklı bir daveti daha önce kullanmış bir adres ikincisini alamaz ve arayana yine de 202 bildirilir. Kanıt hesaptan daha uzun yaşar: hesaplardan biri silindiğinde davet satırı adresini ve kullanım anını korur, bu nedenle kendi kendini silmenin ardından bir arkadaşın yeniden davet etmesi yeni bir kota sağlamaz. Tarama denemesi çalıştıran bir örnekte silme işlemi bunun yerine adresi satırdan kaldırır ve §5.15'teki anahtarlı karmayı saklar, kural da bu karmayı okur. Bir operatörün basımı üye kaynaklı bir davet değildir ve bu kural tarafından asla alıkonmaz.

Başka bir kapıdan gelen bekleyen bir davete dokunulmaz ve arayana yine de 202 bildirilir. Bir üretim işlemi adresteki bekleyen davetin yerine geçer, dolayısıyla bu kural olmadan bir üye bir işletmecinin, §5.8.3'teki istek kapısının veya başka bir üyenin az önce gönderdiği mektubu geri çekebilir ve yerine kendi kapısının koşullarını koyabilirdi. Hiçbir satır yazılmaz ve mektup gönderilmez. Bir üye, daha önce olduğu gibi eskisinin yerine geçecek şekilde kendi bekleyen davetini yeniden GÖNDEREBİLİR. Adlandırılmış bir ret arayana bu kişiyi zaten başka birinin davet ettiğini söyleyeceğinden, ret işlemi adlandırılmak yerine sessizdir. Bu rotayı kullanan bir yönetici, yönetici üretimindeki gibi bundan muaftır.

aynı işlemde Bir hesap silindiğinde, gönderdiği ve hâlâ beklemede olan davetler geri çekilir (§5.15). Kullanılmış ve süresi dolmuş olanlar olduğu gibi saklanır ve yine kimseye karşı sayılmaz: davet eden kişi gitmiştir.

Kullanım ömrü sınırı, satır olarak sayılmak üzere hesap başına toplam beştir. Geri çekilen ve süresi dolan davetler sayılır: Sınır, kaç mektubun işe yaradığına değil, bir hesabın kaç mektuba neden olduğuna ilişkindir. Bunu aşmak 403 {"error":"member-invite-cap-reached"} sonucunu verir ve bu, bu uç noktanın arayanın kendi hesabı hakkında söylediği tek şeydir; bu durum kendileri hakkındaki bir gerçektir, başkası hakkında değil. Bir yönetici bu yolda ve yönetici yolunda muaftır, invitesLeft: null ifadesinin anlamı da budur (§5.15).

Kimsenin ödeme yapmadığı bir tarama denemesi kimseyi davet etmez. MEMBER_INVITE_TRIAL altındaki her üye daveti yeni bir tarama denemesidir, bu yüzden davet edebilen ücretsiz bir hesap daha fazla ücretsiz hesap üretirdi. trialScans taşıyan ve gelecekte bir allowanceExpiresAt tarihi bulunmayan bir hesap 403 {"error":"invites-need-a-plan"} yanıtı verir, satır yazmaz ve mektup göndermez. İleri bir tarih, onu kimin yazdığına bakılmaksızın rotayı açar: ödeme sırasında faturalandırıcı veya bir operatör. Önce yaşam boyu üst sınır sorgulanır, bu nedenle kotasını tüketmiş bir hesap member-invite-cap-reached yanıtı alır, çünkü ödeme yapmak hesaba fayda sağlamaz. Yönetici burada da muaftır ve yönetici üretimine (§5.20) dokunulmaz. Kişi denemeden önce hesap görünümündeki (§5.15) invitesNeedAPlan da aynı şeyi söyler.

202, yönetici üretiminin aksine belirteç yok ve bağlantı yok da taşır. Arayan kişi işletmeci değildir ve hesap oluşturan bir yetkiyi elinde tutmamalıdır.

5.22 /v1/plans/*: Bir faturalandırıcıya doğrudan geçiş

Yalnızca işletmeci bir faturalandırıcı yapılandırdığında mevcuttur. Biri olmadan alt ağacın tamamı, kimlik bilgisine sahip olsun ya da olmasın herkese olağan bilinmeyen yol 404 yanıtını verir ve el sıkışmada (§5.6) instance.plans değeri false olur. Bu protokolün bir uygulaması alt ağacı tamamen atlayabilir; bir istemci, yolu yoklamak yerine bir plan kapısı sunmadan önce instance.plans değerini okumalıdır.

Bu ön ekin arkasındaki hiçbir şey bu protokolün parçası değildir. Yollar, istek gövdeleri ve yanıt gövdeleri, kendi sürüm döngüsüne sahip ayrı bir servis olan faturalandırıcıya aittir. Bu belge yalnızca ağ geçidinin oraya giden bir isteğe ve geri dönen bir yanıta ne yaptığını belirtir. Bu kasıtlıdır: Alternatifi, başkasının KDV takvimine göre sürekli değişen, kendi sunucusunu barındıranların kullanamayacağı kural koyucu bir belgedir.

Hesabın normal erişim belirteci ile kimlik doğrulaması yapılmıştır (§4.1). Anonim bir çağıran normal 401 alır. Tek istisna aşağıda yer alan GET /v1/plans/prices değeridir: bir yol ve bir yöntem, alt ağaçta başka hiçbir şey yok.

POST /v1/plans/order
Authorization: Bearer <accessToken>
Content-Type: application/json

{ "plan": "…", "locale": "…", "consentVersion": "…", "consents": { … } }

Örnek açıklama amaçlıdır: faturalandırıcının yolları kendisine aittir. openplate'in faturalandırıcısı GET /v1/plans/prices, GET /v1/plans/offer, POST /v1/plans/order, GET /v1/plans/me, portal yolunu ve POST /v1/plans/pending-change/cancel sunar, eski POST /v1/plans/checkout yolu artık 410 yanıtını verir.

Uyumlu bir uygulamanın sağlaması ZORUNLU olan beş özellik:

  1. Yalnızca GET ve POST iletilir. Alt ağaçtaki diğer tüm yöntemler bir Allow başlığıyla birlikte 405 {"error":"plans-method-not-allowed"} olur ve asla yukarı akışa ulaşmaz. Ağ geçidi faturalandırıcının hangi yollara sahip olduğunu bilmez, bu yüzden her şeyi geçiren bir vekil sunucu, abonelik durumunu tutan bir servise giden genel amaçlı bir tünel haline gelirdi.
  2. İletilen başlıklar İNŞA EDİLİR, asla kopyalanıp üzerine yazılmaz. Bunlar tam olarak çözümlenen oturumdan gelen X-Account-Id, hesap satırından okunan X-Account-Email, paylaşılan gizli anahtarı tutan X-Plans-Secret ve gelen Content-Type başlığıdır. Kopyalayıp üzerine yazma yaklaşımı, çerezleri ve sonraki istemcinin göndermeye karar verdiği her şeyi iletir.
  3. Arayanın kendi kimlik bilgisi asla iletilmez. Tüm düzenlemenin dayandığı kural şudur: Erişim belirtecini iletmek, faturalandırıcıyı çalınan bir belirtecin çalıştığı ikinci bir yer haline getirirdi.
  4. Hesap kimliği oturuma, adres ise satıra aittir. Kendi X-Account-Id veya X-Account-Email değerini gönderen bir istemci, yukarı akışın ne okuduğunu etkileyemez. Bir tarayıcının seçebileceği bir accountId, bir yetkilendirme hatasıdır ve ödeme adımını önceden doldurmak için o hesabın adresini okuyan bir faturalandırıcı, bir adres ifşa kâhini olurdu.
  5. Yanıt durumu ve JSON gövdesiyle doğrudan geçer, onunla birlikte yalnızca Content-Type geri döner. Faturalandırıcıdan gelen bir 402 veya 409, arayanın planı hakkında gerçek bir yanıttır ve öylece iletilir. openplate'in iki faturalandırıcı yolu bunun nedenini gösterir. Yükseltme hemen faturalandırılır ve ödenir, bu nedenle reddedilen bir kartta POST /v1/plans/order yanıtı 402 {"error":"payment-failed"} olur ve arayan eski kademede kalır. Düşürme, ödenen dönemin sonu için ayrılır ve POST /v1/plans/pending-change/cancel (gövdesiz) bunu geri alır: 200 {"kept":{"plan":"…","tier":"…"}}, hiçbir şey ayrılmadığında 409 {"error":"no-pending-change"} (ikinci bir çağrının da yanıtı) veya ödeme sağlayıcısı başarısız olduğunda ve değişiklik hala ayrılmış durumdayken 502 {"error":"pending-change-cancel-failed"}. Ağ geçidi her birini değiştirmeden iletir ve kendisine ait hiçbir yol, kod ve denetim eklemez. Bu 502, faturalandırıcının kendi yanıtıdır ve ağ geçidinin aşağıdaki plans-upstream-* kodlarından biri değildir. GET /v1/plans/me yanıtı ve ayrılmış düşürmenin 200 sipariş yanıtı, hiçbir şey ayrılmadığında bulunmayan pendingTier ve pendingChangeAt taşıyabilir, bunları bilmeyen bir istemci yoksayar.

Ulaşılamayan, zaman aşımına uğrayan, JSON olmayan bir yanıt veren veya aktarım sınırının üzerinde bir gövde döndüren bir yukarı akış, §4 zarfında bir makine koduyla 502 olur: plans-upstream-unreachable, plans-upstream-timeout veya plans-upstream-invalid. Alt ağacın kendi küçük sınırının üzerindeki bir istek gövdesi 413 {"error":"plans-request-too-large"} olur, bu da farklı bir ifadedir: Faturalandırıcı sorunsuzdur ve gönderdiğin şey asla kabul edilmeyecektir. Hiçbir yönde gövde günlüğe kaydedilmez; bir ret durumu yalnızca durum koduyla ve yolla günlüğe kaydedilir, başka hiçbir şey tutulmaz.

Giden çağrı açık bir zaman aşımı taşır. Süre kısadır, çünkü buradaki her yol birisinin az önce bastığı bir düğmedir ve yavaş bir faturalandırıcıyı sınırlandırmak kadar undici kütüphanesinin gizli 300 saniyelik sınırını sınırlandırmak için de vardır.

Silme bildirimi (servisten faturalandırıcıya). Silme yollarından biri (§5.15, §5.20) bir hesabı silmeden önce, servis tam olarak X-Plans-Secret ve X-Account-Id ile boş bir gövdeye sahip POST <PLANS_UPSTREAM_URL>/erase gönderir. Faturalandırıcı, o hesabın her etkin aboneliği iptal edildiğinde 204, hiç abonelik olmadığında ise 204 yanıtını verir. Çağrının beş saniyelik bir zaman aşımı süresi vardır. Bir ret, zaman aşımı veya yanıt vermeyen bir sunucu, hesap kimliğiyle birlikte error seviyesinde günlüğe kaydedilir ve hesap yine de silinir; faturalandırıcının gecelik mutabakatı son güvence olarak kalır.

Operatör PLANS_UPSTREAM_URL ve PLANS_UPSTREAM_SECRET değerlerini yapılandırır, ya ikisi birden ya da hiçbiri. Gizli anahtarı olmayan bir URL, sessizce seviye düşürmek yerine önyüklemeyi reddetme sebebidir: faturalandırıcının okuduğu hesap kimliğinin birinin kimliğini doğrulayan bir ağ geçidinden geldiğini bildiren tek şey bu gizli anahtardır.

GET /v1/plans/prices: giriş yapmadan önceki fiyat listesi

Bir kayıt ekranı, kimsede belirteç olmadan önce fiyatı belirtir, bu nedenle bu TEK yol, bu TEK yöntemle anonimdir. Belirteç gerekmez. Yine de gönderilen bir belirteç okunmaz ve asla iletilmez, bu nedenle eski veya yabancı bir belirteç okumayı bir 401 değerine dönüştüremez. Alt ağaçtaki diğer tüm yollar ve bu yoldaki bir POST veya HEAD, anonim bir çağırana yine de 401 yanıtını verir.

GET /v1/plans/prices

Cache-Control: public, max-age=300 ile → 200:

json
{
  "currency": "EUR",
  "plans": [
    { "key": "monthly", "interval": "month", "grossCents": 500 },
    { "key": "yearly", "interval": "year", "grossCents": 4000 }
  ]
}

Gövde faturalandırıcıya aittir ve bu alt ağaçtaki her yanıt gibi okunmadan iletilir. Örnek, openplate faturalandırıcısının sunduğu şeydir: sattığı planlar, her biri açılışta ödeme sağlayıcısından okunan, vergiler dahil, currency alt biriminde interval başına ücretlendirilen tutarla birlikte. Yukarıdaki rakamlar bir örnektir, asla bir fiyat listesi değildir.

Ağ geçidi gövdeye dokunmadan iletir, bu yüzden faturalandırıcı gövdeye ekleme yapabilir. Ağ geçidi gövdeyi yalnızca izin verilen boyutta bir JSON olup olmadığını denetlemek için ayrıştırır, bir alanı ne okur ne de yeniden yazar. Bu nedenle kademe satan bir faturalandırıcı, currency ve plans yanına bir tiers dizisi ekleyebilir. Bir tiers girdisi, faturalandırıcının teklifinin tiers dizisindeki bir girdiyle (GET /v1/plans/offer) aynı biçimdedir: id, name, description, isSold, dailyAiLimit, capabilities ve kendisine ait plans. Teklif faturalandırıcının sözleşmesidir ve bu protokolün parçası değildir (yukarıya bak), bu yüzden girdi burada yalnızca okuyucu ne bekleyeceğini bilsin diye açıklanmıştır:

json
{
  "currency": "EUR",
  "plans": [
    { "key": "monthly", "interval": "month", "grossCents": 500 },
    { "key": "yearly", "interval": "year", "grossCents": 4000 }
  ],
  "tiers": [
    {
      "id": "tier-a",
      "name": "…",
      "description": "…",
      "isSold": true,
      "dailyAiLimit": 10,
      "capabilities": ["scan"],
      "plans": [
        { "key": "monthly", "interval": "month", "grossCents": 500 },
        { "key": "yearly", "interval": "year", "grossCents": 4000 }
      ]
    }
  ]
}

Bir istemci bilmediği her alanı yoksaymalıdır (MUST), en üst düzeyde ve bir girdinin içinde, ve bir alan yüzünden gövdeyi reddetmemelidir (MUST NOT). tiers içermeyen bir gövde eskisi kadar geçerlidir ve tiers değerini hiç okumayan bir istemci, currency ve plans değerlerini tam olarak önceki gibi okur. Kimlikler faturalandırıcının seçtiği etiketlerdir (tier-a bir yer tutucudur), tiers sırası faturalandırıcının kendi sırasıdır ve tutarlar yalnızca biçim görülebilsin diye yukarıdaki örneğin sayılarını yineler.

Dört özellik bu rotayı alt ağacın geri kalanından ayırır:

  1. Yalnızca X-Plans-Secret ile dışarı çıkar. Hesap yoktur, bu nedenle X-Account-Id ve X-Account-Email yoktur ve gelen istekten hiçbir şey aktarılmaz: ne bir üstbilgi ne de sorgu dizesi.
  2. Bir 200 beş dakika boyunca tutulur ve bellekten sunulur, böylece anlık okuyucu yığını faturalandırıcıya tek bir çağrı anlamına gelir. Faturalandırıcıdan gelen bir ret ve başarısız bir çağrı yukarıdaki gibi iletilir ve tutulmaz, bu nedenle bir sonraki okuyucu tekrar sorar.
  3. Bir kaynak adresi, geriye dönük herhangi bir bir dakika içinde bunu 60 kez okuyabilir. Bir IPv6 arayanı kendi /64 bloğu olarak, IPv4 ile eşlenmiş bir IPv6 adresi ise taşıdığı IPv4 adresi olarak sayılır. Bir sonraki okuma, saniye cinsinden Retry-After ile 429 {"error":"plans-prices-rate-limited"} olur.
  4. Alt ağacın geri kalanı gibi Faturalandırıcı olmadan bu, sıradan bilinmeyen yol 404 olur ve /health bunun için yeni hiçbir şey yayımlamaz: instance.plans okuyan bir istemci, sorup sormayacağını zaten bilir.

5.23 /v1/pulse/*: topluluk nabzı (ADR-0007)

Cihazda tercihe bağlıdır ve bir kişi açana kadar kapalı kalır. Buradaki hiçbir şey bir günlükten türetilmez, sunucudaki hiçbir kod yolu bir günlüğün şifresini çözmez. Aşağıdaki her sayı, cihaz sahibinin talebiyle cihazdan gelen küçük bir fark olarak ulaşır ve ADR-0007, cihazdan neyin neden çıktığını tam olarak belirtir.

Dört rota, hepsi de hesabın olağan erişim belirteci arkasındadır (§4.1). Anonim bir çağıran olağan 401 alır.

POST /v1/pulse/meal
Authorization: Bearer <accessToken>
Idempotency-Key: 6f1c3a1e-9d7b-4a2f-8b31-0f4e9a2c7d55
Content-Type: application/json

{ "kcal": 1234, "protein": 33 }
POST /v1/pulse/photo
POST /v1/pulse/fasting
Authorization: Bearer <accessToken>
Idempotency-Key: <uuid>

İkisi de boş bir gövde taşır.

GET /v1/pulse/today
Authorization: Bearer <accessToken>

200 {
  "day": "2026-09-12",
  "meals": 42,
  "photos": 17,
  "kcal": 68350,
  "protein": 2140,
  "contributors": 9,
  "fastingNow": 4
}

Uyumlu bir uygulamanın sağlaması GEREKEN yedi özellik:

  1. Sunucu tekrar yuvarlar ve kırpar. kcal, en yakın 50'ye yuvarlanır ve 0 ile 5000 arasına kırpılır; protein, en yakın 5'e yuvarlanır ve 0 ile 500 arasına kırpılır. Tam bir rakam, negatif bir rakam veya mantıksız bir rakam gönderen bir cihaz bile diğer herkesin bulunduğu ızgaraya yerleşir. İki sonlu sayı olmayan bir gövde 400 {"error":"invalid request body"} olur.
  2. Her yazma işlemi bir Idempotency-Key üstbilgisi taşır, bir uuid ve bu olmadan yapılan bir istek 400 {"error":"idempotency key required"} olur. Anahtar 24 saat tutulur. Bu aralık içindeki bir tekrar 200 {"duplicate": true} yanıtı verir ve hiçbir şeyi değiştirmez, çevrimdışı bir yeniden oynatmayı veya tekrar denemeyi güvenli kılan da budur.
  3. Hız sınırları hesap başınadır: POST /v1/pulse/meal ve POST /v1/pulse/photo için dakika başına birer tane, POST /v1/pulse/fasting için 10 dakikada bir tane. Sınır aşımı, saniye cinsinden bir Retry-After üstbilgisi ve hiçbir tanımlayıcı belirtmeyen bir gövdeyle birlikte 429 olur.
  4. Bir oruç sinyali, hesap kimliği üzerinde bir upsert işlemidir. İki sinyal, sonraki bitiş süresine sahip tek bir satır bırakır. Satırın süresi son sinyalden 30 dakika sonra dolar ve fastingNow yalnızca süresi dolmamış satırları sayar. Bulunma durumu anonim olmak yerine hesap anahtarlıdır, çünkü hız sınırlama ve tekilleştirmenin her ikisi de bir kimliğe ihtiyaç duyar ve anonim bir sinyal sayıyı şişirmek için yeniden oynatılabilir (ADR-0007).
  5. GET /v1/pulse/today, beş dakikalık bir işlem içi önbellekten sunulur, tüm örnek için tek bir girdi, yazma işlemiyle değil yalnızca zamanla geçersiz kılınır. Bir istemci bunu en fazla beş dakikada bir alır. Bu nedenle aralık içinde yapılan bir yazma işlemi girdi süresi dolana kadar görünmez, bu durum düzeltilmek yerine özellikle belirtilmiştir: sayılar bir onay değil, bir arada olmanın işaretidir.
  6. Gün toplamları 30 gün tutulur. pulse_days ve yanındaki katkıda bulunan satırları, bu yaştan sonra saatlik bir temizlikle silinir, süresi dolmuş bulunma satırları da bunlarla birlikte gider ve tekillik anahtarları 24 saatte kaldırılır.
  7. Nabız rotaları yalnızca bir durum kodu ve bayt sayısı günlüğe kaydeder. Asla hesap kimliği değil ve asla gövdeden bir değer değil.

GET /v1/admin/stats (§5.20), operatöre bugünün nabzını pulse: { meals, photos, kcal, protein, contributors, fastingNow } olarak bildirir; bu da her üyenin zaten okuyabildiği kümenin aynısıdır.

5.24 /v1/push/*: web push (ADR-0008)

*Operatör her üç `VAPID_ değerini de ayarlamadıkça kapalıdır variables**, and then opt in per device. With none of them set the whole subtree answers the ordinary unknown-path 404 to everybody, credentialed or not, and GET /health reports instance.push: false`.

Sunucu hiçbir bildirim metni yazmaz. Gönderdiği her anlık bildirim tek bir alandan oluşur:

json
{ "kind": "catch-up" }
{ "kind": "fast-target" }

Cihaz uyanır, yalnızca kendisinin okuyabildiği günlüğü okur ve kelimeleri yazar. Uyumlu bir istemci, veri yükü ona hiçbir şey bildirmese de her iki tür için bir şeyler oluşturabilmelidir, çünkü veri yükü bunu asla bildirmeyecektir.

Tümü hesabın olağan erişim belirteci ardında bulunan dört rota (§4.1). Yapılandırılmış bir örnekteki anonim bir çağırıcı, olağan 401 alır.

GET /v1/push/config
Authorization: Bearer <accessToken>

200 { "publicKey": "<VAPID application server key, base64url>" }
PUT /v1/push/subscriptions
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "endpoint": "https://push.example.org/f7a1…",
  "keys": { "p256dh": "<base64url>", "auth": "<base64url>" },
  "replaces": "https://push.example.org/older…",
  "timeZone": "Europe/Berlin",
  "locale": "de",
  "catchUpMinute": 480,
  "fastTargetEnabled": true
}

201 { "subscribed": true }   a device seen for the first time
200 { "subscribed": true }   the same endpoint again
PATCH /v1/push/subscriptions
{ "endpoint": "…", "timeZone": "…", "locale": "…", "catchUpMinute": 420, "fastTargetEnabled": false, "wakeAt": "2026-01-15T18:30:00Z" }

200 { "updated": true }
404 { "error": "no such subscription" }
DELETE /v1/push/subscriptions
{ "endpoint": "…" }

200 { "unsubscribed": true }

Uyumlu bir uygulamanın sağlaması gereken dokuz özellik:

  1. Anlık bildirim servisinin ürettiği ve küresel olarak benzersiz olan Bir abonelik, kendi uç noktası ile tanımlanır. PUT bunun üzerinde bir upsert işlemidir: Aynı uç noktayı tekrar kaydeden bir cihaz 200 alır ve ilk görüldüğü günü korur.
  2. replaces, bu kaydın yerine geçtiği uç noktayı adlandırır ve yalnızca aynı hesaba ait olduğunda silinir. Bir service worker yeniden kaydı, eskisinin aboneliğini iptal etmeden yeni bir uç nokta üretir, bu yüzden bu olmasaydı sahipsiz kayıt sonsuza kadar kimseye 201 yanıtı vermeden orada kalırdı. endpoint değerine eşit bir replaces, cihazın kendisini adlandırmasıdır ve hiçbir şeyi silmez.
  3. timeZone bir IANA adıdır ve yazma sırasında doğrulanır. Bilinmeyen bir bölge 400 demektir. Tüm telafi işlemi yerel bir saat meselesidir, dolayısıyla sunucunun okuyamadığı bir bölge bir hata yerine yanlış saatte gelen bir bildirime yol açar.
  4. "Bu cihazda telafi yok" için catchUpMinute, yerel günün 0 ile 1439 arasındaki bir dakikasıdır veya null değeridir. null sessiz varsayılandır.
  5. wakeAt tek seferlik bir andır, ISO 8601 veya temizlemek için null. Sunucu, bu an geçildiğinde hızlı hedef uyarısını gönderir ve aynı yazma işleminde sütunu temizler, böylece asla iki kez tetiklenemez. Bir PATCH içindeki eksik alan, onu tam olarak olduğu gibi bırakır.
  6. Telafi bildirimi, YEREL gün başına bir kez gönderilir, aboneliğin kendi saati o dakikayı geçtiğinde ve orada bugün henüz gönderilmediğinde. Saat değişikliği döneminde yerel bir gün 23 veya 25 saattir, bu nedenle bir UTC periyodu bu kuralın bir uygulaması olamaz.
  7. Yedi günlük sessizlik işlemi duraklatır. Son kaydı veya zamanlama değişikliği yedi yerel günden daha uzun bir süre önce yapılmış bir abonelik, geri gelene kadar telafi bildirimi almaz.
  8. UTC günü başına abonelik başına en fazla iki anlık bildirim. Üçüncüsü atlanır, asla kuyruğa alınmaz.
  9. Anlık bildirim servisinden gelen bir 404 veya bir 410 satırı siler. Başka hiçbir şey silmez: Bir 400, bir 401, bir 403, bir 429 ve her 5xx geçicidir veya göndericiyle ilgilidir; bunlar üzerinden budama yapmak, bir anahtar ilk kez yanlış yapıştırıldığında tabloyu boşaltırdı.
  10. Onayı olmayan bir hesaba hiçbir şey gönderilmez. instance.healthConsent değerinin null olmadığı durumlarda (§5.6), sunucu takvime bakılmaksızın bu tam sürüme sahip olmayan bir hesaba anlık bildirim göndermez (§5.15.1). İletilmeyen bir anlık bildirim işaret yazmaz, bu nedenle geciken bir telafi bildirimi, kişi onay verdikten sonraki ilk döngüde iletilir.

Birleştirme konuları openplate-catchups ve openplate-fast değerleridir, TTL 6 saattir ve aciliyet telafi için normal, hızlı hedef için ise yüksektir. Bir konu, URL için güvenli base64 karakterlerinden oluşmalı, en fazla 32 karakter olmalı ve uzunluğu asla 1 mod 4 olmamalıdır olmalıdır: Aksi halde Apple konuyu çözer ve 400 BadWebPushTopic yanıtı verir; diğer anlık bildirim servisleri ise bunu kabul eder, bu yüzden hata iPhone dışındaki hiçbir yerde görünmez.

Beş sınır (2026-09-30). Tek bir hesabın tüm teslimatları durduramaması veya bu sunucuyu dahili bir ana makineye yönlendirememesi için:

  1. Uç nokta, bilinen bir anlık bildirim servisinde, varsayılan bağlantı noktasında, kullanıcı adı veya parola içermeyen bir https olmalıdır: fcm.googleapis.com, updates.push.services.mozilla.com ve *.push.services.mozilla.com, web.push.apple.com ve *.push.apple.com, *.notify.windows.com ile işletmecinin PUSH_ENDPOINT_HOSTS içinde listelediği tüm ana makineler. Diğer her şey 400 {"error":"endpoint must be an https URL at a known push service"} sayılır ve hiçbir şey yazmaz. Bu kuralı karşılamayan depolanmış bir satır, bir sonraki döngüde gönderilmeden silinir.
  2. Bir hesap en fazla 10 abonelik tutar. Bunu aşan bir kayıt, hesabın en eski diğer satırlarını siler; yeni kaydedilen satır her zaman kalır.
  3. Bir teslimat 10 saniye sonra vazgeçer ve döngü her defasında 8 uç noktaya gönderim yapar, böylece yavaş bir uç nokta başkalarını geciktirmez.
  4. 404 veya 410 dışındaki bir durumla başarısız olan teslimat, satırı geri çekilmeye alır: Bir sonraki deneme bir dakika sonra, ardından iki, dört ve bir güne kadar bu şekilde katlanarak yapılır. Art arda 15 başarısızlıktan, yani yaklaşık dört buçuk günden sonra satır silinir. Başarılı bir teslimat veya uç noktanın yeniden kaydedilmesi sayacı sıfırlar.
  5. Kendi dakikasını aşan bir döngüye ikincisi katılmaz.

Başka bir hesabın elinde tuttuğu bir uç noktada PUT, satırı çağırana taşır. Bu gereklidir: Uygulama, tarayıcının mevcut aboneliğini yeniden kullanır ve cihazı silme işlemi aboneliği bırakamadığında, o tarayıcıdaki bir sonraki hesap aynı uç noktayı kaydeder. Önceki sahibin satırı bu durumda ilgili cihazı uyandırmayı bırakır, yeni sahibin istediği de budur.

Hiçbir rota asla bir uç nokta veya bir cihaz anahtarı döndürmez ve rotalar yalnızca bir yol, bir yöntem, bir durum ve bir bayt sayısı günlüğe kaydeder: Bir yetenek olan uç noktayı asla ve hesap kimliğini asla günlüğe kaydetmez.

GET /v1/admin/stats (§5.20), operatöre iki tamsayıdan oluşan ve asla bir satır olmayan push: { subscriptions, sentToday } bildirir.

5.25 POST /v1/feedback: bildirilen bir tahmin (ADR-0006)

Yalnızca işletmeci SYNC_FEEDBACK değerini ayarladığında mevcuttur. Bu olmadan yol, kimlik bilgisi olsun veya olmasın herkese alışılagelen bilinmeyen yol yanıtı olan 404 değerini döndürür ve GET /health hiçbir instance.feedback taşımaz (§5.6). İstemci bir bildirim sunmadan önce instance.feedback değerini okumalıdır. Yalnızca bu alanın bildirdiği saklama penceresini belirtmelidir, başka hiçbir süreyi değil.

Bu protokolde sunucunun okuyabildiği tek yazma işlemi budur. Bir tahminin yanlış olduğunu düşünen bir kişi, ilgili girdinin sayısal verilerini gönderir. Cihazda tabak fotoğrafı hala duruyorsa onu da gönderir. Kişi öncelikle her ikisinin de cihazdan çıkmasını onaylamalıdır. Sunucu, saklama süresi dolana kadar bunları okunabilir halde tutar. Bu istisnanın neden var olduğunu ADR-0006 açıklar.

Hesabın olağan erişim belirteci bilgisiyle doğrulanır (§4.1). Anonim bir arayan olağan 401 yanıtını alır.

POST /v1/feedback
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "idempotencyKey": "6f1c3a1e-9d7b-4a2f-8b31-0f4e9a2c7d55",
  "measurements": { "…": "…" },
  "consent": { "agreedAt": "2026-09-19T08:12:00.000Z", "wordingVersion": "1" },
  "image": { "contentType": "image/jpeg", "data": "<base64>" }
}

201 { "reportId": 17, "hasImage": true, "createdAt": "2026-09-19T08:12:03.000Z" }

Uyumlu bir uygulamanın sağlaması GEREKEN yedi özellik:

  1. image dışındaki tüm alanlar zorunludur. Kırpıldıktan sonra idempotencyKey 1 ila 128 karakter, consent.wordingVersion 1 ila 64 karakterdir ve consent.agreedAt bir anlık zaman damgasıdır. Cihazın kendi saati olarak saklanır ve asla düzeltilmez. Sunucunun createdAt değeri yanına eklenir. measurements, serileştirildiğinde en fazla 16 KB olan bir JSON nesnesidir. Sunucu bunun için bir şemaya sahip değildir ve kesinlikle bir şema tanımlamamalıdır: bu sınır, hiç kimsenin alana bir günlük yerleştirememesi için vardır. Başka herhangi bir gövde, her alan için bir cümle içeren 400 {"error":"invalid request body"} döndürür.
  2. image isteğe bağlıdır ve eksikliği bir hata değildir. Eksik bir anahtar veya null, fotoğraf olmadığı anlamına gelir. Cihazın fotoğraf önbelleği bunu zaten silmiş olabilir ve sayısal veriler yine de incelenmeye değerdir. Mevcut olduğunda { "contentType", "data" } olur. contentType, image/jpeg, image/png veya image/webp değeridir. Asla betik taşıyabilen image/svg+xml olamaz. data, kodu çözüldüğünde 1 ila 5.000.000 bayt veren bir base64 dizisidir. Daha büyüğü 413, boş veya başka bir türde olması ise 400 döndürür.
  3. Gövde sınırı FEEDBACK_MAX_REQUEST_BYTES değeridir, varsayılan olarak 8 MB boyutundadır ve yalnızca bu rota için geçerlidir. Base64 boyutu üçte bir oranında büyüttüğü için görsel üst sınırının üzerinde yer alır. Daha büyük bir gövde 413 {"error":"request body exceeds the maximum accepted size"} döndürür.
  4. Idempotency anahtarı, yeniden denemeyi güvenli hale getirir. Hesap başına benzersizdir. Var olan bir anahtarla yapılan ikinci bir gönderim, 201 yerine saklanan bildirimle birlikte 200 yanıtını verir. İkinci bir bildirim saklamaz ve günlük sınıra dahil edilmez. Eğer bir fotoğraf eklenmişse fotoğrafı tekrar yazar. Bu, ilk fotoğraf yazma işlemi yarıda kesilmiş bir bildirimi onarır.
  5. Hesap başına günlük sınır, FEEDBACK_DAILY_LIMIT, varsayılan olarak 5 adettir, ekleme işlemiyle aynı veritabanı işleminde UTC günü başına sayılır. Aşılması durumunda hiçbir tanımlayıcı belirtmeyen 429 {"error":"daily limit reached: 5 reports per day for this account"} döner.
  6. Saklananlar yalnızca fotoğraf, sayısal veriler, onay kaydı, hesap kimliği ile varış zamanıdır, başka hiçbir şey değildir. İstek başlıkları, IP adresi, kullanıcı aracısı veya bir cihaz tanımlayıcısı saklanmaz.
  7. Bir bildirim ve fotoğrafı instance.feedback.retentionDays sonra silinir (bu uygulamada 30 gün, saatlik bir temizlikle silinir), bir işletmeci bildirimi sildiğinde (§5.20) ve hesapla birlikte daha erken silinir.

6. Sürüm el sıkışması: zorunludur ve güvenli başarısızlığa uğraması zorunludur

Bir istemci bu belgeyi servisten okumalı ve bir oturumun ilk eşitlemesinden önce kontrol etmelidir.

Bu, istemci ve sunucu tek bir yapı olarak sevk edildiğinde var olan süreç içi sürüm kontrolünün yerini alır. Artık böyle sevk edilmiyorlar: Dağıtılan bir istemci ile dağıtılan bir servis her iki yönde de bir sürüm sapabilir ve kendi sunucusunu barındıran biri, güncel bir istemciyi sekiz ay önce yükselttiği bir servise yönlendirebilir. Bu duruma dair hiçbir şey, bir anlık bildirim üzerindeki başarılı bir 200 üzerinden tespit edilemez.

Kurallar:

  1. protocolVersion, istemcininkine eşit olmalıdır. "≥" değil, "uyumlu gibi" değil.
  2. envelopeVersion, istemcininkine eşit olmalıdır.
  3. Herhangi bir uyumsuzlukta durumunda istemci eşitlemeyi reddeder ve kullanıcıya hangi tarafın daha eski olduğunu gösterir. Veri göndermez, veri çekmez, yeniden denemez ve sessizce işlevini kısıtlamaz.
  4. El sıkışmaya ulaşılamıyorsa veya bozuksa, bunu bir uyumsuzluk olarak ele al. Doğrulanamayan bir servis uyumlu bir servis değildir.

Referans uygulama, her iki protocol.ts dosyasında da bulunan, saf, bütünsel ve bir boolean yerine kullanıcıya gösterilebilir bir cümle döndüren checkProtocolCompatibility() fonksiyonudur.

Neden elden gelenin en iyisi yerine ret: blob çoğu zaman kullanıcının verisinin tek kopyasıdır. Daha yeni bir servisin farklı çerçevelediği bir zarfı gönderen veya yarım anladığı bir zarfın şifresini çözen bir istemci, o kopyayı geri döndürülemez biçimde bozabilir. Reddedilen bir eşitleme, görünür bir aksaklıktır; sessizce yanlış yapılan bir eşitleme ise haftalar sonra fark edilen bir veri kaybı vakasıdır. Bu protokol her seferinde aksaklığı seçer.

7. Sürüm belirleme politikası

  • PROTOCOL_VERSION uç noktaları, istek/yanıt biçimlerini, durum kodu anlambilimini, kimlik doğrulama düzenini ve CAS anlambilimini kapsar. Bunlardaki herhangi bir geriye dönük uyumsuz değişiklikte sürümü artır. Yalnızca ekleme niteliğindeki değişiklikler (isteğe bağlı yeni bir yanıt alanı, eski istemcilerin hiç çağırmadığı yeni bir uç nokta) bu sürümü artırmaz.
  • ENVELOPE_VERSION yalnızca blob'un kriptografisini ve çerçevelemesini kapsar: şifreleyici, IV yerleşimi, sıkıştırma kodeki, etiket işleme. Bunlardan herhangi biri için sürümü artır. Yük şeması değişikliği için sürümü Asla artırma.
  • payloadSchemaVersion, istemcinin yerel depolama şeması sürümüdür. Bu protokol içinde, AAD'ye bağlı opak bir tamsayı olarak taşınır. Sunucu bunu asla yorumlamaz ve yukarıdaki iki sürümü de hiçbir zaman etkilemez.

İki sürüm numarasının bağımsız olması kasıtlıdır: kriptografiyi yeniden çerçevelemek ile HTTP API'yi yeniden biçimlendirmek, etki alanları farklı, başka türde değişikliklerdir.

1.0 öncesi esneklik. İlk genel kullanıma açık sürüme kadar, yayımlanmış bir protokolün gerektireceği geçiş yolu izlenmeden geriye dönük uyumsuz değişiklikler yapılabilir. Sürüm artırımı YAPILMADAN iki değişiklik yapıldı: çerezden bearer kimlik doğrulamasına geçiş ve eşitleme rotalarının /api/sync konumundan /v1/sync konumuna taşınması. Üçüncüsü, yani 0.5.0 sürümünde e-postanın kaldırılması da sürüm artırımı olmadan yapıldı ve böyle yapılmamalıydı (aşağıya bak). Bu paragraf genel kullanıma açık sürümde silinir ve o andan itibaren yukarıdaki kurallar harfi harfine uygulanır.

0.5.0, kimlik doğrulama sözleşmesini değiştirdi ve sürümü ARTIRMADI; bu bölümün şimdi kayda geçirdiği hata da tam olarak buydu. email yerine handle getirdi, verify-email ve request-reset alanlarını kaldırdı, recover ile recover-rotate ekledi (§5.14). Numara 1 seviyesinde kaldığı için §6 el sıkışması bunu yakalayamadı: email gönderen 0.5.0 öncesi bir istemci, onaramayacağı bir 400 aldı; oysa sürüm numaraları eşleşiyordu ve ona her şeyin yolunda olduğunu söylüyordu.

0.6.0, PROTOCOL_VERSION sürümünü 2'ye yükseltir ve bunu tam olarak bu nedenle yapar. Değişiklikler aynı sınıftandır (kimlik doğrulama alanı yine email, kayıt işlemi adrese özel bir davetiye ile her iki anahtar kaydını gerektirir, signupMode el sıkışmasından ayrıldı, AccountView eski hesap gövdesinin yerini aldı ve iki sıfırlama uç noktası §5.12'yi yeniden kullanır), ancak bu kez §6 bunları yakalar: sürüm 1 konuşan bir istemci, yarım yamalak çalışmak yerine iletişimi reddeder. Gerekçe: docs/adr/0005-organization-accounts-and-escrowed-recovery.md.

8. Boyut sınırları ve kapasite planı

SınırDeğerUygulayan
Maksimum blob boyutu2 MiB (MAX_BLOB_BYTES)Servis (413), daha iyi bir hata için istemci tarafında da aynalanır
Saklanan blob sürümleriÜç katman, aşağıya bakServis, kabul edilen her yazma işleminden sonra taranır
Hesap başına anahtar kayıtları2 (her kind için bir tane)Servis

Saklama katmanlıdır (M224). HERHANGİ BİR katman sakladığı sürece bir sürüm tutulur:

KatmanKuralÜst sınır
SonEn yeni sürümler (BLOB_VERSION_RETENTION)5
GünlükBLOB_DAILY_RETENTION_DAYS için her UTC takvim gününün en yeni sürümü14
Küçülme öncesi sabitlemeleriOnaylanmış büyük bir küçülmeyle değiştirilen sürümler, BLOB_PRE_SHRINK_PIN_DAYS için, en fazla BLOB_PRE_SHRINK_PIN_LIMIT adede kadar en yeni olanlar önce14

Böylece hesap başına en fazla 33 sürüm, dolayısıyla en fazla 66 MiB. Günlük katmanı, özellikle adet yerine takvim GÜNÜ başınadır: birleştirme döngüsündeki iki cihaz ağın izin verdiği hızda sürüm üretir ve adede dayalı bir katman bu durumda dakikalar içinde tükenir. Sabitleme katmanı bir üst sınıra tabidir, çünkü sabitleme istemcinin kendi beyanıyla alınır.

M224 öncesinde tüm kural sadece beş adetti ve bir güvenlik ağı olarak zayıftı: istemci hatası yüzünden günlüğü silinen bir hesap, yalnızca iyi sürüm iki cihazın bir dakikada tüketebileceği bir aralıkta şans eseri kaldığı sürece kurtarılabiliyordu.

Kapasite uçurumu, açıkça belirtilmiştir. Tek bir blob, hesabın tüm deposunu tutar. Yemek günlüğü girdileri sıkıştırmadan önce her biri yaklaşık 400 ila 700 bayt JSON kaplar, bu nedenle sıkıştırılmamış bir blob yaklaşık 2 ila 4 yıllık günlük kayıtta 2 MiB sınırını geçer. Bu teorik bir endişe değildir, kesin bir tarihtir.

ENVELOPE_VERSION 1 düz metni gzip ile sıkıştırır, bu da bu kadar tekrarlayan bir JSON üzerinde (binlerce kaydın her birinde aynı anahtar adları) yaklaşık bir büyüklük sırası kazandırır ve uçurumu yakın vadeli bir sorun olmayacak kadar ileriye öteler. Sorunu ortadan kaldırmaz.

Planlanan düzeltme, baskı altında keşfedilmemesi için: tek bir yekpare yapı yerine bağımsız sürümleri olan birçok küçük şifreli metin içeren parçalanmış veya varlık başına bloblar. Bu durum çerçeveleme ve uç noktalarda gerçek bir değişikliktir, bu nedenle bir yama değil, bir protokol sürümü artışı olacaktır. İşletimsel olarak bu çalışmayı başlatma tetikleyicisi, sahadaki blob boyutlarının üst sınırın yaklaşık %80'ini aşmasıdır ve servis bunun için bir uyarı günlüğe kaydeder (M128 spec 02). Uçurum, herhangi bir kullanıcı ona ulaşmadan çok önce gözlemlenebilir olmalıdır.

9. Sunucunun bildikleri

9.1 Neleri bilemez

Sunucu hiçbir zaman DEK, KEK veya parola cümlesini almaz. Kayıt sırasında ve her anahtar rotasyonunda kurtarma kodunu alır ve bu kodu mühürlü olarak saklar (§3.1 ve §9.2 içindeki emanet kaydı). Bu servisteki hiçbir kod yolu koddan bir anahtar türetmez veya bir ikili nesnenin (blob) şifresini çözmez. Servisin kendi kodu için şifre çözme işlemi erişilemez durumdadır, kasıtlı olarak engellenmiş değildir. Hem veritabanını hem de SERVER_SECRET değerini elinde bulunduran kişi için ise erişilebilirdir. Bu da yönetilen bir kopyanın işletmecisi veya kendi sisteminde bizzat sen anlamına gelir.

Üstelik yine de tek bir tanesini bile birleştiremez. Bölüm 5.23'teki topluluk nabzı, sunucunun öğünleri sayması gibi görünür ancak öyle değildir: Bölüm 9.2'deki hiçbir şey bir blob'dan türetilmez ve hiç kimsenin nabzı açmadığı bir kurulumda hiçbir şey sayılmaz. Toplamlar, sahipleri bunu tercih eden cihazlar bunları gönderdiği için vardır; bu yüzden nabız, sunucunun hesapladığı bir şey olarak değil, bildiği bir şey olarak aşağıda listelenmiştir.

9.2 Neleri bilir

Üstveri konusunda dürüst olmak gerekir, çünkü "uçtan uca şifrelenmiş" ifadesi genellikle "sunucu hiçbir şey bilmez" şeklinde anlaşılır:

  • Blob boyutu ve dolayısıyla hesabın yaklaşık ne kadar veri tuttuğu. Sıkıştırma, bunu eskisine göre daha belirsiz bir sinyal haline getirir, gizlenmiş bir sinyal yapmaz.
  • Yazma sıklığı ve zamanlaması: bir cihazın ne zaman ve ne sıklıkla eşitlediği.
  • Sürüm numaraları: blobVersion, envelopeVersion ve saklanan sürüm sayısı.
  • Parola kaydı için KDF parametreleri ve tuz (salt). Bunlar sır değildir, giriş yapmadan önce yeni bir cihaza sunulmak üzere bulunurlar.
  • Bir hesabın kurulumu tamamlayıp tamamlamadığı (anahtar kayıtları var mı) ve daha önce hiç eşitleme yapıp yapmadığı (bir blobu var mı).
  • Hesabın kendisi: bir e-posta adresi, isteğe bağlı bir görünen ad, bir rol, günlük bir yapay zeka kotası, bir askıya alma anı, bir kimlik doğrulama doğrulayıcısı (parolanın anahtarlı özetinin anahtarlı özeti, bkz. §5.8), kurtarma kanıtı üzerinde aynı yapıda ikinci bir doğrulayıcı ve hesabın KDF parametreleri. Adres, dünyadaki bir kişiyi tanımlar, 0.5.0 sürümünün kaldırdığı ve 0.6.0 sürümünün bilinçli olarak geri getirdiği bir kişisel veri sınıfıdır (ADR-0005): bir kuruluştaki kişiler, davetlerinin ulaştığı adresle tanımlanır, çünkü bir ay sonra da bilecekleri tanımlayıcı budur.
  • Hesabın mühürlenmiş KURTARMA KODU (accounts.recovery_code_escrow, §3.1). Okuyucunun üzerinde durması gereken liste girdisi budur. SERVER_SECRET alt anahtarı altında AES-256-GCM ile şifrelenmiştir, bu nedenle yalnızca veritabanının dökümünü almak onu açmaya yetmez, ancak yönetilen bir kurulumun işletmeni her ikisine de sahiptir. Yönetilen bir kurulumun işletmeni, üzerindeki herhangi bir hesabı açabilir. Bir uç nokta üzerinden veya bu hizmetteki herhangi bir kod yoluyla değil, eldeki sırla o sütunu okuyup istemcinin kendi HKDF'sini çalıştırarak bunu yapabilir. Kendi sunucunda barındırdığın (self-hosted) bir kurulumun işletmeni kendinsin, dolayısıyla eski güvence orada geçerlidir. Bu nedenle barındırılan bir kuruluma güvenip güvenmeme kararı, işletmenine dair bir karardır.
  • Bekleyen davetler: Her biri için bir adres, isteğe bağlı bir ad, bir rol ve bir kota bulunur; henüz hesabı OLMAYAN ve onay vermemiş birine aittir. Bir tane üretmek işletmen eylemidir ve DELETE /v1/admin/invites/:id satırı geri çeker. §5.8.3'teki istek kapısının ürettiği bir satır bu şekilde işaretlenir, böylece bir işletmen bunları sayabilir. Bir saat içinde Tamamlanmış bir davet, adresini ve adını kaybeder: Her örnekte iptal edilmiş veya süresi dolmuş bir satır; yalnızca aşağıdaki anahtarlı karmayı tutan TRIAL_ADDRESS_PEPPER bulunan bir örnekte ise kullanılmış bir satır silinir. Çeşni olmadan kullanılmış bir satır adresini korur, çünkü §5.21'deki üye yeniden davet kuralı bunu okur.
  • Tarama denemesi, çalıştıran bir örnekte: hesap satırındaki iki tam sayı olan, tanımlanan ve kullanılan ücretsiz taramalar. Yalnızca bir tarama denemesi hesabı için, yapay zeka eylemi başına bir satır: istemcinin seçtiği opak bir kimlik, bir zaman, bir istek sayısı ve bir yanıtın iletilip iletilmediği, 24 saat saklanır ve ardından silinir, asla günlüğe kaydedilmez. Her davet satırı, adresin ikinci bir kopyasını değil, bir posta kutusunun anahtarlı tek yönlü karması (yalnızca operatörün elinde bulunan bir sır olan TRIAL_ADDRESS_PEPPER altında, §5.8.3'ün deneme anahtarı üzerinde HMAC-SHA256) taşır.
  • Bir hesap silindikten sonra, tarama denemesi yürüten bir örnekte: adres ve ad, o posta kutusuyla ilgili her davet satırından kaldırılır ve yalnızca hesap bir denemeye sahip olduğunda posta kutusunun tek bir anahtarlı karması saklanır, başka hiçbir şey saklanmaz: ad yok, kimlik yok ve tek bir tarih, yani silinme anı yer alır; satırın sonlanmasını sağlayan da budur. Aynı posta kutusunun ikinci bir deneme almasını engelleyen şey budur. İşletmecinin sırrı olmadan özet geri döndürülemez veya bir adres listesiyle eşleştirilemez. Bir süpürme işlemi, GDPR Madde 6(1)(f) yasal dayanağıyla (ücretsiz taramaların kötüye kullanılmasını önlemede meşru menfaat, bir avukat tarafından incelenmemiştir, ADR-0010) bu andan TRIAL_HASH_RETENTION_DAYS sonra (varsayılan olarak 365) onu siler. Tarama denemesi vermeyen bir örnek hiçbir özet tutmaz. Tarama denemesi yürütmeyen bir örnekte davet satırları, §5.21 üye yeniden davet kuralı uyarınca bir silme işleminden sonra adreslerini korur.
  • Yapay zeka kullanımı: UTC günü başına hesap başına bir tamsayı, 90 gün boyunca saklanır ve ardından silinir (§5.20). Bu bir sayıdır, asla bir günlük kaydı değildir: istem yok, yanıt yok, model yok, günün ötesinde bir zaman damgası yok. Bir işletmen, bir hesabın sayaçlarını gün be gün bir şerit olarak okuyabilir (GET /v1/admin/accounts/:id/activity), bu bir kişinin bir sağlık uygulamasını ne zaman kullandığına dair üstveridir ve tam da bu nedenle sınırlandırılmıştır.
  • Açmış olan hesaplar için Topluluk nabzı (§5.23, ADR-0007): öğünlerin, fotoğrafların, kalorilerin ve protein gramlarının kurulum genelindeki günlük toplamları, katkıda bulunan hesap başına günlük bir satır ve bir hesabın şu anda oruç tuttuğunu belirten kısa ömürlü bir mevcudiyet satırı. Toplamlar hiç kimseyle ilişkilendirilemez; katkıda bulunan satırı ve mevcudiyet satırı ilişkilendirilebilir ve yalnızca "bu hesap bugün katkıda bulundu" ve "bu hesap oruç tutuyor" derler. Günlük toplamlar ve katkıda bulunan satırları 30 gün saklanır, mevcudiyet son sinyalden 30 dakika sonra sona erer ve rotalar hiçbir hesap kimliğini günlüğe kaydetmez. Bunu hiç açmamış bir kişi hiçbir şey göndermez ve bunların hiçbirinde yer almaz.
  • Bildirimleri açmış bir cihaz için Bir push aboneliği (§5.24, ADR-0008): push servisi uç noktası, şifreleme yaptığı iki anahtar, sınırlandırılmış bir user agent dizgisi, bir IANA saat dilimi, bir yerel ayar, yerel günde yakalama bildiriminin gönderileceği dakika, en son gönderildiği yerel gün, cihazın en son görüldüğü yerel gün, uyanmak istediği an ve bugün gönderilenlerin sayısı. Bunlar birlikte, bu kişinin kabaca ne zaman uyanık olduğunu, dünyanın kabaca neresinde bulunduğunu ve wake_at üzerinden bir orucunun ne zaman bittiğini gösterir. Bu sonuncusu, pulse'ın varlık satırıyla örtüşür, aynı orucun sürdüğünü belirtir; ADR-0008 bu ilişkiyi keşfedilmeye bırakmak yerine açıkça adlandırır. Hiçbir bildirimin metninden tek bir kelime bile SAKLANMAZ: her push bir tür taşır. Cihaz abonelikten çıktığında, push servisi cihazı sahiplenmeyi bıraktığında veya hesapla birlikte bu satır silinir.
  • Bunu isteyen bir örnekte (§5.15.1) Sağlık verisi onayı: Kişinin kabul ettiği metnin sürümü ve bu servisin bunu kaydettiği an, hesap satırında iki sütundur. Kişinin bir sağlık uygulaması kullandığını ve işletmenin bu verileri işlemesini kabul ettiğini belirtir; işletmen bunu gösterebilmelidir. Bir işletmen tarafından görülebilir (§5.20), hiçbir rota bunu temizlemez ve silme sırasında hesap satırıyla birlikte gider.
  • Bir kişinin en son ne zaman bir işlem yaptığı: Giriş yapıldığında ve vekil üzerinden tamamlanma sağlandığında yazılan, ancak bir belirteç yenilemesi ya da eşitleme sorgusuyla bilerek yazılmayan accounts.last_seen_at; bu sayede "bir istemci çalışıyordu" yerine "biri işlem yaptı" anlamına gelir. Bir işletmen tarafından görülebilir (§5.20) ve silme işlemi sırasında hesap satırıyla birlikte gider.
  • Yasal bildirimler (POST /v1/legal/declarations, her örnekte bir iptal veya cayma bildirimi): kişinin yazdığı ad, adres, sözleşme referansı, neden ve tarihler, ulaştığı zaman ve varsa eşleştiği hesap. Europe/Berlin saatine göre sayılarak ulaştığı yılı takip eden üçüncü takvim yılının sonuna kadar saklanır ve ardından saatlik temizlik tarafından silinir: 2026-09-21 tarihinde alınan biri, Berlin saatiyle 2030-01-01 00:00 itibarıyla silinir. Hesabı silmek bunu daha erken silmez; satır hesap kimliğini kaybeder ve kalır, çünkü kişinin ne beyan ettiğinin kaydıdır. Makbuz e-postası, normalleştirilmiş adres başına üç, gönderici ağı başına LEGAL_DECLARATION_RECEIPTS_PER_NETWORK_PER_DAY (varsayılan olarak 10) ve örnek başına LEGAL_DECLARATION_RECEIPTS_PER_DAY (varsayılan olarak 200) ile sınırlandırılmıştır. Her sınır, geriye dönük 24 saatlik süre için geçerlidir. Bellekte tutulan ağ sayısı hariç, toplamlar bu satırlardan sayılır. Sınırı aşan bir bildirim yine de saklanır, iletilir ve işletmeciye gönderilir ve 202 aynı kalır. Makbuz adı, sözleşme referansını veya nedeni asla tekrarlamaz; bunları yalnızca işletmecinin kopyası taşır.
  • Oturum meta verileri: Kaç etkin oturumun bulunduğu, her birinin ne zaman oluşturulduğu ve belirteçlerin en son ne zaman döndürüldüğü veya iptal edildiği. Belirteç değerlerinin kendileri yalnızca özet olarak saklanır.
  • SYNC_RESEARCH ayarlanmış bir dağıtımda Çalışma grafiği (§5.18): Hangi hesabın hangi çalışmaya, ne zaman, ne sıklıkla ve her defasında ne büyüklükte katkı sağladığı. Buradaki bir kenar, "bu kişinin sağlık verileri Y çalışmasında yer alıyor" anlamına gelir ve aşağıdaki bakım kenarıyla aynı sınıfta, sağlığa bağlı kişisel bir veridir. Bu kaçınılmazdır bir durumdur ve vazgeçme işlemi bunun kanıtıdır: Bir katılımcının satırını silmek için onu bulmak gerekir, hesap silme işlemi onu da kapsayarak basamaklanmalıdır ve hem karşılaştır ve takas et hem de suistimal kontrolü hesap üzerinden anahtarlanır. Sunucuyu körleştiren bir şema bunlardan birini bozar ve trafik analizi zaten bu körlüğü ortadan kaldırır, bu nedenle yarım yamalak kaçınmak yerine bu durum açıkça paylaşılır. Araştırmacı bu eşleştirmeyi asla almaz (§5.18 hesap kimliği taşımaz), vazgeçme işlemi kenarı kalıcı olarak siler ve geriye yalnızca bir takma ad bırakır, bayrağın bulunmadığı bir dağıtımda ise çalışma grafiğini tutacak bir tablo yoktur.
  • SYNC_SHARING ayarlanmış bir dağıtımda Paylaşım grafiği (§5.16): Hangi hesabın hangi diğer hesaba okuma izni verdiği, bu iznin ne zaman tanımlandığı ve izin verilen tarafın bunu ne zaman kullandığı. Bu bir ilişki grafiğidir, bu servisin bildiklerinin gerçek bir genişlemesidir ve özelliğin geliştirildiği ortamda (bir hasta ve diyetisyeni), bu grafikteki bir kenarın kendisi de sağlığa bağlı kişisel bir veridir, çünkü birinin bakım altında olduğunu gösterir. Okuma yetkisi vermek için gereken asgari veri budur; izin veren taraf satırı oluşturduğu ve izin verilen taraf kendi tarafını silebildiği için her iki uç da onay verir; izin iptal edildiğinde kenar kalıcı olarak silinir ve hesaplardan biri silindiğinde basamaklı olarak kaldırılır. SYNC_SHARING değerini ayarlamayan bir dağıtım böyle bir grafiği saklamaz ve bunu koyacak bir tablosu da yoktur.
  • SYNC_FEEDBACK ayarlanmış bir dağıtımda (§5.25, ADR-0006) Bildirilen tahminler: bir kişinin bildirmeyi seçtiği her bir girdinin sayısal verileri, cihazda hala varsa tabak fotoğrafı, hesap kimliği, onay kaydı ve varış zamanı, hepsi okunabilir durumdadır. instance.feedback.retentionDays boyunca saklanır, ardından bir temizlikle silinir ve hesapla birlikte giderler. Bir işletmeci bunları §5.20 aracılığıyla okuyabilir ve bir fotoğrafın her okunması günlüğe kaydedilir. Bu bayrağın bulunmadığı bir dağıtımda tutulacak hiçbir bildirim ve fotoğraf bulunmaz.

Yukarıdaki meta verilerden bilinemeyecek olanlar: ne yendiği, ne zaman yendiği, ne kadar yendiği veya veri yükünün içindeki diğer herhangi bir şey. Yukarıdaki iki kayıt bunu açığa çıkarır. Mühürlü kurtarma kodu, SERVER_SECRET değerini de elinde tutan kişiye günlüğün tamamını açar ve bildirilen bir tahmin taşıdığı tek girdiyi gösterir.

10. Alternatif bir sunucu geliştirmek

Uyumlu bir eşitleme sunucusu, eksiksiz olarak şunları gerektirir:

  1. §5.1 ila §5.4 arasındaki dört uç nokta ile §5.6'daki /health el sıkışması. §5.5 kaldırıldı; bir sunucu yalnızca hamil yetkilendirmesiyle anahtar kaydı silme işlemi sunmamalıdır.
  2. blobVersion üzerinde hesap başına CAS: Atomik. Referans uygulama, satır kilitleme yerine bir UNIQUE (accountId, blobVersion) indeksi kullanır ve benzersizlik ihlalini bir çakışma olarak ele alır; bu, READ COMMITTED altında doğru kalır ve SELECT ... FOR UPDATE yaklaşımından daha basittir. Aynı garantiyi veren herhangi bir mekanizma uygundur; atomik olmayan bir oku ve sonra yaz işlemi okumaz.
  3. expectedUpdatedAt aracılığıyla anahtar kayıtlarında hesap ve türe göre CAS, aynı "eksik alan bir 400" kuralı ve her üzerine yazmada bir parola kontrolü (currentAuthHash).
  4. §8'deki üç katmana göre saklama budaması ve §5.1'deki daralma koruması. Onaylanmamış büyük bir daralmayı kabul eden bir sunucu, etkilenen sürümdeki bir istemci yerel deposunu ilk kaybettiğinde bir hesabın günlüğünü yok eder; bunu reddeden bir sunucuya göre yazılmış ve reddetmeyen bir sunucuya yönlendirilmiş bir istemci, sessizce korumasız kalır.
  5. ciphertext ve wrappedDek için bayt düzeyinde birebir saklama. Bunları asla yeniden kodlama, normalleştirme, kırpma veya "düzeltme". Herhangi bir değişiklik GCM etiketini ve onunla birlikte kullanıcının verilerini yok eder.

Ek olarak, §5.7 ila §5.15 arasındaki hesap uç noktalarını da uygulayan bir sunucu şunları yapmalıdır:

  1. Bilinmeyen adresler için kararlı ve gerçek biçimli bir KDF tanımlayıcısı sun (§5.7), her iki kolda da özdeş iş yürüt ve uç noktayı kaynak adresine göre hız sınırına tabi tut. Bir 404, tembelce türetilmiş sahte bir değer veya hız sınırı uygulanmamış bir uç nokta; yanıtla, zamanlamayla veya hacimle, tasarımın geri kalanının kapattığı listeleme kahinini yeniden açar.
  2. Her iki doğrulayıcıyı da, veritabanının dışında tutulan bir sır altında, iletilen authHash ve recoveryAuthHash değerlerinin anahtarlı karmaları olarak sakla. İletilen değerin kendisini asla saklama ve asla düz metin olarak tutma.
  3. §5.14'teki rotasyon bildirimlerini, yeniden mühürlenmiş emanet dahil olmak üzere atomik olarak uygula ve §4.2'deki tetikleyicilerin her birinde bekleyen tüm oturumları iptal et.
  4. Kayıt sırasında hesabın adresini INVITE üzerinden al ve asla istek gövdesinden alma (§5.8); recover, recover-rotate ve reset/request işlemlerini (IP, e-posta) başına paylaşılan ve başarılı olunduğunda asla temizlenmeyen tek bir havuzda sınırla. Kayıt gövdesinin kendi adresini belirlemesine izin veren bir sunucu, bu adresi doğrulayan tek şeyi ortadan kaldırmış olur.
  5. Özdeş iş yaptıktan sonra her reset/request için 202 yanıtı ver ve reset/open işleminin hesaba hiçbir şey yazmamasını sağla (§5.12). Bir doğrulayıcının yerini alan bir sıfırlama işlemi, adı ne olursa olsun, bu protokolün sildiği hesap ele geçirme yoludur.
  6. Askıya alınmış bir hesabı girişte, yenilemede ve her taşıyıcı rotasında 403 {"error":"account-suspended"} ile, tam olarak bu dizgiyle reddet.
  7. Hesap silme işlemini bloblara, anahtar kayıtlarına, sıfırlama belirteçlerine ve kullanım satırlarına basamaklandır.
  8. Eğer §5.21'deki üye oluşturma uç noktasını uyguluyorsa; yeni bir adres, bekleyen bir daveti olan bir adres ve bir hesabı olan bir adres için TEK bir yanıt ver. Üçüncü durum için 409 yanıtı veren bir sunucu, her üyeye örnekte başka kimlerin bulunduğunu gösteren bir kâhin sunmuş olur; e-posta aktarıcısı çalışmadığında 500 yanıtı veren bir sunucu ise onlara daha yavaş çalışan bir kâhin sunar. Üye davetlerini uygulamayan bir sunucu, yolda sıradan bilinmeyen yol yanıtı olan 404 döndürür ve instance.memberInvites: false bildirir.

Uyumlu bir sunucu şunların yok gereksinim duymaz: §3'teki kripto, herhangi bir yükün JSON olarak ayrıştırılması veya bir yemek kaydının ne olduğuna dair bilgi.

11. Alternatif bir istemci uygulama

§3 ve §5.1'deki 409 döngüsünün ötesinde:

  • İlk eşitlemeden önce §6 el sıkışmasını gerçekleştir ve uyuşmazlık durumunda reddet.
  • Parolayı, KEK'i veya DEK'i asla kalıcı bir depolama alanında saklama. Kilidi açarken türet, bellekte tut, sil.
  • Argon2id algoritmasını ana iş parçacığı dışında çalıştır. 64 MiB değerinde, düşük seviye telefonları gözle görülür şekilde dondurur.
  • Kayıt sırasında kurtarma kodunu üret, DEK'i bununla sar ve emanete alınabilmesi için kayıt gövdesinde sunucuya gönder (§3.1). Bunu atlayan bir istemci, hiçbir sıfırlama işleminin geri getiremeyeceği bir hesap oluşturur. Bunu kullanıcıya GÖSTERİP GÖSTERMEMEK istemcinin tercihidir; yönetilen bir örnekte emanetin mantığı, gösterilmesine gerek olmamasıdır.
  • Kullanıcı içine bir günlük koymadan önce, nasıl bir örneğe giriş yaptığını belirt. Yönetilen bir örnekte işletici emanetteki kodu tutar ve hesabı açabilir; kendi barındırdığın bir örnekte ise işletici kullanıcının kendisidir. İkisi de dürüsttür; ancak yabancı birinin varsayacağı seçenek bunlardan sadece biridir.
  • Kullanıcıdan kendi adresini yazmasını istemek yerine, adresi POST /v1/auth/invite-lookup içinden oku (§5.8.2) ve göster. Adresi hiç yazmazlarsa, kimsenin erişemeyeceği bir hesaba yanlış yazamazlar.
  • Kurtarma kanıtını openplate-sync:recovery-auth:v1 altında türet ve KEK_r değerini asla gönderme. İkisi aynı kod üzerindeki kardeşlerdir ve KEK dalını göndermek, günlüğü açan değerin bir HMAC'ini sunucuya teslim etmek anlamına gelir (§3.1).
  • POST /v1/auth/reset/open kurtarma kodunu geri verdikten sonra, bununla SIRADAN §5.14 recover-rotate işlemini çalıştır: yeni bir parola, yeniden sarılmış bir passphrase kaydı, yeni bir kod, yeniden sarılmış bir recovery kaydı ve emanet için yeni recoveryCode. Yarı yolda bırakmak, emaneti artık doğrulayıcısıyla eşleşmeyen bir hesap bırakır.
  • GET /blob üzerinden gelen 404 yanıtını hata olarak değil, "yeni hesap" olarak ele al.
  • authHash değerini gönder (§3.1'in auth HKDF dalı); parolayı, Argon2id çıktısını veya KEK_p değerini asla gönderme. Yanlış dalı türetmek sessizce gerçekleşir: kimlik doğrulaması sorunsuz çalışır ancak hiçbir şeyin şifresini çözemeyen bir anahtar üretir.
  • Yeni bir cihazda herhangi bir şey türetmeden önce KDF tanımlayıcısını al (§5.7). Varsayılanları varsayma; yükseltilmiş parametrelerle oluşturulmuş bir hesap bunlardan doğru şekilde türetilmez.
  • Yenileme belirtecini erişim belirteciyle aynı depolama katmanında tut ve kullanılmış bir belirteci asla yeniden kullanma: yeniden oynatma tüm aileyi iptal eder ve kullanıcının oturumunu kapatır (§4.2). Yenilemeleri sıraya koy; aynı yenileme belirteci için yarışan iki sekme tamamen bir hırsızlık gibi görünür.
  • 401 durumunda, bir kez yenile ve bir kez yeniden dene. İkinci bir 401 durumunda, döngüye girmek yerine kullanıcıyı giriş yapmaya yönlendir.

Bu sayfayı GitHub üzerinde düzenle