{"_id":"@ascflow/partner-sdk","_rev":"9-1651aa8f2cb41adfffe7ecdea5ef6787","name":"@ascflow/partner-sdk","dist-tags":{"latest":"1.0.9"},"versions":{"1.0.0":{"name":"@ascflow/partner-sdk","version":"1.0.0","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.0","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"1738864410b8ca7b0272cc8d8107a42a291c4998","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.0.tgz","fileCount":6,"integrity":"sha512-7KTvXUN9kRIOwgITq/EiEkKwfozeW7jeEV71wRXMPvav1CE1r4QASUUB8VmdXDYdA2GIkQy69HxCcvtgTcHwFw==","signatures":[{"sig":"MEUCIQCUPaBDEhJ9gi0NVxEARHSu/iNBrcCMR57DprdUQNVQgQIgKOlRqe31ZNWXQ/CfwiewURbhZp69kIlpsFvEGcYuMvo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21001},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.0_1777320966282_0.34432771994832834","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@ascflow/partner-sdk","version":"1.0.1","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.1","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"a94403699655816cad7aaa8741610a01d46fa024","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.1.tgz","fileCount":6,"integrity":"sha512-EAF4nrtlb6MwGMYg8ToA7UYZewdAmZTbxDxf5d6zes8ZEVLXS3nsMTySHaYXKpDSvHzJf+EMf/vuK5PefUmUPQ==","signatures":[{"sig":"MEUCIQCCEmjrVmpwIK2W9dNf6dO0KMCI8m5qoLre+J8KJmgAhAIgS2vGr9EAU0lyR4J9EPhGCavJZ7FOCxZ63QZsRpwN5qw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21001},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.1_1777322007176_0.18340730888073375","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@ascflow/partner-sdk","version":"1.0.2","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.2","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"060ec7fc4125bd26e1dbee85d7120755d83b6573","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.2.tgz","fileCount":6,"integrity":"sha512-71x+UrTEjstmfdudQpsoAPCjbfsYDxs8WLsOUTZnYC9cWDc3fAj6zWGybBX41y4DTM2V1vdYoeEVmao23DCiUg==","signatures":[{"sig":"MEUCIQDSNm+luXvkQHhm5Q0/FypAA4XL0GqiWkYS2zZO7Vnb4QIgIbOollEXup6srgBjy8wS/SmcTVyyrm2MDf6ykX16sOE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20849},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.2_1777322421239_0.6887083001991288","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@ascflow/partner-sdk","version":"1.0.3","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.3","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"d683d13205e1e574902f225e8e1e0c6fe4f585f4","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.3.tgz","fileCount":6,"integrity":"sha512-otrKmKenByWrQUHipT/d2enMuND6eLqIvMdtQfts4cHRaihsp2Ab3/Jfm1I4UzAfVP9myWRLX/26qbfPHT5djg==","signatures":[{"sig":"MEUCIQCP2tDJRGwqLFtU/38wisu9ZEDdTfuucEWIJj01d7eaYQIgVOAfnIpS3LaSBGBSTNb9dJ+teU+V0ui0sxHOzqtslC0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20680},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.3_1777322910521_0.20391888817695736","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@ascflow/partner-sdk","version":"1.0.5","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.5","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"49fb6a6bb884e6ae24432da34fa9508595f9b349","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.5.tgz","fileCount":6,"integrity":"sha512-eCefw0Is66jFcU+ze2+r+KbUtbCOOx6ChdAe6bFnFbyBuj1s7+97BRYupWCHoSlhgsF0IXVTU+DiAKV8EtSIQA==","signatures":[{"sig":"MEUCID3fAa8pdf3MWODI0dUeFlqLCwMAtp6cpCmSQSdL1e1jAiEAtYz5WQuTOIVEq4kjxa1Bp+4LsYfFfeVn1v9k03xThPk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20716},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.5_1777333635141_0.18574323120056468","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@ascflow/partner-sdk","version":"1.0.6","keywords":["ascflow","iframe","embed","partner","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.6","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"dist":{"shasum":"040230a61ee64536d956aeb8e46c66b74ac941b0","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.6.tgz","fileCount":6,"integrity":"sha512-BP7gcEDLSCMiSbpf1ua30mnAgSjw1J8R+FvIaj+cp6DcIidTmHqtaSmYxuMog13nHlRb+CNP8Jb+mG41kDSCXw==","signatures":[{"sig":"MEYCIQC+ZpJIbLaMB8nVfD1EKLbJldBI7Po10iANuIUrGDh+7gIhAOtgpUu32shU5ySt83l27nOsqUInEGRyLeqvzae3iAtV","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20318},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","lint":"tsc --noEmit","test":"node --experimental-vm-modules node_modules/.bin/jest","build":"tsup src/index.ts --format cjs,esm --dts --clean","prepublishOnly":"npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"_npmVersion":"10.9.2","description":"Official SDK for ASCFlow partner iframe integration","directories":{},"_nodeVersion":"22.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.9.3","@types/node":"^25.6.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.6_1777377049093_0.4777564608747127","host":"s3://npm-registry-packages-npm-production"}},"1.0.7":{"name":"@ascflow/partner-sdk","version":"1.0.7","keywords":["ascflow","sso","iframe","embed","partner","hmac","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.7","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"homepage":"https://github.com/ascflow/partner-sdk","bugs":{"url":"https://github.com/ascflow/partner-sdk/issues"},"dist":{"shasum":"dcecf7f651aab455c4afe5d322298a7f271aa44c","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.7.tgz","fileCount":11,"integrity":"sha512-g2cdmXO2bFDEnszKiWKHa8YMTkij+pRahDqx6eQGrQHtfl6iO+HriSQ4v8BrFZ8fldn5qd5RlDWH1qeyWVs4VQ==","signatures":[{"sig":"MEUCIQCmd79Pe7wFuOsdbhnk6G37NS/UBkBCedqW/nzlPJleDAIgfxB8uIGgj2ccKF24wfajBvSGNqWbLMkjgjWzboXq+QA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":149705},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"repository":{"url":"git+https://github.com/ascflow/partner-sdk.git","type":"git"},"_npmVersion":"10.9.2","description":"Official SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived embed URLs with HMAC-signed requests.","directories":{},"_nodeVersion":"22.17.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.4.0","typescript":"^5.4.0","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.7_1777378835379_0.9515832785714227","host":"s3://npm-registry-packages-npm-production"}},"1.0.8":{"name":"@ascflow/partner-sdk","version":"1.0.8","keywords":["ascflow","sso","iframe","embed","partner","hmac","sdk"],"author":{"name":"ASCFlow"},"license":"MIT","_id":"@ascflow/partner-sdk@1.0.8","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"homepage":"https://github.com/ascflow/partner-sdk","bugs":{"url":"https://github.com/ascflow/partner-sdk/issues"},"dist":{"shasum":"7e0fdcbd3efe8bfe186a0b918b2b086ab3820cb6","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.8.tgz","fileCount":11,"integrity":"sha512-6QenvLBgonJi4/LGMH+MZvxlPu1OrgZB3vMyoGM8QJlOrOdaedFy0PvJHV4k0m6rV7Nv4of48wVmTsHa/SRvMQ==","signatures":[{"sig":"MEQCICR5+YA4+D5DlVbxZdlM4go76a3Xa2LhnkwsSXxc2Ra4AiAKhnXVjQtpM9WhR7S4iT/NLKabc/rpKpd+6qDWa/4/0g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":150024},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"repository":{"url":"git+https://github.com/ascflow/partner-sdk.git","type":"git"},"_npmVersion":"10.9.2","description":"Official SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived embed URLs with HMAC-signed requests.","directories":{},"_nodeVersion":"22.17.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.4.0","typescript":"^5.4.0","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/partner-sdk_1.0.8_1777382728955_0.8035748131873535","host":"s3://npm-registry-packages-npm-production"}},"1.0.9":{"name":"@ascflow/partner-sdk","version":"1.0.9","description":"Official SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived embed URLs with HMAC-signed requests.","keywords":["ascflow","sso","iframe","embed","partner","hmac","sdk"],"license":"MIT","author":{"name":"ASCFlow"},"homepage":"https://github.com/ascflow/partner-sdk","repository":{"type":"git","url":"git+https://github.com/ascflow/partner-sdk.git"},"bugs":{"url":"https://github.com/ascflow/partner-sdk/issues"},"engines":{"node":">=18.0.0"},"main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm run test && npm run build"},"devDependencies":{"@types/node":"^20.11.0","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^1.4.0"},"publishConfig":{"access":"public"},"_id":"@ascflow/partner-sdk@1.0.9","_nodeVersion":"22.17.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-IV3sK3D0xY+djQXlnTqW10R8pmPTnQbP2UdP70B/BResG1c6qGKd0tkx9dN9EsiPS1A8u4+NWRd3u6nrJn5GwA==","shasum":"c7c3da9576263312b3fe919dd3db2661516eb7e3","tarball":"https://registry.npmjs.org/@ascflow/partner-sdk/-/partner-sdk-1.0.9.tgz","fileCount":11,"unpackedSize":155200,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDtZjQnV8kRpzqDDm20TYw+RE9IeiBR2x5G1sU+i5f6DQIgapWRUEPQsasSB3ZBczLa/EF4mX3lnUA1QlXLFz1Jwbo="}]},"_npmUser":{"name":"richard17sl","email":"richardlirio.dev@gmail.com"},"directories":{},"maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/partner-sdk_1.0.9_1777552995334_0.9834148369134545"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-27T20:16:06.186Z","modified":"2026-04-30T12:43:15.627Z","1.0.0":"2026-04-27T20:16:06.413Z","1.0.1":"2026-04-27T20:33:27.313Z","1.0.2":"2026-04-27T20:40:21.445Z","1.0.3":"2026-04-27T20:48:30.650Z","1.0.5":"2026-04-27T23:47:15.292Z","1.0.6":"2026-04-28T11:50:49.249Z","1.0.7":"2026-04-28T12:20:35.602Z","1.0.8":"2026-04-28T13:25:29.124Z","1.0.9":"2026-04-30T12:43:15.478Z"},"bugs":{"url":"https://github.com/ascflow/partner-sdk/issues"},"author":{"name":"ASCFlow"},"license":"MIT","homepage":"https://github.com/ascflow/partner-sdk","keywords":["ascflow","sso","iframe","embed","partner","hmac","sdk"],"repository":{"type":"git","url":"git+https://github.com/ascflow/partner-sdk.git"},"description":"Official SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived embed URLs with HMAC-signed requests.","maintainers":[{"name":"richard17sl","email":"richardlirio.dev@gmail.com"}],"readme":"# @ascflow/partner-sdk\n\nOfficial Node.js SDK for partners integrating with **ASCFlow's iframe SSO**.\nGenerates short-lived, single-use **embed URLs** that you render inside an\n`<iframe>` so your end users land in ASCFlow already authenticated — without\never logging in twice.\n\n[![npm version](https://img.shields.io/npm/v/@ascflow/partner-sdk.svg)](https://www.npmjs.com/package/@ascflow/partner-sdk)\n[![Node.js](https://img.shields.io/node/v/@ascflow/partner-sdk.svg)](https://nodejs.org)\n[![License](https://img.shields.io/npm/l/@ascflow/partner-sdk.svg)](./LICENSE)\n\n---\n\n## Table of Contents\n\n- [What this SDK does](#what-this-sdk-does)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Configuration](#configuration)\n- [Embedding the iframe](#embedding-the-iframe)\n- [API reference](#api-reference)\n- [Error handling](#error-handling)\n- [Retries and idempotency](#retries-and-idempotency)\n- [How requests are signed](#how-requests-are-signed)\n- [Security](#security)\n- [Troubleshooting](#troubleshooting)\n- [TypeScript](#typescript)\n- [Versioning and support](#versioning-and-support)\n- [License](#license)\n\n---\n\n## What this SDK does\n\nASCFlow exposes a single endpoint for partner SSO:\n\n```\nPOST /partner/auth/embed-url\n```\n\nCalling it requires:\n\n1. An **HMAC-SHA256 signature** over a canonical string built from the request\n2. An **API key** identifying the partner\n3. An **Origin** registered with ASCFlow for that partner\n4. A short-lived **idempotency key** to safely retry on network failure\n\nThis SDK does all of that for you and returns a one-shot `embedUrl` that you\ndrop into an `<iframe>`. It runs on **your server** (Node.js), never the\nbrowser — your secret must never leave the backend.\n\n```\n                    ┌──────────────────────────────────────────┐\n                    │ Your partner backend (Node.js)           │\n                    │                                          │\n  end user ─── HTTP ─►  ascflow-partner-sdk.createEmbedUrl()   │\n                    │           │                              │\n                    │           ▼  HMAC-signed POST            │\n                    │  ┌──────────────────────────────┐        │\n                    │  │  ASCFlow gateway              │        │\n                    │  │  POST /partner/auth/embed-url │        │\n                    │  └──────────────────────────────┘        │\n                    │           │                              │\n                    │           ▼                              │\n                    │     { embedUrl, expiresIn }              │\n                    │                                          │\n                    │  Render <iframe src={embedUrl} />        │\n                    └──────────────────────────────────────────┘\n```\n\n---\n\n> **Don't want to use the SDK?** Partners on other stacks (PHP, Python, Go,\n> Ruby, Java...) can implement the same protocol by hand following\n> [docs/INTEGRACAO_SEM_SDK.pt-BR.md](./docs/INTEGRACAO_SEM_SDK.pt-BR.md)\n> (Portuguese, with copy-pasteable examples in 7 languages and a fixed\n> test vector).\n\n## Installation\n\n```bash\nnpm install @ascflow/partner-sdk\n# or\npnpm add @ascflow/partner-sdk\n# or\nyarn add @ascflow/partner-sdk\n```\n\n**Requirements**\n\n- Node.js **18 or later** (uses the global `fetch`)\n- TypeScript **5.x** is supported but not required\n\n---\n\n## Quick start\n\n```ts\nimport { AscflowPartnerClient } from '@ascflow/partner-sdk';\n\n// Reads ASCFLOW_PARTNER_* environment variables automatically.\nconst ascflow = new AscflowPartnerClient();\n\nconst { embedUrl, expiresIn } = await ascflow.createEmbedUrl({\n  email: 'user@example.com',\n  name: 'Jane Doe',\n  cpf: '13166917731',\n  role: 'user',\n  redirectPath: '/flow/onboarding',\n});\n\nconsole.log(embedUrl);   // https://app.ascflow.com/embed?code=...&r=%2Fflow%2Fonboarding\nconsole.log(expiresIn);  // 90  (seconds)\n```\n\nA complete Express handler:\n\n```ts\nimport express from 'express';\nimport { AscflowPartnerClient, AscflowError } from '@ascflow/partner-sdk';\n\nconst app = express();\nconst ascflow = new AscflowPartnerClient();\n\napp.get('/render-ascflow', async (req, res) => {\n  // Authenticate and authorize the request on YOUR side first.\n  const user = await getCurrentUser(req);\n  if (!user) return res.status(401).end();\n\n  try {\n    const { embedUrl, expiresIn } = await ascflow.createEmbedUrl({\n      email: user.email,\n      name: user.fullName,\n      cpf: user.cpf,\n      role: user.role,\n      redirectPath: typeof req.query.r === 'string' ? req.query.r : '/',\n    });\n    res.json({ embedUrl, expiresIn });\n  } catch (err) {\n    if (err instanceof AscflowError) {\n      return res.status(502).json({ error: err.code, message: err.message });\n    }\n    throw err;\n  }\n});\n```\n\n---\n\n## Configuration\n\nThe client reads its configuration from environment variables by default. You\ncan override any field by passing it to the constructor.\n\n### Environment variables\n\n| Variable | Required | Description |\n|---|:---:|---|\n| `ASCFLOW_PARTNER_API_KEY` | ✅ | Your public partner identifier. |\n| `ASCFLOW_PARTNER_SECRET` | ✅ | The signing secret. **Server-side only.** |\n| `ASCFLOW_PARTNER_ORIGIN` | ✅ | Origin sent on every request. Must match an origin registered for your partner. |\n| `ASCFLOW_PARTNER_BASE_URL` | ✅ | Gateway base URL (e.g. `https://gateway.ascflow.com`). |\n| `ASCFLOW_PARTNER_TIMEOUT_MS` | – | Request timeout in ms. Default `10000`. |\n| `ASCFLOW_PARTNER_MAX_RETRIES` | – | Max automatic retries on 5xx / network errors. Default `2`. |\n\nA copy-pasteable [`.env.example`](./.env.example) ships with the package.\n\n### Constructor overrides\n\n```ts\nconst ascflow = new AscflowPartnerClient({\n  apiKey: process.env.MY_KEY,\n  secret: process.env.MY_SECRET,\n  origin: 'https://app.partner.com',\n  baseUrl: 'https://gateway.ascflow.com',\n  timeoutMs: 5_000,\n  maxRetries: 3,\n\n  // Advanced — supply your own fetch (e.g. with a proxy agent or for tests).\n  fetch: customFetch,\n\n  // Default 'seconds'. Switch to 'milliseconds' only if your gateway expects it.\n  timestampFormat: 'seconds',\n});\n```\n\n> ⚠️ **Timestamp format** — the canonical string contains the timestamp, so\n> the SDK and the gateway **must agree** on its format. The default `'seconds'`\n> matches the reference Postman pre-request script\n> (`Math.floor(Date.now() / 1000)`). If your gateway validates milliseconds\n> instead, set `timestampFormat: 'milliseconds'`.\n\n---\n\n## Embedding the iframe\n\nOnce you have an `embedUrl`, render it on the page where the partner wants\nASCFlow to appear:\n\n```html\n<iframe\n  src=\"https://app.ascflow.com/embed?code=a3f8...&r=%2Fflow%2Fmeu-processo\"\n  width=\"100%\"\n  height=\"800\"\n  frameborder=\"0\"\n  sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n  allow=\"clipboard-write\"\n></iframe>\n```\n\nThe `sandbox` attribute is your defense-in-depth control. Keep it as\nrestrictive as your use case allows. ASCFlow already enforces strict\n`Content-Security-Policy` and `frame-ancestors` on its side.\n\n---\n\n## API reference\n\n### `new AscflowPartnerClient(config?)`\n\nCreates a client. With no argument, it reads everything from environment\nvariables. See [Configuration](#configuration).\n\n### `client.createEmbedUrl(input)`\n\nGenerates a one-shot embed URL.\n\n**Input** (`CreateEmbedUrlInput`)\n\n| Field | Type | Required | Notes |\n|---|---|:---:|---|\n| `email` | `string` | ✅ | The end user's email. |\n| `name` | `string` | ✅ | Display name shown inside ASCFlow. |\n| `cpf` | `string` | ✅ | End user's CPF (Brazilian tax ID). Digits only, exactly 11 characters. |\n| `role` | `'admin' \\| 'user'` | ✅ | Role of the end user inside ASCFlow. |\n| `redirectPath` | `string` | – | Path inside ASCFlow to land on. Must start with `/` and must not start with `//`. Default `'/'`. |\n| `idempotencyKey` | `string` | – | Custom idempotency key. Default: a random UUID. |\n\n**Returns** (`Promise<CreateEmbedUrlResponse>`)\n\n```ts\n{\n  embedUrl: string;   // URL to put in <iframe src>\n  expiresIn: number;  // seconds until the URL is invalid (typically 60–120)\n}\n```\n\n**Throws** — see [Error handling](#error-handling).\n\n### `client.signedRequest({ method, path, body, idempotencyKey })`\n\nLower-level helper for advanced use cases. Signs and sends an arbitrary\nrequest to the gateway with the same canonicalization rules. **Most\nusers do not need this.**\n\n### Helpers (named exports)\n\nFor testing and tooling integrations, the SDK also exports:\n\n```ts\nimport {\n  buildCanonicalString,\n  hmacSha256Hex,\n  sha256Hex,\n  normalizePath,\n  currentTimestamp,\n} from '@ascflow/partner-sdk';\n```\n\nThese let you reproduce the exact wire format the SDK uses (useful when\ndebugging signature mismatches, writing custom transports, or signing\nrequests from a non-Node environment that does its own HTTP).\n\n---\n\n## Error handling\n\nEvery SDK error extends `AscflowError`, which has these properties:\n\n| Property | Type | Notes |\n|---|---|---|\n| `code` | `string` | Stable machine-readable code (e.g. `AUTH_ERROR`). |\n| `status` | `number?` | HTTP status, when applicable. |\n| `requestId` | `string?` | From the `x-request-id` response header, when present. |\n| `details` | `unknown` | Parsed response body, for forensic logging. |\n\nSubclasses you can `instanceof`-narrow:\n\n| Class | When |\n|---|---|\n| `AscflowConfigError` | Missing or invalid SDK configuration. No request was sent. |\n| `AscflowValidationError` | Local input validation failed (bad email, bad `redirectPath`, oversized body). No request was sent. |\n| `AscflowAuthError` | HTTP `401` / `403`. Signature, timestamp, origin, or partner status problem. |\n| `AscflowRateLimitError` | HTTP `429`. Has `retryAfterSeconds` parsed from `Retry-After`. |\n| `AscflowRequestError` | Other 4xx. Usually means a request-shape problem. |\n| `AscflowServerError` | HTTP 5xx after retries are exhausted. |\n| `AscflowNetworkError` | Connection error or timeout after retries are exhausted. |\n\n```ts\nimport {\n  AscflowAuthError,\n  AscflowRateLimitError,\n  AscflowError,\n} from '@ascflow/partner-sdk';\n\ntry {\n  await ascflow.createEmbedUrl({ email, name });\n} catch (err) {\n  if (err instanceof AscflowAuthError) {\n    logger.error({ requestId: err.requestId }, 'ASCFlow rejected our credentials');\n    return res.status(502).json({ error: 'sso_auth_failed' });\n  }\n  if (err instanceof AscflowRateLimitError) {\n    res.setHeader('Retry-After', String(err.retryAfterSeconds ?? 30));\n    return res.status(429).end();\n  }\n  if (err instanceof AscflowError) {\n    logger.error({ code: err.code, status: err.status }, err.message);\n    return res.status(502).json({ error: 'sso_unavailable' });\n  }\n  throw err;\n}\n```\n\n---\n\n## Retries and idempotency\n\nThe SDK retries automatically on:\n\n- Network errors (DNS, connection refused, timeout)\n- HTTP `408`, `425`, `429`, `500`, `502`, `503`, `504`\n\nRetries use **exponential backoff with jitter** and respect the `Retry-After`\nheader on `429`. The default is **2 retries** (`maxRetries: 2`); set to `0`\nto disable.\n\n**Every retry sends the same `Idempotency-Key`**, so the gateway can dedupe\nduplicates. If you don't supply one, the SDK generates a fresh UUID per\n`createEmbedUrl` call. Supply your own only if you need request-level\nidempotency across **process restarts** (e.g. you're queuing the call and\nmight recover-and-retry minutes later).\n\n---\n\n## How requests are signed\n\nThe SDK builds and signs every request like this:\n\n```\ncanonicalString = METHOD + \"\\n\"\n                + PATH + \"\\n\"\n                + TIMESTAMP + \"\\n\"\n                + SHA256_HEX(BODY)\n\nX-Signature = HMAC_SHA256_HEX(secret, canonicalString)\n```\n\n**Headers sent:**\n\n```\nContent-Type:    application/json\nOrigin:          <ASCFLOW_PARTNER_ORIGIN>\nX-Partner-Key:   <ASCFLOW_PARTNER_API_KEY>\nX-Timestamp:     <unix seconds>\nX-Signature:     <hex HMAC>\nIdempotency-Key: <UUID, auto or caller-supplied>\n```\n\n`PATH` is normalized: leading slash, no trailing slash (except for `/`),\nno empty segments. `BODY` is hashed as the **exact UTF-8 bytes** of the\nserialized JSON, so the SDK serializes it once and uses the same string\nfor both the hash and the request body. There is no \"canonical JSON\";\nthe bytes that are hashed are the bytes that are sent.\n\nIf you ever need to verify a signature manually:\n\n```ts\nimport { buildCanonicalString, hmacSha256Hex } from '@ascflow/partner-sdk';\n\nconst canonical = buildCanonicalString({\n  method: 'POST',\n  path: '/partner/auth/embed-url',\n  timestamp: '1730000000',\n  body: '{\"email\":\"a@b.com\",\"name\":\"A\",\"redirectPath\":\"/\",\"cpf\":\"00000000000\",\"role\":\"user\"}',\n});\nconst signature = hmacSha256Hex(secret, canonical);\n```\n\n---\n\n## Security\n\nIn short: **never expose your secret to the browser**, **never commit it**,\nand rotate it through the ASCFlow admin panel. The SDK enforces good\ndefaults but cannot stop you from leaking a secret you've copied somewhere\nunsafe.\n\nFor a deeper checklist — including handling `redirectPath`, idempotency,\nsecret rotation, and what to log — see [SECURITY.md](./SECURITY.md).\n\n---\n\n## Troubleshooting\n\n### `AscflowAuthError: invalid signature`\n\nIn order of likelihood:\n\n1. **Wrong secret.** Check `ASCFLOW_PARTNER_SECRET`. The secret is shown\n   only once when generated; if you lost it, rotate to a new one.\n2. **Timestamp format mismatch.** Default is seconds. If your gateway is\n   configured for milliseconds, set `timestampFormat: 'milliseconds'`.\n3. **Body was modified after signing.** This shouldn't happen with the\n   SDK alone, but it can if a proxy, middleware, or custom `fetch` rewrites\n   the body. The hash is computed over the exact bytes that go on the wire.\n4. **Path mismatch.** If you put a custom `baseUrl` with a path prefix\n   (e.g. `https://gateway.ascflow.com/v1`), make sure your gateway is\n   configured to canonicalize the same way.\n\n### `AscflowAuthError: origin not allowed`\n\nThe `Origin` you sent is not on your partner's allowlist. Compare:\n\n```ts\nconsole.log(process.env.ASCFLOW_PARTNER_ORIGIN);\n```\n\nagainst the value registered with ASCFlow. The match is **exact** —\ntrailing slashes, scheme, and port all matter.\n\n### `AscflowAuthError: timestamp expired`\n\nYour server clock is more than the allowed skew off real time (typically\n60s). Run NTP on the host. The SDK uses the local clock; it has no way\nto compensate.\n\n### `AscflowValidationError: redirectPath must not start with \"//\" ...`\n\nYou're trying to pass an absolute URL or a protocol-relative path to\n`redirectPath`. This is blocked locally as defense-in-depth — the gateway\nwould reject it anyway. Use a path beginning with a single `/`.\n\n### Requests work in Postman but fail with the SDK\n\nAlmost always one of:\n\n- The Postman script uses **seconds**; your gateway is configured for\n  milliseconds, or vice-versa. Compare with the SDK's `timestampFormat`.\n- The Postman body has different whitespace / key order than what\n  `JSON.stringify` produces. The SDK hashes the bytes it actually sends,\n  so this is fine on the SDK side — the issue would be on the gateway\n  if it validates against a re-serialized body. Confirm the gateway hashes\n  `req.rawBody`, not `JSON.stringify(req.body)`.\n\n---\n\n## TypeScript\n\nTypes ship with the package; no need to install `@types/@ascflow/partner-sdk`.\nBoth ESM (`import`) and CJS (`require`) consumers are supported.\n\n```ts\nimport type {\n  AscflowPartnerClientConfig,\n  CreateEmbedUrlInput,\n  CreateEmbedUrlResponse,\n} from '@ascflow/partner-sdk';\n```\n\n---\n\n## Versioning and support\n\nThis SDK follows [Semantic Versioning](https://semver.org). The current\nmajor version is `0.x`, which means the API may change in minor releases\nuntil `1.0.0`. Pin to an exact version in production until then.\n\n- **Bug reports / questions:** open an issue on GitHub.\n- **Security issues:** see [SECURITY.md](./SECURITY.md) for the disclosure\n  process. **Do not** open a public issue for vulnerabilities.\n\n---\n\n## License\n\n[MIT](./LICENSE) © ASCFlow\n","readmeFilename":"README.md"}