[{"data":1,"prerenderedAt":2085},["ShallowReactive",2],{"navigation":3,"/api/shares/client-side-encryption":258,"/api/shares/client-side-encryption-surround":2080},[4,22,63,85,115,131,219,245],{"title":5,"path":6,"stem":7,"children":8,"page":21},"Getting Started","/getting-started","1.getting-started",[9,13,17],{"title":10,"path":11,"stem":12},"Introduction","/getting-started/introduction","1.getting-started/1.introduction",{"title":14,"path":15,"stem":16},"Quick Start","/getting-started/quick-start","1.getting-started/2.quick-start",{"title":18,"path":19,"stem":20},"Key Concepts","/getting-started/key-concepts","1.getting-started/3.key-concepts",false,{"title":23,"path":24,"stem":25,"children":26,"page":21},"Guides","/guides","2.guides",[27,31,35,39,43,47,51,55,59],{"title":28,"path":29,"stem":30},"Quick Share Guide","/guides/quick-share","2.guides/1.quick-share",{"title":32,"path":33,"stem":34},"Slack Integration","/guides/slack-integration","2.guides/2.slack-integration",{"title":36,"path":37,"stem":38},"Enterprise Setup","/guides/enterprise-setup","2.guides/3.enterprise-setup",{"title":40,"path":41,"stem":42},"Creating a Share","/guides/creating-a-share","2.guides/4.creating-a-share",{"title":44,"path":45,"stem":46},"Zero-Knowledge Custody","/guides/zero-knowledge-custody","2.guides/5.zero-knowledge-custody",{"title":48,"path":49,"stem":50},"Secure Requests","/guides/secure-requests","2.guides/6.secure-requests",{"title":52,"path":53,"stem":54},"Receiving a Secure Request","/guides/receiving-a-secure-request","2.guides/7.receiving-a-secure-request",{"title":56,"path":57,"stem":58},"SecurePaste","/guides/securepaste","2.guides/8.securepaste",{"title":60,"path":61,"stem":62},"Notifications","/guides/notifications","2.guides/9.notifications",{"title":64,"icon":65,"path":66,"stem":67,"children":68,"page":21},"Account","i-lucide-user-cog","/account","3.account",[69,73,77,81],{"title":70,"path":71,"stem":72},"Signing In","/account/signing-in","3.account/1.signing-in",{"title":74,"path":75,"stem":76},"Plans and Share Allowance","/account/plans-and-share-allowance","3.account/2.plans-and-share-allowance",{"title":78,"path":79,"stem":80},"Billing and Subscription","/account/billing-and-subscription","3.account/3.billing-and-subscription",{"title":82,"path":83,"stem":84},"Plans and Limits","/account/plans-and-limits","3.account/4.plans-and-limits",{"title":86,"icon":87,"path":88,"stem":89,"children":90,"page":21},"Teams","i-lucide-users","/teams","4.teams",[91,95,99,103,107,111],{"title":92,"path":93,"stem":94},"Teams and Contexts","/teams/teams-and-contexts","4.teams/1.teams-and-contexts",{"title":96,"path":97,"stem":98},"Members, Roles and Seats","/teams/members-roles-and-seats","4.teams/2.members-roles-and-seats",{"title":100,"path":101,"stem":102},"Joining a Team","/teams/joining-a-team","4.teams/3.joining-a-team",{"title":104,"path":105,"stem":106},"Custom Branding","/teams/custom-branding","4.teams/4.custom-branding",{"title":108,"path":109,"stem":110},"Policy and Audit","/teams/policy-and-audit","4.teams/5.policy-and-audit",{"title":112,"path":113,"stem":114},"Zero-Knowledge for Teams","/teams/zero-knowledge-for-teams","4.teams/6.zero-knowledge-for-teams",{"title":116,"path":117,"stem":118,"children":119,"icon":130},"Help","/help","5.help/0.index",[120,122,126],{"title":121,"path":117,"stem":118},"Help and Support",{"title":123,"path":124,"stem":125},"Link Not Working","/help/link-not-working","5.help/1.link-not-working",{"title":127,"path":128,"stem":129},"Error Messages","/help/error-messages","5.help/2.error-messages","i-lucide-life-buoy",{"title":132,"path":133,"stem":134,"children":135},"API Reference","/api","6.api/0.index",[136,137,141,167,189,193,211,215],{"title":132,"path":133,"stem":134},{"title":138,"path":139,"stem":140},"Authentication","/api/authentication","6.api/1.authentication",{"title":142,"path":143,"stem":144,"children":145},"Shares","/api/shares","6.api/2.shares/0.index",[146,147,151,155,159,163],{"title":142,"path":143,"stem":144},{"title":148,"path":149,"stem":150},"Create a share","/api/shares/create","6.api/2.shares/1.create",{"title":152,"path":153,"stem":154},"List shares","/api/shares/list","6.api/2.shares/2.list",{"title":156,"path":157,"stem":158},"Retrieve a share","/api/shares/retrieve","6.api/2.shares/3.retrieve",{"title":160,"path":161,"stem":162},"Expire a share","/api/shares/delete","6.api/2.shares/4.delete",{"title":164,"path":165,"stem":166},"Client-side encryption","/api/shares/client-side-encryption","6.api/2.shares/5.client-side-encryption",{"title":168,"path":169,"stem":170,"children":171},"Secure requests","/api/requests","6.api/3.requests/0.index",[172,173,177,181,185],{"title":168,"path":169,"stem":170},{"title":174,"path":175,"stem":176},"Create a request","/api/requests/create","6.api/3.requests/1.create",{"title":178,"path":179,"stem":180},"List and retrieve requests","/api/requests/list","6.api/3.requests/2.list",{"title":182,"path":183,"stem":184},"Read submissions","/api/requests/submissions","6.api/3.requests/3.submissions",{"title":186,"path":187,"stem":188},"Expire or delete a request","/api/requests/delete","6.api/3.requests/4.delete",{"title":190,"path":191,"stem":192},"Stats","/api/stats","6.api/4.stats",{"title":194,"path":195,"stem":196,"children":197},"Webhooks","/api/webhooks","6.api/5.webhooks/0.index",[198,199,203,207],{"title":194,"path":195,"stem":196},{"title":200,"path":201,"stem":202},"Event reference","/api/webhooks/events","6.api/5.webhooks/1.events",{"title":204,"path":205,"stem":206},"Verifying signatures","/api/webhooks/verifying-signatures","6.api/5.webhooks/2.verifying-signatures",{"title":208,"path":209,"stem":210},"Delivery and retries","/api/webhooks/delivery-and-retries","6.api/5.webhooks/3.delivery-and-retries",{"title":212,"path":213,"stem":214},"MCP server","/api/mcp","6.api/6.mcp",{"title":216,"path":217,"stem":218},"Errors and Rate Limits","/api/errors-and-limits","6.api/7.errors-and-limits",{"title":220,"path":221,"stem":222,"children":223},"SDKs","/sdks","7.sdks/0.index",[224,225,229,233,237,241],{"title":220,"path":221,"stem":222},{"title":226,"path":227,"stem":228},"Node SDK","/sdks/node","7.sdks/1.node",{"title":230,"path":231,"stem":232},"Python SDK","/sdks/python","7.sdks/2.python",{"title":234,"path":235,"stem":236},"Go SDK","/sdks/go","7.sdks/3.go",{"title":238,"path":239,"stem":240},"Rust SDK","/sdks/rust","7.sdks/4.rust",{"title":242,"path":243,"stem":244},"Conformance vectors","/sdks/conformance-vectors","7.sdks/5.conformance-vectors",{"title":246,"path":247,"stem":248,"children":249,"page":21},"Security","/security","8.security",[250,254],{"title":251,"path":252,"stem":253},"Encryption","/security/encryption","8.security/1.encryption",{"title":255,"path":256,"stem":257},"Compliance","/security/compliance","8.security/2.compliance",{"id":259,"title":164,"api":260,"body":261,"description":2075,"extension":2076,"links":260,"meta":2077,"navigation":1497,"path":165,"seo":2078,"stem":166,"__hash__":2079},"docs/6.api/2.shares/5.client-side-encryption.md",null,{"type":262,"value":263,"toc":2052},"minimark",[264,268,271,291,306,311,406,410,459,462,525,538,552,557,563,648,651,655,665,678,681,685,688,783,890,907,916,920,925,931,989,996,1006,1025,1032,1038,1042,1045,1051,1057,1068,1073,1077,1083,1089,1098,1101,1105,1111,1114,1117,1129,1132,1138,1142,1148,1151,1160,1166,1176,1180,1186,1197,1204,1207,1213,1219,1228,1235,1239,1242,1297,1307,1309,1315,1321,1325,1331,1345,1348,1356,1360,1366,1438,1447,1458,1461,1470,1474,1481,1838,1841,1973,1982,2001,2005,2017,2020,2045,2048],[265,266,267],"p",{},"A content key is generated in your client and never sent to CredenShare. The server stores your ciphertext plus a hash of an access token derived from that key, and neither one lets it decrypt anything. The key reaches the recipient in the URL fragment, which browsers do not transmit to servers.",[265,269,270],{},"That is the whole model. What follows is the exact set of parameters that make it interoperable, because two implementations that disagree on a single one of them produce ciphertext the other cannot read.",[272,273,276],"callout",{"color":274,"icon":275},"warning","i-lucide-alert-triangle",[265,277,278,279,283,284,286,287,290],{},"Do not substitute your own values here. Every constant on this page — algorithm, length, salt, ",[280,281,282],"code",{},"info"," string, encoding, byte order — is part of the wire format. A share encrypted with a different ",[280,285,282],{}," string still posts, still returns ",[280,288,289],{},"201",", and can never be opened by the recipient's browser.",[272,292,294],{"color":282,"icon":293},"i-lucide-package",[265,295,296,300,301,305],{},[297,298,299],"strong",{},"This page is the specification, not the only way to do it."," The ",[302,303,304],"a",{"href":221},"official SDKs"," for Node, Python, Go and Rust implement everything below. Read on if you are writing a client in another language, verifying one, or want to understand what the SDKs do on your behalf.",[307,308,310],"h2",{"id":309},"what-we-receive-and-what-we-never-receive","What we receive and what we never receive",[312,313,314,327],"table",{},[315,316,317],"thead",{},[318,319,320,324],"tr",{},[321,322,323],"th",{},"Your client holds",[321,325,326],{},"CredenShare receives",[328,329,330,339,346,353,361,372,382,392],"tbody",{},[318,331,332,336],{},[333,334,335],"td",{},"The content key (32 bytes)",[333,337,338],{},"Never",[318,340,341,344],{},[333,342,343],{},"The passcode, if any",[333,345,338],{},[318,347,348,351],{},[333,349,350],{},"The plaintext field array",[333,352,338],{},[318,354,355,358],{},[333,356,357],{},"The URL fragment",[333,359,360],{},"Never — browsers do not send fragments",[318,362,363,366],{},[333,364,365],{},"—",[333,367,368,371],{},[280,369,370],{},"data",": your ciphertext, stored verbatim",[318,373,374,376],{},[333,375,365],{},[333,377,378,381],{},[280,379,380],{},"access_token",": stored only as a SHA-256 hash",[318,383,384,386],{},[333,385,365],{},[333,387,388,391],{},[280,389,390],{},"passcode_verifier",": a one-way derivation, if you use a passcode",[318,393,394,396],{},[333,395,365],{},[333,397,398,401,402,405],{},[280,399,400],{},"title"," and ",[280,403,404],{},"description",": plaintext metadata",[307,407,409],{"id":408},"primitives","Primitives",[312,411,412,422],{},[315,413,414],{},[318,415,416,419],{},[321,417,418],{},"Primitive",[321,420,421],{},"Parameters",[328,423,424,432,440,448],{},[318,425,426,429],{},[333,427,428],{},"Hash",[333,430,431],{},"SHA-256",[318,433,434,437],{},[333,435,436],{},"KDF",[333,438,439],{},"HKDF-SHA-256 (RFC 5869), extract-and-expand",[318,441,442,445],{},[333,443,444],{},"AEAD",[333,446,447],{},"AES-256-GCM, 96-bit IV, 128-bit tag",[318,449,450,453],{},[333,451,452],{},"Curve",[333,454,455,456],{},"NIST P-256 (secp256r1), ECDH — only for ",[280,457,458],{},"item_key_wrap",[265,460,461],{},"Two encodings appear below, and they are not interchangeable:",[312,463,464,474],{},[315,465,466],{},[318,467,468,471],{},[321,469,470],{},"Term",[321,472,473],{},"Meaning",[328,475,476,493],{},[318,477,478,483],{},[333,479,480],{},[280,481,482],{},"base64",[333,484,485,486,489,490,492],{},"Standard base64 ",[297,487,488],{},"with"," padding (RFC 4648 §4). Used for ",[280,491,370],{},", which travels in a JSON body.",[318,494,495,500],{},[333,496,497],{},[280,498,499],{},"base64url",[333,501,502,503,506,507,510,511,514,515,510,518,514,521,524],{},"URL-safe base64, ",[297,504,505],{},"no"," padding (RFC 4648 §5): ",[280,508,509],{},"+"," becomes ",[280,512,513],{},"-",", ",[280,516,517],{},"/",[280,519,520],{},"_",[280,522,523],{},"="," stripped. Used for anything that travels in a URL.",[265,526,527,530,531,533,534,537],{},[280,528,529],{},"HKDF-SHA256(ikm, salt, info, len)"," below takes ",[280,532,282],{}," as UTF-8 bytes and returns ",[280,535,536],{},"len"," bytes.",[272,539,541],{"color":282,"icon":540},"i-lucide-info",[265,542,543,544,547,548,551],{},"Where a salt is written ",[280,545,546],{},"\"\""," it is a ",[297,549,550],{},"zero-length byte string",". Some HKDF wrappers make the salt argument mandatory; pass an empty buffer there rather than a placeholder of your own. Any filler value that is not zero bytes changes the output, and the recipient — who derives from the key alone — has no way to reproduce it.",[553,554,556],"h3",{"id":555},"domain-separation","Domain separation",[265,558,559,560,562],{},"Every derivation from the same input uses a distinct ",[280,561,282],{}," string. This is what lets you hand us the access token while keeping the content key: the two outputs are independent, so possession of one says nothing about the other.",[312,564,565,576],{},[315,566,567],{},[318,568,569,573],{},[321,570,571],{},[280,572,282],{},[321,574,575],{},"Purpose",[328,577,578,588,598,608,618,628,638],{},[318,579,580,585],{},[333,581,582],{},[280,583,584],{},"content",[333,586,587],{},"Content encryption key, no passcode",[318,589,590,595],{},[333,591,592],{},[280,593,594],{},"content|\u003Cpasscode>",[333,596,597],{},"Content encryption key with a passcode",[318,599,600,605],{},[333,601,602],{},[280,603,604],{},"access",[333,606,607],{},"Access token",[318,609,610,615],{},[333,611,612],{},[280,613,614],{},"verify",[333,616,617],{},"Passcode verifier",[318,619,620,625],{},[333,621,622],{},[280,623,624],{},"custody",[333,626,627],{},"Custody secret to keypair seed",[318,629,630,635],{},[333,631,632],{},[280,633,634],{},"crs-ecdh-p256-scalar",[333,636,637],{},"Seed to P-256 private scalar",[318,639,640,645],{},[333,641,642],{},[280,643,644],{},"crs-request-submission",[333,646,647],{},"ECDH shared secret to wrapping key",[265,649,650],{},"Do not add, rename or reuse these strings. A collision silently makes two secrets that must stay independent equal to each other, and nothing anywhere looks wrong.",[307,652,654],{"id":653},"the-content-key","The content key",[656,657,663],"pre",{"className":658,"code":660,"language":661,"meta":662},[659],"language-text","contentKey = 32 random bytes\n","text","",[280,664,660],{"__ignoreMap":662},[265,666,667,668,514,671,514,674,677],{},"From a cryptographically secure source — ",[280,669,670],{},"crypto.getRandomValues",[280,672,673],{},"secrets.token_bytes",[280,675,676],{},"crypto/rand",". It is generated per share, used for nothing else, and never transmitted.",[265,679,680],{},"This is the only thing that can decrypt the share. If you lose it between generating it and building the link, the content is unrecoverable — by you, by the recipient, and by CredenShare.",[307,682,684],{"id":683},"the-plaintext-a-field-array","The plaintext: a field array",[265,686,687],{},"The value you encrypt is the JSON serialization of an array of field objects:",[656,689,693],{"className":690,"code":691,"language":692,"meta":662,"style":662},"language-json shiki shiki-themes github-light github-dark github-dark","[\n  { \"key\": \"Database password\", \"value\": \"s3cr3t\", \"type\": \"password\" },\n  { \"key\": \"Host\", \"value\": \"db.internal.example\", \"type\": \"text\" }\n]\n","json",[280,694,695,704,744,777],{"__ignoreMap":662},[696,697,700],"span",{"class":698,"line":699},"line",1,[696,701,703],{"class":702},"slsVL","[\n",[696,705,707,710,714,717,721,723,726,728,731,733,736,738,741],{"class":698,"line":706},2,[696,708,709],{"class":702},"  { ",[696,711,713],{"class":712},"suiK_","\"key\"",[696,715,716],{"class":702},": ",[696,718,720],{"class":719},"sfrk1","\"Database password\"",[696,722,514],{"class":702},[696,724,725],{"class":712},"\"value\"",[696,727,716],{"class":702},[696,729,730],{"class":719},"\"s3cr3t\"",[696,732,514],{"class":702},[696,734,735],{"class":712},"\"type\"",[696,737,716],{"class":702},[696,739,740],{"class":719},"\"password\"",[696,742,743],{"class":702}," },\n",[696,745,747,749,751,753,756,758,760,762,765,767,769,771,774],{"class":698,"line":746},3,[696,748,709],{"class":702},[696,750,713],{"class":712},[696,752,716],{"class":702},[696,754,755],{"class":719},"\"Host\"",[696,757,514],{"class":702},[696,759,725],{"class":712},[696,761,716],{"class":702},[696,763,764],{"class":719},"\"db.internal.example\"",[696,766,514],{"class":702},[696,768,735],{"class":712},[696,770,716],{"class":702},[696,772,773],{"class":719},"\"text\"",[696,775,776],{"class":702}," }\n",[696,778,780],{"class":698,"line":779},4,[696,781,782],{"class":702},"]\n",[312,784,785,798],{},[315,786,787],{},[318,788,789,792,795],{},[321,790,791],{},"Member",[321,793,794],{},"Required",[321,796,797],{},"Notes",[328,799,800,817,829,859,873],{},[318,801,802,807,810],{},[333,803,804],{},[280,805,806],{},"key",[333,808,809],{},"yes",[333,811,812,813,816],{},"The field's visible ",[297,814,815],{},"label"," — what the recipient reads.",[318,818,819,824,826],{},[333,820,821],{},[280,822,823],{},"value",[333,825,809],{},[333,827,828],{},"The field's content.",[318,830,831,836,838],{},[333,832,833],{},[280,834,835],{},"type",[333,837,809],{},[333,839,840,841,514,843,514,846,514,849,514,852,514,855,858],{},"One of ",[280,842,661],{},[280,844,845],{},"password",[280,847,848],{},"date",[280,850,851],{},"multiline",[280,853,854],{},"markdown",[280,856,857],{},"source_code",".",[318,860,861,866,868],{},[333,862,863],{},[280,864,865],{},"selectedProgrammingLanguage",[333,867,505],{},[333,869,870,871,858],{},"Language hint for ",[280,872,857],{},[318,874,875,880,882],{},[333,876,877],{},[280,878,879],{},"filename",[333,881,505],{},[333,883,884,885,401,887,889],{},"For ",[280,886,857],{},[280,888,854],{},", offers the recipient a download using this name.",[272,891,892],{"color":274,"icon":275},[265,893,894,895,897,898,514,900,903,904,906],{},"The label member is ",[280,896,806],{},". It is not ",[280,899,815],{},[280,901,902],{},"name"," or ",[280,905,400],{},". A share built with the wrong member name encrypts, posts, decrypts and renders — with every field label blank and nothing anywhere reporting an error. This is the single most common integration mistake on this path.",[265,908,909,910,912,913,915],{},"Members you add that are not listed here ride along encrypted and come back unchanged on decrypt. An unrecognised ",[280,911,835],{}," is rendered as ",[280,914,661],{}," rather than rejected.",[307,917,919],{"id":918},"the-data-envelope","The data envelope",[265,921,922,924],{},[280,923,370],{}," is one base64 string containing three concatenated parts. There is no JSON, no header block and no separator: the parts are found by fixed offset.",[656,926,929],{"className":927,"code":928,"language":661,"meta":662},[659],"salt  = 16 random bytes\niv    = 12 random bytes\nkey   = HKDF-SHA256(contentKey, salt, \"content\", 32)\nbody  = AES-256-GCM(key, iv, utf8(JSON.stringify(fields)))\n\ndata  = base64(salt || iv || body)\n",[280,930,928],{"__ignoreMap":662},[312,932,933,946],{},[315,934,935],{},[318,936,937,940,943],{},[321,938,939],{},"Bytes",[321,941,942],{},"Length",[321,944,945],{},"Content",[328,947,948,962,976],{},[318,949,950,953,956],{},[333,951,952],{},"0–15",[333,954,955],{},"16",[333,957,958,961],{},[280,959,960],{},"salt"," — the HKDF salt, not a password salt",[318,963,964,967,970],{},[333,965,966],{},"16–27",[333,968,969],{},"12",[333,971,972,975],{},[280,973,974],{},"iv"," — the AES-GCM nonce",[318,977,978,981,984],{},[333,979,980],{},"28 onward",[333,982,983],{},"plaintext length + 16",[333,985,986],{},[280,987,988],{},"ciphertext || tag",[265,990,991,992,995],{},"The IV lives ",[297,993,994],{},"inside"," the envelope, at offset 16. It is not a separate request field and there is nowhere to put one.",[265,997,998,999,1001,1002,1005],{},"AES-GCM output is ",[280,1000,988],{}," in a single buffer, which is what WebCrypto returns. If your AEAD hands back the tag separately, concatenate in that order. The tag is 16 bytes, so the shortest structurally valid envelope is ",[280,1003,1004],{},"16 + 12 + 16"," = 44 bytes; a reader must reject anything shorter before attempting to decrypt.",[265,1007,1008,1010,1011,1013,1014,1016,1017,903,1019,1021,1022,1024],{},[280,1009,370],{}," uses standard base64 ",[297,1012,488],{}," padding. It travels in a JSON body, never in a URL, so the URL-safe alphabet is wrong here: the recipient decodes ",[280,1015,370],{}," with a standard-alphabet decoder, and a blob containing ",[280,1018,513],{},[280,1020,520],{}," fails to decode at all. Keep the two alphabets straight — ",[280,1023,370],{}," standard, everything in a URL url-safe.",[265,1026,1027,1028,1031],{},"There is no version byte in this envelope. The format version lives in the URL fragment instead, and ",[280,1029,1030],{},"encryption_type"," on the create names the scheme:",[656,1033,1036],{"className":1034,"code":1035,"language":661,"meta":662},[659],"encryption_type = \"e2ee-aes256-gcm\"\n",[280,1037,1035],{"__ignoreMap":662},[553,1039,1041],{"id":1040},"a-worked-envelope","A worked envelope",[265,1043,1044],{},"Using the two fields above, with the salt and IV fixed so the output is reproducible:",[656,1046,1049],{"className":1047,"code":1048,"language":661,"meta":662},[659],"contentKey = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\nsalt       = a0a1a2a3a4a5a6a7a8a9aaabacadaeaf\niv         = b0b1b2b3b4b5b6b7b8b9babb\nplaintext  = 123 bytes of UTF-8 JSON\nbody       = 139 bytes (123 ciphertext + 16 tag)\nenvelope   = 16 + 12 + 139 = 167 bytes  ->  224 base64 characters\n",[280,1050,1048],{"__ignoreMap":662},[656,1052,1055],{"className":1053,"code":1054,"language":661,"meta":662},[659],"oKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u1YWPEhJhUdlUBdeJpvQ4hb9yDJknecKV3wgwb0v\nGbPEUpQ3l3Ft8RQVYAGz20PsWDGw4+IT+eydl4C7nMEkAFT9MLCfAXV0TRR3Au1iKgpG/aTqaOZ9\ny4fehNC2KGj6yRX+TR41DQby7+lFCmEu6gbWCIuVjSIfUsdWk0nbrXhs1LBktPzMM2FSx6o=\n",[280,1056,1054],{"__ignoreMap":662},[265,1058,1059,1060,1063,1064,1067],{},"The string is wrapped here for the page; send it as one line. Decode it and the first 16 bytes are ",[280,1061,1062],{},"a0a1…aeaf"," and the next 12 are ",[280,1065,1066],{},"b0b1…babb",", which is a quick way to confirm your offsets.",[272,1069,1070],{"color":274,"icon":275},[265,1071,1072],{},"Fixed values are shown so you can check your implementation byte for byte. In production the salt and the IV must be freshly random per share. Reusing an IV under the same derived key breaks AES-GCM outright.",[553,1074,1076],{"id":1075},"with-a-passcode","With a passcode",[265,1078,1079,1080,1082],{},"A passcode changes exactly one thing — the ",[280,1081,282],{}," string:",[656,1084,1087],{"className":1085,"code":1086,"language":661,"meta":662},[659],"key = HKDF-SHA256(contentKey, salt, \"content|\" + passcode, 32)\n",[280,1088,1086],{"__ignoreMap":662},[265,1090,1091,1092,514,1094,1097],{},"The passcode is mixed into ",[280,1093,282],{},[297,1095,1096],{},"never into the salt",". The salt must stay reproducible from the stored envelope alone; the passcode is not stored anywhere.",[265,1099,1100],{},"The consequence is worth stating plainly: with a passcode, the fragment alone cannot decrypt the share. Someone who intercepts the link still needs the passcode, and CredenShare never has either.",[307,1102,1104],{"id":1103},"the-access-token","The access token",[656,1106,1109],{"className":1107,"code":1108,"language":661,"meta":662},[659],"access_token = base64url(HKDF-SHA256(contentKey, \"\", \"access\", 32))\n",[280,1110,1108],{"__ignoreMap":662},[265,1112,1113],{},"43 characters, no padding. The salt is empty on purpose: the recipient's browser must reproduce this value from the fragment alone, before it has fetched any ciphertext and therefore before it has seen any salt.",[265,1115,1116],{},"CredenShare stores only the hex-encoded SHA-256 of the token's trimmed value, and compares presented tokens in constant time. The token itself is never retained.",[265,1118,1119,1120,1123,1124,401,1126,1128],{},"It is a ",[297,1121,1122],{},"bearer capability for reading",", not a decryption key. Presenting it proves you hold the link; it is what stops someone who guesses a short code from burning a view-once share or stamping its first-view timestamp before being refused. It buys ciphertext and nothing else — the ",[280,1125,604],{},[280,1127,584],{}," derivations are independent, so the token reveals nothing about the key.",[265,1130,1131],{},"For the example content key above:",[656,1133,1136],{"className":1134,"code":1135,"language":661,"meta":662},[659],"access_token = d2DJ6L1GBXjLD-YhpdyVxJQNkLOHIovvBRUbSMcZf0A\n",[280,1137,1135],{"__ignoreMap":662},[307,1139,1141],{"id":1140},"the-passcode-verifier","The passcode verifier",[656,1143,1146],{"className":1144,"code":1145,"language":661,"meta":662},[659],"passcode_verifier = base64url(HKDF-SHA256(utf8(passcode), \"\", \"verify\", 32))\n",[280,1147,1145],{"__ignoreMap":662},[265,1149,1150],{},"Send the verifier, never the passcode. The passcode feeds the content key derivation, so handing us the passcode would hand us a component of the key — which is the one thing this design exists to prevent. The verifier is one-way, so the server can count failed attempts and enforce lockout without gaining any ability to decrypt.",[265,1152,1153,1154,1156,1157,1159],{},"Derive it from the passcode exactly as the user typed it, with the same bytes you fed into the ",[280,1155,594],{}," ",[280,1158,282],{}," string. Normalising in one place and not the other produces a share that rejects its own correct passcode.",[656,1161,1164],{"className":1162,"code":1163,"language":661,"meta":662},[659],"passcode \"hunter2\"  ->  SzNZ-iyCO2S6YZkhDBGLG162TOJ_zgHxThgosEtPcXY\n",[280,1165,1163],{"__ignoreMap":662},[265,1167,1168,1169,401,1172,1175],{},"Passcode protection is a plan feature; a create carrying a verifier on a plan without view protection is refused with a ",[280,1170,1171],{},"403",[280,1173,1174],{},"error_code"," 53.",[307,1177,1179],{"id":1178},"the-url-fragment-and-the-link","The URL fragment and the link",[656,1181,1184],{"className":1182,"code":1183,"language":661,"meta":662},[659],"fragment = \"1\" || base64url(contentKey)\n",[280,1185,1183],{"__ignoreMap":662},[265,1187,1188,1189,1192,1193,1196],{},"A single leading version character, then 43 characters of base64url — 44 characters in total. The fragment is ",[297,1190,1191],{},"bare",": there is no ",[280,1194,1195],{},"k="," prefix and no query-string shape.",[265,1198,1199,1200,1203],{},"That is a safety choice. A visible ",[280,1201,1202],{},"k=…"," tail reads as an optional appendix and invites truncation by link-mangling clients, and a truncated fragment produces a permanently unreadable share. As one opaque token it survives copy and paste.",[265,1205,1206],{},"The create response returns a short code, so you assemble the link:",[656,1208,1211],{"className":1209,"code":1210,"language":661,"meta":662},[659],"https://crs.sh/{short_code}#1{base64url(contentKey)}\n",[280,1212,1210],{"__ignoreMap":662},[656,1214,1217],{"className":1215,"code":1216,"language":661,"meta":662},[659],"https://crs.sh/a1b2c3d4#1AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8\n",[280,1218,1216],{"__ignoreMap":662},[272,1220,1221],{"color":274,"icon":275},[265,1222,1223,1224,1227],{},"A link built without the fragment is not a degraded link — it is ciphertext nobody can ever read. Not the recipient, not you, not CredenShare. If your integration ever emits ",[280,1225,1226],{},"https://crs.sh/{short_code}"," on its own, it is silently producing dead shares that look fine until someone opens one.",[265,1229,1230,1231,1234],{},"This is also why the create response has no ",[280,1232,1233],{},"url"," field. A link contains the content key, we have never had the content key, and returning a keyless link would look correct and fail at the moment of use.",[553,1236,1238],{"id":1237},"reading-a-fragment-back","Reading a fragment back",[265,1240,1241],{},"A client that opens links as well as creating them must reject a bad fragment rather than guess, and must distinguish the reasons — \"your link is incomplete\" and \"this share expired\" look identical on screen and have opposite remedies.",[312,1243,1244,1254],{},[315,1245,1246],{},[318,1247,1248,1251],{},[321,1249,1250],{},"Condition",[321,1252,1253],{},"Reason",[328,1255,1256,1266,1279,1288],{},[318,1257,1258,1261],{},[333,1259,1260],{},"Empty or absent",[333,1262,1263],{},[280,1264,1265],{},"missing-key",[318,1267,1268,1274],{},[333,1269,1270,1271],{},"Leading character is not ",[280,1272,1273],{},"1",[333,1275,1276],{},[280,1277,1278],{},"malformed-key",[318,1280,1281,1284],{},[333,1282,1283],{},"Body is not valid base64url",[333,1285,1286],{},[280,1287,1278],{},[318,1289,1290,1293],{},[333,1291,1292],{},"Decoded length is not 32",[333,1294,1295],{},[280,1296,1278],{},[265,1298,1299,1300,1303,1304,1306],{},"A leading ",[280,1301,1302],{},"#"," handed over by the browser is stripped before parsing. Note that base64url decoding must restore the stripped ",[280,1305,523],{}," padding first: some decoders reject unpadded input.",[307,1308,458],{"id":458},[265,1310,1311,1312,1314],{},"An API-created share is readable only from its link, because only the link carries the key. ",[280,1313,458],{}," is the optional field that also makes it readable later from your dashboard: it is the content key wrapped to your API key's custody public key, which we can store without being able to open.",[265,1316,1317,1318,1320],{},"You are the only party who can compute it. It requires the content key, which never reaches us, and the custody secret, which is the third part of your credential and must never be transmitted. See ",[302,1319,138],{"href":139}," for custody levels and what each one buys.",[553,1322,1324],{"id":1323},"the-custody-public-key","The custody public key",[656,1326,1329],{"className":1327,"code":1328,"language":661,"meta":662},[659],"seed      = HKDF-SHA256(utf8(custodySecret), \"\", \"custody\", 32)\nwide      = HKDF-SHA256(seed, \"\", \"crs-ecdh-p256-scalar\", 48)\nscalar    = (bigEndianInt(wide) mod (n - 1)) + 1        // n = the P-256 group order\npublicKey = scalar · G, uncompressed: 0x04 || X(32) || Y(32), 65 bytes\n",[280,1330,1328],{"__ignoreMap":662},[265,1332,1333,1336,1337,1340,1341,1344],{},[280,1334,1335],{},"scalar"," is encoded big-endian in exactly 32 bytes. Deriving 48 bytes rather than 32 before the reduction is deliberate: the extra 128 bits make the modular bias negligible. Adding 1 after reducing mod ",[280,1338,1339],{},"n - 1"," yields a scalar in ",[280,1342,1343],{},"[1, n - 1]",", excluding zero, which is not a valid private key.",[265,1346,1347],{},"Any machine holding the credential derives the same keypair, so stateless multi-runner automation needs no local key storage. Revoking the key revokes custody in the same motion: the wraps remain and nothing can open them.",[272,1349,1350],{"color":282,"icon":540},[265,1351,1352,1353,1355],{},"WebCrypto has no scalar-multiplication entry point, so this one step needs a P-256 implementation rather than a browser primitive. Every other construction on this page is pure WebCrypto. This is why ",[280,1354,458],{}," is optional, and why the example below omits it.",[553,1357,1359],{"id":1358},"the-wrap","The wrap",[656,1361,1364],{"className":1362,"code":1363,"language":661,"meta":662},[659],"ephemeral    = a fresh random P-256 keypair, per wrap, never reused\nsharedSecret = ECDH(ephemeral.private, custodyPublicKey)     // the 32-byte X coordinate\nsalt         = 16 random bytes\niv           = 12 random bytes\nwrappingKey  = HKDF-SHA256(sharedSecret, salt, \"crs-request-submission\", 32)\nbody         = AES-256-GCM(wrappingKey, iv, contentKey)\n\nitem_key_wrap = base64(0x01 || ephemeral.public(65) || salt(16) || iv(12) || body)\n",[280,1365,1363],{"__ignoreMap":662},[312,1367,1368,1378],{},[315,1369,1370],{},[318,1371,1372,1374,1376],{},[321,1373,939],{},[321,1375,942],{},[321,1377,945],{},[328,1379,1380,1393,1404,1415,1426],{},[318,1381,1382,1385,1387],{},[333,1383,1384],{},"0",[333,1386,1273],{},[333,1388,1389,1390],{},"Wrap format version, currently ",[280,1391,1392],{},"0x01",[318,1394,1395,1398,1401],{},[333,1396,1397],{},"1–65",[333,1399,1400],{},"65",[333,1402,1403],{},"Ephemeral public key, uncompressed",[318,1405,1406,1409,1411],{},[333,1407,1408],{},"66–81",[333,1410,955],{},[333,1412,1413],{},[280,1414,960],{},[318,1416,1417,1420,1422],{},[333,1418,1419],{},"82–93",[333,1421,969],{},[333,1423,1424],{},[280,1425,974],{},[318,1427,1428,1431,1434],{},[333,1429,1430],{},"94 onward",[333,1432,1433],{},"payload + 16",[333,1435,1436],{},[280,1437,988],{},[265,1439,1440,1441,1443,1444,1446],{},"The ",[280,1442,282],{}," string is literally ",[280,1445,644],{},", even here. It is one shared wrapping construction used for request submissions and for every zero-knowledge wrap; there is no share-specific label, and inventing one produces a wrap that unwraps to nothing.",[265,1448,1449,1450,1453,1454,1457],{},"Wrapping a 32-byte content key produces exactly ",[297,1451,1452],{},"142 bytes",", or 192 base64 characters: ",[280,1455,1456],{},"1 + 65 + 16 + 12 + 32 + 16",". That arithmetic is a useful field check on a new implementation.",[265,1459,1460],{},"The ephemeral keypair must be freshly generated for every wrap. Reusing one leaks the relationship between the wraps made with it.",[265,1462,1463,1464,1466,1467,537],{},"A reader must reject a leading byte other than ",[280,1465,1392],{}," rather than guessing, and must reject a blob shorter than ",[280,1468,1469],{},"1 + 65 + 16 + 12 + 16",[307,1471,1473],{"id":1472},"a-complete-example","A complete example",[265,1475,1476,1477,1480],{},"Dependency-free, WebCrypto only. This produces a valid ",[280,1478,1479],{},"POST /v1/shares"," body and the fragment to build the link with.",[656,1482,1486],{"className":1483,"code":1484,"language":1485,"meta":662,"style":662},"language-js shiki shiki-themes github-light github-dark github-dark","const enc = new TextEncoder()\n\nfunction b64(bytes) {\n  let s = ''\n  for (const b of bytes) s += String.fromCharCode(b)\n  return btoa(s)\n}\n\nconst b64url = (bytes) => b64(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n\nasync function hkdf(ikm, salt, info, lengthBytes) {\n  const base = await crypto.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits'])\n  const bits = await crypto.subtle.deriveBits(\n    { name: 'HKDF', hash: 'SHA-256', salt, info: enc.encode(info) },\n    base,\n    lengthBytes * 8\n  )\n  return new Uint8Array(bits)\n}\n\nasync function buildShare(fields, passcode) {\n  const contentKey = crypto.getRandomValues(new Uint8Array(32))\n  const salt = crypto.getRandomValues(new Uint8Array(16))\n  const iv = crypto.getRandomValues(new Uint8Array(12))\n\n  const info = passcode ? `content|${passcode}` : 'content'\n  const aesKey = await crypto.subtle.importKey(\n    'raw',\n    await hkdf(contentKey, salt, info, 32),\n    { name: 'AES-GCM' },\n    false,\n    ['encrypt']\n  )\n  const body = new Uint8Array(\n    await crypto.subtle.encrypt(\n      { name: 'AES-GCM', iv },\n      aesKey,\n      enc.encode(JSON.stringify(fields))\n    )\n  )\n\n  // salt(16) || iv(12) || ciphertext+tag\n  const envelope = new Uint8Array(16 + 12 + body.length)\n  envelope.set(salt, 0)\n  envelope.set(iv, 16)\n  envelope.set(body, 28)\n\n  const payload = {\n    encryption_type: 'e2ee-aes256-gcm',\n    data: b64(envelope),\n    access_token: b64url(await hkdf(contentKey, new Uint8Array(0), 'access', 32))\n  }\n  if (passcode) {\n    payload.passcode_verifier = b64url(\n      await hkdf(enc.encode(passcode), new Uint8Array(0), 'verify', 32)\n    )\n  }\n\n  // The content key never enters `payload`. It leaves only in the fragment.\n  return { payload, fragment: '1' + b64url(contentKey) }\n}\n","js",[280,1487,1488,1493,1499,1504,1509,1515,1521,1527,1532,1538,1543,1549,1555,1561,1567,1573,1579,1585,1591,1596,1601,1607,1613,1619,1625,1630,1636,1642,1648,1654,1660,1666,1672,1677,1683,1689,1695,1701,1707,1713,1718,1723,1729,1735,1741,1747,1753,1758,1764,1770,1776,1782,1788,1794,1800,1806,1811,1816,1821,1827,1833],{"__ignoreMap":662},[696,1489,1490],{"class":698,"line":699},[696,1491,1492],{},"const enc = new TextEncoder()\n",[696,1494,1495],{"class":698,"line":706},[696,1496,1498],{"emptyLinePlaceholder":1497},true,"\n",[696,1500,1501],{"class":698,"line":746},[696,1502,1503],{},"function b64(bytes) {\n",[696,1505,1506],{"class":698,"line":779},[696,1507,1508],{},"  let s = ''\n",[696,1510,1512],{"class":698,"line":1511},5,[696,1513,1514],{},"  for (const b of bytes) s += String.fromCharCode(b)\n",[696,1516,1518],{"class":698,"line":1517},6,[696,1519,1520],{},"  return btoa(s)\n",[696,1522,1524],{"class":698,"line":1523},7,[696,1525,1526],{},"}\n",[696,1528,1530],{"class":698,"line":1529},8,[696,1531,1498],{"emptyLinePlaceholder":1497},[696,1533,1535],{"class":698,"line":1534},9,[696,1536,1537],{},"const b64url = (bytes) => b64(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n",[696,1539,1541],{"class":698,"line":1540},10,[696,1542,1498],{"emptyLinePlaceholder":1497},[696,1544,1546],{"class":698,"line":1545},11,[696,1547,1548],{},"async function hkdf(ikm, salt, info, lengthBytes) {\n",[696,1550,1552],{"class":698,"line":1551},12,[696,1553,1554],{},"  const base = await crypto.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits'])\n",[696,1556,1558],{"class":698,"line":1557},13,[696,1559,1560],{},"  const bits = await crypto.subtle.deriveBits(\n",[696,1562,1564],{"class":698,"line":1563},14,[696,1565,1566],{},"    { name: 'HKDF', hash: 'SHA-256', salt, info: enc.encode(info) },\n",[696,1568,1570],{"class":698,"line":1569},15,[696,1571,1572],{},"    base,\n",[696,1574,1576],{"class":698,"line":1575},16,[696,1577,1578],{},"    lengthBytes * 8\n",[696,1580,1582],{"class":698,"line":1581},17,[696,1583,1584],{},"  )\n",[696,1586,1588],{"class":698,"line":1587},18,[696,1589,1590],{},"  return new Uint8Array(bits)\n",[696,1592,1594],{"class":698,"line":1593},19,[696,1595,1526],{},[696,1597,1599],{"class":698,"line":1598},20,[696,1600,1498],{"emptyLinePlaceholder":1497},[696,1602,1604],{"class":698,"line":1603},21,[696,1605,1606],{},"async function buildShare(fields, passcode) {\n",[696,1608,1610],{"class":698,"line":1609},22,[696,1611,1612],{},"  const contentKey = crypto.getRandomValues(new Uint8Array(32))\n",[696,1614,1616],{"class":698,"line":1615},23,[696,1617,1618],{},"  const salt = crypto.getRandomValues(new Uint8Array(16))\n",[696,1620,1622],{"class":698,"line":1621},24,[696,1623,1624],{},"  const iv = crypto.getRandomValues(new Uint8Array(12))\n",[696,1626,1628],{"class":698,"line":1627},25,[696,1629,1498],{"emptyLinePlaceholder":1497},[696,1631,1633],{"class":698,"line":1632},26,[696,1634,1635],{},"  const info = passcode ? `content|${passcode}` : 'content'\n",[696,1637,1639],{"class":698,"line":1638},27,[696,1640,1641],{},"  const aesKey = await crypto.subtle.importKey(\n",[696,1643,1645],{"class":698,"line":1644},28,[696,1646,1647],{},"    'raw',\n",[696,1649,1651],{"class":698,"line":1650},29,[696,1652,1653],{},"    await hkdf(contentKey, salt, info, 32),\n",[696,1655,1657],{"class":698,"line":1656},30,[696,1658,1659],{},"    { name: 'AES-GCM' },\n",[696,1661,1663],{"class":698,"line":1662},31,[696,1664,1665],{},"    false,\n",[696,1667,1669],{"class":698,"line":1668},32,[696,1670,1671],{},"    ['encrypt']\n",[696,1673,1675],{"class":698,"line":1674},33,[696,1676,1584],{},[696,1678,1680],{"class":698,"line":1679},34,[696,1681,1682],{},"  const body = new Uint8Array(\n",[696,1684,1686],{"class":698,"line":1685},35,[696,1687,1688],{},"    await crypto.subtle.encrypt(\n",[696,1690,1692],{"class":698,"line":1691},36,[696,1693,1694],{},"      { name: 'AES-GCM', iv },\n",[696,1696,1698],{"class":698,"line":1697},37,[696,1699,1700],{},"      aesKey,\n",[696,1702,1704],{"class":698,"line":1703},38,[696,1705,1706],{},"      enc.encode(JSON.stringify(fields))\n",[696,1708,1710],{"class":698,"line":1709},39,[696,1711,1712],{},"    )\n",[696,1714,1716],{"class":698,"line":1715},40,[696,1717,1584],{},[696,1719,1721],{"class":698,"line":1720},41,[696,1722,1498],{"emptyLinePlaceholder":1497},[696,1724,1726],{"class":698,"line":1725},42,[696,1727,1728],{},"  // salt(16) || iv(12) || ciphertext+tag\n",[696,1730,1732],{"class":698,"line":1731},43,[696,1733,1734],{},"  const envelope = new Uint8Array(16 + 12 + body.length)\n",[696,1736,1738],{"class":698,"line":1737},44,[696,1739,1740],{},"  envelope.set(salt, 0)\n",[696,1742,1744],{"class":698,"line":1743},45,[696,1745,1746],{},"  envelope.set(iv, 16)\n",[696,1748,1750],{"class":698,"line":1749},46,[696,1751,1752],{},"  envelope.set(body, 28)\n",[696,1754,1756],{"class":698,"line":1755},47,[696,1757,1498],{"emptyLinePlaceholder":1497},[696,1759,1761],{"class":698,"line":1760},48,[696,1762,1763],{},"  const payload = {\n",[696,1765,1767],{"class":698,"line":1766},49,[696,1768,1769],{},"    encryption_type: 'e2ee-aes256-gcm',\n",[696,1771,1773],{"class":698,"line":1772},50,[696,1774,1775],{},"    data: b64(envelope),\n",[696,1777,1779],{"class":698,"line":1778},51,[696,1780,1781],{},"    access_token: b64url(await hkdf(contentKey, new Uint8Array(0), 'access', 32))\n",[696,1783,1785],{"class":698,"line":1784},52,[696,1786,1787],{},"  }\n",[696,1789,1791],{"class":698,"line":1790},53,[696,1792,1793],{},"  if (passcode) {\n",[696,1795,1797],{"class":698,"line":1796},54,[696,1798,1799],{},"    payload.passcode_verifier = b64url(\n",[696,1801,1803],{"class":698,"line":1802},55,[696,1804,1805],{},"      await hkdf(enc.encode(passcode), new Uint8Array(0), 'verify', 32)\n",[696,1807,1809],{"class":698,"line":1808},56,[696,1810,1712],{},[696,1812,1814],{"class":698,"line":1813},57,[696,1815,1787],{},[696,1817,1819],{"class":698,"line":1818},58,[696,1820,1498],{"emptyLinePlaceholder":1497},[696,1822,1824],{"class":698,"line":1823},59,[696,1825,1826],{},"  // The content key never enters `payload`. It leaves only in the fragment.\n",[696,1828,1830],{"class":698,"line":1829},60,[696,1831,1832],{},"  return { payload, fragment: '1' + b64url(contentKey) }\n",[696,1834,1836],{"class":698,"line":1835},61,[696,1837,1526],{},[265,1839,1840],{},"Then the request, and the link:",[656,1842,1844],{"className":1483,"code":1843,"language":1485,"meta":662,"style":662},"const { payload, fragment } = await buildShare([\n  { key: 'Database password', value: 's3cr3t', type: 'password' },\n  { key: 'Host', value: 'db.internal.example', type: 'text' }\n])\n\n// The first TWO parts of the credential only: crs_sk_live_\u003CkeyId>.\u003CauthSecret>\n// The third part, the custody secret, must never be transmitted.\nconst credential = process.env.CREDENSHARE_API_KEY\n\nconst res = await fetch('https://api.credenshare.io/v1/shares', {\n  method: 'POST',\n  headers: {\n    Authorization: `Bearer ${credential}`,\n    'Idempotency-Key': 'deploy-42',\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({\n    title: 'Deploy credentials',\n    expired_at: '2026-09-01T00:00:00Z',\n    access_counts_left: 1,\n    ...payload\n  })\n})\n\nconst { short_code } = await res.json()\nconst link = `https://crs.sh/${short_code}#${fragment}`\n",[280,1845,1846,1851,1856,1861,1866,1870,1875,1880,1885,1889,1894,1899,1904,1909,1914,1919,1924,1929,1934,1939,1944,1949,1954,1959,1963,1968],{"__ignoreMap":662},[696,1847,1848],{"class":698,"line":699},[696,1849,1850],{},"const { payload, fragment } = await buildShare([\n",[696,1852,1853],{"class":698,"line":706},[696,1854,1855],{},"  { key: 'Database password', value: 's3cr3t', type: 'password' },\n",[696,1857,1858],{"class":698,"line":746},[696,1859,1860],{},"  { key: 'Host', value: 'db.internal.example', type: 'text' }\n",[696,1862,1863],{"class":698,"line":779},[696,1864,1865],{},"])\n",[696,1867,1868],{"class":698,"line":1511},[696,1869,1498],{"emptyLinePlaceholder":1497},[696,1871,1872],{"class":698,"line":1517},[696,1873,1874],{},"// The first TWO parts of the credential only: crs_sk_live_\u003CkeyId>.\u003CauthSecret>\n",[696,1876,1877],{"class":698,"line":1523},[696,1878,1879],{},"// The third part, the custody secret, must never be transmitted.\n",[696,1881,1882],{"class":698,"line":1529},[696,1883,1884],{},"const credential = process.env.CREDENSHARE_API_KEY\n",[696,1886,1887],{"class":698,"line":1534},[696,1888,1498],{"emptyLinePlaceholder":1497},[696,1890,1891],{"class":698,"line":1540},[696,1892,1893],{},"const res = await fetch('https://api.credenshare.io/v1/shares', {\n",[696,1895,1896],{"class":698,"line":1545},[696,1897,1898],{},"  method: 'POST',\n",[696,1900,1901],{"class":698,"line":1551},[696,1902,1903],{},"  headers: {\n",[696,1905,1906],{"class":698,"line":1557},[696,1907,1908],{},"    Authorization: `Bearer ${credential}`,\n",[696,1910,1911],{"class":698,"line":1563},[696,1912,1913],{},"    'Idempotency-Key': 'deploy-42',\n",[696,1915,1916],{"class":698,"line":1569},[696,1917,1918],{},"    'Content-Type': 'application/json'\n",[696,1920,1921],{"class":698,"line":1575},[696,1922,1923],{},"  },\n",[696,1925,1926],{"class":698,"line":1581},[696,1927,1928],{},"  body: JSON.stringify({\n",[696,1930,1931],{"class":698,"line":1587},[696,1932,1933],{},"    title: 'Deploy credentials',\n",[696,1935,1936],{"class":698,"line":1593},[696,1937,1938],{},"    expired_at: '2026-09-01T00:00:00Z',\n",[696,1940,1941],{"class":698,"line":1598},[696,1942,1943],{},"    access_counts_left: 1,\n",[696,1945,1946],{"class":698,"line":1603},[696,1947,1948],{},"    ...payload\n",[696,1950,1951],{"class":698,"line":1609},[696,1952,1953],{},"  })\n",[696,1955,1956],{"class":698,"line":1615},[696,1957,1958],{},"})\n",[696,1960,1961],{"class":698,"line":1621},[696,1962,1498],{"emptyLinePlaceholder":1497},[696,1964,1965],{"class":698,"line":1627},[696,1966,1967],{},"const { short_code } = await res.json()\n",[696,1969,1970],{"class":698,"line":1632},[696,1971,1972],{},"const link = `https://crs.sh/${short_code}#${fragment}`\n",[265,1974,1975,1978,1979,1981],{},[280,1976,1977],{},"Idempotency-Key"," is mandatory on a create — see ",[302,1980,148],{"href":149}," for why, and for every other field you can send.",[272,1983,1984],{"color":274,"icon":275},[265,1985,1986,1989,1990,1992,1993,1996,1997,2000],{},[280,1987,1988],{},"link"," is the only artefact of this whole operation that can ever open the share. Hand it to the recipient, or send an ",[280,1991,458],{}," on the create so your dashboard can rebuild it. An automation that logs the ",[280,1994,1995],{},"short_code"," and discards ",[280,1998,1999],{},"fragment"," has written something nobody can open.",[307,2002,2004],{"id":2003},"check-your-implementation-before-you-rely-on-it","Check your implementation before you rely on it",[265,2006,2007,2008,2010,2011,2013,2014,2016],{},"Nothing validates the internal structure of ",[280,2009,370],{}," on create. A malformed envelope, a wrong ",[280,2012,282],{}," string and a truncated fragment are all accepted with a ",[280,2015,289],{}," and fail only when a recipient opens the share. A green create tells you the request was well formed, not that the crypto was.",[265,2018,2019],{},"Two checks catch almost every mistake:",[2021,2022,2023,2039],"ol",{},[2024,2025,2026,2029,2030,2032,2033,2035,2036,2038],"li",{},[297,2027,2028],{},"Round-trip against fixed inputs."," Feed your implementation the content key, salt and IV from the worked envelope above and compare the base64 output character for character. A mismatch localises immediately: wrong ",[280,2031,370],{}," with a correct ",[280,2034,380],{}," points at the content derivation or the byte layout; both wrong points at your HKDF wiring — the ",[280,2037,282],{}," bytes, the output length, or the base64 alphabet.",[2024,2040,2041,2044],{},[297,2042,2043],{},"Open one in a real browser."," Create a share through your client and click the link you assembled. This is the only check that catches a blank-label field array, a fragment your link builder dropped, and a passcode normalised on one side but not the other — none of which produce an error anywhere.",[265,2046,2047],{},"Then verify the failure path on purpose: strip the fragment from a link and confirm the recipient page reports a missing key rather than appearing to work.",[2049,2050,2051],"style",{},"html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sfrk1, html code.shiki .sfrk1{--shiki-light:#032F62;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":662,"searchDepth":699,"depth":706,"links":2053},[2054,2055,2058,2059,2060,2064,2065,2066,2069,2073,2074],{"id":309,"depth":706,"text":310},{"id":408,"depth":706,"text":409,"children":2056},[2057],{"id":555,"depth":746,"text":556},{"id":653,"depth":706,"text":654},{"id":683,"depth":706,"text":684},{"id":918,"depth":706,"text":919,"children":2061},[2062,2063],{"id":1040,"depth":746,"text":1041},{"id":1075,"depth":746,"text":1076},{"id":1103,"depth":706,"text":1104},{"id":1140,"depth":706,"text":1141},{"id":1178,"depth":706,"text":1179,"children":2067},[2068],{"id":1237,"depth":746,"text":1238},{"id":458,"depth":706,"text":458,"children":2070},[2071,2072],{"id":1323,"depth":746,"text":1324},{"id":1358,"depth":746,"text":1359},{"id":1472,"depth":706,"text":1473},{"id":2003,"depth":706,"text":2004},"Every constant, derivation and byte layout your client needs to produce a share the recipient can actually open.","md",{},{"title":164,"description":2075},"UwCRh_l9YPmBKXLUhMPCd47OyVjsu8ZH5r04gtIV0jQ",[2081,2083],{"title":160,"path":161,"stem":162,"description":2082,"children":-1},"DELETE /v1/shares/{shortCode} — end a share early, before its expiry or view limit.",{"title":168,"path":169,"stem":170,"description":2084,"children":-1},"Ask somebody else to hand you a secret, over a link that carries no key and that CredenShare cannot read.",1788908852283]