2026-09-04 00:41:46 -04:00
<!DOCTYPE html>
<!-- Generated by scripts/build-docs.js from doc/*.md. Edits here are overwritten;
change the Markdown instead. -->
< html lang = "en" dir = "ltr" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< meta http-equiv = "Content-Security-Policy" content = "default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests;" >
< meta http-equiv = "X-Content-Type-Options" content = "nosniff" >
< meta http-equiv = "Referrer-Policy" content = "strict-origin-when-cross-origin" >
< title > API Notes - SecureBit.chat</ title >
< meta name = "description" content = "API Notes — SecureBit.chat technical documentation." >
< meta name = "robots" content = "index, follow, max-image-preview:large, max-snippet:-1" >
< link rel = "canonical" href = "https://securebit.chat/docs/api/" >
< link rel = "icon" type = "image/x-icon" href = "/logo/favicon.ico" >
< meta property = "og:site_name" content = "SecureBit.chat" >
< meta property = "og:title" content = "API Notes - SecureBit.chat" >
< meta property = "og:description" content = "API Notes — SecureBit.chat technical documentation." >
< meta property = "og:url" content = "https://securebit.chat/docs/api/" >
< meta property = "og:type" content = "article" >
< meta property = "og:locale" content = "en_US" >
< meta property = "og:image" content = "https://securebit.chat/assets/social-card.png" >
< meta name = "twitter:card" content = "summary_large_image" >
< meta name = "twitter:title" content = "API Notes - SecureBit.chat" >
< meta name = "twitter:description" content = "API Notes — SecureBit.chat technical documentation." >
< meta name = "twitter:image" content = "https://securebit.chat/assets/social-card.png" >
< script type = "application/ld+json" >
{
"@context" : "https://schema.org" ,
"@type" : "TechArticle" ,
"headline" : "API Notes" ,
"description" : "API Notes — SecureBit.chat technical documentation." ,
"url" : "https://securebit.chat/docs/api/" ,
"inLanguage" : "en" ,
"isPartOf" : {
"@type" : "WebSite" ,
"@id" : "https://securebit.chat/#website"
},
"publisher" : {
"@id" : "https://securebit.chat/#organization"
}
}
</ script >
< style >
2026-09-04 17:38:41 -04:00
/* These pages carry no script — that is the point of them — so the theme can only
come from the media query. There is no toggle here and no stored preference: a
reference page follows the reader's system and nothing else. The values are the
same two palettes as src/styles/theme.css, restated because these pages do not
load the app's stylesheet and are not going to start for eleven declarations. */
: root {
color-scheme : dark ;
--d-ink : 255 , 255 , 255 ;
--d-bg : #0f0f11 ;
--d-bg-deep : #0b0b0e ;
--d-code-bg : #17171c ;
--d-text : #d6d6dc ;
--d-heading : #f4f4f6 ;
--d-strong : #e8e8eb ;
--d-body : #a9a9b3 ;
--d-pre : #c9c9d1 ;
--d-muted : #8a8a92 ;
--d-faint : #6b6b73 ;
--d-accent : #f0892a ;
--d-accent-rgb : 240 , 137 , 42 ;
}
@ media ( prefers-color-scheme : light ) {
: root {
color-scheme : light ;
--d-ink : 0 , 0 , 0 ;
--d-bg : #fbfbfc ;
--d-bg-deep : #f2f3f5 ;
--d-code-bg : #f2f3f5 ;
--d-text : #26262c ;
--d-heading : #0e0e12 ;
--d-strong : #1b1b20 ;
--d-body : #43434c ;
--d-pre : #2f3340 ;
--d-muted : #63636c ;
--d-faint : #7c7c85 ;
--d-accent : #b05c08 ;
--d-accent-rgb : 176 , 92 , 8 ;
}
}
2026-09-04 00:41:46 -04:00
* { box-sizing : border-box ; }
body {
margin : 0 ;
2026-09-04 17:38:41 -04:00
background : var ( -- d - bg );
color : var ( -- d - text );
2026-09-04 00:41:46 -04:00
font-family : Inter , system-ui , - apple-system , "Segoe UI" , sans-serif ;
font-size : 16 px ;
line-height : 1.68 ;
}
. wrap { max-width : 46 rem ; margin : 0 auto ; padding : 28 px 24 px 90 px ; }
. top {
display : flex ; flex-wrap : wrap ; gap : 8 px 18 px ; align-items : baseline ;
padding-bottom : 16 px ; margin-bottom : 40 px ;
2026-09-04 17:38:41 -04:00
border-bottom : 1 px solid rgba ( var ( -- d - ink ), .08 );
2026-09-04 00:41:46 -04:00
font-size : 13.5 px ;
}
2026-09-04 17:38:41 -04:00
. top a { color : var ( -- d - muted ); text-decoration : none ; }
. top a : hover , . top a : focus-visible { color : var ( -- d - accent ); }
. top . brand { color : var ( -- d - accent ); font-weight : 700 ; letter-spacing : .04 em ; }
. top . here { color : var ( -- d - text ); margin-inline-start : auto ; }
h1 { font-size : clamp ( 28 px , 5 vw , 36 px ); font-weight : 800 ; letter-spacing : -1 px ; line-height : 1.14 ; color : var ( -- d - heading ); margin : 0 0 24 px ; }
h2 { font-size : 22 px ; font-weight : 700 ; letter-spacing : -.4 px ; color : var ( -- d - heading ); margin : 46 px 0 12 px ; padding-top : 14 px ; border-top : 1 px solid rgba ( var ( -- d - ink ), .07 ); }
h3 { font-size : 17.5 px ; font-weight : 700 ; color : var ( -- d - strong ); margin : 30 px 0 8 px ; }
h4 { font-size : 15.5 px ; font-weight : 700 ; color : var ( -- d - strong ); margin : 22 px 0 6 px ; }
p , li { color : var ( -- d - body ); }
2026-09-04 00:41:46 -04:00
p { margin : 0 0 16 px ; }
ul , ol { padding-inline-start : 22 px ; margin : 0 0 16 px ; }
li { margin : 5 px 0 ; }
2026-09-04 17:38:41 -04:00
a { color : var ( -- d - accent ); text-underline-offset : 2 px ; }
strong { color : var ( -- d - strong ); }
code { font-family : ui-monospace , "SF Mono" , Menlo , monospace ; font-size : .88 em ; background : var ( -- d - code - bg ); border : 1 px solid rgba ( var ( -- d - ink ), .07 ); border-radius : 4 px ; padding : 1 px 5 px ; color : var ( -- d - strong ); }
pre { background : var ( -- d - bg - deep ); border : 1 px solid rgba ( var ( -- d - ink ), .08 ); border-radius : 8 px ; padding : 14 px 16 px ; overflow-x : auto ; margin : 0 0 18 px ; }
pre code { background : none ; border : 0 ; padding : 0 ; font-size : 13 px ; line-height : 1.62 ; color : var ( -- d - pre ); }
2026-09-04 00:41:46 -04:00
. tablewrap { overflow-x : auto ; margin : 0 0 20 px ; }
table { border-collapse : collapse ; width : 100 % ; font-size : 14.5 px ; min-width : 30 rem ; }
2026-09-04 17:38:41 -04:00
th { text-align : start ; color : var ( -- d - muted ); font-weight : 600 ; font-size : 12 px ; letter-spacing : .08 em ; text-transform : uppercase ; padding : 0 14 px 8 px 0 ; border-bottom : 1 px solid rgba ( var ( -- d - ink ), .12 ); }
td { padding : 9 px 14 px 9 px 0 ; border-bottom : 1 px solid rgba ( var ( -- d - ink ), .06 ); vertical-align : top ; color : var ( -- d - body ); }
2026-09-04 00:41:46 -04:00
td : first-child , th : first-child { padding-inline-start : 0 ; }
2026-09-04 17:38:41 -04:00
blockquote { margin : 0 0 18 px ; padding : 2 px 0 2 px 16 px ; border-inline-start : 3 px solid rgba ( var ( -- d - accent - rgb ), .4 ); color : var ( -- d - muted ); }
hr { border : 0 ; border-top : 1 px solid rgba ( var ( -- d - ink ), .08 ); margin : 34 px 0 ; }
2026-09-04 00:41:46 -04:00
img { max-width : 100 % ; height : auto ; }
2026-09-04 17:38:41 -04:00
a : focus-visible { outline : 2 px solid var ( -- d - accent ); outline-offset : 2 px ; border-radius : 2 px ; }
. more { margin-top : 64 px ; padding-top : 22 px ; border-top : 1 px solid rgba ( var ( -- d - ink ), .08 ); }
. more h2 { font-size : 13 px ; letter-spacing : .12 em ; text-transform : uppercase ; color : var ( -- d - faint ); border : 0 ; margin : 0 0 12 px ; padding : 0 ; font-weight : 700 ; }
2026-09-04 00:41:46 -04:00
. more ul { list-style : none ; padding : 0 ; margin : 0 ; display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 15 rem , 1 fr )); gap : 4 px 24 px ; }
2026-09-04 17:38:41 -04:00
. more li { margin : 0 ; padding : 7 px 0 ; border-bottom : 1 px solid rgba ( var ( -- d - ink ), .05 ); font-size : 14.5 px ; }
2026-09-04 00:41:46 -04:00
@ media ( prefers-reduced-motion : reduce ) { * { animation : none !important ; transition : none !important ; } }
</ style >
</ head >
< body >
< div class = "wrap" >
< nav class = "top" >
< a class = "brand" href = "/" > SecureBit.chat</ a >
< a href = "/docs/" > Documentation</ a >
< a href = "https://github.com/SecureBitChat/securebit-chat" rel = "noopener" > GitHub</ a >
< span class = "here" > API Notes</ span >
</ nav >
< h1 > API Notes</ h1 >
< h2 id = "enhancedsecurewebrtcmanager" > EnhancedSecureWebRTCManager</ h2 >
< h3 id = "verification" > Verification</ h3 >
< ul >
< li >< code > confirmVerification(userCode)</ code > validates a manually entered SAS code.</ li >
< li > Verification succeeds only after both local and remote confirmations are present.</ li >
< li >< code > isVerified</ code > is assigned in one place (< code > _setVerifiedStatus</ code > ), which refuses any
SAS-based transition without a recorded local confirmation.</ li >
< li > Control frames listed in < code > POST_VERIFICATION_CONTROL_TYPES</ code > (reconnection
signalling, call setup, message deletion, delivery receipts) are only acted on
after verification. The set is an allowlist; unrecognised frame types are
rejected by the chat channel' s default-deny branch.</ li >
< li > Protocol version < code > 4.1</ code > is enforced during offer/answer processing.</ li >
</ ul >
< h3 id = "forward-secrecy" > Forward secrecy</ h3 >
< ul >
< li >< code > isRatchetActive()</ code > reports whether the Double Ratchet is running on this
connection. It is negotiated: both peers advertise < code > RATCHET_VERSION</ code > in the
offer and answer, and a peer that does not falls back to per-session keys.</ li >
< li >< code > _ratchet.canEncrypt</ code > is false on the joining peer until the inviting peer' s
first message arrives, because the sending chain does not exist until then. Callers
must check it rather than assume; the send path falls back to session keys for
those first frames.</ li >
< li >< code > _ratchet.getState()</ code > returns counters and the number of retained keys for
diagnostics. It exposes no key material.</ li >
< li > Ratcheted chat arrives as < code > MESSAGE_TYPES.RATCHET_MESSAGE</ code > with < code > h</ code > (the header
string, used verbatim as AES-GCM additional data) and < code > c</ code > (base64 body). The
header must be passed back to < code > decrypt()</ code > exactly as received; re-serialising
it can change a byte and fail authentication.</ li >
</ ul >
< h3 id = "privacy-mode" > Privacy mode</ h3 >
< ul >
< li > relay-only configuration sets WebRTC < code > iceTransportPolicy</ code > to < code > " relay" </ code > .</ li >
< li > TURN availability is checked before claiming IP protection.</ li >
</ ul >
< h3 id = "file-transfer-callbacks" > File transfer callbacks</ h3 >
< ul >
< li >< code > setFileTransferCallbacks(onProgress, onReceived, onError, onIncomingRequest)</ code > updates manager fields and any live < code > EnhancedSecureFileTransfer</ code > instance.</ li >
< li > Passing < code > null</ code > values detaches callbacks from the active transfer system.</ li >
</ ul >
< h3 id = "voice-messages" > Voice messages</ h3 >
< ul >
< li >< code > sendFile(file, options)</ code > accepts an optional < code > options</ code > object. < code > options.voice</ code >
(< code > { dur, bars }</ code > ) marks the transfer as a voice note and rides along as unsigned
metadata; < code > options.uiId</ code > correlates progress events to a UI bubble before the
< code > fileId</ code > resolves.</ li >
< li >< code > onProgress</ code > receives < code > { fileId, uiId, direction, progress, isVoice, voice }</ code > .
< code > onIncomingFileRequest</ code > and < code > onReceived</ code > include < code > isVoice</ code > and < code > voice</ code > so the UI
can auto-accept and render a voice bubble instead of a file card.</ li >
< li > The < code > isVoice</ code > a callback receives is the < strong > receiver' s</ strong > verdict, not the
sender' s claim: < code > validateIncomingMetadata</ code > clears it unless the transfer
declares a recognised audio MIME type and fits the per-note and per-session
size budgets. A transfer that fails those checks is not rejected; it simply
loses the consent-free shortcut and is offered as a normal file.</ li >
</ ul >
< h2 id = "enhancedsecurefiletransfer" > EnhancedSecureFileTransfer</ h2 >
< h3 id = "incoming-transfers" > Incoming transfers</ h3 >
< ul >
< li > metadata is validated before prompting</ li >
< li > acceptance is explicit</ li >
< li > receive buffers are allocated only after consent</ li >
< li > file type acceptance is allowlist-based</ li >
</ ul >
< h3 id = "cleanup" > Cleanup</ h3 >
< ul >
< li > pending sender consent promises are rejected on cleanup</ li >
< li > consent timeouts are cleared immediately</ li >
< li > retained received buffers are bounded</ li >
< li > evicted download handles fail with a user-facing availability message</ li >
</ ul >
< h2 id = "securepersistentkeystorage" > SecurePersistentKeyStorage</ h2 >
< ul >
< li > metadata is encrypted before storage</ li >
< li > legacy plaintext records migrate lazily</ li >
< li > corrupted encrypted metadata is ignored safely</ li >
</ ul >
< nav class = "more" >
< h2 > More documentation</ h2 >
< ul >
< li >< a href = "/docs/" > Documentation</ a ></ li >
< li >< a href = "/docs/faq/" > Frequently asked questions</ a ></ li >
< li >< a href = "/docs/architecture/" > Architecture</ a ></ li >
< li >< a href = "/docs/cryptography/" > Cryptography</ a ></ li >
< li >< a href = "/docs/descriptor-sbq2/" > SBQ2 — connection descriptor v2</ a ></ li >
< li >< a href = "/docs/configuration/" > Configuration</ a ></ li >
< li >< a href = "/docs/calls/" > Voice and video calls</ a ></ li >
< li >< a href = "/docs/contributing/" > Contributing</ a ></ li >
< li >< a href = "/docs/use-policy/" > Use policy and limitations</ a ></ li >
</ ul >
</ nav >
</ div >
</ body >
</ html >