[{"data":1,"prerenderedAt":879},["ShallowReactive",2],{"doc-\u002Fapi\u002Fcookbook":3},{"id":4,"title":5,"body":6,"description":870,"edit":871,"extension":872,"meta":873,"navigation":274,"path":874,"seo":875,"stem":876,"vertical":871,"weight":877,"__hash__":878},"content\u002Fapi\u002Fcookbook.md","API cookbook",{"type":7,"value":8,"toc":851},"minimark",[9,19,24,37,41,62,66,81,85,97,101,112,116,125,129,138,142,151,155,164,168,182,186,199,203,216,220,243,247,496,500,511,515,526,549,553,847],[10,11,12,13,18],"p",{},"A grab-bag of HTTP recipes for everyday operations. Examples are in\nshell + cURL by default; Python equivalents are in\n",[14,15,17],"a",{"href":16},"\u002Fapi\u002Fingest","POST \u002Fv1\u002Fingest"," and one block per recipe below.",[20,21,23],"h2",{"id":22},"authentication-setup","Authentication setup",[25,26,28],"cmd",{"label":27},"$ env",[10,29,30,31,36],{},"export OS_BASE=\"",[14,32,33],{"href":33,"rel":34},"https:\u002F\u002Fapi.opensense.murzin.digital",[35],"nofollow","\"\nexport OS_TOKEN=\"ua_live_4f3c…\"     # user-scoped token\nexport OS_DEVICE=\"ds_live_b1d2…\"    # device-scoped token",[20,38,40],{"id":39},"send-a-single-measurement","Send a single measurement",[25,42,44],{"label":43},"$ ingest one",[10,45,46,47,50,51,53,54,56,57,61],{},"curl -X POST $OS_BASE\u002Fv1\u002Fingest ",[48,49],"br",{},"\n-H \"Authorization: Bearer $OS_DEVICE\" ",[48,52],{},"\n-H \"Content-Type: application\u002Fjson\" ",[48,55],{},"\n-d '{\n\"device\": \"fridge01\",\n\"measurements\": ",[58,59,60],"span",{},"\n{\"type\":\"temperature\",\"value\":4.2}\n","\n}'",[20,63,65],{"id":64},"send-a-batch","Send a batch",[25,67,69],{"label":68},"$ ingest batch",[10,70,46,71,50,73,53,75,77,78,61],{},[48,72],{},[48,74],{},[48,76],{},"\n-d '{\n\"device\": \"panel1\",\n\"measurements\": ",[58,79,80],{},"\n{\"ts\":\"2026-05-17T08:00:00Z\",\"type\":\"power\",\"value\":1240},\n{\"ts\":\"2026-05-17T08:00:01Z\",\"type\":\"power\",\"value\":1255},\n{\"ts\":\"2026-05-17T08:00:02Z\",\"type\":\"power\",\"value\":1268}\n",[20,82,84],{"id":83},"list-all-sites","List all sites",[25,86,88],{"label":87},"$ list sites",[10,89,90,91,93,94,96],{},"curl -s $OS_BASE\u002Fv1\u002Fsites ",[48,92],{},"\n-H \"Authorization: Bearer $OS_TOKEN\" | jq '.data",[58,95],{}," | {id, name, vertical}'",[20,98,100],{"id":99},"list-devices-on-a-site","List devices on a site",[25,102,104],{"label":103},"$ list devices",[10,105,106,107,93,109,111],{},"curl -s \"$OS_BASE\u002Fv1\u002Fdevices?site_id=site_4f3c\" ",[48,108],{},[58,110],{}," | {local_id, online, last_seen_at}'",[20,113,115],{"id":114},"rotate-a-devices-ingest-token","Rotate a device's ingest token",[25,117,119],{"label":118},"$ rotate token",[10,120,121,122,124],{},"curl -X POST $OS_BASE\u002Fv1\u002Fdevices\u002Fdev_b1d2\u002Frotate-token ",[48,123],{},"\n-H \"Authorization: Bearer $OS_TOKEN\" | jq '.ingest_token'",[20,126,128],{"id":127},"read-last-24-h-of-a-channel-5-min-mean","Read last 24 h of a channel, 5-min mean",[25,130,132],{"label":131},"$ read 24h",[10,133,134,135,137],{},"curl -s \"$OS_BASE\u002Fv1\u002Fchannels\u002Fchn_a1b2\u002Fmeasurements?agg=5m&fn=mean&from=$(date -u -d '24 hours ago' +%FT%TZ)\" ",[48,136],{},"\n-H \"Authorization: Bearer $OS_TOKEN\" | jq '.data | length'",[20,139,141],{"id":140},"read-last-week-as-csv","Read last week as CSV",[25,143,145],{"label":144},"$ csv export",[10,146,147,148,150],{},"curl -s -o week.csv \"$OS_BASE\u002Fv1\u002Fchannels\u002Fchn_a1b2\u002Fmeasurements?agg=1h&from=$(date -u -d '7 days ago' +%FT%TZ)&format=csv\" ",[48,149],{},"\n-H \"Authorization: Bearer $OS_TOKEN\"",[20,152,154],{"id":153},"read-multiple-channels-in-one-call","Read multiple channels in one call",[25,156,158],{"label":157},"$ multi read",[10,159,160,161,163],{},"curl -s \"$OS_BASE\u002Fv1\u002Fmeasurements?channel_ids=chn_a1b2,chn_a1b3,chn_a1b4&agg=1h&from=$(date -u -d '7 days ago' +%FT%TZ)\" ",[48,162],{},"\n-H \"Authorization: Bearer $OS_TOKEN\" | jq 'keys'",[20,165,167],{"id":166},"edit-a-channels-operating-range","Edit a channel's operating range",[25,169,171],{"label":170},"$ retune range",[10,172,173,174,176,177,53,179,181],{},"curl -X PATCH $OS_BASE\u002Fv1\u002Fsensors\u002Fsnr_a1b2 ",[48,175],{},"\n-H \"Authorization: Bearer $OS_TOKEN\" ",[48,178],{},[48,180],{},"\n-d '{\"ok_min\": -2, \"ok_max\": 6, \"grace_min\": 20}'",[20,183,185],{"id":184},"acknowledge-an-alarm","Acknowledge an alarm",[25,187,189],{"label":188},"$ ack alarm",[10,190,191,192,176,194,53,196,198],{},"curl -X POST $OS_BASE\u002Fv1\u002Fevents\u002Fevt_4f3c\u002Fack ",[48,193],{},[48,195],{},[48,197],{},"\n-d '{\"note\": \"Door propped open during delivery; closed within 8 min.\"}'",[20,200,202],{"id":201},"silence-a-channel-for-4-hours","Silence a channel for 4 hours",[25,204,206],{"label":205},"$ silence",[10,207,208,209,176,211,53,213,215],{},"FROM=$(date -u +%FT%TZ)\nTO=$(date -u -d '+4 hours' +%FT%TZ)\ncurl -X POST $OS_BASE\u002Fv1\u002Fsensors\u002Fsnr_a1b2\u002Fsilence ",[48,210],{},[48,212],{},[48,214],{},"\n-d \"{\"from\":\"$FROM\",\"to\":\"$TO\",\"reason\":\"scheduled defrost\"}\"",[20,217,219],{"id":218},"generate-a-monthly-haccp-report","Generate a monthly HACCP report",[25,221,223,233,240],{"label":222},"$ report",[10,224,225,226,176,228,53,230,232],{},"RPT=$(curl -X POST $OS_BASE\u002Fv1\u002Freports ",[48,227],{},[48,229],{},[48,231],{},"\n-d '{\n\"site_id\": \"site_4f3c\",\n\"template\": \"haccp_monthly\",\n\"period\": {\"from\":\"2026-04-01T00:00:00+02:00\",\"to\":\"2026-04-30T23:59:59+02:00\"},\n\"language\": \"en\"\n}' | jq -r .id)",[10,234,235,236,239],{},"while true; do\nSTATE=$(curl -s $OS_BASE\u002Fv1\u002Freports\u002F$RPT -H \"Authorization: Bearer $OS_TOKEN\" | jq -r .state)\n",[58,237,238],{}," \"$STATE\" = \"ready\""," && break\necho \"  state=$STATE  waiting...\"; sleep 5\ndone",[10,241,242],{},"curl -s -o report.pdf $OS_BASE\u002Fv1\u002Freports\u002F$RPT\u002Ffile -H \"Authorization: Bearer $OS_TOKEN\"\necho \"report.pdf ($(wc -c \u003C report.pdf) bytes)\"",[20,244,246],{"id":245},"verify-a-webhook-signature-python","Verify a webhook signature (Python)",[248,249,254],"pre",{"className":250,"code":251,"language":252,"meta":253,"style":253},"language-python shiki shiki-themes github-dark github-dark","import hmac, hashlib, base64, time\n\ndef verify(secret: str, headers: dict, body: bytes) -> bool:\n    sig = headers[\"X-OpenSense-Signature\"]\n    parts = dict(p.split(\"=\", 1) for p in sig.split(\",\"))\n    t, v1 = parts[\"t\"], parts[\"v1\"]\n    if abs(time.time() - int(t)) > 300:\n        return False\n    expected = base64.b64encode(\n        hmac.new(secret.encode(), f\"{t}.\".encode() + body, hashlib.sha256).digest()\n    ).decode()\n    return hmac.compare_digest(v1, expected)\n","python","",[255,256,257,269,276,314,333,377,399,428,437,448,481,487],"code",{"__ignoreMap":253},[58,258,261,265],{"class":259,"line":260},"line",1,[58,262,264],{"class":263},"sOPea","import",[58,266,268],{"class":267},"suv1-"," hmac, hashlib, base64, time\n",[58,270,272],{"class":259,"line":271},2,[58,273,275],{"emptyLinePlaceholder":274},true,"\n",[58,277,279,282,286,289,293,296,299,302,305,308,311],{"class":259,"line":278},3,[58,280,281],{"class":263},"def",[58,283,285],{"class":284},"sFR8T"," verify",[58,287,288],{"class":267},"(secret: ",[58,290,292],{"class":291},"s8ozJ","str",[58,294,295],{"class":267},", headers: ",[58,297,298],{"class":291},"dict",[58,300,301],{"class":267},", body: ",[58,303,304],{"class":291},"bytes",[58,306,307],{"class":267},") -> ",[58,309,310],{"class":291},"bool",[58,312,313],{"class":267},":\n",[58,315,317,320,323,326,330],{"class":259,"line":316},4,[58,318,319],{"class":267},"    sig ",[58,321,322],{"class":263},"=",[58,324,325],{"class":267}," headers[",[58,327,329],{"class":328},"s4wv1","\"X-OpenSense-Signature\"",[58,331,332],{"class":267},"]\n",[58,334,336,339,341,344,347,350,353,356,359,362,365,368,371,374],{"class":259,"line":335},5,[58,337,338],{"class":267},"    parts ",[58,340,322],{"class":263},[58,342,343],{"class":291}," dict",[58,345,346],{"class":267},"(p.split(",[58,348,349],{"class":328},"\"=\"",[58,351,352],{"class":267},", ",[58,354,355],{"class":291},"1",[58,357,358],{"class":267},") ",[58,360,361],{"class":263},"for",[58,363,364],{"class":267}," p ",[58,366,367],{"class":263},"in",[58,369,370],{"class":267}," sig.split(",[58,372,373],{"class":328},"\",\"",[58,375,376],{"class":267},"))\n",[58,378,380,383,385,388,391,394,397],{"class":259,"line":379},6,[58,381,382],{"class":267},"    t, v1 ",[58,384,322],{"class":263},[58,386,387],{"class":267}," parts[",[58,389,390],{"class":328},"\"t\"",[58,392,393],{"class":267},"], parts[",[58,395,396],{"class":328},"\"v1\"",[58,398,332],{"class":267},[58,400,402,405,408,411,414,417,420,423,426],{"class":259,"line":401},7,[58,403,404],{"class":263},"    if",[58,406,407],{"class":291}," abs",[58,409,410],{"class":267},"(time.time() ",[58,412,413],{"class":263},"-",[58,415,416],{"class":291}," int",[58,418,419],{"class":267},"(t)) ",[58,421,422],{"class":263},">",[58,424,425],{"class":291}," 300",[58,427,313],{"class":267},[58,429,431,434],{"class":259,"line":430},8,[58,432,433],{"class":263},"        return",[58,435,436],{"class":291}," False\n",[58,438,440,443,445],{"class":259,"line":439},9,[58,441,442],{"class":267},"    expected ",[58,444,322],{"class":263},[58,446,447],{"class":267}," base64.b64encode(\n",[58,449,451,454,457,460,463,466,469,472,475,478],{"class":259,"line":450},10,[58,452,453],{"class":267},"        hmac.new(secret.encode(), ",[58,455,456],{"class":263},"f",[58,458,459],{"class":328},"\"",[58,461,462],{"class":291},"{",[58,464,465],{"class":267},"t",[58,467,468],{"class":291},"}",[58,470,471],{"class":328},".\"",[58,473,474],{"class":267},".encode() ",[58,476,477],{"class":263},"+",[58,479,480],{"class":267}," body, hashlib.sha256).digest()\n",[58,482,484],{"class":259,"line":483},11,[58,485,486],{"class":267},"    ).decode()\n",[58,488,490,493],{"class":259,"line":489},12,[58,491,492],{"class":263},"    return",[58,494,495],{"class":267}," hmac.compare_digest(v1, expected)\n",[20,497,499],{"id":498},"get-the-audit-log-for-the-last-30-days","Get the audit log for the last 30 days",[25,501,503],{"label":502},"$ audit",[10,504,505,506,93,508,510],{},"curl -s \"$OS_BASE\u002Fv1\u002Faccounts\u002Fme\u002Faudit?from=$(date -u -d '30 days ago' +%FT%TZ)\" ",[48,507],{},[58,509],{}," | {ts, kind, by, target}'",[20,512,514],{"id":513},"export-everything","Export everything",[25,516,518],{"label":517},"$ export",[10,519,520,521,523,524,150],{},"curl -X POST -o opensense-export.zip ",[48,522],{},"\n$OS_BASE\u002Fv1\u002Faccounts\u002Fme\u002Fexport ",[48,525],{},[10,527,528,529,352,532,535,536,539,540,543,544,548],{},"The export is a single ZIP with ",[255,530,531],{},"measurements.csv",[255,533,534],{},"events.csv",",\n",[255,537,538],{},"audit.csv",", plus a ",[255,541,542],{},"meta.json"," describing the schemas. See\n",[14,545,547],{"href":546},"\u002Fsecurity\u002Fcompliance#customer-rights","security\u002Fcompliance",".",[20,550,552],{"id":551},"in-python-with-a-session","In Python, with a session",[248,554,556],{"className":250,"code":555,"language":252,"meta":253,"style":253},"import requests, os, datetime as dt\n\nS = requests.Session()\nS.headers.update({\"Authorization\": f\"Bearer {os.environ['OS_TOKEN']}\"})\nBASE = \"https:\u002F\u002Fapi.opensense.murzin.digital\"\n\ndef list_devices(site_id):\n    return S.get(f\"{BASE}\u002Fv1\u002Fdevices\", params={\"site_id\": site_id}).json()[\"data\"]\n\ndef last_value(channel_id):\n    r = S.get(f\"{BASE}\u002Fv1\u002Fchannels\u002F{channel_id}\u002Fmeasurements\",\n              params={\"limit\": 1, \"fn\": \"last\"}).json()\n    return r[\"data\"][0] if r[\"data\"] else None\n\ndef ack(event_id, note):\n    return S.post(f\"{BASE}\u002Fv1\u002Fevents\u002F{event_id}\u002Fack\", json={\"note\": note}).json()\n",[255,557,558,571,575,585,619,630,634,644,682,686,696,726,755,789,794,805],{"__ignoreMap":253},[58,559,560,562,565,568],{"class":259,"line":260},[58,561,264],{"class":263},[58,563,564],{"class":267}," requests, os, datetime ",[58,566,567],{"class":263},"as",[58,569,570],{"class":267}," dt\n",[58,572,573],{"class":259,"line":271},[58,574,275],{"emptyLinePlaceholder":274},[58,576,577,580,582],{"class":259,"line":278},[58,578,579],{"class":267},"S ",[58,581,322],{"class":263},[58,583,584],{"class":267}," requests.Session()\n",[58,586,587,590,593,596,598,601,603,606,609,612,614,616],{"class":259,"line":316},[58,588,589],{"class":267},"S.headers.update({",[58,591,592],{"class":328},"\"Authorization\"",[58,594,595],{"class":267},": ",[58,597,456],{"class":263},[58,599,600],{"class":328},"\"Bearer ",[58,602,462],{"class":291},[58,604,605],{"class":267},"os.environ[",[58,607,608],{"class":328},"'OS_TOKEN'",[58,610,611],{"class":267},"]",[58,613,468],{"class":291},[58,615,459],{"class":328},[58,617,618],{"class":267},"})\n",[58,620,621,624,627],{"class":259,"line":335},[58,622,623],{"class":291},"BASE",[58,625,626],{"class":263}," =",[58,628,629],{"class":328}," \"https:\u002F\u002Fapi.opensense.murzin.digital\"\n",[58,631,632],{"class":259,"line":379},[58,633,275],{"emptyLinePlaceholder":274},[58,635,636,638,641],{"class":259,"line":401},[58,637,281],{"class":263},[58,639,640],{"class":284}," list_devices",[58,642,643],{"class":267},"(site_id):\n",[58,645,646,648,651,653,655,658,661,663,667,669,671,674,677,680],{"class":259,"line":430},[58,647,492],{"class":263},[58,649,650],{"class":267}," S.get(",[58,652,456],{"class":263},[58,654,459],{"class":328},[58,656,657],{"class":291},"{BASE}",[58,659,660],{"class":328},"\u002Fv1\u002Fdevices\"",[58,662,352],{"class":267},[58,664,666],{"class":665},"s-3mD","params",[58,668,322],{"class":263},[58,670,462],{"class":267},[58,672,673],{"class":328},"\"site_id\"",[58,675,676],{"class":267},": site_id}).json()[",[58,678,679],{"class":328},"\"data\"",[58,681,332],{"class":267},[58,683,684],{"class":259,"line":439},[58,685,275],{"emptyLinePlaceholder":274},[58,687,688,690,693],{"class":259,"line":450},[58,689,281],{"class":263},[58,691,692],{"class":284}," last_value",[58,694,695],{"class":267},"(channel_id):\n",[58,697,698,701,703,705,707,709,711,714,716,719,721,724],{"class":259,"line":483},[58,699,700],{"class":267},"    r ",[58,702,322],{"class":263},[58,704,650],{"class":267},[58,706,456],{"class":263},[58,708,459],{"class":328},[58,710,657],{"class":291},[58,712,713],{"class":328},"\u002Fv1\u002Fchannels\u002F",[58,715,462],{"class":291},[58,717,718],{"class":267},"channel_id",[58,720,468],{"class":291},[58,722,723],{"class":328},"\u002Fmeasurements\"",[58,725,535],{"class":267},[58,727,728,731,733,735,738,740,742,744,747,749,752],{"class":259,"line":489},[58,729,730],{"class":665},"              params",[58,732,322],{"class":263},[58,734,462],{"class":267},[58,736,737],{"class":328},"\"limit\"",[58,739,595],{"class":267},[58,741,355],{"class":291},[58,743,352],{"class":267},[58,745,746],{"class":328},"\"fn\"",[58,748,595],{"class":267},[58,750,751],{"class":328},"\"last\"",[58,753,754],{"class":267},"}).json()\n",[58,756,758,760,763,765,768,771,774,777,779,781,783,786],{"class":259,"line":757},13,[58,759,492],{"class":263},[58,761,762],{"class":267}," r[",[58,764,679],{"class":328},[58,766,767],{"class":267},"][",[58,769,770],{"class":291},"0",[58,772,773],{"class":267},"] ",[58,775,776],{"class":263},"if",[58,778,762],{"class":267},[58,780,679],{"class":328},[58,782,773],{"class":267},[58,784,785],{"class":263},"else",[58,787,788],{"class":291}," None\n",[58,790,792],{"class":259,"line":791},14,[58,793,275],{"emptyLinePlaceholder":274},[58,795,797,799,802],{"class":259,"line":796},15,[58,798,281],{"class":263},[58,800,801],{"class":284}," ack",[58,803,804],{"class":267},"(event_id, note):\n",[58,806,808,810,813,815,817,819,822,824,827,829,832,834,837,839,841,844],{"class":259,"line":807},16,[58,809,492],{"class":263},[58,811,812],{"class":267}," S.post(",[58,814,456],{"class":263},[58,816,459],{"class":328},[58,818,657],{"class":291},[58,820,821],{"class":328},"\u002Fv1\u002Fevents\u002F",[58,823,462],{"class":291},[58,825,826],{"class":267},"event_id",[58,828,468],{"class":291},[58,830,831],{"class":328},"\u002Fack\"",[58,833,352],{"class":267},[58,835,836],{"class":665},"json",[58,838,322],{"class":263},[58,840,462],{"class":267},[58,842,843],{"class":328},"\"note\"",[58,845,846],{"class":267},": note}).json()\n",[848,849,850],"style",{},"html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}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 .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}",{"title":253,"searchDepth":278,"depth":278,"links":852},[853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869],{"id":22,"depth":271,"text":23},{"id":39,"depth":271,"text":40},{"id":64,"depth":271,"text":65},{"id":83,"depth":271,"text":84},{"id":99,"depth":271,"text":100},{"id":114,"depth":271,"text":115},{"id":127,"depth":271,"text":128},{"id":140,"depth":271,"text":141},{"id":153,"depth":271,"text":154},{"id":166,"depth":271,"text":167},{"id":184,"depth":271,"text":185},{"id":201,"depth":271,"text":202},{"id":218,"depth":271,"text":219},{"id":245,"depth":271,"text":246},{"id":498,"depth":271,"text":499},{"id":513,"depth":271,"text":514},{"id":551,"depth":271,"text":552},"Copy-paste recipes for common tasks",null,"md",{},"\u002Fapi\u002Fcookbook",{"title":5,"description":870},"api\u002Fcookbook",298,"95rVDTx80dtxlVqwm0oXpu49z31JBuROPcbDB5zRR2Q",1779022954671]