[{"data":1,"prerenderedAt":1534},["ShallowReactive",2],{"navigation":3,"/sdks/python":258,"/sdks/python-surround":1529},[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":230,"api":260,"body":261,"description":1524,"extension":1525,"links":260,"meta":1526,"navigation":374,"path":231,"seo":1527,"stem":232,"__hash__":1528},"docs/7.sdks/2.python.md",null,{"type":262,"value":263,"toc":1513},"minimark",[264,274,286,291,316,319,333,337,541,547,554,558,564,585,622,631,635,754,761,790,797,801,882,900,918,925,956,963,978,982,1107,1113,1119,1148,1169,1184,1188,1271,1301,1305,1308,1476,1480,1500,1509],[265,266,267],"p",{},[268,269,273],"a",{"href":270,"rel":271},"https://github.com/CredenShare/credenshare-sdk-python",[272],"nofollow","github.com/CredenShare/credenshare-sdk-python",[265,275,276,277,281,282,285],{},"Python 3.9 and above, with two runtime dependencies: ",[278,279,280],"code",{},"cryptography"," and ",[278,283,284],{},"httpx",".",[287,288,290],"h2",{"id":289},"installing","Installing",[292,293,298],"pre",{"className":294,"code":295,"language":296,"meta":297,"style":297},"language-bash shiki shiki-themes github-light github-dark github-dark","pip install credenshare\n","bash","",[278,299,300],{"__ignoreMap":297},[301,302,305,309,313],"span",{"class":303,"line":304},"line",1,[301,306,308],{"class":307},"shcOC","pip",[301,310,312],{"class":311},"sfrk1"," install",[301,314,315],{"class":311}," credenshare\n",[265,317,318],{},"Installing from the repository also works, if you want a specific tag or an unreleased fix:",[292,320,322],{"className":294,"code":321,"language":296,"meta":297,"style":297},"pip install \"git+https://github.com/CredenShare/credenshare-sdk-python@v0.1.3\"\n",[278,323,324],{"__ignoreMap":297},[301,325,326,328,330],{"class":303,"line":304},[301,327,308],{"class":307},[301,329,312],{"class":311},[301,331,332],{"class":311}," \"git+https://github.com/CredenShare/credenshare-sdk-python@v0.1.3\"\n",[287,334,336],{"id":335},"quickstart","Quickstart",[292,338,343],{"className":339,"code":340,"filename":341,"language":342,"meta":297,"style":297},"language-python shiki shiki-themes github-light github-dark github-dark","import os\nfrom credenshare import CredenShare\n\nwith CredenShare(os.environ[\"CREDENSHARE_KEY\"]) as crs:\n    share = crs.shares.create(\n        title=\"Staging deploy credentials\",\n        fields=[\n            {\"key\": \"Username\", \"value\": \"deploy-bot\",    \"type\": \"text\"},\n            {\"key\": \"Password\", \"value\": \"correct horse\", \"type\": \"password\"},\n        ],\n    )\n\nprint(share.link)\n# https://crs.sh/aB3dEf12#1xK9...\n","share.py","python",[278,344,345,355,369,376,397,409,424,435,475,507,513,519,524,534],{"__ignoreMap":297},[301,346,347,351],{"class":303,"line":304},[301,348,350],{"class":349},"so5gQ","import",[301,352,354],{"class":353},"slsVL"," os\n",[301,356,358,361,364,366],{"class":303,"line":357},2,[301,359,360],{"class":349},"from",[301,362,363],{"class":353}," credenshare ",[301,365,350],{"class":349},[301,367,368],{"class":353}," CredenShare\n",[301,370,372],{"class":303,"line":371},3,[301,373,375],{"emptyLinePlaceholder":374},true,"\n",[301,377,379,382,385,388,391,394],{"class":303,"line":378},4,[301,380,381],{"class":349},"with",[301,383,384],{"class":353}," CredenShare(os.environ[",[301,386,387],{"class":311},"\"CREDENSHARE_KEY\"",[301,389,390],{"class":353},"]) ",[301,392,393],{"class":349},"as",[301,395,396],{"class":353}," crs:\n",[301,398,400,403,406],{"class":303,"line":399},5,[301,401,402],{"class":353},"    share ",[301,404,405],{"class":349},"=",[301,407,408],{"class":353}," crs.shares.create(\n",[301,410,412,416,418,421],{"class":303,"line":411},6,[301,413,415],{"class":414},"sQHwn","        title",[301,417,405],{"class":349},[301,419,420],{"class":311},"\"Staging deploy credentials\"",[301,422,423],{"class":353},",\n",[301,425,427,430,432],{"class":303,"line":426},7,[301,428,429],{"class":414},"        fields",[301,431,405],{"class":349},[301,433,434],{"class":353},"[\n",[301,436,438,441,444,447,450,453,456,458,461,464,467,469,472],{"class":303,"line":437},8,[301,439,440],{"class":353},"            {",[301,442,443],{"class":311},"\"key\"",[301,445,446],{"class":353},": ",[301,448,449],{"class":311},"\"Username\"",[301,451,452],{"class":353},", ",[301,454,455],{"class":311},"\"value\"",[301,457,446],{"class":353},[301,459,460],{"class":311},"\"deploy-bot\"",[301,462,463],{"class":353},",    ",[301,465,466],{"class":311},"\"type\"",[301,468,446],{"class":353},[301,470,471],{"class":311},"\"text\"",[301,473,474],{"class":353},"},\n",[301,476,478,480,482,484,487,489,491,493,496,498,500,502,505],{"class":303,"line":477},9,[301,479,440],{"class":353},[301,481,443],{"class":311},[301,483,446],{"class":353},[301,485,486],{"class":311},"\"Password\"",[301,488,452],{"class":353},[301,490,455],{"class":311},[301,492,446],{"class":353},[301,494,495],{"class":311},"\"correct horse\"",[301,497,452],{"class":353},[301,499,466],{"class":311},[301,501,446],{"class":353},[301,503,504],{"class":311},"\"password\"",[301,506,474],{"class":353},[301,508,510],{"class":303,"line":509},10,[301,511,512],{"class":353},"        ],\n",[301,514,516],{"class":303,"line":515},11,[301,517,518],{"class":353},"    )\n",[301,520,522],{"class":303,"line":521},12,[301,523,375],{"emptyLinePlaceholder":374},[301,525,527,531],{"class":303,"line":526},13,[301,528,530],{"class":529},"suiK_","print",[301,532,533],{"class":353},"(share.link)\n",[301,535,537],{"class":303,"line":536},14,[301,538,540],{"class":539},"sCsY4","# https://crs.sh/aB3dEf12#1xK9...\n",[265,542,543,544,546],{},"The client is a context manager, which closes the underlying ",[278,545,284],{}," client on exit. Use it that way unless you are holding one for the life of the process.",[265,548,549,553],{},[550,551,552],"strong",{},"That link is the secret."," The key rides in the fragment, which browsers never transmit. Anyone holding the link can read the content; we cannot, and cannot recover it for you.",[287,555,557],{"id":556},"the-field-object","The field object",[265,559,560,561,285],{},"Each field is ",[278,562,563],{},"{\"key\": ..., \"value\": ..., \"type\": ...}",[265,565,566,569,570,573,574,452,577,580,581,584],{},[278,567,568],{},"key"," is the ",[550,571,572],{},"visible label",", not an identifier — it is what the recipient reads. It is not ",[278,575,576],{},"label",[278,578,579],{},"name"," or ",[278,582,583],{},"title","; those spellings encrypt, post, decrypt and render perfectly, with every field blank and nothing erroring anywhere. The SDK refuses them rather than letting the mistake through.",[265,586,587,590,591,452,594,452,597,452,600,452,603,452,606,609,610,613,614,616,617,621],{},[278,588,589],{},"type"," is one of ",[278,592,593],{},"text",[278,595,596],{},"password",[278,598,599],{},"date",[278,601,602],{},"multiline",[278,604,605],{},"markdown",[278,607,608],{},"source_code",", exported as ",[278,611,612],{},"FIELD_TYPES",". Validation checks only that ",[278,615,589],{}," is ",[618,619,620],"em",{},"present",", not that it is a member of that tuple — a typo encrypts and misrenders silently, so check against the constant if the value is dynamic.",[265,623,624,281,627,630],{},[278,625,626],{},"selectedProgrammingLanguage",[278,628,629],{},"filename"," are optional, and this client preserves them along with any other member you add.",[287,632,634],{"id":633},"creating","Creating",[292,636,638],{"className":339,"code":637,"language":342,"meta":297,"style":297},"crs.shares.create(\n    title=\"Production database\",\n    fields=[{\"key\": \"Password\", \"value\": \"s3cr3t\", \"type\": \"password\"}],\n    passcode=\"hunter2\",\n    expired_at=\"2026-09-01T00:00:00Z\",\n    access_counts_left=3,   # readable three times, max 10000\n    timed_view=60,          # visible for 60s once opened\n)\n",[278,639,640,645,657,693,705,717,733,749],{"__ignoreMap":297},[301,641,642],{"class":303,"line":304},[301,643,644],{"class":353},"crs.shares.create(\n",[301,646,647,650,652,655],{"class":303,"line":357},[301,648,649],{"class":414},"    title",[301,651,405],{"class":349},[301,653,654],{"class":311},"\"Production database\"",[301,656,423],{"class":353},[301,658,659,662,664,667,669,671,673,675,677,679,682,684,686,688,690],{"class":303,"line":371},[301,660,661],{"class":414},"    fields",[301,663,405],{"class":349},[301,665,666],{"class":353},"[{",[301,668,443],{"class":311},[301,670,446],{"class":353},[301,672,486],{"class":311},[301,674,452],{"class":353},[301,676,455],{"class":311},[301,678,446],{"class":353},[301,680,681],{"class":311},"\"s3cr3t\"",[301,683,452],{"class":353},[301,685,466],{"class":311},[301,687,446],{"class":353},[301,689,504],{"class":311},[301,691,692],{"class":353},"}],\n",[301,694,695,698,700,703],{"class":303,"line":378},[301,696,697],{"class":414},"    passcode",[301,699,405],{"class":349},[301,701,702],{"class":311},"\"hunter2\"",[301,704,423],{"class":353},[301,706,707,710,712,715],{"class":303,"line":399},[301,708,709],{"class":414},"    expired_at",[301,711,405],{"class":349},[301,713,714],{"class":311},"\"2026-09-01T00:00:00Z\"",[301,716,423],{"class":353},[301,718,719,722,724,727,730],{"class":303,"line":411},[301,720,721],{"class":414},"    access_counts_left",[301,723,405],{"class":349},[301,725,726],{"class":529},"3",[301,728,729],{"class":353},",   ",[301,731,732],{"class":539},"# readable three times, max 10000\n",[301,734,735,738,740,743,746],{"class":303,"line":426},[301,736,737],{"class":414},"    timed_view",[301,739,405],{"class":349},[301,741,742],{"class":529},"60",[301,744,745],{"class":353},",          ",[301,747,748],{"class":539},"# visible for 60s once opened\n",[301,750,751],{"class":303,"line":437},[301,752,753],{"class":353},")\n",[265,755,756,757,760],{},"A passcode is mixed into the ",[550,758,759],{},"content key derivation",", not just checked by the server — so it is not a server-side gate you could bypass, and a passcode-protected share cannot be opened from the link alone. The server receives only a one-way verifier. Send the link and the passcode over different channels.",[265,762,763,764,767,768,770,771,774,775,778,779,281,782,785,786,789],{},"Pass ",[278,765,766],{},"custody=True"," to also wrap the content key to the custody public key derived from your credential's third part, which keeps the share readable from your dashboard rather than only from its link. The wrap is computed locally and the custody secret never leaves your machine. Passing both ",[278,769,766],{}," and an explicit ",[278,772,773],{},"item_key_wrap"," raises ",[278,776,777],{},"InvalidFieldError",", which subclasses both ",[278,780,781],{},"CredenShareError",[278,783,784],{},"ValueError",", so either kind of ",[278,787,788],{},"except"," catches it.",[265,791,792,793,796],{},"Requires the ",[278,794,795],{},"shares:write"," scope.",[287,798,800],{"id":799},"listing-and-expiring","Listing and expiring",[292,802,804],{"className":339,"code":803,"language":342,"meta":297,"style":297},"for row in crs.shares.list(limit=50):\n    print(row.short_code, row.expired_at)\n\nfor row in crs.shares.iter_all():       # every page, not just the first\n    print(row.short_code)\n\ncrs.shares.expire(\"aB3dEf12\")           # irreversible\n",[278,805,806,831,839,843,857,864,868],{"__ignoreMap":297},[301,807,808,811,814,817,820,823,825,828],{"class":303,"line":304},[301,809,810],{"class":349},"for",[301,812,813],{"class":353}," row ",[301,815,816],{"class":349},"in",[301,818,819],{"class":353}," crs.shares.list(",[301,821,822],{"class":414},"limit",[301,824,405],{"class":349},[301,826,827],{"class":529},"50",[301,829,830],{"class":353},"):\n",[301,832,833,836],{"class":303,"line":357},[301,834,835],{"class":529},"    print",[301,837,838],{"class":353},"(row.short_code, row.expired_at)\n",[301,840,841],{"class":303,"line":371},[301,842,375],{"emptyLinePlaceholder":374},[301,844,845,847,849,851,854],{"class":303,"line":378},[301,846,810],{"class":349},[301,848,813],{"class":353},[301,850,816],{"class":349},[301,852,853],{"class":353}," crs.shares.iter_all():       ",[301,855,856],{"class":539},"# every page, not just the first\n",[301,858,859,861],{"class":303,"line":399},[301,860,835],{"class":529},[301,862,863],{"class":353},"(row.short_code)\n",[301,865,866],{"class":303,"line":411},[301,867,375],{"emptyLinePlaceholder":374},[301,869,870,873,876,879],{"class":303,"line":426},[301,871,872],{"class":353},"crs.shares.expire(",[301,874,875],{"class":311},"\"aB3dEf12\"",[301,877,878],{"class":353},")           ",[301,880,881],{"class":539},"# irreversible\n",[265,883,884,887,888,891,892,895,896,899],{},[278,885,886],{},"list()"," returns a ",[278,889,890],{},"ShareList",", which subclasses ",[278,893,894],{},"list",", so it iterates directly and also carries the paging fields. ",[278,897,898],{},"iter_all()"," walks every page, and specifically does not stop on a short middle page — the way a hand-rolled paging loop usually goes wrong. There is a regression test named after that bug.",[265,901,902,905,906,909,910,913,914,917],{},[278,903,904],{},"has_more"," no longer answers ",[278,907,908],{},"False"," on a page the server failed to count — it falls back through ",[278,911,912],{},"total_pages",", then ",[278,915,916],{},"total",", then the page's own row count — so this client no longer stops at page one and calls it the whole account.",[265,919,920,921,924],{},"It can fail rather than loop. A response echoing a page number other than the one requested raises ",[278,922,923],{},"ApiError",", because a server doing that makes progress unobservable, and the walk stops at a hard ceiling of 100,000 pages rather than returning a partial result silently. The constant is internal — only the Rust crate re-exports its equivalent.",[265,926,927,281,929,932,933,936,937,281,940,932,943,945,946,948,949,951,952,955],{},[278,928,894],{},[278,930,931],{},"get"," need ",[278,934,935],{},"shares:read","; ",[278,938,939],{},"create",[278,941,942],{},"expire",[278,944,795],{},". There is no hierarchy between them, so a key minted with only ",[278,947,795],{}," fails on ",[278,950,886],{}," with ",[278,953,954],{},"PermissionError_"," — the most common first surprise.",[265,957,958,959,962],{},"Both return ",[550,960,961],{},"metadata only",", never content and never a key. A short code belonging to another account reports exactly as one that does not exist.",[265,964,965,967,968,971,972,774,974,977],{},[278,966,942],{}," ",[550,969,970],{},"removes"," the share rather than flagging it, so a later ",[278,973,931],{},[278,975,976],{},"NotFoundError",". A share you expired and one that never existed are indistinguishable afterwards.",[287,979,981],{"id":980},"verifying-webhooks","Verifying webhooks",[292,983,985],{"className":339,"code":984,"language":342,"meta":297,"style":297},"from credenshare import webhooks\n\n@app.post(\"/hooks/credenshare\")\nasync def hook(request):\n    try:\n        webhooks.verify(\n            await request.body(),                          # the RAW bytes\n            request.headers[\"X-CredenShare-Signature\"],\n            secrets=WEBHOOK_SECRET,\n        )\n    except webhooks.WebhookVerificationError:\n        return Response(status_code=400)\n",[278,986,987,998,1002,1015,1029,1037,1042,1053,1064,1076,1081,1089],{"__ignoreMap":297},[301,988,989,991,993,995],{"class":303,"line":304},[301,990,360],{"class":349},[301,992,363],{"class":353},[301,994,350],{"class":349},[301,996,997],{"class":353}," webhooks\n",[301,999,1000],{"class":303,"line":357},[301,1001,375],{"emptyLinePlaceholder":374},[301,1003,1004,1007,1010,1013],{"class":303,"line":371},[301,1005,1006],{"class":307},"@app.post",[301,1008,1009],{"class":353},"(",[301,1011,1012],{"class":311},"\"/hooks/credenshare\"",[301,1014,753],{"class":353},[301,1016,1017,1020,1023,1026],{"class":303,"line":378},[301,1018,1019],{"class":349},"async",[301,1021,1022],{"class":349}," def",[301,1024,1025],{"class":307}," hook",[301,1027,1028],{"class":353},"(request):\n",[301,1030,1031,1034],{"class":303,"line":399},[301,1032,1033],{"class":349},"    try",[301,1035,1036],{"class":353},":\n",[301,1038,1039],{"class":303,"line":411},[301,1040,1041],{"class":353},"        webhooks.verify(\n",[301,1043,1044,1047,1050],{"class":303,"line":426},[301,1045,1046],{"class":349},"            await",[301,1048,1049],{"class":353}," request.body(),                          ",[301,1051,1052],{"class":539},"# the RAW bytes\n",[301,1054,1055,1058,1061],{"class":303,"line":437},[301,1056,1057],{"class":353},"            request.headers[",[301,1059,1060],{"class":311},"\"X-CredenShare-Signature\"",[301,1062,1063],{"class":353},"],\n",[301,1065,1066,1069,1071,1074],{"class":303,"line":477},[301,1067,1068],{"class":414},"            secrets",[301,1070,405],{"class":349},[301,1072,1073],{"class":529},"WEBHOOK_SECRET",[301,1075,423],{"class":353},[301,1077,1078],{"class":303,"line":509},[301,1079,1080],{"class":353},"        )\n",[301,1082,1083,1086],{"class":303,"line":515},[301,1084,1085],{"class":349},"    except",[301,1087,1088],{"class":353}," webhooks.WebhookVerificationError:\n",[301,1090,1091,1094,1097,1100,1102,1105],{"class":303,"line":521},[301,1092,1093],{"class":349},"        return",[301,1095,1096],{"class":353}," Response(",[301,1098,1099],{"class":414},"status_code",[301,1101,405],{"class":349},[301,1103,1104],{"class":529},"400",[301,1106,753],{"class":353},[265,1108,1109,1112],{},[550,1110,1111],{},"Verify the raw body."," Re-serialising parsed JSON changes the bytes — key order, spacing, escapes — and the signature will not match. It is the most common reason a correct integration looks broken.",[265,1114,1115,1118],{},[550,1116,1117],{},"Pass both secrets while rotating."," For 24 hours after a rotation, deliveries carry both signatures:",[292,1120,1122],{"className":339,"code":1121,"language":342,"meta":297,"style":297},"webhooks.verify(body, header, secrets=[NEW_SECRET, OLD_SECRET])\n",[278,1123,1124],{"__ignoreMap":297},[301,1125,1126,1129,1132,1134,1137,1140,1142,1145],{"class":303,"line":304},[301,1127,1128],{"class":353},"webhooks.verify(body, header, ",[301,1130,1131],{"class":414},"secrets",[301,1133,405],{"class":349},[301,1135,1136],{"class":353},"[",[301,1138,1139],{"class":529},"NEW_SECRET",[301,1141,452],{"class":353},[301,1143,1144],{"class":529},"OLD_SECRET",[301,1146,1147],{"class":353},"])\n",[265,1149,1150,1153,1154,1157,1158,1160,1161,1164,1165,1168],{},[278,1151,1152],{},"verify"," returns ",[278,1155,1156],{},"True"," or raises; it never returns ",[278,1159,908],{},", because a falsy return is too easy to drop with an ",[278,1162,1163],{},"if"," and no ",[278,1166,1167],{},"else",", which yields a receiver that accepts everything while looking like it checks.",[265,1170,1171,1172,1175,1176,1179,1180,1183],{},"The ±5-minute replay window is exported as ",[278,1173,1174],{},"DEFAULT_TOLERANCE_SECONDS",", and the header name as ",[278,1177,1178],{},"webhooks.SIGNATURE_HEADER",". ",[278,1181,1182],{},"_SIGNATURE_HEADER"," is kept as an alias, so code importing the private name still works.",[287,1185,1187],{"id":1186},"configuration","Configuration",[292,1189,1191],{"className":339,"code":1190,"language":342,"meta":297,"style":297},"CredenShare(\n    credential,\n    base_url=\"https://api.credenshare.io/v1\",\n    link_origin=\"https://crs.sh\",\n    timeout=30.0,\n    max_retries=2,\n    transport=None,       # any httpx.BaseTransport\n)\n",[278,1192,1193,1198,1203,1215,1227,1239,1251,1267],{"__ignoreMap":297},[301,1194,1195],{"class":303,"line":304},[301,1196,1197],{"class":353},"CredenShare(\n",[301,1199,1200],{"class":303,"line":357},[301,1201,1202],{"class":353},"    credential,\n",[301,1204,1205,1208,1210,1213],{"class":303,"line":371},[301,1206,1207],{"class":414},"    base_url",[301,1209,405],{"class":349},[301,1211,1212],{"class":311},"\"https://api.credenshare.io/v1\"",[301,1214,423],{"class":353},[301,1216,1217,1220,1222,1225],{"class":303,"line":378},[301,1218,1219],{"class":414},"    link_origin",[301,1221,405],{"class":349},[301,1223,1224],{"class":311},"\"https://crs.sh\"",[301,1226,423],{"class":353},[301,1228,1229,1232,1234,1237],{"class":303,"line":399},[301,1230,1231],{"class":414},"    timeout",[301,1233,405],{"class":349},[301,1235,1236],{"class":529},"30.0",[301,1238,423],{"class":353},[301,1240,1241,1244,1246,1249],{"class":303,"line":411},[301,1242,1243],{"class":414},"    max_retries",[301,1245,405],{"class":349},[301,1247,1248],{"class":529},"2",[301,1250,423],{"class":353},[301,1252,1253,1256,1258,1261,1264],{"class":303,"line":426},[301,1254,1255],{"class":414},"    transport",[301,1257,405],{"class":349},[301,1259,1260],{"class":529},"None",[301,1262,1263],{"class":353},",       ",[301,1265,1266],{"class":539},"# any httpx.BaseTransport\n",[301,1268,1269],{"class":303,"line":437},[301,1270,753],{"class":353},[265,1272,1273,1276,1277,1280,1281,1284,1285,1288,1289,1292,1293,1296,1297,1300],{},[278,1274,1275],{},"link_origin"," changes only the links ",[278,1278,1279],{},"create()"," hands back; it is never sent to the API. ",[278,1282,1283],{},"transport"," accepts an ",[278,1286,1287],{},"httpx.BaseTransport",", a testing seam Node (an injectable ",[278,1290,1291],{},"fetch",") and Go (",[278,1294,1295],{},"Options.HTTPClient",") also offer. Rust is the only one of the four without one. The SDK reads no environment variables — ",[278,1298,1299],{},"os.environ"," above is your own read.",[287,1302,1304],{"id":1303},"rough-edges","Rough edges",[265,1306,1307],{},"Real behaviour worth knowing before it surprises you.",[1309,1310,1311,1332,1344,1365,1377,1393,1432,1443,1470],"ul",{},[1312,1313,1314,1317,1318,452,1321,281,1324,1327,1328,1331],"li",{},[550,1315,1316],{},"Short codes are percent-encoded now",", so ",[278,1319,1320],{},"/",[278,1322,1323],{},"?",[278,1325,1326],{},"#"," cannot make a crafted value leave ",[278,1329,1330],{},"/v1/shares/",". They are still opaque — pass only what the API gave you.",[1312,1333,1334,1343],{},[550,1335,1336,1339,1340],{},[278,1337,1338],{},"DEFAULT_MAX_RETRIES"," is reachable only as ",[278,1341,1342],{},"credenshare.client.DEFAULT_MAX_RETRIES",", not from the package root. Node, Go and Rust all export theirs.",[1312,1345,1346,1352,1353,1356,1357,1360,1361,1364],{},[550,1347,1348,1351],{},[278,1349,1350],{},"__version__"," reports the wrong number."," It reads ",[278,1354,1355],{},"0.1.0"," in the published ",[278,1358,1359],{},"0.1.3"," package — hardcoded in ",[278,1362,1363],{},"__init__.py"," and not bumped with the release. Read the installed distribution's metadata if you need the real version.",[1312,1366,1367,1372,1373,1376],{},[550,1368,1369,1371],{},[278,1370,954],{}," has a trailing underscore",", to avoid shadowing the Python builtin. Catching ",[278,1374,1375],{},"PermissionError"," catches the wrong thing entirely.",[1312,1378,1379,1382,1383,1386,1387,1389,1390,285],{},[550,1380,1381],{},"The idempotency key is generated per call and never surfaced."," It protects a same-process network retry, which the client performs itself. It does not protect a ",[618,1384,1385],{},"re-run"," — a job that crashes after POSTing generates a fresh UUID next time and mints a second copy of the secret. Pass your own if you need that, and note that doing so does not make a second ",[278,1388,1279],{}," a no-op: salt and IV are fresh per call, so the body differs and the API answers ",[278,1391,1392],{},"409",[1312,1394,1395,1401,1402,1405,1406,1409,1410,1413,1414,452,1417,281,1419,1422,1423,1425,1426,1428,1429,789],{},[550,1396,1397,1400],{},[278,1398,1399],{},"ServiceUnavailableError"," now means only what its docstring says"," — a genuine ",[278,1403,1404],{},"503",", or exhausted connect failures, where nothing was created. An exhausted read timeout, where the request was written and the server may have committed, raises ",[278,1407,1408],{},"DeliveryUnknownError"," carrying ",[278,1411,1412],{},"attempts",". It arrives with ",[278,1415,1416],{},"status",[278,1418,278],{},[278,1420,1421],{},"request_id"," all ",[278,1424,1260],{},", because no response was ever read, and it extends ",[278,1427,923],{},", so an ",[278,1430,1431],{},"except ApiError",[1312,1433,1434,1440,1441,285],{},[550,1435,1436,1439],{},[278,1437,1438],{},"AuthenticationError"," is narrower than it sounds."," A revoked or unknown credential arrives as HTTP 403 and therefore as ",[278,1442,954],{},[1312,1444,1445,967,1451,1454,1455,1458,1459,1462,1463,452,1466,1469],{},[550,1446,1447,1450],{},[278,1448,1449],{},"print(share)"," does not show the link.",[278,1452,1453],{},"Share.__repr__"," deliberately withholds it; print ",[278,1456,1457],{},"share.link"," when you actually want the secret. Node behaves the same way now — its ",[278,1460,1461],{},"Share"," is a class whose ",[278,1464,1465],{},"toString",[278,1467,1468],{},"toJSON"," and inspect hook all redact the link.",[1312,1471,1472,1475],{},[550,1473,1474],{},"A webhook secret with a trailing newline fails."," The blank check trims, but the HMAC is keyed with the untrimmed string. Python, Go and Rust behave this way — trim it yourself. Node now refuses such a secret by name instead of blaming the signature.",[287,1477,1479],{"id":1478},"checking-your-build","Checking your build",[292,1481,1483],{"className":294,"code":1482,"language":296,"meta":297,"style":297},"python -m credenshare.conformance\n# 24 passed. This installation conforms to the wire specification.\n",[278,1484,1485,1495],{"__ignoreMap":297},[301,1486,1487,1489,1492],{"class":303,"line":304},[301,1488,342],{"class":307},[301,1490,1491],{"class":529}," -m",[301,1493,1494],{"class":311}," credenshare.conformance\n",[301,1496,1497],{"class":303,"line":357},[301,1498,1499],{"class":539},"# 24 passed. This installation conforms to the wire specification.\n",[265,1501,1502,1503,1506,1507,285],{},"Add ",[278,1504,1505],{},"-v"," for one line per vector. It needs no test runner and no dev dependencies, and exits non-zero on failure, so it works as a deployment gate — run it in the environment that will actually do the encrypting. See ",[268,1508,242],{"href":243},[1510,1511,1512],"style",{},"html pre.shiki code .shcOC, html code.shiki .shcOC{--shiki-light:#6F42C1;--shiki-default:#B392F0;--shiki-dark:#B392F0}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);}html pre.shiki code .suiK_, html code.shiki .suiK_{--shiki-light:#005CC5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .so5gQ, html code.shiki .so5gQ{--shiki-light:#D73A49;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .slsVL, html code.shiki .slsVL{--shiki-light:#24292E;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sQHwn, html code.shiki .sQHwn{--shiki-light:#E36209;--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":297,"searchDepth":304,"depth":357,"links":1514},[1515,1516,1517,1518,1519,1520,1521,1522,1523],{"id":289,"depth":357,"text":290},{"id":335,"depth":357,"text":336},{"id":556,"depth":357,"text":557},{"id":633,"depth":357,"text":634},{"id":799,"depth":357,"text":800},{"id":980,"depth":357,"text":981},{"id":1186,"depth":357,"text":1187},{"id":1303,"depth":357,"text":1304},{"id":1478,"depth":357,"text":1479},"The official Python client — encrypts locally, assembles the link, verifies webhooks. Python 3.9+, two dependencies.","md",{},{"title":230,"description":1524},"Sugbdjo7Pa90vYmOfRVn6Oo-cwQUI5_DG1Qip9gFLlU",[1530,1532],{"title":226,"path":227,"stem":228,"description":1531,"children":-1},"The official TypeScript client — encrypts locally, assembles the link, verifies webhooks. ESM only, no runtime dependencies.",{"title":234,"path":235,"stem":236,"description":1533,"children":-1},"The official Go client — encrypts locally, assembles the link, verifies webhooks. Standard library only.",1788908850707]