diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 3d7f2589..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,50 +0,0 @@
-## Base ########################################################################
-# Use a larger node image to do the build for native deps (e.g., gcc, python)
-FROM node:lts-bookworm@sha256:f4698d49371c8a9fa7dd78b97fb2a532213903066e47966542b3b1d403449da4 as base
-
-# Reduce npm log spam and colour during install within Docker
-ENV NPM_CONFIG_LOGLEVEL=warn
-ENV NPM_CONFIG_COLOR=false
-
-# Switch to the node user vs. root
-USER node
-
-RUN mkdir /home/node/app
-
-# We'll run the app as the `node` user, so put it in their home directory
-WORKDIR /home/node/app
-
-# Install dependencies
-COPY --chown=node:node package.json /home/node/app/
-COPY --chown=node:node package-lock.json /home/node/app/
-COPY --chown=node:node yarn.lock /home/node/app/
-RUN npm install
-
-
-# Copy the source code over
-COPY --chown=node:node . /home/node/app/
-
-## Development #################################################################
-# Define a development target that installs devDeps and runs in dev mode
-FROM base as development
-WORKDIR /home/node/app
-COPY --chown=node:node --from=base /home/node/app/node_modules /home/node/app/node_modules
-# Expose port 3000
-EXPOSE 3000
-# Start the app in debug mode so we can attach the debugger
-CMD ["npm", "start"]
-
-## Production ##################################################################
-# Also define a production target which doesn't use devDeps
-FROM base as production
-WORKDIR /home/node/app
-COPY --chown=node:node --from=development /home/node/app/node_modules /home/node/app/node_modules
-# Build the Docusaurus app
-RUN npm run build
-
-## Deploy ######################################################################
-# Use a stable nginx image
-FROM nginx:bookworm@sha256:08bc36ad52474e528cc1ea3426b5e3f4bad8a130318e3140d6cfe29c8892c7ef as deploy
-WORKDIR /home/node/app
-# Copy what we've installed/built from production
-COPY --chown=node:node --from=production /home/node/app/build /usr/share/nginx/html/
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 53dd04c7..00000000
--- a/Makefile
+++ /dev/null
@@ -1,26 +0,0 @@
-ifeq ($(shell uname -s), Darwin)
-BUILD_COMMAND = buildx build --load
-else
-BUILD_COMMAND = build
-endif
-
-# Lists all make commands in a Makefile
-.PHONY: list
-list:
- @LC_ALL=C $(MAKE) -pRrq -f $(firstword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
-
-.PHONY: image
-image:
- docker $(BUILD_COMMAND) -t tkhq/docs --target production .
-
-.PHONY: dev-image
-dev-image: Dockerfile
- docker $(BUILD_COMMAND) -t tkhq/docs:dev --target development .
-
-.PHONY: run-dev
-run-dev: dev-image
- docker run -p 3000:3000 -v $(PWD):/home/node/app/ tkhq/docs:dev
-
-.PHONY: stop-dev
-stop-dev:
- docker stop $(shell docker ps -a -q --filter ancestor=tkhq/docs:dev)
diff --git a/README.md b/README.md
index 1a828aca..d6210daf 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,56 @@
This repo hosts the documentation hosted at https://docs.turnkey.com.
-It's built with [Docusaurus](https://docusaurus.io/).
+## Development
+
+### Prerequisites
+
+To work with this documentation locally, you'll need:
+
+- Node.js (see `.nvmrc` for the recommended version)
+- npm or yarn
+
+### Local Development
+
+Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview documentation changes locally:
+
+```sh
+# Install Mintlify CLI globally
+npm i -g mintlify
+
+# Start the local development server
+mintlify dev
+```
+
+This will start a local development server where you can preview your changes in real-time.
+
+If you encounter any issues with the development server:
-## Algolia
+- Run `mintlify install` to reinstall dependencies
+- Ensure you're running the command in a folder with `docs.json` (Mintlify's configuration file)
+
+> **Important:** If you previously worked with the Docusaurus version of this site, make sure to delete the `build` and `.docusaurus` folders before running the Mintlify docs site locally. Otherwise, you may experience style conflicts.
+
+### Deployment
+
+Changes to the documentation are automatically deployed when merged to the main branch through our CI/CD pipeline.
+
+### Mintlify Dashboard
+
+You can access the Mintlify dashboard for this project at:
+[dashboard.mintlify.com](https://dashboard.mintlify.com/turnkey-0e7c1f5b/turnkey-0e7c1f5b)
+
+## The dashboard provides analytics, deployment status, and other management features for our documentation.
+
+## Legacy Documentation
+
+The following information pertains to the previous Docusaurus-based documentation setup.
+
+### Algolia
We use the Algolia plugin for Docusaurus to manage search on our docs page. The primary dashboard can be accessed via https://dashboard.algolia.com/apps/89KSB43UFT/dashboard. Reach out to Jack, Arnaud, or Andrew for access.
-### Crawling
+#### Crawling
Our crawler settings can be found at https://crawler.algolia.com/admin/crawlers/15584ae7-61de-4f26-af35-4bc55d0de0b5/overview. Algolia crawls our docs site once a week on Monday at 12:31 (UTC). This is simply the default behavior. There are cases where we may want to forcefully trigger Algolia to crawl/index our site, i.e. when we do a big refactor or otherwise reorganize the structure of our docs significantly.
@@ -18,8 +61,6 @@ In order to manually trigger a new crawl, use the `Restart crawling` button:
Our docs site is small, so each crawl is quick (~30-60s).
-## Development
-
### Vercel
Each push to Github will trigger a Vercel build:
@@ -28,7 +69,7 @@ Each push to Github will trigger a Vercel build:
This is a convenient way to view changes, add feedback, and collaborate overall. Any build can also be promoted to production, if need be.
-### Local Development
+### Legacy Local Development
#### With yarn
diff --git a/api-overview.mdx b/api-overview.mdx
new file mode 100644
index 00000000..42506559
--- /dev/null
+++ b/api-overview.mdx
@@ -0,0 +1,27 @@
+---
+title: API Overview
+mode: wide
+sidebarTitle: Overview
+---
+
+
+
+ Getting started with the Turnkey API
+
+
+
+ Creating your first signed request
+
+
+
+ Fetching data from Turnkey
+
+
+
+ Secure execution with Turnkey
+
+
+
+ Enumerating all errors received using the Turnkey API
+
+
diff --git a/api-reference/activities/get-activity.mdx b/api-reference/activities/get-activity.mdx
new file mode 100644
index 00000000..494b42a8
--- /dev/null
+++ b/api-reference/activities/get-activity.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_activity
+---
\ No newline at end of file
diff --git a/api-reference/activities/list-activities.mdx b/api-reference/activities/list-activities.mdx
new file mode 100644
index 00000000..447585d2
--- /dev/null
+++ b/api-reference/activities/list-activities.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_activities
+---
\ No newline at end of file
diff --git a/api-reference/activities/overview.mdx b/api-reference/activities/overview.mdx
new file mode 100644
index 00000000..cf9a3ec1
--- /dev/null
+++ b/api-reference/activities/overview.mdx
@@ -0,0 +1,7 @@
+---
+title: "Activities"
+description: "Activities encapsulate all the possible actions that can be taken with Turnkey. Some examples include adding a new user, creating a private key, and signing a transaction."
+sidebarTitle: "Overview"
+mode: wide
+---
+Activities that modify your Organization are processed asynchronously. To confirm processing is complete and retrieve the Activity results, these activities must be polled until that status has been updated to a finalized state: `COMPLETED` when the activity is successful or `FAILED` when the activity has failed
diff --git a/api-reference/api-keys/create-api-keys.mdx b/api-reference/api-keys/create-api-keys.mdx
new file mode 100644
index 00000000..e21db99e
--- /dev/null
+++ b/api-reference/api-keys/create-api-keys.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_api_keys
+---
\ No newline at end of file
diff --git a/api-reference/api-keys/delete-api-keys.mdx b/api-reference/api-keys/delete-api-keys.mdx
new file mode 100644
index 00000000..8452cade
--- /dev/null
+++ b/api-reference/api-keys/delete-api-keys.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_api_keys
+---
\ No newline at end of file
diff --git a/api-reference/api-keys/get-api-key-1.mdx b/api-reference/api-keys/get-api-key-1.mdx
new file mode 100644
index 00000000..88804c22
--- /dev/null
+++ b/api-reference/api-keys/get-api-key-1.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_api_keys
+---
\ No newline at end of file
diff --git a/api-reference/api-keys/get-api-key.mdx b/api-reference/api-keys/get-api-key.mdx
new file mode 100644
index 00000000..6a8aea69
--- /dev/null
+++ b/api-reference/api-keys/get-api-key.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_api_key
+---
\ No newline at end of file
diff --git a/api-reference/api-keys/overview.mdx b/api-reference/api-keys/overview.mdx
new file mode 100644
index 00000000..c18500a4
--- /dev/null
+++ b/api-reference/api-keys/overview.mdx
@@ -0,0 +1,7 @@
+---
+title: "API Keys"
+description: "API Keys are used to authenticate requests"
+sidebarTitle: "Overview"
+mode: wide
+---
+See our [CLI](https://github.com/tkhq/tkcli) for instructions on generating API Keys
diff --git a/api-reference/authenticators/create-authenticators.mdx b/api-reference/authenticators/create-authenticators.mdx
new file mode 100644
index 00000000..e193d22c
--- /dev/null
+++ b/api-reference/authenticators/create-authenticators.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_authenticators
+---
\ No newline at end of file
diff --git a/api-reference/authenticators/delete-authenticators.mdx b/api-reference/authenticators/delete-authenticators.mdx
new file mode 100644
index 00000000..0800be70
--- /dev/null
+++ b/api-reference/authenticators/delete-authenticators.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_authenticators
+---
\ No newline at end of file
diff --git a/api-reference/authenticators/get-authenticator.mdx b/api-reference/authenticators/get-authenticator.mdx
new file mode 100644
index 00000000..784e5624
--- /dev/null
+++ b/api-reference/authenticators/get-authenticator.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_authenticator
+---
\ No newline at end of file
diff --git a/api-reference/authenticators/get-authenticators.mdx b/api-reference/authenticators/get-authenticators.mdx
new file mode 100644
index 00000000..65dde603
--- /dev/null
+++ b/api-reference/authenticators/get-authenticators.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_authenticators
+---
\ No newline at end of file
diff --git a/api-reference/authenticators/overview.mdx b/api-reference/authenticators/overview.mdx
new file mode 100644
index 00000000..ab43e076
--- /dev/null
+++ b/api-reference/authenticators/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Authenticators"
+description: "Authenticators are WebAuthN hardware devices, such as a Macbook TouchID or Yubikey, that can be used to authenticate requests."
+sidebarTitle: "Overview"
+mode: wide
+---
diff --git a/api-reference/consensus/approve-activity.mdx b/api-reference/consensus/approve-activity.mdx
new file mode 100644
index 00000000..2e3163b0
--- /dev/null
+++ b/api-reference/consensus/approve-activity.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/approve_activity
+---
\ No newline at end of file
diff --git a/api-reference/consensus/overview.mdx b/api-reference/consensus/overview.mdx
new file mode 100644
index 00000000..a851322d
--- /dev/null
+++ b/api-reference/consensus/overview.mdx
@@ -0,0 +1,8 @@
+---
+title: "Consensus"
+description: "Policies can enforce consensus requirements for Activities. For example, adding a new user requires two admins to approve the request."
+sidebarTitle: "Overview"
+mode: wide
+---
+
+Activities that have been proposed, but don't yet meet the Consesnsus requirements will have the status: `REQUIRES_CONSENSUS`. Activities in this state can be approved or rejected using the unique fingerprint generated when an Activity is created.
diff --git a/api-reference/consensus/reject-activity.mdx b/api-reference/consensus/reject-activity.mdx
new file mode 100644
index 00000000..ddeae665
--- /dev/null
+++ b/api-reference/consensus/reject-activity.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/reject_activity
+---
\ No newline at end of file
diff --git a/api-reference/features/remove-organization-feature.mdx b/api-reference/features/remove-organization-feature.mdx
new file mode 100644
index 00000000..b5089a6a
--- /dev/null
+++ b/api-reference/features/remove-organization-feature.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/remove_organization_feature
+---
\ No newline at end of file
diff --git a/api-reference/features/set-organization-feature.mdx b/api-reference/features/set-organization-feature.mdx
new file mode 100644
index 00000000..49ad2190
--- /dev/null
+++ b/api-reference/features/set-organization-feature.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/set_organization_feature
+---
\ No newline at end of file
diff --git a/api-reference/invitations/create-invitations.mdx b/api-reference/invitations/create-invitations.mdx
new file mode 100644
index 00000000..8ae62b8a
--- /dev/null
+++ b/api-reference/invitations/create-invitations.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_invitations
+---
\ No newline at end of file
diff --git a/api-reference/invitations/delete-invitation.mdx b/api-reference/invitations/delete-invitation.mdx
new file mode 100644
index 00000000..aecf0889
--- /dev/null
+++ b/api-reference/invitations/delete-invitation.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_invitation
+---
\ No newline at end of file
diff --git a/api-reference/invitations/overview.mdx b/api-reference/invitations/overview.mdx
new file mode 100644
index 00000000..2124b85b
--- /dev/null
+++ b/api-reference/invitations/overview.mdx
@@ -0,0 +1,8 @@
+---
+title: "Invitations"
+description: "Invitations allow you to invite Users into your Organization via email. Alternatively, Users can be added directly without an Invitation if their ApiKey or Authenticator credentials are known ahead of time."
+sidebarTitle: "Overview"
+mode: wide
+---
+
+See [Users](/api-reference/users/overview) for more information
diff --git a/api-reference/organizations/create-sub-organization.mdx b/api-reference/organizations/create-sub-organization.mdx
new file mode 100644
index 00000000..f409d60e
--- /dev/null
+++ b/api-reference/organizations/create-sub-organization.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_sub_organization
+---
\ No newline at end of file
diff --git a/api-reference/organizations/delete-sub-organization.mdx b/api-reference/organizations/delete-sub-organization.mdx
new file mode 100644
index 00000000..e7608f9c
--- /dev/null
+++ b/api-reference/organizations/delete-sub-organization.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_sub_organization
+---
\ No newline at end of file
diff --git a/api-reference/organizations/get-configs.mdx b/api-reference/organizations/get-configs.mdx
new file mode 100644
index 00000000..468afbcb
--- /dev/null
+++ b/api-reference/organizations/get-configs.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_organization_configs
+---
\ No newline at end of file
diff --git a/api-reference/organizations/get-suborgs.mdx b/api-reference/organizations/get-suborgs.mdx
new file mode 100644
index 00000000..5da7d699
--- /dev/null
+++ b/api-reference/organizations/get-suborgs.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_suborgs
+---
\ No newline at end of file
diff --git a/api-reference/organizations/get-verified-suborgs.mdx b/api-reference/organizations/get-verified-suborgs.mdx
new file mode 100644
index 00000000..c7c78d70
--- /dev/null
+++ b/api-reference/organizations/get-verified-suborgs.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_verified_suborgs
+---
\ No newline at end of file
diff --git a/api-reference/organizations/overview.mdx b/api-reference/organizations/overview.mdx
new file mode 100644
index 00000000..74da18da
--- /dev/null
+++ b/api-reference/organizations/overview.mdx
@@ -0,0 +1,8 @@
+---
+title: "Organizations"
+description: "An Organization is the highest level of hierarchy in Turnkey. It can contain many Users, Private Keys, and Policies managed by a Root Quorum. The Root Quorum consists of a set of Users with a consensus threshold. This consensus threshold must be reached by Quorum members in order for any actions to take place."
+sidebarTitle: "Overview"
+mode: wide
+---
+
+See [Root Quorum](/concepts/users/root-quorum) for more information
diff --git a/api-reference/organizations/update-root-quorum.mdx b/api-reference/organizations/update-root-quorum.mdx
new file mode 100644
index 00000000..7b922125
--- /dev/null
+++ b/api-reference/organizations/update-root-quorum.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_root_quorum
+---
\ No newline at end of file
diff --git a/api-reference/overview.mdx b/api-reference/overview.mdx
new file mode 100644
index 00000000..c6c017c9
--- /dev/null
+++ b/api-reference/overview.mdx
@@ -0,0 +1,7 @@
+---
+title: "API Reference"
+description: "Review our [API Introduction](/developer-reference/api-overview/intro) to get started."
+sidebarTitle: "Overview"
+mode: wide
+---
+
diff --git a/api-reference/policies/create-policies.mdx b/api-reference/policies/create-policies.mdx
new file mode 100644
index 00000000..ac23d384
--- /dev/null
+++ b/api-reference/policies/create-policies.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_policies
+---
\ No newline at end of file
diff --git a/api-reference/policies/create-policy.mdx b/api-reference/policies/create-policy.mdx
new file mode 100644
index 00000000..047f3aac
--- /dev/null
+++ b/api-reference/policies/create-policy.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_policy
+---
\ No newline at end of file
diff --git a/api-reference/policies/delete-policy.mdx b/api-reference/policies/delete-policy.mdx
new file mode 100644
index 00000000..5138f9ca
--- /dev/null
+++ b/api-reference/policies/delete-policy.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_policy
+---
\ No newline at end of file
diff --git a/api-reference/policies/get-policy.mdx b/api-reference/policies/get-policy.mdx
new file mode 100644
index 00000000..5f5a1b67
--- /dev/null
+++ b/api-reference/policies/get-policy.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_policy
+---
\ No newline at end of file
diff --git a/api-reference/policies/list-policies.mdx b/api-reference/policies/list-policies.mdx
new file mode 100644
index 00000000..68e83ce4
--- /dev/null
+++ b/api-reference/policies/list-policies.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_policies
+---
\ No newline at end of file
diff --git a/api-reference/policies/overview.mdx b/api-reference/policies/overview.mdx
new file mode 100644
index 00000000..57ec8b79
--- /dev/null
+++ b/api-reference/policies/overview.mdx
@@ -0,0 +1,7 @@
+---
+title: "Policies"
+description: "Policies allow for deep customization of the security of your Organization. They can be used to grant permissions or restrict usage of Users and Private Keys. The Policy Engine analyzes all of your Policies on each request to determine whether an Activity is allowed."
+sidebarTitle: "Overview"
+mode: wide
+---
+See [Policy Overview](/concepts/policies/overview) for more information
diff --git a/api-reference/policies/update-policy.mdx b/api-reference/policies/update-policy.mdx
new file mode 100644
index 00000000..83b6dafd
--- /dev/null
+++ b/api-reference/policies/update-policy.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_policy
+---
\ No newline at end of file
diff --git a/api-reference/private-key-tags/create-private-key-tag.mdx b/api-reference/private-key-tags/create-private-key-tag.mdx
new file mode 100644
index 00000000..b87c4d9a
--- /dev/null
+++ b/api-reference/private-key-tags/create-private-key-tag.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_private_key_tag
+---
\ No newline at end of file
diff --git a/api-reference/private-key-tags/delete-private-key-tags.mdx b/api-reference/private-key-tags/delete-private-key-tags.mdx
new file mode 100644
index 00000000..e122dabb
--- /dev/null
+++ b/api-reference/private-key-tags/delete-private-key-tags.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_private_key_tags
+---
\ No newline at end of file
diff --git a/api-reference/private-key-tags/list-private-key-tags.mdx b/api-reference/private-key-tags/list-private-key-tags.mdx
new file mode 100644
index 00000000..864e8820
--- /dev/null
+++ b/api-reference/private-key-tags/list-private-key-tags.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_private_key_tags
+---
\ No newline at end of file
diff --git a/api-reference/private-key-tags/overview.mdx b/api-reference/private-key-tags/overview.mdx
new file mode 100644
index 00000000..933b1a76
--- /dev/null
+++ b/api-reference/private-key-tags/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Private Key Tags"
+description: "Private Key Tags allow you to easily group and permission Private Keys through Policies."
+sidebarTitle: "Overview"
+mode: wide
+---
diff --git a/api-reference/private-key-tags/update-private-key-tag.mdx b/api-reference/private-key-tags/update-private-key-tag.mdx
new file mode 100644
index 00000000..1f10b26f
--- /dev/null
+++ b/api-reference/private-key-tags/update-private-key-tag.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_private_key_tag
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/create-private-keys.mdx b/api-reference/private-keys/create-private-keys.mdx
new file mode 100644
index 00000000..888fd6bc
--- /dev/null
+++ b/api-reference/private-keys/create-private-keys.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_private_keys
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/delete-private-keys.mdx b/api-reference/private-keys/delete-private-keys.mdx
new file mode 100644
index 00000000..1fb3aaef
--- /dev/null
+++ b/api-reference/private-keys/delete-private-keys.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_private_keys
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/export-private-key.mdx b/api-reference/private-keys/export-private-key.mdx
new file mode 100644
index 00000000..b201ce92
--- /dev/null
+++ b/api-reference/private-keys/export-private-key.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/export_private_key
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/get-private-key.mdx b/api-reference/private-keys/get-private-key.mdx
new file mode 100644
index 00000000..39a4ec54
--- /dev/null
+++ b/api-reference/private-keys/get-private-key.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_private_key
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/import-private-key.mdx b/api-reference/private-keys/import-private-key.mdx
new file mode 100644
index 00000000..38205427
--- /dev/null
+++ b/api-reference/private-keys/import-private-key.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/import_private_key
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/init-import-private-key.mdx b/api-reference/private-keys/init-import-private-key.mdx
new file mode 100644
index 00000000..ec5d5863
--- /dev/null
+++ b/api-reference/private-keys/init-import-private-key.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/init_import_private_key
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/list-private-keys.mdx b/api-reference/private-keys/list-private-keys.mdx
new file mode 100644
index 00000000..f68b663e
--- /dev/null
+++ b/api-reference/private-keys/list-private-keys.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_private_keys
+---
\ No newline at end of file
diff --git a/api-reference/private-keys/overview.mdx b/api-reference/private-keys/overview.mdx
new file mode 100644
index 00000000..30e60cd3
--- /dev/null
+++ b/api-reference/private-keys/overview.mdx
@@ -0,0 +1,8 @@
+---
+title: "Private Keys"
+description: "Private Keys are cryptographic public / private key pairs that can be used for cryptocurrency needs or more generalized encryption. Turnkey securely holds all private key materials for you, but only you can access them."
+sidebarTitle: "Overview"
+mode: wide
+---
+
+The Private Key ID or any derived address can be used to create digital signatures. See [Signing](/api-reference/signing/overview) for more information
diff --git a/api-reference/sessions/create-read-only-session.mdx b/api-reference/sessions/create-read-only-session.mdx
new file mode 100644
index 00000000..addda1aa
--- /dev/null
+++ b/api-reference/sessions/create-read-only-session.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_read_only_session
+---
\ No newline at end of file
diff --git a/api-reference/sessions/create-read-write-session.mdx b/api-reference/sessions/create-read-write-session.mdx
new file mode 100644
index 00000000..e72d96fa
--- /dev/null
+++ b/api-reference/sessions/create-read-write-session.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_read_write_session
+---
\ No newline at end of file
diff --git a/api-reference/sessions/who-am-i.mdx b/api-reference/sessions/who-am-i.mdx
new file mode 100644
index 00000000..edee15c2
--- /dev/null
+++ b/api-reference/sessions/who-am-i.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/whoami
+---
\ No newline at end of file
diff --git a/api-reference/signing/overview.mdx b/api-reference/signing/overview.mdx
new file mode 100644
index 00000000..2e23e8e4
--- /dev/null
+++ b/api-reference/signing/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Signing"
+description: "Signers allow you to create digital signatures. Signatures are used to validate the authenticity and integrity of a digital message. Turnkey makes it easy to produce signatures by allowing you to sign with an address. If Turnkey doesn't yet support an address format you need, you can generate and sign with the public key instead by using the address format `ADDRESS_FORMAT_COMPRESSED`."
+sidebarTitle: "Overview"
+mode: wide
+---
diff --git a/api-reference/signing/sign-raw-payload.mdx b/api-reference/signing/sign-raw-payload.mdx
new file mode 100644
index 00000000..1036e977
--- /dev/null
+++ b/api-reference/signing/sign-raw-payload.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/sign_raw_payload
+---
\ No newline at end of file
diff --git a/api-reference/signing/sign-raw-payloads.mdx b/api-reference/signing/sign-raw-payloads.mdx
new file mode 100644
index 00000000..f4fe4382
--- /dev/null
+++ b/api-reference/signing/sign-raw-payloads.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/sign_raw_payloads
+---
\ No newline at end of file
diff --git a/api-reference/signing/sign-transaction.mdx b/api-reference/signing/sign-transaction.mdx
new file mode 100644
index 00000000..4b4725ef
--- /dev/null
+++ b/api-reference/signing/sign-transaction.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/sign_transaction
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/create-oauth-providers.mdx b/api-reference/user-auth/create-oauth-providers.mdx
new file mode 100644
index 00000000..aa8b098d
--- /dev/null
+++ b/api-reference/user-auth/create-oauth-providers.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_oauth_providers
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/delete-oauth-providers.mdx b/api-reference/user-auth/delete-oauth-providers.mdx
new file mode 100644
index 00000000..28ba9c50
--- /dev/null
+++ b/api-reference/user-auth/delete-oauth-providers.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_oauth_providers
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/get-oauth-providers.mdx b/api-reference/user-auth/get-oauth-providers.mdx
new file mode 100644
index 00000000..d06346e6
--- /dev/null
+++ b/api-reference/user-auth/get-oauth-providers.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_oauth_providers
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/init-otp-auth.mdx b/api-reference/user-auth/init-otp-auth.mdx
new file mode 100644
index 00000000..8a5b0140
--- /dev/null
+++ b/api-reference/user-auth/init-otp-auth.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/init_otp_auth
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/oauth.mdx b/api-reference/user-auth/oauth.mdx
new file mode 100644
index 00000000..a713e955
--- /dev/null
+++ b/api-reference/user-auth/oauth.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/oauth
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/otp-auth.mdx b/api-reference/user-auth/otp-auth.mdx
new file mode 100644
index 00000000..8a04906c
--- /dev/null
+++ b/api-reference/user-auth/otp-auth.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/otp_auth
+---
\ No newline at end of file
diff --git a/api-reference/user-auth/perform-email-auth.mdx b/api-reference/user-auth/perform-email-auth.mdx
new file mode 100644
index 00000000..f749244f
--- /dev/null
+++ b/api-reference/user-auth/perform-email-auth.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/email_auth
+---
\ No newline at end of file
diff --git a/api-reference/user-recovery/init-email-recovery.mdx b/api-reference/user-recovery/init-email-recovery.mdx
new file mode 100644
index 00000000..904e49ea
--- /dev/null
+++ b/api-reference/user-recovery/init-email-recovery.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/init_user_email_recovery
+---
\ No newline at end of file
diff --git a/api-reference/user-recovery/recover-a-user.mdx b/api-reference/user-recovery/recover-a-user.mdx
new file mode 100644
index 00000000..e953fcb5
--- /dev/null
+++ b/api-reference/user-recovery/recover-a-user.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/recover_user
+---
\ No newline at end of file
diff --git a/api-reference/user-tags/create-user-tag.mdx b/api-reference/user-tags/create-user-tag.mdx
new file mode 100644
index 00000000..71be3653
--- /dev/null
+++ b/api-reference/user-tags/create-user-tag.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_user_tag
+---
\ No newline at end of file
diff --git a/api-reference/user-tags/delete-user-tags.mdx b/api-reference/user-tags/delete-user-tags.mdx
new file mode 100644
index 00000000..1f5b95cd
--- /dev/null
+++ b/api-reference/user-tags/delete-user-tags.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_user_tags
+---
\ No newline at end of file
diff --git a/api-reference/user-tags/list-user-tags.mdx b/api-reference/user-tags/list-user-tags.mdx
new file mode 100644
index 00000000..7745eb91
--- /dev/null
+++ b/api-reference/user-tags/list-user-tags.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_user_tags
+---
\ No newline at end of file
diff --git a/api-reference/user-tags/overview.mdx b/api-reference/user-tags/overview.mdx
new file mode 100644
index 00000000..e0839796
--- /dev/null
+++ b/api-reference/user-tags/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "User Tags"
+description: "User Key Tags allow you to easily group and permission Users through Policies."
+sidebarTitle: "Overview"
+mode: wide
+---
diff --git a/api-reference/user-tags/update-user-tag.mdx b/api-reference/user-tags/update-user-tag.mdx
new file mode 100644
index 00000000..6400b249
--- /dev/null
+++ b/api-reference/user-tags/update-user-tag.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_user_tag
+---
\ No newline at end of file
diff --git a/api-reference/users/create-users.mdx b/api-reference/users/create-users.mdx
new file mode 100644
index 00000000..3a3d9528
--- /dev/null
+++ b/api-reference/users/create-users.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_users
+---
\ No newline at end of file
diff --git a/api-reference/users/delete-users.mdx b/api-reference/users/delete-users.mdx
new file mode 100644
index 00000000..fffbf8b3
--- /dev/null
+++ b/api-reference/users/delete-users.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_users
+---
\ No newline at end of file
diff --git a/api-reference/users/get-user.mdx b/api-reference/users/get-user.mdx
new file mode 100644
index 00000000..93080224
--- /dev/null
+++ b/api-reference/users/get-user.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_user
+---
\ No newline at end of file
diff --git a/api-reference/users/list-users.mdx b/api-reference/users/list-users.mdx
new file mode 100644
index 00000000..dc557c94
--- /dev/null
+++ b/api-reference/users/list-users.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_users
+---
\ No newline at end of file
diff --git a/api-reference/users/overview.mdx b/api-reference/users/overview.mdx
new file mode 100644
index 00000000..dd1783d0
--- /dev/null
+++ b/api-reference/users/overview.mdx
@@ -0,0 +1,6 @@
+---
+title: "Users"
+description: "Users are responsible for any action taken within an Organization. They can have ApiKey or Auuthenticator credentials, allowing you to onboard teammates to the Organization, or create API-only Users to run as part of your infrastructure."
+sidebarTitle: "Overview"
+mode: wide
+---
diff --git a/api-reference/users/update-user.mdx b/api-reference/users/update-user.mdx
new file mode 100644
index 00000000..b73a3a15
--- /dev/null
+++ b/api-reference/users/update-user.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_user
+---
\ No newline at end of file
diff --git a/api-reference/wallets/create-wallet-accounts.mdx b/api-reference/wallets/create-wallet-accounts.mdx
new file mode 100644
index 00000000..891e3fe9
--- /dev/null
+++ b/api-reference/wallets/create-wallet-accounts.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_wallet_accounts
+---
\ No newline at end of file
diff --git a/api-reference/wallets/create-wallet.mdx b/api-reference/wallets/create-wallet.mdx
new file mode 100644
index 00000000..370d7710
--- /dev/null
+++ b/api-reference/wallets/create-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/create_wallet
+---
\ No newline at end of file
diff --git a/api-reference/wallets/delete-wallets.mdx b/api-reference/wallets/delete-wallets.mdx
new file mode 100644
index 00000000..d7b4f7cb
--- /dev/null
+++ b/api-reference/wallets/delete-wallets.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/delete_wallets
+---
\ No newline at end of file
diff --git a/api-reference/wallets/export-wallet-account.mdx b/api-reference/wallets/export-wallet-account.mdx
new file mode 100644
index 00000000..cbb9545a
--- /dev/null
+++ b/api-reference/wallets/export-wallet-account.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/export_wallet_account
+---
\ No newline at end of file
diff --git a/api-reference/wallets/export-wallet.mdx b/api-reference/wallets/export-wallet.mdx
new file mode 100644
index 00000000..f1aded9b
--- /dev/null
+++ b/api-reference/wallets/export-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/export_wallet
+---
\ No newline at end of file
diff --git a/api-reference/wallets/get-wallet-account.mdx b/api-reference/wallets/get-wallet-account.mdx
new file mode 100644
index 00000000..fab095e0
--- /dev/null
+++ b/api-reference/wallets/get-wallet-account.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_wallet_account
+---
\ No newline at end of file
diff --git a/api-reference/wallets/get-wallet.mdx b/api-reference/wallets/get-wallet.mdx
new file mode 100644
index 00000000..4d6125f5
--- /dev/null
+++ b/api-reference/wallets/get-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/get_wallet
+---
\ No newline at end of file
diff --git a/api-reference/wallets/import-wallet.mdx b/api-reference/wallets/import-wallet.mdx
new file mode 100644
index 00000000..14ff050b
--- /dev/null
+++ b/api-reference/wallets/import-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/import_wallet
+---
\ No newline at end of file
diff --git a/api-reference/wallets/init-import-wallet.mdx b/api-reference/wallets/init-import-wallet.mdx
new file mode 100644
index 00000000..68a49619
--- /dev/null
+++ b/api-reference/wallets/init-import-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/init_import_wallet
+---
\ No newline at end of file
diff --git a/api-reference/wallets/list-wallets-accounts.mdx b/api-reference/wallets/list-wallets-accounts.mdx
new file mode 100644
index 00000000..38722a0c
--- /dev/null
+++ b/api-reference/wallets/list-wallets-accounts.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_wallet_accounts
+---
\ No newline at end of file
diff --git a/api-reference/wallets/list-wallets.mdx b/api-reference/wallets/list-wallets.mdx
new file mode 100644
index 00000000..5f709253
--- /dev/null
+++ b/api-reference/wallets/list-wallets.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/query/list_wallets
+---
\ No newline at end of file
diff --git a/api-reference/wallets/overview.mdx b/api-reference/wallets/overview.mdx
new file mode 100644
index 00000000..8ec536c7
--- /dev/null
+++ b/api-reference/wallets/overview.mdx
@@ -0,0 +1,9 @@
+---
+title: "Wallets"
+description: "Wallets contain collections of deterministically generated cryptographic public / private key pairs that share a common seed. Turnkey securely holds the common seed, but only you can access it. In most cases, Wallets should be preferred over Private Keys since they can be represented by a mnemonic phrase, used across a variety of cryptographic curves, and can derive many addresses."
+sidebarTitle: "Overview"
+mode: wide
+---
+
+
+Derived addresses can be used to create digital signatures using the corresponding underlying private key. See [Signing](/api-reference/signing/overview) for more information
diff --git a/api-reference/wallets/update-wallet.mdx b/api-reference/wallets/update-wallet.mdx
new file mode 100644
index 00000000..1b365629
--- /dev/null
+++ b/api-reference/wallets/update-wallet.mdx
@@ -0,0 +1,3 @@
+---
+openapi: post /public/v1/submit/update_wallet
+---
\ No newline at end of file
diff --git a/api/public_api.swagger.json b/api/public_api.swagger.json
deleted file mode 100644
index f8c70dcd..00000000
--- a/api/public_api.swagger.json
+++ /dev/null
@@ -1,8322 +0,0 @@
-{
- "swagger": "2.0",
- "info": {
- "title": "API Reference",
- "description": "Review our [API Introduction](../api-introduction) to get started.",
- "version": "1.0",
- "contact": {}
- },
- "tags": [
- {
- "name": "Organizations",
- "description": "An Organization is the highest level of hierarchy in Turnkey. It can contain many Users, Private Keys, and Policies managed by a Root Quorum. The Root Quorum consists of a set of Users with a consensus threshold. This consensus threshold must be reached by Quorum members in order for any actions to take place.\n\nSee [Root Quorum](../concepts/users/root-quorum) for more information"
- },
- {
- "name": "Invitations",
- "description": "Invitations allow you to invite Users into your Organization via email. Alternatively, Users can be added directly without an Invitation if their ApiKey or Authenticator credentials are known ahead of time.\n\nSee [Users](./api#tag/Users) for more information"
- },
- {
- "name": "Policies",
- "description": "Policies allow for deep customization of the security of your Organization. They can be used to grant permissions or restrict usage of Users and Private Keys. The Policy Engine analyzes all of your Policies on each request to determine whether an Activity is allowed.\n\nSee [Policy Overview](../managing-policies/overview) for more information"
- },
- {
- "name": "Wallets",
- "description": "Wallets contain collections of deterministically generated cryptographic public / private key pairs that share a common seed. Turnkey securely holds the common seed, but only you can access it. In most cases, Wallets should be preferred over Private Keys since they can be represented by a mnemonic phrase, used across a variety of cryptographic curves, and can derive many addresses.\n\nDerived addresses can be used to create digital signatures using the corresponding underlying private key. See [Signing](./api#tag/Signing) for more information"
- },
- {
- "name": "Signing",
- "description": "Signers allow you to create digital signatures. Signatures are used to validate the authenticity and integrity of a digital message. Turnkey makes it easy to produce signatures by allowing you to sign with an address. If Turnkey doesn't yet support an address format you need, you can generate and sign with the public key instead by using the address format `ADDRESS_FORMAT_COMPRESSED`."
- },
- {
- "name": "Private Keys",
- "description": "Private Keys are cryptographic public / private key pairs that can be used for cryptocurrency needs or more generalized encryption. Turnkey securely holds all private key materials for you, but only you can access them.\n\nThe Private Key ID or any derived address can be used to create digital signatures. See [Signing](./api#tag/Signing) for more information"
- },
- {
- "name": "Private Key Tags",
- "description": "Private Key Tags allow you to easily group and permission Private Keys through Policies."
- },
- {
- "name": "Users",
- "description": "Users are responsible for any action taken within an Organization. They can have ApiKey or Auuthenticator credentials, allowing you to onboard teammates to the Organization, or create API-only Users to run as part of your infrastructure."
- },
- {
- "name": "User Tags",
- "description": "User Key Tags allow you to easily group and permission Users through Policies."
- },
- {
- "name": "Authenticators",
- "description": "Authenticators are WebAuthN hardware devices, such as a Macbook TouchID or Yubikey, that can be used to authenticate requests."
- },
- {
- "name": "API Keys",
- "description": "API Keys are used to authenticate requests\n\nSee our [CLI](https://github.com/tkhq/tkcli) for instructions on generating API Keys"
- },
- {
- "name": "Activities",
- "description": "Activities encapsulate all the possible actions that can be taken with Turnkey. Some examples include adding a new user, creating a private key, and signing a transaction.\n\nActivities that modify your Organization are processed asynchronously. To confirm processing is complete and retrieve the Activity results, these activities must be polled until that status has been updated to a finalized state: `COMPLETED` when the activity is successful or `FAILED` when the activity has failed"
- },
- {
- "name": "Consensus",
- "description": "Policies can enforce consensus requirements for Activities. For example, adding a new user requires two admins to approve the request.\n\nActivities that have been proposed, but don't yet meet the Consesnsus requirements will have the status: `REQUIRES_CONSENSUS`. Activities in this state can be approved or rejected using the unique fingerprint generated when an Activity is created."
- }
- ],
- "host": "api.turnkey.com",
- "schemes": ["https"],
- "consumes": ["application/json"],
- "produces": ["application/json"],
- "paths": {
- "/public/v1/query/get_activity": {
- "post": {
- "summary": "Get Activity",
- "description": "Get details about an Activity",
- "operationId": "GetActivity",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetActivityRequest"
- }
- }
- ],
- "tags": ["Activities"]
- }
- },
- "/public/v1/query/get_api_key": {
- "post": {
- "summary": "Get API key",
- "description": "Get details about an API key",
- "operationId": "GetApiKey",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetApiKeyResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetApiKeyRequest"
- }
- }
- ],
- "tags": ["API keys"]
- }
- },
- "/public/v1/query/get_api_keys": {
- "post": {
- "summary": "Get API key",
- "description": "Get details about API keys for a user",
- "operationId": "GetApiKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetApiKeysResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetApiKeysRequest"
- }
- }
- ],
- "tags": ["API keys"]
- }
- },
- "/public/v1/query/get_authenticator": {
- "post": {
- "summary": "Get Authenticator",
- "description": "Get details about an authenticator",
- "operationId": "GetAuthenticator",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetAuthenticatorResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetAuthenticatorRequest"
- }
- }
- ],
- "tags": ["Authenticators"]
- }
- },
- "/public/v1/query/get_authenticators": {
- "post": {
- "summary": "Get Authenticators",
- "description": "Get details about authenticators for a user",
- "operationId": "GetAuthenticators",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetAuthenticatorsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetAuthenticatorsRequest"
- }
- }
- ],
- "tags": ["Authenticators"]
- }
- },
- "/public/v1/query/get_oauth_providers": {
- "post": {
- "summary": "Get Oauth providers",
- "description": "Get details about Oauth providers for a user",
- "operationId": "GetOauthProviders",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetOauthProvidersResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetOauthProvidersRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/query/get_organization_configs": {
- "post": {
- "summary": "Get Configs",
- "description": "Get quorum settings and features for an organization",
- "operationId": "GetOrganizationConfigs",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetOrganizationConfigsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetOrganizationConfigsRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/query/get_policy": {
- "post": {
- "summary": "Get Policy",
- "description": "Get details about a Policy",
- "operationId": "GetPolicy",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetPolicyResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetPolicyRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/query/get_private_key": {
- "post": {
- "summary": "Get Private Key",
- "description": "Get details about a Private Key",
- "operationId": "GetPrivateKey",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetPrivateKeyResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetPrivateKeyRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/query/get_user": {
- "post": {
- "summary": "Get User",
- "description": "Get details about a User",
- "operationId": "GetUser",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetUserResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetUserRequest"
- }
- }
- ],
- "tags": ["Users"]
- }
- },
- "/public/v1/query/get_wallet": {
- "post": {
- "summary": "Get Wallet",
- "description": "Get details about a Wallet",
- "operationId": "GetWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetWalletResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/query/get_wallet_account": {
- "post": {
- "summary": "Get Wallet Account",
- "description": "Get a single wallet account",
- "operationId": "GetWalletAccount",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetWalletAccountResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetWalletAccountRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/query/list_activities": {
- "post": {
- "summary": "List Activities",
- "description": "List all Activities within an Organization",
- "operationId": "GetActivities",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetActivitiesResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetActivitiesRequest"
- }
- }
- ],
- "tags": ["Activities"]
- }
- },
- "/public/v1/query/list_policies": {
- "post": {
- "summary": "List Policies",
- "description": "List all Policies within an Organization",
- "operationId": "GetPolicies",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetPoliciesResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetPoliciesRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/query/list_private_key_tags": {
- "post": {
- "summary": "List Private Key Tags",
- "description": "List all Private Key Tags within an Organization",
- "operationId": "ListPrivateKeyTags",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ListPrivateKeyTagsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ListPrivateKeyTagsRequest"
- }
- }
- ],
- "tags": ["Private Key Tags"]
- }
- },
- "/public/v1/query/list_private_keys": {
- "post": {
- "summary": "List Private Keys",
- "description": "List all Private Keys within an Organization",
- "operationId": "GetPrivateKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetPrivateKeysResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetPrivateKeysRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/query/list_suborgs": {
- "post": {
- "summary": "Get Suborgs",
- "description": "Get all suborg IDs associated given a parent org ID and an optional filter.",
- "operationId": "GetSubOrgIds",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetSubOrgIdsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetSubOrgIdsRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/query/list_user_tags": {
- "post": {
- "summary": "List User Tags",
- "description": "List all User Tags within an Organization",
- "operationId": "ListUserTags",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ListUserTagsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ListUserTagsRequest"
- }
- }
- ],
- "tags": ["User Tags"]
- }
- },
- "/public/v1/query/list_users": {
- "post": {
- "summary": "List Users",
- "description": "List all Users within an Organization",
- "operationId": "GetUsers",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetUsersResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetUsersRequest"
- }
- }
- ],
- "tags": ["Users"]
- }
- },
- "/public/v1/query/list_verified_suborgs": {
- "post": {
- "summary": "Get Verified Suborgs",
- "description": "Get all email or phone verified suborg IDs associated given a parent org ID.",
- "operationId": "GetVerifiedSubOrgIds",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetVerifiedSubOrgIdsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetVerifiedSubOrgIdsRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/query/list_wallet_accounts": {
- "post": {
- "summary": "List Wallets Accounts",
- "description": "List all Accounts within a Wallet",
- "operationId": "GetWalletAccounts",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetWalletAccountsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetWalletAccountsRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/query/list_wallets": {
- "post": {
- "summary": "List Wallets",
- "description": "List all Wallets within an Organization",
- "operationId": "GetWallets",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetWalletsResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetWalletsRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/query/whoami": {
- "post": {
- "summary": "Who am I?",
- "description": "Get basic information about your current API or WebAuthN user and their organization. Affords Sub-Organization look ups via Parent Organization for WebAuthN or API key users.",
- "operationId": "GetWhoami",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/GetWhoamiResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GetWhoamiRequest"
- }
- }
- ],
- "tags": ["Sessions"]
- }
- },
- "/public/v1/submit/approve_activity": {
- "post": {
- "summary": "Approve Activity",
- "description": "Approve an Activity",
- "operationId": "ApproveActivity",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ApproveActivityRequest"
- }
- }
- ],
- "tags": ["Consensus"]
- }
- },
- "/public/v1/submit/create_api_keys": {
- "post": {
- "summary": "Create API Keys",
- "description": "Add api keys to an existing User",
- "operationId": "CreateApiKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateApiKeysRequest"
- }
- }
- ],
- "tags": ["API Keys"]
- }
- },
- "/public/v1/submit/create_authenticators": {
- "post": {
- "summary": "Create Authenticators",
- "description": "Create Authenticators to authenticate requests to Turnkey",
- "operationId": "CreateAuthenticators",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateAuthenticatorsRequest"
- }
- }
- ],
- "tags": ["Authenticators"]
- }
- },
- "/public/v1/submit/create_invitations": {
- "post": {
- "summary": "Create Invitations",
- "description": "Create Invitations to join an existing Organization",
- "operationId": "CreateInvitations",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateInvitationsRequest"
- }
- }
- ],
- "tags": ["Invitations"]
- }
- },
- "/public/v1/submit/create_oauth_providers": {
- "post": {
- "summary": "Create Oauth Providers",
- "description": "Creates Oauth providers for a specified user - BETA",
- "operationId": "CreateOauthProviders",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateOauthProvidersRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/create_policies": {
- "post": {
- "summary": "Create Policies",
- "description": "Create new Policies",
- "operationId": "CreatePolicies",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreatePoliciesRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/submit/create_policy": {
- "post": {
- "summary": "Create Policy",
- "description": "Create a new Policy",
- "operationId": "CreatePolicy",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreatePolicyRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/submit/create_private_key_tag": {
- "post": {
- "summary": "Create Private Key Tag",
- "description": "Create a private key tag and add it to private keys.",
- "operationId": "CreatePrivateKeyTag",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreatePrivateKeyTagRequest"
- }
- }
- ],
- "tags": ["Private Key Tags"]
- }
- },
- "/public/v1/submit/create_private_keys": {
- "post": {
- "summary": "Create Private Keys",
- "description": "Create new Private Keys",
- "operationId": "CreatePrivateKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreatePrivateKeysRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/submit/create_read_only_session": {
- "post": {
- "summary": "Create Read Only Session",
- "description": "Create a read only session for a user (valid for 1 hour)",
- "operationId": "CreateReadOnlySession",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateReadOnlySessionRequest"
- }
- }
- ],
- "tags": ["Sessions"]
- }
- },
- "/public/v1/submit/create_read_write_session": {
- "post": {
- "summary": "Create Read Write Session",
- "description": "Create a read write session for a user",
- "operationId": "CreateReadWriteSession",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateReadWriteSessionRequest"
- }
- }
- ],
- "tags": ["Sessions"]
- }
- },
- "/public/v1/submit/create_sub_organization": {
- "post": {
- "summary": "Create Sub-Organization",
- "description": "Create a new Sub-Organization",
- "operationId": "CreateSubOrganization",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateSubOrganizationRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/submit/create_user_tag": {
- "post": {
- "summary": "Create User Tag",
- "description": "Create a user tag and add it to users.",
- "operationId": "CreateUserTag",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateUserTagRequest"
- }
- }
- ],
- "tags": ["User Tags"]
- }
- },
- "/public/v1/submit/create_users": {
- "post": {
- "summary": "Create Users",
- "description": "Create Users in an existing Organization",
- "operationId": "CreateUsers",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateUsersRequest"
- }
- }
- ],
- "tags": ["Users"]
- }
- },
- "/public/v1/submit/create_wallet": {
- "post": {
- "summary": "Create Wallet",
- "description": "Create a Wallet and derive addresses",
- "operationId": "CreateWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/create_wallet_accounts": {
- "post": {
- "summary": "Create Wallet Accounts",
- "description": "Derive additional addresses using an existing wallet",
- "operationId": "CreateWalletAccounts",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/CreateWalletAccountsRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/delete_api_keys": {
- "post": {
- "summary": "Delete API Keys",
- "description": "Remove api keys from a User",
- "operationId": "DeleteApiKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteApiKeysRequest"
- }
- }
- ],
- "tags": ["API Keys"]
- }
- },
- "/public/v1/submit/delete_authenticators": {
- "post": {
- "summary": "Delete Authenticators",
- "description": "Remove authenticators from a User",
- "operationId": "DeleteAuthenticators",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteAuthenticatorsRequest"
- }
- }
- ],
- "tags": ["Authenticators"]
- }
- },
- "/public/v1/submit/delete_invitation": {
- "post": {
- "summary": "Delete Invitation",
- "description": "Delete an existing Invitation",
- "operationId": "DeleteInvitation",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteInvitationRequest"
- }
- }
- ],
- "tags": ["Invitations"]
- }
- },
- "/public/v1/submit/delete_oauth_providers": {
- "post": {
- "summary": "Delete Oauth Providers",
- "description": "Removes Oauth providers for a specified user - BETA",
- "operationId": "DeleteOauthProviders",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteOauthProvidersRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/delete_policy": {
- "post": {
- "summary": "Delete Policy",
- "description": "Delete an existing Policy",
- "operationId": "DeletePolicy",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeletePolicyRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/submit/delete_private_key_tags": {
- "post": {
- "summary": "Delete Private Key Tags",
- "description": "Delete Private Key Tags within an Organization",
- "operationId": "DeletePrivateKeyTags",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeletePrivateKeyTagsRequest"
- }
- }
- ],
- "tags": ["Private Key Tags"]
- }
- },
- "/public/v1/submit/delete_private_keys": {
- "post": {
- "summary": "Delete Private Keys",
- "description": "Deletes private keys for an organization",
- "operationId": "DeletePrivateKeys",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeletePrivateKeysRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/submit/delete_sub_organization": {
- "post": {
- "summary": "Delete Sub Organization",
- "description": "Deletes a sub organization",
- "operationId": "DeleteSubOrganization",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteSubOrganizationRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/submit/delete_user_tags": {
- "post": {
- "summary": "Delete User Tags",
- "description": "Delete User Tags within an Organization",
- "operationId": "DeleteUserTags",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteUserTagsRequest"
- }
- }
- ],
- "tags": ["User Tags"]
- }
- },
- "/public/v1/submit/delete_users": {
- "post": {
- "summary": "Delete Users",
- "description": "Delete Users within an Organization",
- "operationId": "DeleteUsers",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteUsersRequest"
- }
- }
- ],
- "tags": ["Users"]
- }
- },
- "/public/v1/submit/delete_wallets": {
- "post": {
- "summary": "Delete Wallets",
- "description": "Deletes wallets for an organization",
- "operationId": "DeleteWallets",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeleteWalletsRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/email_auth": {
- "post": {
- "summary": "Perform Email Auth",
- "description": "Authenticate a user via Email",
- "operationId": "EmailAuth",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/EmailAuthRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/export_private_key": {
- "post": {
- "summary": "Export Private Key",
- "description": "Exports a Private Key",
- "operationId": "ExportPrivateKey",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ExportPrivateKeyRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/submit/export_wallet": {
- "post": {
- "summary": "Export Wallet",
- "description": "Exports a Wallet",
- "operationId": "ExportWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ExportWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/export_wallet_account": {
- "post": {
- "summary": "Export Wallet Account",
- "description": "Exports a Wallet Account",
- "operationId": "ExportWalletAccount",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ExportWalletAccountRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/import_private_key": {
- "post": {
- "summary": "Import Private Key",
- "description": "Imports a private key",
- "operationId": "ImportPrivateKey",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ImportPrivateKeyRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/submit/import_wallet": {
- "post": {
- "summary": "Import Wallet",
- "description": "Imports a wallet",
- "operationId": "ImportWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/ImportWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/init_import_private_key": {
- "post": {
- "summary": "Init Import Private Key",
- "description": "Initializes a new private key import",
- "operationId": "InitImportPrivateKey",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/InitImportPrivateKeyRequest"
- }
- }
- ],
- "tags": ["Private Keys"]
- }
- },
- "/public/v1/submit/init_import_wallet": {
- "post": {
- "summary": "Init Import Wallet",
- "description": "Initializes a new wallet import",
- "operationId": "InitImportWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/InitImportWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- },
- "/public/v1/submit/init_otp_auth": {
- "post": {
- "summary": "Init OTP auth",
- "description": "Initiate an OTP auth activity",
- "operationId": "InitOtpAuth",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/InitOtpAuthRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/init_user_email_recovery": {
- "post": {
- "summary": "Init Email Recovery",
- "description": "Initializes a new email recovery",
- "operationId": "InitUserEmailRecovery",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/InitUserEmailRecoveryRequest"
- }
- }
- ],
- "tags": ["User Recovery"]
- }
- },
- "/public/v1/submit/oauth": {
- "post": {
- "summary": "Oauth",
- "description": "Authenticate a user with an Oidc token (Oauth) - BETA",
- "operationId": "Oauth",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/OauthRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/otp_auth": {
- "post": {
- "summary": "OTP auth",
- "description": "Authenticate a user with an OTP code sent via email or SMS",
- "operationId": "OtpAuth",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/OtpAuthRequest"
- }
- }
- ],
- "tags": ["User Auth"]
- }
- },
- "/public/v1/submit/recover_user": {
- "post": {
- "summary": "Recover a user",
- "description": "Completes the process of recovering a user by adding an authenticator",
- "operationId": "RecoverUser",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/RecoverUserRequest"
- }
- }
- ],
- "tags": ["User Recovery"]
- }
- },
- "/public/v1/submit/reject_activity": {
- "post": {
- "summary": "Reject Activity",
- "description": "Reject an Activity",
- "operationId": "RejectActivity",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/RejectActivityRequest"
- }
- }
- ],
- "tags": ["Consensus"]
- }
- },
- "/public/v1/submit/remove_organization_feature": {
- "post": {
- "summary": "Remove Organization Feature",
- "description": "Removes an organization feature. This activity must be approved by the current root quorum.",
- "operationId": "RemoveOrganizationFeature",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/RemoveOrganizationFeatureRequest"
- }
- }
- ],
- "tags": ["Features"]
- }
- },
- "/public/v1/submit/set_organization_feature": {
- "post": {
- "summary": "Set Organization Feature",
- "description": "Sets an organization feature. This activity must be approved by the current root quorum.",
- "operationId": "SetOrganizationFeature",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/SetOrganizationFeatureRequest"
- }
- }
- ],
- "tags": ["Features"]
- }
- },
- "/public/v1/submit/sign_raw_payload": {
- "post": {
- "summary": "Sign Raw Payload",
- "description": "Sign a raw payload",
- "operationId": "SignRawPayload",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/SignRawPayloadRequest"
- }
- }
- ],
- "tags": ["Signing"]
- }
- },
- "/public/v1/submit/sign_raw_payloads": {
- "post": {
- "summary": "Sign Raw Payloads",
- "description": "Sign multiple raw payloads with the same signing parameters",
- "operationId": "SignRawPayloads",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/SignRawPayloadsRequest"
- }
- }
- ],
- "tags": ["Signing"]
- }
- },
- "/public/v1/submit/sign_transaction": {
- "post": {
- "summary": "Sign Transaction",
- "description": "Sign a transaction",
- "operationId": "SignTransaction",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/SignTransactionRequest"
- }
- }
- ],
- "tags": ["Signing"]
- }
- },
- "/public/v1/submit/update_policy": {
- "post": {
- "summary": "Update Policy",
- "description": "Update an existing Policy",
- "operationId": "UpdatePolicy",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdatePolicyRequest"
- }
- }
- ],
- "tags": ["Policies"]
- }
- },
- "/public/v1/submit/update_private_key_tag": {
- "post": {
- "summary": "Update Private Key Tag",
- "description": "Update human-readable name or associated private keys. Note that this activity is atomic: all of the updates will succeed at once, or all of them will fail.",
- "operationId": "UpdatePrivateKeyTag",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdatePrivateKeyTagRequest"
- }
- }
- ],
- "tags": ["Private Key Tags"]
- }
- },
- "/public/v1/submit/update_root_quorum": {
- "post": {
- "summary": "Update Root Quorum",
- "description": "Set the threshold and members of the root quorum. This activity must be approved by the current root quorum.",
- "operationId": "UpdateRootQuorum",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdateRootQuorumRequest"
- }
- }
- ],
- "tags": ["Organizations"]
- }
- },
- "/public/v1/submit/update_user": {
- "post": {
- "summary": "Update User",
- "description": "Update a User in an existing Organization",
- "operationId": "UpdateUser",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdateUserRequest"
- }
- }
- ],
- "tags": ["Users"]
- }
- },
- "/public/v1/submit/update_user_tag": {
- "post": {
- "summary": "Update User Tag",
- "description": "Update human-readable name or associated users. Note that this activity is atomic: all of the updates will succeed at once, or all of them will fail.",
- "operationId": "UpdateUserTag",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdateUserTagRequest"
- }
- }
- ],
- "tags": ["User Tags"]
- }
- },
- "/public/v1/submit/update_wallet": {
- "post": {
- "summary": "Update Wallet",
- "description": "Update a wallet for an organization",
- "operationId": "UpdateWallet",
- "responses": {
- "200": {
- "description": "A successful response.",
- "schema": {
- "$ref": "#/definitions/ActivityResponse"
- }
- }
- },
- "parameters": [
- {
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/UpdateWalletRequest"
- }
- }
- ],
- "tags": ["Wallets"]
- }
- }
- },
- "definitions": {
- "AcceptInvitationIntent": {
- "type": "object",
- "properties": {
- "invitationId": {
- "type": "string",
- "description": "Unique identifier for a given Invitation object."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "authenticator": {
- "$ref": "#/definitions/AuthenticatorParams",
- "description": "WebAuthN hardware devices that can be used to log in to the Turnkey web app."
- }
- },
- "required": ["invitationId", "userId", "authenticator"]
- },
- "AcceptInvitationIntentV2": {
- "type": "object",
- "properties": {
- "invitationId": {
- "type": "string",
- "description": "Unique identifier for a given Invitation object."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "authenticator": {
- "$ref": "#/definitions/AuthenticatorParamsV2",
- "description": "WebAuthN hardware devices that can be used to log in to the Turnkey web app."
- }
- },
- "required": ["invitationId", "userId", "authenticator"]
- },
- "AcceptInvitationResult": {
- "type": "object",
- "properties": {
- "invitationId": {
- "type": "string",
- "description": "Unique identifier for a given Invitation."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["invitationId", "userId"]
- },
- "AccessType": {
- "type": "string",
- "enum": ["ACCESS_TYPE_WEB", "ACCESS_TYPE_API", "ACCESS_TYPE_ALL"]
- },
- "ActivateBillingTierIntent": {
- "type": "object",
- "properties": {
- "productId": {
- "type": "string",
- "description": "The product that the customer wants to subscribe to."
- }
- },
- "required": ["productId"]
- },
- "ActivateBillingTierResult": {
- "type": "object",
- "properties": {
- "productId": {
- "type": "string",
- "description": "The id of the product being subscribed to."
- }
- },
- "required": ["productId"]
- },
- "Activity": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "description": "Unique identifier for a given Activity object."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "status": {
- "$ref": "#/definitions/ActivityStatus",
- "description": "The current processing status of a specified Activity."
- },
- "type": {
- "$ref": "#/definitions/ActivityType",
- "description": "Type of Activity, such as Add User, or Sign Transaction."
- },
- "intent": {
- "$ref": "#/definitions/Intent",
- "description": "Intent object crafted by Turnkey based on the user request, used to assess the permissibility of an action."
- },
- "result": {
- "$ref": "#/definitions/Result",
- "description": "Result of the intended action."
- },
- "votes": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Vote"
- },
- "description": "A list of objects representing a particular User's approval or rejection of a Consensus request, including all relevant metadata."
- },
- "fingerprint": {
- "type": "string",
- "description": "An artifact verifying a User's action."
- },
- "canApprove": {
- "type": "boolean"
- },
- "canReject": {
- "type": "boolean"
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "failure": {
- "$ref": "#/definitions/Status",
- "description": "Failure reason of the intended action."
- }
- },
- "required": [
- "id",
- "organizationId",
- "status",
- "type",
- "intent",
- "result",
- "votes",
- "fingerprint",
- "canApprove",
- "canReject",
- "createdAt",
- "updatedAt"
- ]
- },
- "ActivityResponse": {
- "type": "object",
- "properties": {
- "activity": {
- "$ref": "#/definitions/Activity",
- "description": "An action that can that can be taken within the Turnkey infrastructure."
- }
- },
- "required": ["activity"]
- },
- "ActivityStatus": {
- "type": "string",
- "enum": [
- "ACTIVITY_STATUS_CREATED",
- "ACTIVITY_STATUS_PENDING",
- "ACTIVITY_STATUS_COMPLETED",
- "ACTIVITY_STATUS_FAILED",
- "ACTIVITY_STATUS_CONSENSUS_NEEDED",
- "ACTIVITY_STATUS_REJECTED"
- ]
- },
- "ActivityType": {
- "type": "string",
- "enum": [
- "ACTIVITY_TYPE_CREATE_API_KEYS",
- "ACTIVITY_TYPE_CREATE_USERS",
- "ACTIVITY_TYPE_CREATE_PRIVATE_KEYS",
- "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD",
- "ACTIVITY_TYPE_CREATE_INVITATIONS",
- "ACTIVITY_TYPE_ACCEPT_INVITATION",
- "ACTIVITY_TYPE_CREATE_POLICY",
- "ACTIVITY_TYPE_DISABLE_PRIVATE_KEY",
- "ACTIVITY_TYPE_DELETE_USERS",
- "ACTIVITY_TYPE_DELETE_API_KEYS",
- "ACTIVITY_TYPE_DELETE_INVITATION",
- "ACTIVITY_TYPE_DELETE_ORGANIZATION",
- "ACTIVITY_TYPE_DELETE_POLICY",
- "ACTIVITY_TYPE_CREATE_USER_TAG",
- "ACTIVITY_TYPE_DELETE_USER_TAGS",
- "ACTIVITY_TYPE_CREATE_ORGANIZATION",
- "ACTIVITY_TYPE_SIGN_TRANSACTION",
- "ACTIVITY_TYPE_APPROVE_ACTIVITY",
- "ACTIVITY_TYPE_REJECT_ACTIVITY",
- "ACTIVITY_TYPE_DELETE_AUTHENTICATORS",
- "ACTIVITY_TYPE_CREATE_AUTHENTICATORS",
- "ACTIVITY_TYPE_CREATE_PRIVATE_KEY_TAG",
- "ACTIVITY_TYPE_DELETE_PRIVATE_KEY_TAGS",
- "ACTIVITY_TYPE_SET_PAYMENT_METHOD",
- "ACTIVITY_TYPE_ACTIVATE_BILLING_TIER",
- "ACTIVITY_TYPE_DELETE_PAYMENT_METHOD",
- "ACTIVITY_TYPE_CREATE_POLICY_V2",
- "ACTIVITY_TYPE_CREATE_POLICY_V3",
- "ACTIVITY_TYPE_CREATE_API_ONLY_USERS",
- "ACTIVITY_TYPE_UPDATE_ROOT_QUORUM",
- "ACTIVITY_TYPE_UPDATE_USER_TAG",
- "ACTIVITY_TYPE_UPDATE_PRIVATE_KEY_TAG",
- "ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2",
- "ACTIVITY_TYPE_CREATE_ORGANIZATION_V2",
- "ACTIVITY_TYPE_CREATE_USERS_V2",
- "ACTIVITY_TYPE_ACCEPT_INVITATION_V2",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V2",
- "ACTIVITY_TYPE_UPDATE_ALLOWED_ORIGINS",
- "ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2",
- "ACTIVITY_TYPE_UPDATE_USER",
- "ACTIVITY_TYPE_UPDATE_POLICY",
- "ACTIVITY_TYPE_SET_PAYMENT_METHOD_V2",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V3",
- "ACTIVITY_TYPE_CREATE_WALLET",
- "ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS",
- "ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY",
- "ACTIVITY_TYPE_RECOVER_USER",
- "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
- "ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE",
- "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
- "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
- "ACTIVITY_TYPE_EXPORT_PRIVATE_KEY",
- "ACTIVITY_TYPE_EXPORT_WALLET",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V4",
- "ACTIVITY_TYPE_EMAIL_AUTH",
- "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT",
- "ACTIVITY_TYPE_INIT_IMPORT_WALLET",
- "ACTIVITY_TYPE_IMPORT_WALLET",
- "ACTIVITY_TYPE_INIT_IMPORT_PRIVATE_KEY",
- "ACTIVITY_TYPE_IMPORT_PRIVATE_KEY",
- "ACTIVITY_TYPE_CREATE_POLICIES",
- "ACTIVITY_TYPE_SIGN_RAW_PAYLOADS",
- "ACTIVITY_TYPE_CREATE_READ_ONLY_SESSION",
- "ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS",
- "ACTIVITY_TYPE_DELETE_OAUTH_PROVIDERS",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V5",
- "ACTIVITY_TYPE_OAUTH",
- "ACTIVITY_TYPE_CREATE_API_KEYS_V2",
- "ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION",
- "ACTIVITY_TYPE_EMAIL_AUTH_V2",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V6",
- "ACTIVITY_TYPE_DELETE_PRIVATE_KEYS",
- "ACTIVITY_TYPE_DELETE_WALLETS",
- "ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2",
- "ACTIVITY_TYPE_DELETE_SUB_ORGANIZATION",
- "ACTIVITY_TYPE_INIT_OTP_AUTH",
- "ACTIVITY_TYPE_OTP_AUTH",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7",
- "ACTIVITY_TYPE_UPDATE_WALLET"
- ]
- },
- "AddressFormat": {
- "type": "string",
- "enum": [
- "ADDRESS_FORMAT_UNCOMPRESSED",
- "ADDRESS_FORMAT_COMPRESSED",
- "ADDRESS_FORMAT_ETHEREUM",
- "ADDRESS_FORMAT_SOLANA",
- "ADDRESS_FORMAT_COSMOS",
- "ADDRESS_FORMAT_TRON",
- "ADDRESS_FORMAT_SUI",
- "ADDRESS_FORMAT_APTOS",
- "ADDRESS_FORMAT_BITCOIN_MAINNET_P2PKH",
- "ADDRESS_FORMAT_BITCOIN_MAINNET_P2SH",
- "ADDRESS_FORMAT_BITCOIN_MAINNET_P2WPKH",
- "ADDRESS_FORMAT_BITCOIN_MAINNET_P2WSH",
- "ADDRESS_FORMAT_BITCOIN_MAINNET_P2TR",
- "ADDRESS_FORMAT_BITCOIN_TESTNET_P2PKH",
- "ADDRESS_FORMAT_BITCOIN_TESTNET_P2SH",
- "ADDRESS_FORMAT_BITCOIN_TESTNET_P2WPKH",
- "ADDRESS_FORMAT_BITCOIN_TESTNET_P2WSH",
- "ADDRESS_FORMAT_BITCOIN_TESTNET_P2TR",
- "ADDRESS_FORMAT_BITCOIN_SIGNET_P2PKH",
- "ADDRESS_FORMAT_BITCOIN_SIGNET_P2SH",
- "ADDRESS_FORMAT_BITCOIN_SIGNET_P2WPKH",
- "ADDRESS_FORMAT_BITCOIN_SIGNET_P2WSH",
- "ADDRESS_FORMAT_BITCOIN_SIGNET_P2TR",
- "ADDRESS_FORMAT_BITCOIN_REGTEST_P2PKH",
- "ADDRESS_FORMAT_BITCOIN_REGTEST_P2SH",
- "ADDRESS_FORMAT_BITCOIN_REGTEST_P2WPKH",
- "ADDRESS_FORMAT_BITCOIN_REGTEST_P2WSH",
- "ADDRESS_FORMAT_BITCOIN_REGTEST_P2TR",
- "ADDRESS_FORMAT_SEI",
- "ADDRESS_FORMAT_XLM",
- "ADDRESS_FORMAT_DOGE_MAINNET",
- "ADDRESS_FORMAT_DOGE_TESTNET",
- "ADDRESS_FORMAT_TON_V3R2",
- "ADDRESS_FORMAT_TON_V4R2",
- "ADDRESS_FORMAT_XRP"
- ]
- },
- "Any": {
- "type": "object",
- "properties": {
- "@type": {
- "type": "string"
- }
- },
- "additionalProperties": {}
- },
- "ApiKey": {
- "type": "object",
- "properties": {
- "credential": {
- "$ref": "#/definitions/external.data.v1.Credential",
- "description": "A User credential that can be used to authenticate to Turnkey."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for a given API Key."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Human-readable name for an API Key."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "expirationSeconds": {
- "type": "string",
- "format": "uint64",
- "description": "Optional window (in seconds) indicating how long the API Key should last."
- }
- },
- "required": [
- "credential",
- "apiKeyId",
- "apiKeyName",
- "createdAt",
- "updatedAt"
- ]
- },
- "ApiKeyCurve": {
- "type": "string",
- "enum": [
- "API_KEY_CURVE_P256",
- "API_KEY_CURVE_SECP256K1",
- "API_KEY_CURVE_ED25519"
- ]
- },
- "ApiKeyParams": {
- "type": "object",
- "properties": {
- "apiKeyName": {
- "type": "string",
- "description": "Human-readable name for an API Key."
- },
- "publicKey": {
- "type": "string",
- "description": "The public component of a cryptographic key pair used to sign messages and transactions."
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Optional window (in seconds) indicating how long the API Key should last."
- }
- },
- "required": ["apiKeyName", "publicKey"]
- },
- "ApiKeyParamsV2": {
- "type": "object",
- "properties": {
- "apiKeyName": {
- "type": "string",
- "description": "Human-readable name for an API Key."
- },
- "publicKey": {
- "type": "string",
- "description": "The public component of a cryptographic key pair used to sign messages and transactions."
- },
- "curveType": {
- "$ref": "#/definitions/ApiKeyCurve",
- "description": "The curve type to be used for processing API key signatures."
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Optional window (in seconds) indicating how long the API Key should last."
- }
- },
- "required": ["apiKeyName", "publicKey", "curveType"]
- },
- "ApiOnlyUserParams": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "The name of the new API-only User."
- },
- "userEmail": {
- "type": "string",
- "description": "The email address for this API-only User (optional)."
- },
- "userTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of tags assigned to the new API-only User. This field, if not needed, should be an empty array in your request body."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "userTags", "apiKeys"]
- },
- "ApproveActivityIntent": {
- "type": "object",
- "properties": {
- "fingerprint": {
- "type": "string",
- "description": "An artifact verifying a User's action."
- }
- },
- "required": ["fingerprint"]
- },
- "ApproveActivityRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_APPROVE_ACTIVITY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ApproveActivityIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "Attestation": {
- "type": "object",
- "properties": {
- "credentialId": {
- "type": "string",
- "description": "The cbor encoded then base64 url encoded id of the credential."
- },
- "clientDataJson": {
- "type": "string",
- "description": "A base64 url encoded payload containing metadata about the signing context and the challenge."
- },
- "attestationObject": {
- "type": "string",
- "description": "A base64 url encoded payload containing authenticator data and any attestation the webauthn provider chooses."
- },
- "transports": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AuthenticatorTransport"
- },
- "description": "The type of authenticator transports."
- }
- },
- "required": [
- "credentialId",
- "clientDataJson",
- "attestationObject",
- "transports"
- ]
- },
- "Authenticator": {
- "type": "object",
- "properties": {
- "transports": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AuthenticatorTransport"
- },
- "description": "Types of transports that may be used by an Authenticator (e.g., USB, NFC, BLE)."
- },
- "attestationType": {
- "type": "string"
- },
- "aaguid": {
- "type": "string",
- "description": "Identifier indicating the type of the Security Key."
- },
- "credentialId": {
- "type": "string",
- "description": "Unique identifier for a WebAuthn credential."
- },
- "model": {
- "type": "string",
- "description": "The type of Authenticator device."
- },
- "credential": {
- "$ref": "#/definitions/external.data.v1.Credential",
- "description": "A User credential that can be used to authenticate to Turnkey."
- },
- "authenticatorId": {
- "type": "string",
- "description": "Unique identifier for a given Authenticator."
- },
- "authenticatorName": {
- "type": "string",
- "description": "Human-readable name for an Authenticator."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": [
- "transports",
- "attestationType",
- "aaguid",
- "credentialId",
- "model",
- "credential",
- "authenticatorId",
- "authenticatorName",
- "createdAt",
- "updatedAt"
- ]
- },
- "AuthenticatorAttestationResponse": {
- "type": "object",
- "properties": {
- "clientDataJson": {
- "type": "string"
- },
- "attestationObject": {
- "type": "string"
- },
- "transports": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AuthenticatorTransport"
- }
- },
- "authenticatorAttachment": {
- "type": "string",
- "enum": ["cross-platform", "platform"],
- "x-nullable": true
- }
- },
- "required": ["clientDataJson", "attestationObject"]
- },
- "AuthenticatorParams": {
- "type": "object",
- "properties": {
- "authenticatorName": {
- "type": "string",
- "description": "Human-readable name for an Authenticator."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "attestation": {
- "$ref": "#/definitions/PublicKeyCredentialWithAttestation"
- },
- "challenge": {
- "type": "string",
- "description": "Challenge presented for authentication purposes."
- }
- },
- "required": ["authenticatorName", "userId", "attestation", "challenge"]
- },
- "AuthenticatorParamsV2": {
- "type": "object",
- "properties": {
- "authenticatorName": {
- "type": "string",
- "description": "Human-readable name for an Authenticator."
- },
- "challenge": {
- "type": "string",
- "description": "Challenge presented for authentication purposes."
- },
- "attestation": {
- "$ref": "#/definitions/Attestation",
- "description": "The attestation that proves custody of the authenticator and provides metadata about it."
- }
- },
- "required": ["authenticatorName", "challenge", "attestation"]
- },
- "AuthenticatorTransport": {
- "type": "string",
- "enum": [
- "AUTHENTICATOR_TRANSPORT_BLE",
- "AUTHENTICATOR_TRANSPORT_INTERNAL",
- "AUTHENTICATOR_TRANSPORT_NFC",
- "AUTHENTICATOR_TRANSPORT_USB",
- "AUTHENTICATOR_TRANSPORT_HYBRID"
- ]
- },
- "Config": {
- "type": "object",
- "properties": {
- "features": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Feature"
- }
- },
- "quorum": {
- "$ref": "#/definitions/external.data.v1.Quorum"
- }
- }
- },
- "CreateApiKeysIntent": {
- "type": "object",
- "properties": {
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Keys."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["apiKeys", "userId"]
- },
- "CreateApiKeysIntentV2": {
- "type": "object",
- "properties": {
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParamsV2"
- },
- "description": "A list of API Keys."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["apiKeys", "userId"]
- },
- "CreateApiKeysRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_API_KEYS_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateApiKeysIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateApiKeysResult": {
- "type": "object",
- "properties": {
- "apiKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of API Key IDs."
- }
- },
- "required": ["apiKeyIds"]
- },
- "CreateApiOnlyUsersIntent": {
- "type": "object",
- "properties": {
- "apiOnlyUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiOnlyUserParams"
- },
- "description": "A list of API-only Users to create."
- }
- },
- "required": ["apiOnlyUsers"]
- },
- "CreateApiOnlyUsersResult": {
- "type": "object",
- "properties": {
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of API-only User IDs."
- }
- },
- "required": ["userIds"]
- },
- "CreateAuthenticatorsIntent": {
- "type": "object",
- "properties": {
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParams"
- },
- "description": "A list of Authenticators."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["authenticators", "userId"]
- },
- "CreateAuthenticatorsIntentV2": {
- "type": "object",
- "properties": {
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticators."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["authenticators", "userId"]
- },
- "CreateAuthenticatorsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateAuthenticatorsIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateAuthenticatorsResult": {
- "type": "object",
- "properties": {
- "authenticatorIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Authenticator IDs."
- }
- },
- "required": ["authenticatorIds"]
- },
- "CreateInvitationsIntent": {
- "type": "object",
- "properties": {
- "invitations": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/InvitationParams"
- },
- "description": "A list of Invitations."
- }
- },
- "required": ["invitations"]
- },
- "CreateInvitationsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_INVITATIONS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateInvitationsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateInvitationsResult": {
- "type": "object",
- "properties": {
- "invitationIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Invitation IDs"
- }
- },
- "required": ["invitationIds"]
- },
- "CreateOauthProvidersIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User to add an Oauth provider to"
- },
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProviderParams"
- },
- "description": "A list of Oauth providers."
- }
- },
- "required": ["userId", "oauthProviders"]
- },
- "CreateOauthProvidersRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateOauthProvidersIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateOauthProvidersResult": {
- "type": "object",
- "properties": {
- "providerIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of unique identifiers for Oauth Providers"
- }
- },
- "required": ["providerIds"]
- },
- "CreateOrganizationIntent": {
- "type": "object",
- "properties": {
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "rootEmail": {
- "type": "string",
- "description": "The root user's email address."
- },
- "rootAuthenticator": {
- "$ref": "#/definitions/AuthenticatorParams",
- "description": "The root user's Authenticator."
- },
- "rootUserId": {
- "type": "string",
- "description": "Unique identifier for the root user object."
- }
- },
- "required": ["organizationName", "rootEmail", "rootAuthenticator"]
- },
- "CreateOrganizationIntentV2": {
- "type": "object",
- "properties": {
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "rootEmail": {
- "type": "string",
- "description": "The root user's email address."
- },
- "rootAuthenticator": {
- "$ref": "#/definitions/AuthenticatorParamsV2",
- "description": "The root user's Authenticator."
- },
- "rootUserId": {
- "type": "string",
- "description": "Unique identifier for the root user object."
- }
- },
- "required": ["organizationName", "rootEmail", "rootAuthenticator"]
- },
- "CreateOrganizationResult": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "CreatePoliciesIntent": {
- "type": "object",
- "properties": {
- "policies": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/CreatePolicyIntentV3"
- },
- "description": "An array of policy intents to be created."
- }
- },
- "required": ["policies"]
- },
- "CreatePoliciesRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_POLICIES"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreatePoliciesIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreatePoliciesResult": {
- "type": "object",
- "properties": {
- "policyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of unique identifiers for the created policies."
- }
- },
- "required": ["policyIds"]
- },
- "CreatePolicyIntent": {
- "type": "object",
- "properties": {
- "policyName": {
- "type": "string",
- "description": "Human-readable name for a Policy."
- },
- "selectors": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Selector"
- },
- "description": "A list of simple functions each including a subject, target and boolean. See Policy Engine Language section for additional details."
- },
- "effect": {
- "$ref": "#/definitions/Effect",
- "description": "The instruction to DENY or ALLOW a particular activity following policy selector(s)."
- },
- "notes": {
- "type": "string"
- }
- },
- "required": ["policyName", "selectors", "effect"]
- },
- "CreatePolicyIntentV2": {
- "type": "object",
- "properties": {
- "policyName": {
- "type": "string",
- "description": "Human-readable name for a Policy."
- },
- "selectors": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/SelectorV2"
- },
- "description": "A list of simple functions each including a subject, target and boolean. See Policy Engine Language section for additional details."
- },
- "effect": {
- "$ref": "#/definitions/Effect",
- "description": "Whether to ALLOW or DENY requests that match the condition and consensus requirements."
- },
- "notes": {
- "type": "string"
- }
- },
- "required": ["policyName", "selectors", "effect"]
- },
- "CreatePolicyIntentV3": {
- "type": "object",
- "properties": {
- "policyName": {
- "type": "string",
- "description": "Human-readable name for a Policy."
- },
- "effect": {
- "$ref": "#/definitions/Effect",
- "description": "The instruction to DENY or ALLOW an activity."
- },
- "condition": {
- "type": "string",
- "description": "The condition expression that triggers the Effect"
- },
- "consensus": {
- "type": "string",
- "description": "The consensus expression that triggers the Effect"
- },
- "notes": {
- "type": "string"
- }
- },
- "required": ["policyName", "effect"]
- },
- "CreatePolicyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_POLICY_V3"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreatePolicyIntentV3"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreatePolicyResult": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- }
- },
- "required": ["policyId"]
- },
- "CreatePrivateKeyTagIntent": {
- "type": "object",
- "properties": {
- "privateKeyTagName": {
- "type": "string",
- "description": "Human-readable name for a Private Key Tag."
- },
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key IDs."
- }
- },
- "required": ["privateKeyTagName", "privateKeyIds"]
- },
- "CreatePrivateKeyTagRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_PRIVATE_KEY_TAG"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreatePrivateKeyTagIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreatePrivateKeyTagResult": {
- "type": "object",
- "properties": {
- "privateKeyTagId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key Tag."
- },
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key IDs."
- }
- },
- "required": ["privateKeyTagId", "privateKeyIds"]
- },
- "CreatePrivateKeysIntent": {
- "type": "object",
- "properties": {
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKeyParams"
- },
- "description": "A list of Private Keys."
- }
- },
- "required": ["privateKeys"]
- },
- "CreatePrivateKeysIntentV2": {
- "type": "object",
- "properties": {
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKeyParams"
- },
- "description": "A list of Private Keys."
- }
- },
- "required": ["privateKeys"]
- },
- "CreatePrivateKeysRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreatePrivateKeysIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreatePrivateKeysResult": {
- "type": "object",
- "properties": {
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key IDs."
- }
- },
- "required": ["privateKeyIds"]
- },
- "CreatePrivateKeysResultV2": {
- "type": "object",
- "properties": {
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKeyResult"
- },
- "description": "A list of Private Key IDs and addresses."
- }
- },
- "required": ["privateKeys"]
- },
- "CreateReadOnlySessionIntent": {
- "type": "object"
- },
- "CreateReadOnlySessionRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_READ_ONLY_SESSION"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateReadOnlySessionIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateReadOnlySessionResult": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization. If the request is being made by a user and their Sub-Organization ID is unknown, this can be the Parent Organization ID. However, using the Sub-Organization ID is preferred due to performance reasons."
- },
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "username": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "session": {
- "type": "string",
- "description": "String representing a read only session"
- },
- "sessionExpiry": {
- "type": "string",
- "format": "uint64",
- "description": "UTC timestamp in seconds representing the expiry time for the read only session."
- }
- },
- "required": [
- "organizationId",
- "organizationName",
- "userId",
- "username",
- "session",
- "sessionExpiry"
- ]
- },
- "CreateReadWriteSessionIntent": {
- "type": "object",
- "properties": {
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the read write session bundle (credentials) will be encrypted."
- },
- "email": {
- "type": "string",
- "description": "Email of the user to create a read write session for"
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to Read Write Session - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- }
- },
- "required": ["targetPublicKey", "email"]
- },
- "CreateReadWriteSessionIntentV2": {
- "type": "object",
- "properties": {
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the read write session bundle (credentials) will be encrypted."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to Read Write Session - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- }
- },
- "required": ["targetPublicKey"]
- },
- "CreateReadWriteSessionRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateReadWriteSessionIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateReadWriteSessionResult": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization. If the request is being made by a user and their Sub-Organization ID is unknown, this can be the Parent Organization ID. However, using the Sub-Organization ID is preferred due to performance reasons."
- },
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "username": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for the created API key."
- },
- "credentialBundle": {
- "type": "string",
- "description": "HPKE encrypted credential bundle"
- }
- },
- "required": [
- "organizationId",
- "organizationName",
- "userId",
- "username",
- "apiKeyId",
- "credentialBundle"
- ]
- },
- "CreateReadWriteSessionResultV2": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization. If the request is being made by a user and their Sub-Organization ID is unknown, this can be the Parent Organization ID. However, using the Sub-Organization ID is preferred due to performance reasons."
- },
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "username": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for the created API key."
- },
- "credentialBundle": {
- "type": "string",
- "description": "HPKE encrypted credential bundle"
- }
- },
- "required": [
- "organizationId",
- "organizationName",
- "userId",
- "username",
- "apiKeyId",
- "credentialBundle"
- ]
- },
- "CreateSubOrganizationIntent": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootAuthenticator": {
- "$ref": "#/definitions/AuthenticatorParamsV2",
- "description": "Root User authenticator for this new sub-organization"
- }
- },
- "required": ["name", "rootAuthenticator"]
- },
- "CreateSubOrganizationIntentV2": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParams"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- }
- },
- "required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
- },
- "CreateSubOrganizationIntentV3": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParams"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- },
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKeyParams"
- },
- "description": "A list of Private Keys."
- }
- },
- "required": [
- "subOrganizationName",
- "rootUsers",
- "rootQuorumThreshold",
- "privateKeys"
- ]
- },
- "CreateSubOrganizationIntentV4": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParams"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- },
- "wallet": {
- "$ref": "#/definitions/WalletParams",
- "description": "The wallet to create for the sub-organization"
- },
- "disableEmailRecovery": {
- "type": "boolean",
- "description": "Disable email recovery for the sub-organization"
- },
- "disableEmailAuth": {
- "type": "boolean",
- "description": "Disable email auth for the sub-organization"
- }
- },
- "required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
- },
- "CreateSubOrganizationIntentV5": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParamsV2"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- },
- "wallet": {
- "$ref": "#/definitions/WalletParams",
- "description": "The wallet to create for the sub-organization"
- },
- "disableEmailRecovery": {
- "type": "boolean",
- "description": "Disable email recovery for the sub-organization"
- },
- "disableEmailAuth": {
- "type": "boolean",
- "description": "Disable email auth for the sub-organization"
- }
- },
- "required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
- },
- "CreateSubOrganizationIntentV6": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParamsV3"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- },
- "wallet": {
- "$ref": "#/definitions/WalletParams",
- "description": "The wallet to create for the sub-organization"
- },
- "disableEmailRecovery": {
- "type": "boolean",
- "description": "Disable email recovery for the sub-organization"
- },
- "disableEmailAuth": {
- "type": "boolean",
- "description": "Disable email auth for the sub-organization"
- }
- },
- "required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
- },
- "CreateSubOrganizationIntentV7": {
- "type": "object",
- "properties": {
- "subOrganizationName": {
- "type": "string",
- "description": "Name for this sub-organization"
- },
- "rootUsers": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/RootUserParamsV4"
- },
- "description": "Root users to create within this sub-organization"
- },
- "rootQuorumThreshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach root quorum. This value must be less than or equal to the number of root users"
- },
- "wallet": {
- "$ref": "#/definitions/WalletParams",
- "description": "The wallet to create for the sub-organization"
- },
- "disableEmailRecovery": {
- "type": "boolean",
- "description": "Disable email recovery for the sub-organization"
- },
- "disableEmailAuth": {
- "type": "boolean",
- "description": "Disable email auth for the sub-organization"
- },
- "disableSmsAuth": {
- "type": "boolean",
- "description": "Disable OTP SMS auth for the sub-organization"
- },
- "disableOtpEmailAuth": {
- "type": "boolean",
- "description": "Disable OTP email auth for the sub-organization"
- }
- },
- "required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
- },
- "CreateSubOrganizationRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV7"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateSubOrganizationResult": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId"]
- },
- "CreateSubOrganizationResultV3": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKeyResult"
- },
- "description": "A list of Private Key IDs and addresses."
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId", "privateKeys"]
- },
- "CreateSubOrganizationResultV4": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "wallet": {
- "$ref": "#/definitions/WalletResult"
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId"]
- },
- "CreateSubOrganizationResultV5": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "wallet": {
- "$ref": "#/definitions/WalletResult"
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId"]
- },
- "CreateSubOrganizationResultV6": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "wallet": {
- "$ref": "#/definitions/WalletResult"
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId"]
- },
- "CreateSubOrganizationResultV7": {
- "type": "object",
- "properties": {
- "subOrganizationId": {
- "type": "string"
- },
- "wallet": {
- "$ref": "#/definitions/WalletResult"
- },
- "rootUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["subOrganizationId"]
- },
- "CreateUserTagIntent": {
- "type": "object",
- "properties": {
- "userTagName": {
- "type": "string",
- "description": "Human-readable name for a User Tag."
- },
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userTagName", "userIds"]
- },
- "CreateUserTagRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_USER_TAG"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateUserTagIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateUserTagResult": {
- "type": "object",
- "properties": {
- "userTagId": {
- "type": "string",
- "description": "Unique identifier for a given User Tag."
- },
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userTagId", "userIds"]
- },
- "CreateUsersIntent": {
- "type": "object",
- "properties": {
- "users": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/UserParams"
- },
- "description": "A list of Users."
- }
- },
- "required": ["users"]
- },
- "CreateUsersIntentV2": {
- "type": "object",
- "properties": {
- "users": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/UserParamsV2"
- },
- "description": "A list of Users."
- }
- },
- "required": ["users"]
- },
- "CreateUsersRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_USERS_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateUsersIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateUsersResult": {
- "type": "object",
- "properties": {
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userIds"]
- },
- "CreateWalletAccountsIntent": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "accounts": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/WalletAccountParams"
- },
- "description": "A list of wallet Accounts."
- }
- },
- "required": ["walletId", "accounts"]
- },
- "CreateWalletAccountsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateWalletAccountsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateWalletAccountsResult": {
- "type": "object",
- "properties": {
- "addresses": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of derived addresses."
- }
- },
- "required": ["addresses"]
- },
- "CreateWalletIntent": {
- "type": "object",
- "properties": {
- "walletName": {
- "type": "string",
- "description": "Human-readable name for a Wallet."
- },
- "accounts": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/WalletAccountParams"
- },
- "description": "A list of wallet Accounts. This field, if not needed, should be an empty array in your request body."
- },
- "mnemonicLength": {
- "type": "integer",
- "format": "int32",
- "description": "Length of mnemonic to generate the Wallet seed. Defaults to 12. Accepted values: 12, 15, 18, 21, 24."
- }
- },
- "required": ["walletName", "accounts"]
- },
- "CreateWalletRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_CREATE_WALLET"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/CreateWalletIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "CreateWalletResult": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a Wallet."
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of account addresses."
- }
- },
- "required": ["walletId", "addresses"]
- },
- "CredPropsAuthenticationExtensionsClientOutputs": {
- "type": "object",
- "properties": {
- "rk": {
- "type": "boolean"
- }
- },
- "required": ["rk"]
- },
- "CredentialType": {
- "type": "string",
- "enum": [
- "CREDENTIAL_TYPE_WEBAUTHN_AUTHENTICATOR",
- "CREDENTIAL_TYPE_API_KEY_P256",
- "CREDENTIAL_TYPE_RECOVER_USER_KEY_P256",
- "CREDENTIAL_TYPE_API_KEY_SECP256K1",
- "CREDENTIAL_TYPE_EMAIL_AUTH_KEY_P256",
- "CREDENTIAL_TYPE_API_KEY_ED25519",
- "CREDENTIAL_TYPE_OTP_AUTH_KEY_P256"
- ]
- },
- "Curve": {
- "type": "string",
- "enum": ["CURVE_SECP256K1", "CURVE_ED25519"]
- },
- "DeleteApiKeysIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "apiKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of API Key IDs."
- }
- },
- "required": ["userId", "apiKeyIds"]
- },
- "DeleteApiKeysRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_API_KEYS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteApiKeysIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteApiKeysResult": {
- "type": "object",
- "properties": {
- "apiKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of API Key IDs."
- }
- },
- "required": ["apiKeyIds"]
- },
- "DeleteAuthenticatorsIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "authenticatorIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Authenticator IDs."
- }
- },
- "required": ["userId", "authenticatorIds"]
- },
- "DeleteAuthenticatorsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_AUTHENTICATORS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteAuthenticatorsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteAuthenticatorsResult": {
- "type": "object",
- "properties": {
- "authenticatorIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "Unique identifier for a given Authenticator."
- }
- },
- "required": ["authenticatorIds"]
- },
- "DeleteInvitationIntent": {
- "type": "object",
- "properties": {
- "invitationId": {
- "type": "string",
- "description": "Unique identifier for a given Invitation object."
- }
- },
- "required": ["invitationId"]
- },
- "DeleteInvitationRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_INVITATION"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteInvitationIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteInvitationResult": {
- "type": "object",
- "properties": {
- "invitationId": {
- "type": "string",
- "description": "Unique identifier for a given Invitation."
- }
- },
- "required": ["invitationId"]
- },
- "DeleteOauthProvidersIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User to remove an Oauth provider from"
- },
- "providerIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "Unique identifier for a given Provider."
- }
- },
- "required": ["userId", "providerIds"]
- },
- "DeleteOauthProvidersRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_OAUTH_PROVIDERS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteOauthProvidersIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteOauthProvidersResult": {
- "type": "object",
- "properties": {
- "providerIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of unique identifiers for Oauth Providers"
- }
- },
- "required": ["providerIds"]
- },
- "DeleteOrganizationIntent": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "DeleteOrganizationResult": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "DeletePaymentMethodIntent": {
- "type": "object",
- "properties": {
- "paymentMethodId": {
- "type": "string",
- "description": "The payment method that the customer wants to remove."
- }
- },
- "required": ["paymentMethodId"]
- },
- "DeletePaymentMethodResult": {
- "type": "object",
- "properties": {
- "paymentMethodId": {
- "type": "string",
- "description": "The payment method that was removed."
- }
- },
- "required": ["paymentMethodId"]
- },
- "DeletePolicyIntent": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- }
- },
- "required": ["policyId"]
- },
- "DeletePolicyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_POLICY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeletePolicyIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeletePolicyResult": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- }
- },
- "required": ["policyId"]
- },
- "DeletePrivateKeyTagsIntent": {
- "type": "object",
- "properties": {
- "privateKeyTagIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key Tag IDs."
- }
- },
- "required": ["privateKeyTagIds"]
- },
- "DeletePrivateKeyTagsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_PRIVATE_KEY_TAGS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeletePrivateKeyTagsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeletePrivateKeyTagsResult": {
- "type": "object",
- "properties": {
- "privateKeyTagIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key Tag IDs."
- },
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key IDs."
- }
- },
- "required": ["privateKeyTagIds", "privateKeyIds"]
- },
- "DeletePrivateKeysIntent": {
- "type": "object",
- "properties": {
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "List of unique identifiers for private keys within an organization"
- },
- "deleteWithoutExport": {
- "type": "boolean",
- "description": "Optional parameter for deleting the private keys, even if any have not been previously exported. If they have been exported, this field is ignored."
- }
- },
- "required": ["privateKeyIds"]
- },
- "DeletePrivateKeysRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_PRIVATE_KEYS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeletePrivateKeysIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeletePrivateKeysResult": {
- "type": "object",
- "properties": {
- "privateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of private key unique identifiers that were removed"
- }
- },
- "required": ["privateKeyIds"]
- },
- "DeleteSubOrganizationIntent": {
- "type": "object",
- "properties": {
- "deleteWithoutExport": {
- "type": "boolean",
- "description": "Sub-organization deletion, by default, requires associated wallets and private keys to be exported for security reasons. Set this boolean to true to force sub-organization deletion even if some wallets or private keys within it have not been exported yet. Default: false."
- }
- }
- },
- "DeleteSubOrganizationRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_SUB_ORGANIZATION"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteSubOrganizationIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteSubOrganizationResult": {
- "type": "object",
- "properties": {
- "subOrganizationUuid": {
- "type": "string",
- "description": "Unique identifier of the sub organization that was removed"
- }
- },
- "required": ["subOrganizationUuid"]
- },
- "DeleteUserTagsIntent": {
- "type": "object",
- "properties": {
- "userTagIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User Tag IDs."
- }
- },
- "required": ["userTagIds"]
- },
- "DeleteUserTagsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_USER_TAGS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteUserTagsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteUserTagsResult": {
- "type": "object",
- "properties": {
- "userTagIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User Tag IDs."
- },
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userTagIds", "userIds"]
- },
- "DeleteUsersIntent": {
- "type": "object",
- "properties": {
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userIds"]
- },
- "DeleteUsersRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_USERS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteUsersIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteUsersResult": {
- "type": "object",
- "properties": {
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs."
- }
- },
- "required": ["userIds"]
- },
- "DeleteWalletsIntent": {
- "type": "object",
- "properties": {
- "walletIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "List of unique identifiers for wallets within an organization"
- },
- "deleteWithoutExport": {
- "type": "boolean",
- "description": "Optional parameter for deleting the wallets, even if any have not been previously exported. If they have been exported, this field is ignored."
- }
- },
- "required": ["walletIds"]
- },
- "DeleteWalletsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_DELETE_WALLETS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/DeleteWalletsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "DeleteWalletsResult": {
- "type": "object",
- "properties": {
- "walletIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of wallet unique identifiers that were removed"
- }
- },
- "required": ["walletIds"]
- },
- "DisablePrivateKeyIntent": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- }
- },
- "required": ["privateKeyId"]
- },
- "DisablePrivateKeyResult": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- }
- },
- "required": ["privateKeyId"]
- },
- "Effect": {
- "type": "string",
- "enum": ["EFFECT_ALLOW", "EFFECT_DENY"]
- },
- "EmailAuthIntent": {
- "type": "object",
- "properties": {
- "email": {
- "type": "string",
- "description": "Email of the authenticating user."
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the email auth bundle (credentials) will be encrypted."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to Email Auth - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- },
- "emailCustomization": {
- "$ref": "#/definitions/EmailCustomizationParams",
- "description": "Optional parameters for customizing emails. If not provided, the default email will be used."
- },
- "invalidateExisting": {
- "type": "boolean",
- "description": "Invalidate all other previously generated Email Auth API keys"
- },
- "sendFromEmailAddress": {
- "type": "string",
- "description": "Optional custom email address from which to send the email"
- }
- },
- "required": ["email", "targetPublicKey"]
- },
- "EmailAuthIntentV2": {
- "type": "object",
- "properties": {
- "email": {
- "type": "string",
- "description": "Email of the authenticating user."
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the email auth bundle (credentials) will be encrypted."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to Email Auth - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- },
- "emailCustomization": {
- "$ref": "#/definitions/EmailCustomizationParams",
- "description": "Optional parameters for customizing emails. If not provided, the default email will be used."
- },
- "invalidateExisting": {
- "type": "boolean",
- "description": "Invalidate all other previously generated Email Auth API keys"
- },
- "sendFromEmailAddress": {
- "type": "string",
- "description": "Optional custom email address from which to send the email"
- }
- },
- "required": ["email", "targetPublicKey"]
- },
- "EmailAuthRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_EMAIL_AUTH_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/EmailAuthIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "EmailAuthResult": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for the authenticating User."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for the created API key."
- }
- },
- "required": ["userId", "apiKeyId"]
- },
- "EmailCustomizationParams": {
- "type": "object",
- "properties": {
- "appName": {
- "type": "string",
- "description": "The name of the application."
- },
- "logoUrl": {
- "type": "string",
- "description": "A URL pointing to a logo in PNG format. Note this logo will be resized to fit into 340px x 124px."
- },
- "magicLinkTemplate": {
- "type": "string",
- "description": "A template for the URL to be used in a magic link button, e.g. `https://dapp.xyz/%s`. The auth bundle will be interpolated into the `%s`."
- },
- "templateVariables": {
- "type": "string",
- "description": "JSON object containing key/value pairs to be used with custom templates."
- },
- "templateId": {
- "type": "string",
- "description": "Unique identifier for a given Email Template. If not specified, the default is the most recent Email Template."
- }
- }
- },
- "ExportPrivateKeyIntent": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the export bundle will be encrypted."
- }
- },
- "required": ["privateKeyId", "targetPublicKey"]
- },
- "ExportPrivateKeyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_EXPORT_PRIVATE_KEY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ExportPrivateKeyIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "ExportPrivateKeyResult": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- },
- "exportBundle": {
- "type": "string",
- "description": "Export bundle containing a private key encrypted to the client's target public key."
- }
- },
- "required": ["privateKeyId", "exportBundle"]
- },
- "ExportWalletAccountIntent": {
- "type": "object",
- "properties": {
- "address": {
- "type": "string",
- "description": "Address to identify Wallet Account."
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the export bundle will be encrypted."
- }
- },
- "required": ["address", "targetPublicKey"]
- },
- "ExportWalletAccountRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ExportWalletAccountIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "ExportWalletAccountResult": {
- "type": "object",
- "properties": {
- "address": {
- "type": "string",
- "description": "Address to identify Wallet Account."
- },
- "exportBundle": {
- "type": "string",
- "description": "Export bundle containing a private key encrypted by the client's target public key."
- }
- },
- "required": ["address", "exportBundle"]
- },
- "ExportWalletIntent": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the export bundle will be encrypted."
- },
- "language": {
- "$ref": "#/definitions/MnemonicLanguage",
- "description": "The language of the mnemonic to export. Defaults to English."
- }
- },
- "required": ["walletId", "targetPublicKey"]
- },
- "ExportWalletRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_EXPORT_WALLET"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ExportWalletIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "ExportWalletResult": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "exportBundle": {
- "type": "string",
- "description": "Export bundle containing a wallet mnemonic + optional newline passphrase encrypted by the client's target public key."
- }
- },
- "required": ["walletId", "exportBundle"]
- },
- "Feature": {
- "type": "object",
- "properties": {
- "name": {
- "$ref": "#/definitions/FeatureName"
- },
- "value": {
- "type": "string"
- }
- }
- },
- "FeatureName": {
- "type": "string",
- "enum": [
- "FEATURE_NAME_ROOT_USER_EMAIL_RECOVERY",
- "FEATURE_NAME_WEBAUTHN_ORIGINS",
- "FEATURE_NAME_EMAIL_AUTH",
- "FEATURE_NAME_EMAIL_RECOVERY",
- "FEATURE_NAME_WEBHOOK",
- "FEATURE_NAME_SMS_AUTH",
- "FEATURE_NAME_OTP_EMAIL_AUTH"
- ]
- },
- "GetActivitiesRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "filterByStatus": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ActivityStatus"
- },
- "description": "Array of Activity Statuses filtering which Activities will be listed in the response."
- },
- "paginationOptions": {
- "$ref": "#/definitions/Pagination",
- "description": "Parameters used for cursor-based pagination."
- },
- "filterByType": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ActivityType"
- },
- "description": "Array of Activity Types filtering which Activities will be listed in the response."
- }
- },
- "required": ["organizationId"]
- },
- "GetActivitiesResponse": {
- "type": "object",
- "properties": {
- "activities": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Activity"
- },
- "description": "A list of Activities."
- }
- },
- "required": ["activities"]
- },
- "GetActivityRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "activityId": {
- "type": "string",
- "description": "Unique identifier for a given Activity object."
- }
- },
- "required": ["organizationId", "activityId"]
- },
- "GetApiKeyRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for a given API key."
- }
- },
- "required": ["organizationId", "apiKeyId"]
- },
- "GetApiKeyResponse": {
- "type": "object",
- "properties": {
- "apiKey": {
- "$ref": "#/definitions/ApiKey",
- "description": "An API key."
- }
- },
- "required": ["apiKey"]
- },
- "GetApiKeysRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["organizationId"]
- },
- "GetApiKeysResponse": {
- "type": "object",
- "properties": {
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKey"
- },
- "description": "A list of API keys."
- }
- },
- "required": ["apiKeys"]
- },
- "GetAuthenticatorRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "authenticatorId": {
- "type": "string",
- "description": "Unique identifier for a given Authenticator."
- }
- },
- "required": ["organizationId", "authenticatorId"]
- },
- "GetAuthenticatorResponse": {
- "type": "object",
- "properties": {
- "authenticator": {
- "$ref": "#/definitions/Authenticator",
- "description": "An authenticator."
- }
- },
- "required": ["authenticator"]
- },
- "GetAuthenticatorsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["organizationId", "userId"]
- },
- "GetAuthenticatorsResponse": {
- "type": "object",
- "properties": {
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Authenticator"
- },
- "description": "A list of authenticators."
- }
- },
- "required": ["authenticators"]
- },
- "GetOauthProvidersRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["organizationId"]
- },
- "GetOauthProvidersResponse": {
- "type": "object",
- "properties": {
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProvider"
- },
- "description": "A list of Oauth Providers"
- }
- },
- "required": ["oauthProviders"]
- },
- "GetOrganizationConfigsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "GetOrganizationConfigsResponse": {
- "type": "object",
- "properties": {
- "configs": {
- "$ref": "#/definitions/Config",
- "description": "Organization configs including quorum settings and organization features"
- }
- },
- "required": ["configs"]
- },
- "GetPoliciesRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "GetPoliciesResponse": {
- "type": "object",
- "properties": {
- "policies": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Policy"
- },
- "description": "A list of Policies."
- }
- },
- "required": ["policies"]
- },
- "GetPolicyRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- }
- },
- "required": ["organizationId", "policyId"]
- },
- "GetPolicyResponse": {
- "type": "object",
- "properties": {
- "policy": {
- "$ref": "#/definitions/Policy",
- "description": "Object that codifies rules defining the actions that are permissible within an Organization."
- }
- },
- "required": ["policy"]
- },
- "GetPrivateKeyRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- }
- },
- "required": ["organizationId", "privateKeyId"]
- },
- "GetPrivateKeyResponse": {
- "type": "object",
- "properties": {
- "privateKey": {
- "$ref": "#/definitions/PrivateKey",
- "description": "Cryptographic public/private key pair that can be used for cryptocurrency needs or more generalized encryption."
- }
- },
- "required": ["privateKey"]
- },
- "GetPrivateKeysRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "GetPrivateKeysResponse": {
- "type": "object",
- "properties": {
- "privateKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/PrivateKey"
- },
- "description": "A list of Private Keys."
- }
- },
- "required": ["privateKeys"]
- },
- "GetSubOrgIdsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for the parent Organization. This is used to find sub-organizations within it."
- },
- "filterType": {
- "type": "string",
- "description": "Specifies the type of filter to apply, i.e 'CREDENTIAL_ID', 'NAME', 'USERNAME', 'EMAIL', 'PHONE_NUMBER', 'OIDC_TOKEN' or 'PUBLIC_KEY'"
- },
- "filterValue": {
- "type": "string",
- "description": "The value of the filter to apply for the specified type. For example, a specific email or name string."
- },
- "paginationOptions": {
- "$ref": "#/definitions/Pagination",
- "description": "Parameters used for cursor-based pagination."
- }
- },
- "required": ["organizationId"]
- },
- "GetSubOrgIdsResponse": {
- "type": "object",
- "properties": {
- "organizationIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "List of unique identifiers for the matching sub-organizations."
- }
- },
- "required": ["organizationIds"]
- },
- "GetUserRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- }
- },
- "required": ["organizationId", "userId"]
- },
- "GetUserResponse": {
- "type": "object",
- "properties": {
- "user": {
- "$ref": "#/definitions/User",
- "description": "Web and/or API user within your Organization."
- }
- },
- "required": ["user"]
- },
- "GetUsersRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "GetUsersResponse": {
- "type": "object",
- "properties": {
- "users": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/User"
- },
- "description": "A list of Users."
- }
- },
- "required": ["users"]
- },
- "GetVerifiedSubOrgIdsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for the parent Organization. This is used to find sub-organizations within it."
- },
- "filterType": {
- "type": "string",
- "description": "Specifies the type of filter to apply, i.e 'EMAIL', 'PHONE_NUMBER'"
- },
- "filterValue": {
- "type": "string",
- "description": "The value of the filter to apply for the specified type. For example, a specific email or phone number string."
- },
- "paginationOptions": {
- "$ref": "#/definitions/Pagination",
- "description": "Parameters used for cursor-based pagination."
- }
- },
- "required": ["organizationId"]
- },
- "GetVerifiedSubOrgIdsResponse": {
- "type": "object",
- "properties": {
- "organizationIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "List of unique identifiers for the matching sub-organizations."
- }
- },
- "required": ["organizationIds"]
- },
- "GetWalletAccountRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "address": {
- "type": "string",
- "description": "Address corresponding to a Wallet Account."
- },
- "path": {
- "type": "string",
- "description": "Path corresponding to a Wallet Account."
- }
- },
- "required": ["organizationId", "walletId"]
- },
- "GetWalletAccountResponse": {
- "type": "object",
- "properties": {
- "account": {
- "$ref": "#/definitions/WalletAccount",
- "description": "The resulting Wallet Account."
- }
- },
- "required": ["account"]
- },
- "GetWalletAccountsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "paginationOptions": {
- "$ref": "#/definitions/Pagination",
- "description": "Parameters used for cursor-based pagination."
- }
- },
- "required": ["organizationId", "walletId"]
- },
- "GetWalletAccountsResponse": {
- "type": "object",
- "properties": {
- "accounts": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/WalletAccount"
- },
- "description": "A list of Accounts generated from a Wallet that share a common seed."
- }
- },
- "required": ["accounts"]
- },
- "GetWalletRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- }
- },
- "required": ["organizationId", "walletId"]
- },
- "GetWalletResponse": {
- "type": "object",
- "properties": {
- "wallet": {
- "$ref": "#/definitions/Wallet",
- "description": "A collection of deterministically generated cryptographic public / private key pairs that share a common seed"
- }
- },
- "required": ["wallet"]
- },
- "GetWalletsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "GetWalletsResponse": {
- "type": "object",
- "properties": {
- "wallets": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Wallet"
- },
- "description": "A list of Wallets."
- }
- },
- "required": ["wallets"]
- },
- "GetWhoamiRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization. If the request is being made by a WebAuthN user and their Sub-Organization ID is unknown, this can be the Parent Organization ID; using the Sub-Organization ID when possible is preferred due to performance reasons."
- }
- },
- "required": ["organizationId"]
- },
- "GetWhoamiResponse": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "organizationName": {
- "type": "string",
- "description": "Human-readable name for an Organization."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "username": {
- "type": "string",
- "description": "Human-readable name for a User."
- }
- },
- "required": ["organizationId", "organizationName", "userId", "username"]
- },
- "HashFunction": {
- "type": "string",
- "enum": [
- "HASH_FUNCTION_NO_OP",
- "HASH_FUNCTION_SHA256",
- "HASH_FUNCTION_KECCAK256",
- "HASH_FUNCTION_NOT_APPLICABLE"
- ]
- },
- "ImportPrivateKeyIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User importing a Private Key."
- },
- "privateKeyName": {
- "type": "string",
- "description": "Human-readable name for a Private Key."
- },
- "encryptedBundle": {
- "type": "string",
- "description": "Bundle containing a raw private key encrypted to the enclave's target public key."
- },
- "curve": {
- "$ref": "#/definitions/Curve",
- "description": "Cryptographic Curve used to generate a given Private Key."
- },
- "addressFormats": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AddressFormat"
- },
- "description": "Cryptocurrency-specific formats for a derived address (e.g., Ethereum)."
- }
- },
- "required": [
- "userId",
- "privateKeyName",
- "encryptedBundle",
- "curve",
- "addressFormats"
- ]
- },
- "ImportPrivateKeyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_IMPORT_PRIVATE_KEY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ImportPrivateKeyIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "ImportPrivateKeyResult": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a Private Key."
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/activity.v1.Address"
- },
- "description": "A list of addresses."
- }
- },
- "required": ["privateKeyId", "addresses"]
- },
- "ImportWalletIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User importing a Wallet."
- },
- "walletName": {
- "type": "string",
- "description": "Human-readable name for a Wallet."
- },
- "encryptedBundle": {
- "type": "string",
- "description": "Bundle containing a wallet mnemonic encrypted to the enclave's target public key."
- },
- "accounts": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/WalletAccountParams"
- },
- "description": "A list of wallet Accounts."
- }
- },
- "required": ["userId", "walletName", "encryptedBundle", "accounts"]
- },
- "ImportWalletRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_IMPORT_WALLET"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/ImportWalletIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "ImportWalletResult": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a Wallet."
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of account addresses."
- }
- },
- "required": ["walletId", "addresses"]
- },
- "InitImportPrivateKeyIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User importing a Private Key."
- }
- },
- "required": ["userId"]
- },
- "InitImportPrivateKeyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_INIT_IMPORT_PRIVATE_KEY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/InitImportPrivateKeyIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "InitImportPrivateKeyResult": {
- "type": "object",
- "properties": {
- "importBundle": {
- "type": "string",
- "description": "Import bundle containing a public key and signature to use for importing client data."
- }
- },
- "required": ["importBundle"]
- },
- "InitImportWalletIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "The ID of the User importing a Wallet."
- }
- },
- "required": ["userId"]
- },
- "InitImportWalletRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_INIT_IMPORT_WALLET"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/InitImportWalletIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "InitImportWalletResult": {
- "type": "object",
- "properties": {
- "importBundle": {
- "type": "string",
- "description": "Import bundle containing a public key and signature to use for importing client data."
- }
- },
- "required": ["importBundle"]
- },
- "InitOtpAuthIntent": {
- "type": "object",
- "properties": {
- "otpType": {
- "type": "string",
- "description": "Enum to specifiy whether to send OTP via SMS or email"
- },
- "contact": {
- "type": "string",
- "description": "Email or phone number to send the OTP code to"
- },
- "emailCustomization": {
- "$ref": "#/definitions/EmailCustomizationParams",
- "description": "Optional parameters for customizing emails. If not provided, the default email will be used."
- },
- "smsCustomization": {
- "$ref": "#/definitions/SmsCustomizationParams",
- "description": "Optional parameters for customizing SMS message. If not provided, the default sms message will be used."
- },
- "userIdentifier": {
- "type": "string",
- "description": "Optional client-generated user identifier to enable per-user rate limiting for SMS auth. We recommend using a hash of the client-side IP address."
- },
- "sendFromEmailAddress": {
- "type": "string",
- "description": "Optional custom email address from which to send the OTP email"
- }
- },
- "required": ["otpType", "contact"]
- },
- "InitOtpAuthRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_INIT_OTP_AUTH"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/InitOtpAuthIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "InitOtpAuthResult": {
- "type": "object",
- "properties": {
- "otpId": {
- "type": "string",
- "description": "Unique identifier for an OTP authentication"
- }
- },
- "required": ["otpId"]
- },
- "InitUserEmailRecoveryIntent": {
- "type": "object",
- "properties": {
- "email": {
- "type": "string",
- "description": "Email of the user starting recovery"
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the recovery bundle will be encrypted."
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the recovery credential is valid. If not provided, a default of 15 minutes will be used."
- },
- "emailCustomization": {
- "$ref": "#/definitions/EmailCustomizationParams",
- "description": "Optional parameters for customizing emails. If not provided, the default email will be used."
- }
- },
- "required": ["email", "targetPublicKey"]
- },
- "InitUserEmailRecoveryRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/InitUserEmailRecoveryIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "InitUserEmailRecoveryResult": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for the user being recovered."
- }
- },
- "required": ["userId"]
- },
- "Intent": {
- "type": "object",
- "properties": {
- "createOrganizationIntent": {
- "$ref": "#/definitions/CreateOrganizationIntent"
- },
- "createAuthenticatorsIntent": {
- "$ref": "#/definitions/CreateAuthenticatorsIntent"
- },
- "createUsersIntent": {
- "$ref": "#/definitions/CreateUsersIntent"
- },
- "createPrivateKeysIntent": {
- "$ref": "#/definitions/CreatePrivateKeysIntent"
- },
- "signRawPayloadIntent": {
- "$ref": "#/definitions/SignRawPayloadIntent"
- },
- "createInvitationsIntent": {
- "$ref": "#/definitions/CreateInvitationsIntent"
- },
- "acceptInvitationIntent": {
- "$ref": "#/definitions/AcceptInvitationIntent"
- },
- "createPolicyIntent": {
- "$ref": "#/definitions/CreatePolicyIntent"
- },
- "disablePrivateKeyIntent": {
- "$ref": "#/definitions/DisablePrivateKeyIntent"
- },
- "deleteUsersIntent": {
- "$ref": "#/definitions/DeleteUsersIntent"
- },
- "deleteAuthenticatorsIntent": {
- "$ref": "#/definitions/DeleteAuthenticatorsIntent"
- },
- "deleteInvitationIntent": {
- "$ref": "#/definitions/DeleteInvitationIntent"
- },
- "deleteOrganizationIntent": {
- "$ref": "#/definitions/DeleteOrganizationIntent"
- },
- "deletePolicyIntent": {
- "$ref": "#/definitions/DeletePolicyIntent"
- },
- "createUserTagIntent": {
- "$ref": "#/definitions/CreateUserTagIntent"
- },
- "deleteUserTagsIntent": {
- "$ref": "#/definitions/DeleteUserTagsIntent"
- },
- "signTransactionIntent": {
- "$ref": "#/definitions/SignTransactionIntent"
- },
- "createApiKeysIntent": {
- "$ref": "#/definitions/CreateApiKeysIntent"
- },
- "deleteApiKeysIntent": {
- "$ref": "#/definitions/DeleteApiKeysIntent"
- },
- "approveActivityIntent": {
- "$ref": "#/definitions/ApproveActivityIntent"
- },
- "rejectActivityIntent": {
- "$ref": "#/definitions/RejectActivityIntent"
- },
- "createPrivateKeyTagIntent": {
- "$ref": "#/definitions/CreatePrivateKeyTagIntent"
- },
- "deletePrivateKeyTagsIntent": {
- "$ref": "#/definitions/DeletePrivateKeyTagsIntent"
- },
- "createPolicyIntentV2": {
- "$ref": "#/definitions/CreatePolicyIntentV2"
- },
- "setPaymentMethodIntent": {
- "$ref": "#/definitions/SetPaymentMethodIntent"
- },
- "activateBillingTierIntent": {
- "$ref": "#/definitions/ActivateBillingTierIntent"
- },
- "deletePaymentMethodIntent": {
- "$ref": "#/definitions/DeletePaymentMethodIntent"
- },
- "createPolicyIntentV3": {
- "$ref": "#/definitions/CreatePolicyIntentV3"
- },
- "createApiOnlyUsersIntent": {
- "$ref": "#/definitions/CreateApiOnlyUsersIntent"
- },
- "updateRootQuorumIntent": {
- "$ref": "#/definitions/UpdateRootQuorumIntent"
- },
- "updateUserTagIntent": {
- "$ref": "#/definitions/UpdateUserTagIntent"
- },
- "updatePrivateKeyTagIntent": {
- "$ref": "#/definitions/UpdatePrivateKeyTagIntent"
- },
- "createAuthenticatorsIntentV2": {
- "$ref": "#/definitions/CreateAuthenticatorsIntentV2"
- },
- "acceptInvitationIntentV2": {
- "$ref": "#/definitions/AcceptInvitationIntentV2"
- },
- "createOrganizationIntentV2": {
- "$ref": "#/definitions/CreateOrganizationIntentV2"
- },
- "createUsersIntentV2": {
- "$ref": "#/definitions/CreateUsersIntentV2"
- },
- "createSubOrganizationIntent": {
- "$ref": "#/definitions/CreateSubOrganizationIntent"
- },
- "createSubOrganizationIntentV2": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV2"
- },
- "updateAllowedOriginsIntent": {
- "$ref": "#/definitions/UpdateAllowedOriginsIntent"
- },
- "createPrivateKeysIntentV2": {
- "$ref": "#/definitions/CreatePrivateKeysIntentV2"
- },
- "updateUserIntent": {
- "$ref": "#/definitions/UpdateUserIntent"
- },
- "updatePolicyIntent": {
- "$ref": "#/definitions/UpdatePolicyIntent"
- },
- "setPaymentMethodIntentV2": {
- "$ref": "#/definitions/SetPaymentMethodIntentV2"
- },
- "createSubOrganizationIntentV3": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV3"
- },
- "createWalletIntent": {
- "$ref": "#/definitions/CreateWalletIntent"
- },
- "createWalletAccountsIntent": {
- "$ref": "#/definitions/CreateWalletAccountsIntent"
- },
- "initUserEmailRecoveryIntent": {
- "$ref": "#/definitions/InitUserEmailRecoveryIntent"
- },
- "recoverUserIntent": {
- "$ref": "#/definitions/RecoverUserIntent"
- },
- "setOrganizationFeatureIntent": {
- "$ref": "#/definitions/SetOrganizationFeatureIntent"
- },
- "removeOrganizationFeatureIntent": {
- "$ref": "#/definitions/RemoveOrganizationFeatureIntent"
- },
- "signRawPayloadIntentV2": {
- "$ref": "#/definitions/SignRawPayloadIntentV2"
- },
- "signTransactionIntentV2": {
- "$ref": "#/definitions/SignTransactionIntentV2"
- },
- "exportPrivateKeyIntent": {
- "$ref": "#/definitions/ExportPrivateKeyIntent"
- },
- "exportWalletIntent": {
- "$ref": "#/definitions/ExportWalletIntent"
- },
- "createSubOrganizationIntentV4": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV4"
- },
- "emailAuthIntent": {
- "$ref": "#/definitions/EmailAuthIntent"
- },
- "exportWalletAccountIntent": {
- "$ref": "#/definitions/ExportWalletAccountIntent"
- },
- "initImportWalletIntent": {
- "$ref": "#/definitions/InitImportWalletIntent"
- },
- "importWalletIntent": {
- "$ref": "#/definitions/ImportWalletIntent"
- },
- "initImportPrivateKeyIntent": {
- "$ref": "#/definitions/InitImportPrivateKeyIntent"
- },
- "importPrivateKeyIntent": {
- "$ref": "#/definitions/ImportPrivateKeyIntent"
- },
- "createPoliciesIntent": {
- "$ref": "#/definitions/CreatePoliciesIntent"
- },
- "signRawPayloadsIntent": {
- "$ref": "#/definitions/SignRawPayloadsIntent"
- },
- "createReadOnlySessionIntent": {
- "$ref": "#/definitions/CreateReadOnlySessionIntent"
- },
- "createOauthProvidersIntent": {
- "$ref": "#/definitions/CreateOauthProvidersIntent"
- },
- "deleteOauthProvidersIntent": {
- "$ref": "#/definitions/DeleteOauthProvidersIntent"
- },
- "createSubOrganizationIntentV5": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV5"
- },
- "oauthIntent": {
- "$ref": "#/definitions/OauthIntent"
- },
- "createApiKeysIntentV2": {
- "$ref": "#/definitions/CreateApiKeysIntentV2"
- },
- "createReadWriteSessionIntent": {
- "$ref": "#/definitions/CreateReadWriteSessionIntent"
- },
- "emailAuthIntentV2": {
- "$ref": "#/definitions/EmailAuthIntentV2"
- },
- "createSubOrganizationIntentV6": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV6"
- },
- "deletePrivateKeysIntent": {
- "$ref": "#/definitions/DeletePrivateKeysIntent"
- },
- "deleteWalletsIntent": {
- "$ref": "#/definitions/DeleteWalletsIntent"
- },
- "createReadWriteSessionIntentV2": {
- "$ref": "#/definitions/CreateReadWriteSessionIntentV2"
- },
- "deleteSubOrganizationIntent": {
- "$ref": "#/definitions/DeleteSubOrganizationIntent"
- },
- "initOtpAuthIntent": {
- "$ref": "#/definitions/InitOtpAuthIntent"
- },
- "otpAuthIntent": {
- "$ref": "#/definitions/OtpAuthIntent"
- },
- "createSubOrganizationIntentV7": {
- "$ref": "#/definitions/CreateSubOrganizationIntentV7"
- },
- "updateWalletIntent": {
- "$ref": "#/definitions/UpdateWalletIntent"
- }
- }
- },
- "InvitationParams": {
- "type": "object",
- "properties": {
- "receiverUserName": {
- "type": "string",
- "description": "The name of the intended Invitation recipient."
- },
- "receiverUserEmail": {
- "type": "string",
- "description": "The email address of the intended Invitation recipient."
- },
- "receiverUserTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of tags assigned to the Invitation recipient. This field, if not needed, should be an empty array in your request body."
- },
- "accessType": {
- "$ref": "#/definitions/AccessType",
- "description": "The User's permissible access method(s)."
- },
- "senderUserId": {
- "type": "string",
- "description": "Unique identifier for the Sender of an Invitation."
- }
- },
- "required": [
- "receiverUserName",
- "receiverUserEmail",
- "receiverUserTags",
- "accessType",
- "senderUserId"
- ]
- },
- "ListPrivateKeyTagsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "ListPrivateKeyTagsResponse": {
- "type": "object",
- "properties": {
- "privateKeyTags": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/v1.Tag"
- },
- "description": "A list of Private Key Tags"
- }
- },
- "required": ["privateKeyTags"]
- },
- "ListUserTagsRequest": {
- "type": "object",
- "properties": {
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- }
- },
- "required": ["organizationId"]
- },
- "ListUserTagsResponse": {
- "type": "object",
- "properties": {
- "userTags": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/v1.Tag"
- },
- "description": "A list of User Tags"
- }
- },
- "required": ["userTags"]
- },
- "MnemonicLanguage": {
- "type": "string",
- "enum": [
- "MNEMONIC_LANGUAGE_ENGLISH",
- "MNEMONIC_LANGUAGE_SIMPLIFIED_CHINESE",
- "MNEMONIC_LANGUAGE_TRADITIONAL_CHINESE",
- "MNEMONIC_LANGUAGE_CZECH",
- "MNEMONIC_LANGUAGE_FRENCH",
- "MNEMONIC_LANGUAGE_ITALIAN",
- "MNEMONIC_LANGUAGE_JAPANESE",
- "MNEMONIC_LANGUAGE_KOREAN",
- "MNEMONIC_LANGUAGE_SPANISH"
- ]
- },
- "OauthIntent": {
- "type": "object",
- "properties": {
- "oidcToken": {
- "type": "string",
- "description": "Base64 encoded OIDC token"
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the oauth bundle (credentials) will be encrypted."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to Oauth - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- }
- },
- "required": ["oidcToken", "targetPublicKey"]
- },
- "OauthProvider": {
- "type": "object",
- "properties": {
- "providerId": {
- "type": "string",
- "description": "Unique identifier for an OAuth Provider"
- },
- "providerName": {
- "type": "string",
- "description": "Human-readable name to identify a Provider."
- },
- "issuer": {
- "type": "string",
- "description": "The issuer of the token, typically a URL indicating the authentication server, e.g https://accounts.google.com"
- },
- "audience": {
- "type": "string",
- "description": "Expected audience ('aud' attribute of the signed token) which represents the app ID"
- },
- "subject": {
- "type": "string",
- "description": "Expected subject ('sub' attribute of the signed token) which represents the user ID"
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": [
- "providerId",
- "providerName",
- "issuer",
- "audience",
- "subject",
- "createdAt",
- "updatedAt"
- ]
- },
- "OauthProviderParams": {
- "type": "object",
- "properties": {
- "providerName": {
- "type": "string",
- "description": "Human-readable name to identify a Provider."
- },
- "oidcToken": {
- "type": "string",
- "description": "Base64 encoded OIDC token"
- }
- },
- "required": ["providerName", "oidcToken"]
- },
- "OauthRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_OAUTH"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/OauthIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "OauthResult": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for the authenticating User."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for the created API key."
- },
- "credentialBundle": {
- "type": "string",
- "description": "HPKE encrypted credential bundle"
- }
- },
- "required": ["userId", "apiKeyId", "credentialBundle"]
- },
- "Operator": {
- "type": "string",
- "enum": [
- "OPERATOR_EQUAL",
- "OPERATOR_MORE_THAN",
- "OPERATOR_MORE_THAN_OR_EQUAL",
- "OPERATOR_LESS_THAN",
- "OPERATOR_LESS_THAN_OR_EQUAL",
- "OPERATOR_CONTAINS",
- "OPERATOR_NOT_EQUAL",
- "OPERATOR_IN",
- "OPERATOR_NOT_IN",
- "OPERATOR_CONTAINS_ONE",
- "OPERATOR_CONTAINS_ALL"
- ]
- },
- "OtpAuthIntent": {
- "type": "object",
- "properties": {
- "otpId": {
- "type": "string",
- "description": "ID representing the result of an init OTP activity."
- },
- "otpCode": {
- "type": "string",
- "description": "6 digit OTP code sent out to a user's contact (email or SMS)"
- },
- "targetPublicKey": {
- "type": "string",
- "description": "Client-side public key generated by the user, to which the OTP bundle (credentials) will be encrypted."
- },
- "apiKeyName": {
- "type": "string",
- "description": "Optional human-readable name for an API Key. If none provided, default to OTP Auth - \u003cTimestamp\u003e"
- },
- "expirationSeconds": {
- "type": "string",
- "description": "Expiration window (in seconds) indicating how long the API key is valid. If not provided, a default of 15 minutes will be used."
- },
- "invalidateExisting": {
- "type": "boolean",
- "description": "Invalidate all other previously generated OTP Auth API keys"
- }
- },
- "required": ["otpId", "otpCode"]
- },
- "OtpAuthRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_OTP_AUTH"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/OtpAuthIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "OtpAuthResult": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for the authenticating User."
- },
- "apiKeyId": {
- "type": "string",
- "description": "Unique identifier for the created API key."
- },
- "credentialBundle": {
- "type": "string",
- "description": "HPKE encrypted credential bundle"
- }
- },
- "required": ["userId"]
- },
- "Pagination": {
- "type": "object",
- "properties": {
- "limit": {
- "type": "string",
- "description": "A limit of the number of object to be returned, between 1 and 100. Defaults to 10."
- },
- "before": {
- "type": "string",
- "description": "A pagination cursor. This is an object ID that enables you to fetch all objects before this ID."
- },
- "after": {
- "type": "string",
- "description": "A pagination cursor. This is an object ID that enables you to fetch all objects after this ID."
- }
- }
- },
- "PathFormat": {
- "type": "string",
- "enum": ["PATH_FORMAT_BIP32"]
- },
- "PayloadEncoding": {
- "type": "string",
- "enum": ["PAYLOAD_ENCODING_HEXADECIMAL", "PAYLOAD_ENCODING_TEXT_UTF8"]
- },
- "Policy": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- },
- "policyName": {
- "type": "string",
- "description": "Human-readable name for a Policy."
- },
- "effect": {
- "$ref": "#/definitions/Effect",
- "description": "The instruction to DENY or ALLOW a particular activity following policy selector(s)."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "notes": {
- "type": "string",
- "description": "Human-readable notes added by a User to describe a particular policy."
- },
- "consensus": {
- "type": "string",
- "description": "A consensus expression that evalutes to true or false."
- },
- "condition": {
- "type": "string",
- "description": "A condition expression that evalutes to true or false."
- }
- },
- "required": [
- "policyId",
- "policyName",
- "effect",
- "createdAt",
- "updatedAt",
- "notes",
- "consensus",
- "condition"
- ]
- },
- "PrivateKey": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- },
- "publicKey": {
- "type": "string",
- "description": "The public component of a cryptographic key pair used to sign messages and transactions."
- },
- "privateKeyName": {
- "type": "string",
- "description": "Human-readable name for a Private Key."
- },
- "curve": {
- "$ref": "#/definitions/Curve",
- "description": "Cryptographic Curve used to generate a given Private Key."
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/data.v1.Address"
- },
- "description": "Derived cryptocurrency addresses for a given Private Key."
- },
- "privateKeyTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key Tag IDs."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "exported": {
- "type": "boolean",
- "description": "True when a given Private Key is exported, false otherwise."
- },
- "imported": {
- "type": "boolean",
- "description": "True when a given Private Key is imported, false otherwise."
- }
- },
- "required": [
- "privateKeyId",
- "publicKey",
- "privateKeyName",
- "curve",
- "addresses",
- "privateKeyTags",
- "createdAt",
- "updatedAt",
- "exported",
- "imported"
- ]
- },
- "PrivateKeyParams": {
- "type": "object",
- "properties": {
- "privateKeyName": {
- "type": "string",
- "description": "Human-readable name for a Private Key."
- },
- "curve": {
- "$ref": "#/definitions/Curve",
- "description": "Cryptographic Curve used to generate a given Private Key."
- },
- "privateKeyTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key Tag IDs. This field, if not needed, should be an empty array in your request body."
- },
- "addressFormats": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AddressFormat"
- },
- "description": "Cryptocurrency-specific formats for a derived address (e.g., Ethereum)."
- }
- },
- "required": [
- "privateKeyName",
- "curve",
- "privateKeyTags",
- "addressFormats"
- ]
- },
- "PrivateKeyResult": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string"
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/activity.v1.Address"
- }
- }
- }
- },
- "PublicKeyCredentialWithAttestation": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string"
- },
- "type": {
- "type": "string",
- "enum": ["public-key"]
- },
- "rawId": {
- "type": "string"
- },
- "authenticatorAttachment": {
- "type": "string",
- "enum": ["cross-platform", "platform"],
- "x-nullable": true
- },
- "response": {
- "$ref": "#/definitions/AuthenticatorAttestationResponse"
- },
- "clientExtensionResults": {
- "$ref": "#/definitions/SimpleClientExtensionResults"
- }
- },
- "required": ["id", "type", "rawId", "response", "clientExtensionResults"]
- },
- "RecoverUserIntent": {
- "type": "object",
- "properties": {
- "authenticator": {
- "$ref": "#/definitions/AuthenticatorParamsV2",
- "description": "The new authenticator to register."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for the user performing recovery."
- }
- },
- "required": ["authenticator", "userId"]
- },
- "RecoverUserRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_RECOVER_USER"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/RecoverUserIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "RecoverUserResult": {
- "type": "object",
- "properties": {
- "authenticatorId": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "ID of the authenticator created."
- }
- },
- "required": ["authenticatorId"]
- },
- "RejectActivityIntent": {
- "type": "object",
- "properties": {
- "fingerprint": {
- "type": "string",
- "description": "An artifact verifying a User's action."
- }
- },
- "required": ["fingerprint"]
- },
- "RejectActivityRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_REJECT_ACTIVITY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/RejectActivityIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "RemoveOrganizationFeatureIntent": {
- "type": "object",
- "properties": {
- "name": {
- "$ref": "#/definitions/FeatureName",
- "description": "Name of the feature to remove"
- }
- },
- "required": ["name"]
- },
- "RemoveOrganizationFeatureRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/RemoveOrganizationFeatureIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "RemoveOrganizationFeatureResult": {
- "type": "object",
- "properties": {
- "features": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Feature"
- },
- "description": "Resulting list of organization features."
- }
- },
- "required": ["features"]
- },
- "Result": {
- "type": "object",
- "properties": {
- "createOrganizationResult": {
- "$ref": "#/definitions/CreateOrganizationResult"
- },
- "createAuthenticatorsResult": {
- "$ref": "#/definitions/CreateAuthenticatorsResult"
- },
- "createUsersResult": {
- "$ref": "#/definitions/CreateUsersResult"
- },
- "createPrivateKeysResult": {
- "$ref": "#/definitions/CreatePrivateKeysResult"
- },
- "createInvitationsResult": {
- "$ref": "#/definitions/CreateInvitationsResult"
- },
- "acceptInvitationResult": {
- "$ref": "#/definitions/AcceptInvitationResult"
- },
- "signRawPayloadResult": {
- "$ref": "#/definitions/SignRawPayloadResult"
- },
- "createPolicyResult": {
- "$ref": "#/definitions/CreatePolicyResult"
- },
- "disablePrivateKeyResult": {
- "$ref": "#/definitions/DisablePrivateKeyResult"
- },
- "deleteUsersResult": {
- "$ref": "#/definitions/DeleteUsersResult"
- },
- "deleteAuthenticatorsResult": {
- "$ref": "#/definitions/DeleteAuthenticatorsResult"
- },
- "deleteInvitationResult": {
- "$ref": "#/definitions/DeleteInvitationResult"
- },
- "deleteOrganizationResult": {
- "$ref": "#/definitions/DeleteOrganizationResult"
- },
- "deletePolicyResult": {
- "$ref": "#/definitions/DeletePolicyResult"
- },
- "createUserTagResult": {
- "$ref": "#/definitions/CreateUserTagResult"
- },
- "deleteUserTagsResult": {
- "$ref": "#/definitions/DeleteUserTagsResult"
- },
- "signTransactionResult": {
- "$ref": "#/definitions/SignTransactionResult"
- },
- "deleteApiKeysResult": {
- "$ref": "#/definitions/DeleteApiKeysResult"
- },
- "createApiKeysResult": {
- "$ref": "#/definitions/CreateApiKeysResult"
- },
- "createPrivateKeyTagResult": {
- "$ref": "#/definitions/CreatePrivateKeyTagResult"
- },
- "deletePrivateKeyTagsResult": {
- "$ref": "#/definitions/DeletePrivateKeyTagsResult"
- },
- "setPaymentMethodResult": {
- "$ref": "#/definitions/SetPaymentMethodResult"
- },
- "activateBillingTierResult": {
- "$ref": "#/definitions/ActivateBillingTierResult"
- },
- "deletePaymentMethodResult": {
- "$ref": "#/definitions/DeletePaymentMethodResult"
- },
- "createApiOnlyUsersResult": {
- "$ref": "#/definitions/CreateApiOnlyUsersResult"
- },
- "updateRootQuorumResult": {
- "$ref": "#/definitions/UpdateRootQuorumResult"
- },
- "updateUserTagResult": {
- "$ref": "#/definitions/UpdateUserTagResult"
- },
- "updatePrivateKeyTagResult": {
- "$ref": "#/definitions/UpdatePrivateKeyTagResult"
- },
- "createSubOrganizationResult": {
- "$ref": "#/definitions/CreateSubOrganizationResult"
- },
- "updateAllowedOriginsResult": {
- "$ref": "#/definitions/UpdateAllowedOriginsResult"
- },
- "createPrivateKeysResultV2": {
- "$ref": "#/definitions/CreatePrivateKeysResultV2"
- },
- "updateUserResult": {
- "$ref": "#/definitions/UpdateUserResult"
- },
- "updatePolicyResult": {
- "$ref": "#/definitions/UpdatePolicyResult"
- },
- "createSubOrganizationResultV3": {
- "$ref": "#/definitions/CreateSubOrganizationResultV3"
- },
- "createWalletResult": {
- "$ref": "#/definitions/CreateWalletResult"
- },
- "createWalletAccountsResult": {
- "$ref": "#/definitions/CreateWalletAccountsResult"
- },
- "initUserEmailRecoveryResult": {
- "$ref": "#/definitions/InitUserEmailRecoveryResult"
- },
- "recoverUserResult": {
- "$ref": "#/definitions/RecoverUserResult"
- },
- "setOrganizationFeatureResult": {
- "$ref": "#/definitions/SetOrganizationFeatureResult"
- },
- "removeOrganizationFeatureResult": {
- "$ref": "#/definitions/RemoveOrganizationFeatureResult"
- },
- "exportPrivateKeyResult": {
- "$ref": "#/definitions/ExportPrivateKeyResult"
- },
- "exportWalletResult": {
- "$ref": "#/definitions/ExportWalletResult"
- },
- "createSubOrganizationResultV4": {
- "$ref": "#/definitions/CreateSubOrganizationResultV4"
- },
- "emailAuthResult": {
- "$ref": "#/definitions/EmailAuthResult"
- },
- "exportWalletAccountResult": {
- "$ref": "#/definitions/ExportWalletAccountResult"
- },
- "initImportWalletResult": {
- "$ref": "#/definitions/InitImportWalletResult"
- },
- "importWalletResult": {
- "$ref": "#/definitions/ImportWalletResult"
- },
- "initImportPrivateKeyResult": {
- "$ref": "#/definitions/InitImportPrivateKeyResult"
- },
- "importPrivateKeyResult": {
- "$ref": "#/definitions/ImportPrivateKeyResult"
- },
- "createPoliciesResult": {
- "$ref": "#/definitions/CreatePoliciesResult"
- },
- "signRawPayloadsResult": {
- "$ref": "#/definitions/SignRawPayloadsResult"
- },
- "createReadOnlySessionResult": {
- "$ref": "#/definitions/CreateReadOnlySessionResult"
- },
- "createOauthProvidersResult": {
- "$ref": "#/definitions/CreateOauthProvidersResult"
- },
- "deleteOauthProvidersResult": {
- "$ref": "#/definitions/DeleteOauthProvidersResult"
- },
- "createSubOrganizationResultV5": {
- "$ref": "#/definitions/CreateSubOrganizationResultV5"
- },
- "oauthResult": {
- "$ref": "#/definitions/OauthResult"
- },
- "createReadWriteSessionResult": {
- "$ref": "#/definitions/CreateReadWriteSessionResult"
- },
- "createSubOrganizationResultV6": {
- "$ref": "#/definitions/CreateSubOrganizationResultV6"
- },
- "deletePrivateKeysResult": {
- "$ref": "#/definitions/DeletePrivateKeysResult"
- },
- "deleteWalletsResult": {
- "$ref": "#/definitions/DeleteWalletsResult"
- },
- "createReadWriteSessionResultV2": {
- "$ref": "#/definitions/CreateReadWriteSessionResultV2"
- },
- "deleteSubOrganizationResult": {
- "$ref": "#/definitions/DeleteSubOrganizationResult"
- },
- "initOtpAuthResult": {
- "$ref": "#/definitions/InitOtpAuthResult"
- },
- "otpAuthResult": {
- "$ref": "#/definitions/OtpAuthResult"
- },
- "createSubOrganizationResultV7": {
- "$ref": "#/definitions/CreateSubOrganizationResultV7"
- },
- "updateWalletResult": {
- "$ref": "#/definitions/UpdateWalletResult"
- }
- }
- },
- "RootUserParams": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "apiKeys", "authenticators"]
- },
- "RootUserParamsV2": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- },
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProviderParams"
- },
- "description": "A list of Oauth providers. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "apiKeys", "authenticators", "oauthProviders"]
- },
- "RootUserParamsV3": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParamsV2"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- },
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProviderParams"
- },
- "description": "A list of Oauth providers. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "apiKeys", "authenticators", "oauthProviders"]
- },
- "RootUserParamsV4": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "userPhoneNumber": {
- "type": "string",
- "description": "The user's phone number in E.164 format e.g. +13214567890"
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParamsV2"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- },
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProviderParams"
- },
- "description": "A list of Oauth providers. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "apiKeys", "authenticators", "oauthProviders"]
- },
- "Selector": {
- "type": "object",
- "properties": {
- "subject": {
- "type": "string"
- },
- "operator": {
- "$ref": "#/definitions/Operator"
- },
- "target": {
- "type": "string"
- }
- }
- },
- "SelectorV2": {
- "type": "object",
- "properties": {
- "subject": {
- "type": "string"
- },
- "operator": {
- "$ref": "#/definitions/Operator"
- },
- "targets": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- }
- },
- "SetOrganizationFeatureIntent": {
- "type": "object",
- "properties": {
- "name": {
- "$ref": "#/definitions/FeatureName",
- "description": "Name of the feature to set"
- },
- "value": {
- "type": "string",
- "description": "Optional value for the feature. Will override existing values if feature is already set."
- }
- },
- "required": ["name", "value"]
- },
- "SetOrganizationFeatureRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/SetOrganizationFeatureIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "SetOrganizationFeatureResult": {
- "type": "object",
- "properties": {
- "features": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Feature"
- },
- "description": "Resulting list of organization features."
- }
- },
- "required": ["features"]
- },
- "SetPaymentMethodIntent": {
- "type": "object",
- "properties": {
- "number": {
- "type": "string",
- "description": "The account number of the customer's credit card."
- },
- "cvv": {
- "type": "string",
- "description": "The verification digits of the customer's credit card."
- },
- "expiryMonth": {
- "type": "string",
- "description": "The month that the credit card expires."
- },
- "expiryYear": {
- "type": "string",
- "description": "The year that the credit card expires."
- },
- "cardHolderEmail": {
- "type": "string",
- "description": "The email that will receive invoices for the credit card."
- },
- "cardHolderName": {
- "type": "string",
- "description": "The name associated with the credit card."
- }
- },
- "required": [
- "number",
- "cvv",
- "expiryMonth",
- "expiryYear",
- "cardHolderEmail",
- "cardHolderName"
- ]
- },
- "SetPaymentMethodIntentV2": {
- "type": "object",
- "properties": {
- "paymentMethodId": {
- "type": "string",
- "description": "The id of the payment method that was created clientside."
- },
- "cardHolderEmail": {
- "type": "string",
- "description": "The email that will receive invoices for the credit card."
- },
- "cardHolderName": {
- "type": "string",
- "description": "The name associated with the credit card."
- }
- },
- "required": ["paymentMethodId", "cardHolderEmail", "cardHolderName"]
- },
- "SetPaymentMethodResult": {
- "type": "object",
- "properties": {
- "lastFour": {
- "type": "string",
- "description": "The last four digits of the credit card added."
- },
- "cardHolderName": {
- "type": "string",
- "description": "The name associated with the payment method."
- },
- "cardHolderEmail": {
- "type": "string",
- "description": "The email address associated with the payment method."
- }
- },
- "required": ["lastFour", "cardHolderName", "cardHolderEmail"]
- },
- "SignRawPayloadIntent": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- },
- "payload": {
- "type": "string",
- "description": "Raw unsigned payload to be signed."
- },
- "encoding": {
- "$ref": "#/definitions/PayloadEncoding",
- "description": "Encoding of the `payload` string. Turnkey uses this information to convert `payload` into bytes with the correct decoder (e.g. hex, utf8)."
- },
- "hashFunction": {
- "$ref": "#/definitions/HashFunction",
- "description": "Hash function to apply to payload bytes before signing. This field must be set to HASH_FUNCTION_NOT_APPLICABLE for EdDSA/ed25519 signature requests; configurable payload hashing is not supported by RFC 8032."
- }
- },
- "required": ["privateKeyId", "payload", "encoding", "hashFunction"]
- },
- "SignRawPayloadIntentV2": {
- "type": "object",
- "properties": {
- "signWith": {
- "type": "string",
- "description": "A Wallet account address, Private Key address, or Private Key identifier."
- },
- "payload": {
- "type": "string",
- "description": "Raw unsigned payload to be signed."
- },
- "encoding": {
- "$ref": "#/definitions/PayloadEncoding",
- "description": "Encoding of the `payload` string. Turnkey uses this information to convert `payload` into bytes with the correct decoder (e.g. hex, utf8)."
- },
- "hashFunction": {
- "$ref": "#/definitions/HashFunction",
- "description": "Hash function to apply to payload bytes before signing. This field must be set to HASH_FUNCTION_NOT_APPLICABLE for EdDSA/ed25519 signature requests; configurable payload hashing is not supported by RFC 8032."
- }
- },
- "required": ["signWith", "payload", "encoding", "hashFunction"]
- },
- "SignRawPayloadRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/SignRawPayloadIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "SignRawPayloadResult": {
- "type": "object",
- "properties": {
- "r": {
- "type": "string",
- "description": "Component of an ECSDA signature."
- },
- "s": {
- "type": "string",
- "description": "Component of an ECSDA signature."
- },
- "v": {
- "type": "string",
- "description": "Component of an ECSDA signature."
- }
- },
- "required": ["r", "s", "v"]
- },
- "SignRawPayloadsIntent": {
- "type": "object",
- "properties": {
- "signWith": {
- "type": "string",
- "description": "A Wallet account address, Private Key address, or Private Key identifier."
- },
- "payloads": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "An array of raw unsigned payloads to be signed."
- },
- "encoding": {
- "$ref": "#/definitions/PayloadEncoding",
- "description": "Encoding of the `payload` string. Turnkey uses this information to convert `payload` into bytes with the correct decoder (e.g. hex, utf8)."
- },
- "hashFunction": {
- "$ref": "#/definitions/HashFunction",
- "description": "Hash function to apply to payload bytes before signing. This field must be set to HASH_FUNCTION_NOT_APPLICABLE for EdDSA/ed25519 signature requests; configurable payload hashing is not supported by RFC 8032."
- }
- },
- "required": ["signWith", "payloads", "encoding", "hashFunction"]
- },
- "SignRawPayloadsRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_SIGN_RAW_PAYLOADS"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/SignRawPayloadsIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "SignRawPayloadsResult": {
- "type": "object",
- "properties": {
- "signatures": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/SignRawPayloadResult"
- }
- }
- }
- },
- "SignTransactionIntent": {
- "type": "object",
- "properties": {
- "privateKeyId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key."
- },
- "unsignedTransaction": {
- "type": "string",
- "description": "Raw unsigned transaction to be signed by a particular Private Key."
- },
- "type": {
- "$ref": "#/definitions/TransactionType"
- }
- },
- "required": ["privateKeyId", "unsignedTransaction", "type"]
- },
- "SignTransactionIntentV2": {
- "type": "object",
- "properties": {
- "signWith": {
- "type": "string",
- "description": "A Wallet account address, Private Key address, or Private Key identifier."
- },
- "unsignedTransaction": {
- "type": "string",
- "description": "Raw unsigned transaction to be signed"
- },
- "type": {
- "$ref": "#/definitions/TransactionType"
- }
- },
- "required": ["signWith", "unsignedTransaction", "type"]
- },
- "SignTransactionRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_SIGN_TRANSACTION_V2"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/SignTransactionIntentV2"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "SignTransactionResult": {
- "type": "object",
- "properties": {
- "signedTransaction": {
- "type": "string"
- }
- },
- "required": ["signedTransaction"]
- },
- "SimpleClientExtensionResults": {
- "type": "object",
- "properties": {
- "appid": {
- "type": "boolean"
- },
- "appidExclude": {
- "type": "boolean"
- },
- "credProps": {
- "$ref": "#/definitions/CredPropsAuthenticationExtensionsClientOutputs"
- }
- }
- },
- "SmsCustomizationParams": {
- "type": "object",
- "properties": {
- "template": {
- "type": "string",
- "description": "Template containing references to .OtpCode i.e Your OTP is {{.OtpCode}}"
- }
- }
- },
- "Status": {
- "type": "object",
- "properties": {
- "code": {
- "type": "integer",
- "format": "int32"
- },
- "message": {
- "type": "string"
- },
- "details": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Any"
- }
- }
- }
- },
- "TagType": {
- "type": "string",
- "enum": ["TAG_TYPE_USER", "TAG_TYPE_PRIVATE_KEY"]
- },
- "TransactionType": {
- "type": "string",
- "enum": ["TRANSACTION_TYPE_ETHEREUM", "TRANSACTION_TYPE_SOLANA"]
- },
- "UpdateAllowedOriginsIntent": {
- "type": "object",
- "properties": {
- "allowedOrigins": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "Additional origins requests are allowed from besides Turnkey origins"
- }
- },
- "required": ["allowedOrigins"]
- },
- "UpdateAllowedOriginsResult": {
- "type": "object"
- },
- "UpdatePolicyIntent": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- },
- "policyName": {
- "type": "string",
- "description": "Human-readable name for a Policy."
- },
- "policyEffect": {
- "$ref": "#/definitions/Effect",
- "description": "The instruction to DENY or ALLOW an activity (optional)."
- },
- "policyCondition": {
- "type": "string",
- "description": "The condition expression that triggers the Effect (optional)."
- },
- "policyConsensus": {
- "type": "string",
- "description": "The consensus expression that triggers the Effect (optional)."
- },
- "policyNotes": {
- "type": "string",
- "description": "Accompanying notes for a Policy (optional)."
- }
- },
- "required": ["policyId"]
- },
- "UpdatePolicyRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_POLICY"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdatePolicyIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdatePolicyResult": {
- "type": "object",
- "properties": {
- "policyId": {
- "type": "string",
- "description": "Unique identifier for a given Policy."
- }
- },
- "required": ["policyId"]
- },
- "UpdatePrivateKeyTagIntent": {
- "type": "object",
- "properties": {
- "privateKeyTagId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key Tag."
- },
- "newPrivateKeyTagName": {
- "type": "string",
- "description": "The new, human-readable name for the tag with the given ID."
- },
- "addPrivateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Keys IDs to add this tag to."
- },
- "removePrivateKeyIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of Private Key IDs to remove this tag from."
- }
- },
- "required": ["privateKeyTagId", "addPrivateKeyIds", "removePrivateKeyIds"]
- },
- "UpdatePrivateKeyTagRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_PRIVATE_KEY_TAG"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdatePrivateKeyTagIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdatePrivateKeyTagResult": {
- "type": "object",
- "properties": {
- "privateKeyTagId": {
- "type": "string",
- "description": "Unique identifier for a given Private Key Tag."
- }
- },
- "required": ["privateKeyTagId"]
- },
- "UpdateRootQuorumIntent": {
- "type": "object",
- "properties": {
- "threshold": {
- "type": "integer",
- "format": "int32",
- "description": "The threshold of unique approvals to reach quorum."
- },
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "The unique identifiers of users who comprise the quorum set."
- }
- },
- "required": ["threshold", "userIds"]
- },
- "UpdateRootQuorumRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_ROOT_QUORUM"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdateRootQuorumIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdateRootQuorumResult": {
- "type": "object"
- },
- "UpdateUserIntent": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "userTagIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "An updated list of User Tags to apply to this User. This field, if not needed, should be an empty array in your request body."
- },
- "userPhoneNumber": {
- "type": "string",
- "description": "The user's phone number in E.164 format e.g. +13214567890"
- }
- },
- "required": ["userId"]
- },
- "UpdateUserRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_USER"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdateUserIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdateUserResult": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "A User ID."
- }
- },
- "required": ["userId"]
- },
- "UpdateUserTagIntent": {
- "type": "object",
- "properties": {
- "userTagId": {
- "type": "string",
- "description": "Unique identifier for a given User Tag."
- },
- "newUserTagName": {
- "type": "string",
- "description": "The new, human-readable name for the tag with the given ID."
- },
- "addUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs to add this tag to."
- },
- "removeUserIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User IDs to remove this tag from."
- }
- },
- "required": ["userTagId", "addUserIds", "removeUserIds"]
- },
- "UpdateUserTagRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_USER_TAG"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdateUserTagIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdateUserTagResult": {
- "type": "object",
- "properties": {
- "userTagId": {
- "type": "string",
- "description": "Unique identifier for a given User Tag."
- }
- },
- "required": ["userTagId"]
- },
- "UpdateWalletIntent": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "walletName": {
- "type": "string",
- "description": "Human-readable name for a Wallet."
- }
- },
- "required": ["walletId"]
- },
- "UpdateWalletRequest": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["ACTIVITY_TYPE_UPDATE_WALLET"]
- },
- "timestampMs": {
- "type": "string",
- "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
- },
- "organizationId": {
- "type": "string",
- "description": "Unique identifier for a given Organization."
- },
- "parameters": {
- "$ref": "#/definitions/UpdateWalletIntent"
- }
- },
- "required": ["type", "timestampMs", "organizationId", "parameters"]
- },
- "UpdateWalletResult": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "A Wallet ID."
- }
- },
- "required": ["walletId"]
- },
- "User": {
- "type": "object",
- "properties": {
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "userPhoneNumber": {
- "type": "string",
- "description": "The user's phone number in E.164 format e.g. +13214567890"
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/Authenticator"
- },
- "description": "A list of Authenticator parameters."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKey"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "userTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User Tag IDs."
- },
- "oauthProviders": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/OauthProvider"
- },
- "description": "A list of Oauth Providers."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": [
- "userId",
- "userName",
- "authenticators",
- "apiKeys",
- "userTags",
- "oauthProviders",
- "createdAt",
- "updatedAt"
- ]
- },
- "UserParams": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "accessType": {
- "$ref": "#/definitions/AccessType",
- "description": "The User's permissible access method(s)."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParams"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- },
- "userTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User Tag IDs. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": [
- "userName",
- "accessType",
- "apiKeys",
- "authenticators",
- "userTags"
- ]
- },
- "UserParamsV2": {
- "type": "object",
- "properties": {
- "userName": {
- "type": "string",
- "description": "Human-readable name for a User."
- },
- "userEmail": {
- "type": "string",
- "description": "The user's email address."
- },
- "apiKeys": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/ApiKeyParams"
- },
- "description": "A list of API Key parameters. This field, if not needed, should be an empty array in your request body."
- },
- "authenticators": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/AuthenticatorParamsV2"
- },
- "description": "A list of Authenticator parameters. This field, if not needed, should be an empty array in your request body."
- },
- "userTags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of User Tag IDs. This field, if not needed, should be an empty array in your request body."
- }
- },
- "required": ["userName", "apiKeys", "authenticators", "userTags"]
- },
- "Vote": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "description": "Unique identifier for a given Vote object."
- },
- "userId": {
- "type": "string",
- "description": "Unique identifier for a given User."
- },
- "user": {
- "$ref": "#/definitions/User",
- "description": "Web and/or API user within your Organization."
- },
- "activityId": {
- "type": "string",
- "description": "Unique identifier for a given Activity object."
- },
- "selection": {
- "type": "string",
- "enum": ["VOTE_SELECTION_APPROVED", "VOTE_SELECTION_REJECTED"]
- },
- "message": {
- "type": "string",
- "description": "The raw message being signed within a Vote."
- },
- "publicKey": {
- "type": "string",
- "description": "The public component of a cryptographic key pair used to sign messages and transactions."
- },
- "signature": {
- "type": "string",
- "description": "The signature applied to a particular vote."
- },
- "scheme": {
- "type": "string",
- "description": "Method used to produce a signature."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": [
- "id",
- "userId",
- "user",
- "activityId",
- "selection",
- "message",
- "publicKey",
- "signature",
- "scheme",
- "createdAt"
- ]
- },
- "Wallet": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet."
- },
- "walletName": {
- "type": "string",
- "description": "Human-readable name for a Wallet."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "exported": {
- "type": "boolean",
- "description": "True when a given Wallet is exported, false otherwise."
- },
- "imported": {
- "type": "boolean",
- "description": "True when a given Wallet is imported, false otherwise."
- }
- },
- "required": [
- "walletId",
- "walletName",
- "createdAt",
- "updatedAt",
- "exported",
- "imported"
- ]
- },
- "WalletAccount": {
- "type": "object",
- "properties": {
- "walletAccountId": {
- "type": "string",
- "description": "Unique identifier for a given Wallet Account."
- },
- "organizationId": {
- "type": "string",
- "description": "The Organization the Account belongs to."
- },
- "walletId": {
- "type": "string",
- "description": "The Wallet the Account was derived from."
- },
- "curve": {
- "$ref": "#/definitions/Curve",
- "description": "Cryptographic curve used to generate the Account."
- },
- "pathFormat": {
- "$ref": "#/definitions/PathFormat",
- "description": "Path format used to generate the Account."
- },
- "path": {
- "type": "string",
- "description": "Path used to generate the Account."
- },
- "addressFormat": {
- "$ref": "#/definitions/AddressFormat",
- "description": "Address format used to generate the Acccount."
- },
- "address": {
- "type": "string",
- "description": "Address generated using the Wallet seed and Account parameters."
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": [
- "walletAccountId",
- "organizationId",
- "walletId",
- "curve",
- "pathFormat",
- "path",
- "addressFormat",
- "address",
- "createdAt",
- "updatedAt"
- ]
- },
- "WalletAccountParams": {
- "type": "object",
- "properties": {
- "curve": {
- "$ref": "#/definitions/Curve",
- "description": "Cryptographic curve used to generate a wallet Account."
- },
- "pathFormat": {
- "$ref": "#/definitions/PathFormat",
- "description": "Path format used to generate a wallet Account."
- },
- "path": {
- "type": "string",
- "description": "Path used to generate a wallet Account."
- },
- "addressFormat": {
- "$ref": "#/definitions/AddressFormat",
- "description": "Address format used to generate a wallet Acccount."
- }
- },
- "required": ["curve", "pathFormat", "path", "addressFormat"]
- },
- "WalletParams": {
- "type": "object",
- "properties": {
- "walletName": {
- "type": "string",
- "description": "Human-readable name for a Wallet."
- },
- "accounts": {
- "type": "array",
- "items": {
- "type": "object",
- "$ref": "#/definitions/WalletAccountParams"
- },
- "description": "A list of wallet Accounts. This field, if not needed, should be an empty array in your request body."
- },
- "mnemonicLength": {
- "type": "integer",
- "format": "int32",
- "description": "Length of mnemonic to generate the Wallet seed. Defaults to 12. Accepted values: 12, 15, 18, 21, 24."
- }
- },
- "required": ["walletName", "accounts"]
- },
- "WalletResult": {
- "type": "object",
- "properties": {
- "walletId": {
- "type": "string"
- },
- "addresses": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "A list of account addresses."
- }
- },
- "required": ["walletId", "addresses"]
- },
- "activity.v1.Address": {
- "type": "object",
- "properties": {
- "format": {
- "$ref": "#/definitions/AddressFormat"
- },
- "address": {
- "type": "string"
- }
- }
- },
- "data.v1.Address": {
- "type": "object",
- "properties": {
- "format": {
- "$ref": "#/definitions/AddressFormat"
- },
- "address": {
- "type": "string"
- }
- }
- },
- "external.data.v1.Credential": {
- "type": "object",
- "properties": {
- "publicKey": {
- "type": "string",
- "description": "The public component of a cryptographic key pair used to sign messages and transactions."
- },
- "type": {
- "$ref": "#/definitions/CredentialType"
- }
- },
- "required": ["publicKey", "type"]
- },
- "external.data.v1.Quorum": {
- "type": "object",
- "properties": {
- "threshold": {
- "type": "integer",
- "format": "int32",
- "description": "Count of unique approvals required to meet quorum."
- },
- "userIds": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "description": "Unique identifiers of quorum set members."
- }
- },
- "required": ["threshold", "userIds"]
- },
- "external.data.v1.Timestamp": {
- "type": "object",
- "properties": {
- "seconds": {
- "type": "string"
- },
- "nanos": {
- "type": "string"
- }
- },
- "required": ["seconds", "nanos"]
- },
- "v1.Tag": {
- "type": "object",
- "properties": {
- "tagId": {
- "type": "string",
- "description": "Unique identifier for a given Tag."
- },
- "tagName": {
- "type": "string",
- "description": "Human-readable name for a Tag."
- },
- "tagType": {
- "$ref": "#/definitions/TagType"
- },
- "createdAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- },
- "updatedAt": {
- "$ref": "#/definitions/external.data.v1.Timestamp"
- }
- },
- "required": ["tagId", "tagName", "tagType", "createdAt", "updatedAt"]
- }
- },
- "securityDefinitions": {
- "ApiKeyAuth": {
- "type": "apiKey",
- "name": "X-Stamp",
- "in": "header"
- },
- "AuthenticatorAuth": {
- "type": "apiKey",
- "name": "X-Stamp-WebAuthn",
- "in": "header"
- }
- },
- "security": [
- {
- "ApiKeyAuth": []
- },
- {
- "AuthenticatorAuth": []
- }
- ],
- "x-tagGroups": [
- {
- "name": "ORGANIZATIONS",
- "tags": ["Organizations", "Invitations", "Policies", "Features"]
- },
- {
- "name": "WALLETS AND PRIVATE KEYS",
- "tags": ["Wallets", "Signing", "Private Keys", "Private Key Tags"]
- },
- {
- "name": "USERS",
- "tags": ["Users", "User Tags", "User Recovery", "User Auth"]
- },
- {
- "name": "CREDENTIALS",
- "tags": ["Authenticators", "API Keys", "Sessions"]
- },
- {
- "name": "ACTIVITIES",
- "tags": ["Activities", "Consensus"]
- }
- ]
-}
diff --git a/assets/files/email_auth_steps-d4f7ca188fd3fb9a2de5557b4cf39c7b.png.png b/assets/files/email_auth_steps-d4f7ca188fd3fb9a2de5557b4cf39c7b.png.png
new file mode 100644
index 00000000..3759fad0
Binary files /dev/null and b/assets/files/email_auth_steps-d4f7ca188fd3fb9a2de5557b4cf39c7b.png.png differ
diff --git a/static/.nojekyll b/assets/files/email_recovery_steps-d43c6b808682671a8294b7f420efe2a5.png
similarity index 100%
rename from static/.nojekyll
rename to assets/files/email_recovery_steps-d43c6b808682671a8294b7f420efe2a5.png
diff --git a/assets/files/wallet_export_steps-5bb19c72eb9596fab8db3b1dcc52e60a.png.png b/assets/files/wallet_export_steps-5bb19c72eb9596fab8db3b1dcc52e60a.png.png
new file mode 100644
index 00000000..c7fd8254
Binary files /dev/null and b/assets/files/wallet_export_steps-5bb19c72eb9596fab8db3b1dcc52e60a.png.png differ
diff --git a/assets/files/wallet_import_steps-6c4753c1e726e1632ce475bc838388c2.png.png b/assets/files/wallet_import_steps-6c4753c1e726e1632ce475bc838388c2.png.png
new file mode 100644
index 00000000..3e6230c8
Binary files /dev/null and b/assets/files/wallet_import_steps-6c4753c1e726e1632ce475bc838388c2.png.png differ
diff --git a/docs/documentation/authentication/email.mdx b/authentication/email.mdx
similarity index 75%
rename from docs/documentation/authentication/email.mdx
rename to authentication/email.mdx
index 67173750..2ae5c549 100644
--- a/docs/documentation/authentication/email.mdx
+++ b/authentication/email.mdx
@@ -1,15 +1,10 @@
---
-title: Email Authentication
-sidebar_label: Email
-sidebar_position: 2
-slug: /authentication/email
+title: "Overview"
+description: "Email Authentication enables users to authenticate and recover their Turnkey accounts using email-based verification. There are two methods of email authentication:"
+sidebarTitle: "Email"
---
-# Overview
-
-Email Authentication enables users to authenticate and recover their Turnkey accounts using email-based verification. There are two methods of email authentication:
-
-** One-Time Password**
+**One-Time Password**
- Uses a 6-digit one-time password sent via email
- Simple, and familiar user experience
@@ -30,16 +25,19 @@ Email Authentication is built with expiring API keys as the foundation. The two
The authentication process happens in two steps:
-1. A 6-digit OTP code is sent to the user's verified email address
-2. Upon verification of the correct code, an API key credential is generated and encrypted for the client
+
+ A 6-digit OTP code is sent to the user's verified email address
+
+ Upon verification of the correct code, an API key credential is generated
+ and encrypted for the client
+
+
### Credential Bundle Method
The API key credential is encrypted and delivered directly through email to the user.
-In both cases, once the credential is live on the client side (within the context of an iframe), it
-is readily available to stamp (authenticate) requests. See the [enclave to end-user secure channel](../security/enclave-secure-channels.md)
-for more info on how we achieve secure delivery.
+In both cases, once the credential is live on the client side (within the context of an iframe), it is readily available to stamp (authenticate) requests. See the [enclave to end-user secure channel](/security/enclave-secure-channels) for more info on how we achieve secure delivery.
## User Experience
@@ -50,7 +48,9 @@ The flow begins with a new activity of type `ACTIVITY_TYPE_INIT_OTP_AUTH` with t
- `otpType`: specify `"OTP_TYPE_EMAIL"`
- `contact`: user's email address (must match their registered email)
- `emailCustomization`: optional parameters for customizing emails
-- `userIdentifier`: optional parameter for rate limiting (recommended for production)
+- `userIdentifier`: optional parameter for rate limiting SMS OTP requests per user.
+ We recommend generating this server-side based on the user's IP address or public key.
+ See the [OTP Rate Limits](#otp-rate-limits) section below for more details.
After receiving the 6-digit code, users complete authentication with `ACTIVITY_TYPE_OTP_AUTH`:
@@ -61,13 +61,20 @@ After receiving the 6-digit code, users complete authentication with `ACTIVITY_T
- `expirationSeconds`: optional validity window (defaults to 15 minutes)
- `invalidateExisting`: optional boolean to invalidate previous OTP Auth API keys
-
+
-
+
+
+### OTP Rate Limits
+
+In order to safeguard users, Turnkey enforces rate limits for OTP auth activities. If a `userIdentifier` parameter is provided, the following limits are enforced:
+
+- 3 requests per 3 minutes per unique `userIdentifier`
+- 3 retries max per code, after which point that code will be locked
+- 3 active codes per user, each with a 5 minute TTL
### Credential Bundle Authentication Flow
@@ -80,9 +87,9 @@ This alternative method uses `ACTIVITY_TYPE_EMAIL_AUTH` with these parameters:
- `emailCustomization`: optional parameters for customizing emails
- `invalidateExisting`: optional boolean to invalidate previous Email Auth API keys
-
-
-
+
+
+
### Recovery Flow
@@ -96,9 +103,9 @@ The recovery process consists of two phases:
1. **Initiation**: Generates a temporary recovery credential and sends it via email
2. **Finalization**: User decrypts the recovery credential and uses it to sign an `ACTIVITY_TYPE_RECOVER_USER` activity, which can add new authenticators to regain account access
-
-
-
+
+
+
## Authorization
@@ -120,23 +127,25 @@ Specifically:
### Recovery
- `ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY`:
+
- Initiates the recovery process
- Requires proper authorization via policies
- Can target any user in the organization or sub-organizations
+
- `ACTIVITY_TYPE_RECOVER_USER`:
+
- Must be signed by the recovery credential received via email
- Users can add credentials to their own user when authenticated
- Recovery credentials expire after 15 minutes
- Only the most recent recovery credential remains valid
- Users can add new authenticators to regain account access when authenticated with a recovery credential
-
+
-
+
## Implementation in Sub-organizations
@@ -151,17 +160,33 @@ Both authentication methods and recovery work seamlessly with [sub-organizations
For implementation details:
-- [Sub-Organization Email Auth Guide](/embedded-wallets/sub-organization-auth)
-- [Sub-Organization Recovery Guide](/embedded-wallets/sub-organization-recovery)
+
+
+
+
## Implementation in Organizations
For organizations accessed via dashboard:
1. Ensure the required features are enabled:
+
- `FEATURE_NAME_OTP_EMAIL_AUTH` for OTP-based authentication
- `FEATURE_NAME_EMAIL_AUTH` for credential bundle authentication
- Recovery features if needed
+
2. Users initiating the request must have appropriate permissions
## Opting Out
@@ -180,9 +205,10 @@ When creating sub-organizations, use:
- `disableEmailAuth` parameter for credential bundle authentication
- `disableEmailRecovery` parameter for recovery
-## Implementation Notes
+## Implementation Notes[](#implementation-notes "Direct link to Implementation Notes")
- Users are limited to:
+
- 10 long-lived API keys
- 10 expiring API keys (oldest are discarded when limit is reached)
@@ -198,7 +224,7 @@ When creating sub-organizations, use:
Example of enabling OTP-based Email Auth:
-```sh
+```bash
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
"timestampMs": "'"$(date +%s)"'000",
"type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
@@ -211,7 +237,7 @@ turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization
Example of enabling credential bundle Email Auth:
-```sh
+```bash
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
"timestampMs": "'"$(date +%s)"'000",
"type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
diff --git a/docs/documentation/authentication/passkeys/discoverable.md b/authentication/passkeys/discoverable-vs-non-discoverable.mdx
similarity index 80%
rename from docs/documentation/authentication/passkeys/discoverable.md
rename to authentication/passkeys/discoverable-vs-non-discoverable.mdx
index 018589da..9227e847 100644
--- a/docs/documentation/authentication/passkeys/discoverable.md
+++ b/authentication/passkeys/discoverable-vs-non-discoverable.mdx
@@ -1,19 +1,14 @@
---
-description: Learn about discoverable vs. non-discoverable credentials and how they affect UX
-slug: /authentication/passkeys/discoverable-vs-non-discoverable
-sidebar_position: 5
+title: "Discoverable vs. Non-Discoverable"
+description: 'Also known as "resident" vs. "non-resident" credentials. From [the spec](https://www.w3.org/TR/webauthn-2/)'
---
-# Discoverable vs. non-discoverable
-
-Also known as "resident" vs. "non-resident" credentials. From [the spec](https://www.w3.org/TR/webauthn-2/):
-
> Historically, client-side discoverable credentials have been known as resident credentials or resident keys. Due to the phrases ResidentKey and residentKey being widely used in both the WebAuthn API and also in the Authenticator Model (e.g., in dictionary member names, algorithm variable names, and operation parameters) the usage of resident within their names has not been changed for backwards compatibility purposes. Also, the term resident key is defined here as equivalent to a client-side discoverable credential.
What does this mean exactly?
-- "resident" credentials and "discoverable" credentials are the same
-- "non-resident" credentials and "non-discoverable" credentials are the same.
+* "resident" credentials and "discoverable" credentials are the same
+* "non-resident" credentials and "non-discoverable" credentials are the same.
The spec authors made this rename for clarity.
@@ -23,18 +18,14 @@ With terminology out of the way, what is a "discoverable" credential compared to
A discoverable credential is a self-contained key pair, stored on the end-user's device. Discoverable credentials are preferred because keys are self-contained, can easily be synced and can be used across devices independently. Crucially for UX, the end-user is able to list their passkeys and choose which device/passkey they'd like to use:
-
-
-
-
+
+
+
+
+
+
+
+
With discoverable credentials you don't have to keep track of credential IDs. Your authentication flow can simply be: "prompt the user with passkey authentication", and let the browser or device native UX handle the rest! The downside is you lose some control over these prompts, because they will vary depending on your users' OS and browser.
@@ -46,10 +37,10 @@ A non-discoverable credential isn’t stored on the end-user's device fully: Tur
Why would you choose non-discoverable credentials?
-- Most hardware security keys have limited slots to store discoverable credentials, or will refuse to create new discoverable credentials on the hardware altogether. YubiKey 5 [advertises 25 slots](https://support.yubico.com/hc/en-us/articles/4404456942738-FAQ#h_01FFHQFVBW0995G2MKZGCKQVEJ), SoloKeys [support 50](https://github.com/solokeys/solo1/issues/156#issuecomment-477645573), NitroKeys 3 [support 10](https://github.com/Nitrokey/nitrokey-3-firmware/blob/0e23c75318e2016ac1cfb8345de9279e3ad2eaf9/components/apps/src/lib.rs#L390). Non-discoverable credentials aren't subject to these limits because they work off of a single hardware secret.
-- Security keys can only allow clearing of individual slots if they support [CTAP 2.1](https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html). This is described in [this blog post](https://fy.blackhats.net.au/blog/2023-02-02-how-hype-will-turn-your-security-key-into-junk/). When security keys do not support CTAP 2.1, slots can only be freed up by resetting the hardware entirely, erasing all secrets at once.
-- Non-discoverable credentials take less space. This is important in some environments, but unlikely to be relevant if your users are storing passkeys in their Google or Apple accounts (plenty of space available there!)
-- Credential IDs have to be communicated during authentication (via the `allowCredentials` field). This allows browsers to offer better, more tailored prompts in some cases. For example: if the list contains a single authenticator with `"transports": ["AUTHENTICATOR_TRANSPORT_INTERNAL"]`, Chrome does “the right thing” by skipping the device selection popup: users go straight to the fingerprint popup, with no need to select “this device”!
+* Most hardware security keys have limited slots to store discoverable credentials, or will refuse to create new discoverable credentials on the hardware altogether. YubiKey 5 [advertises 25 slots](https://support.yubico.com/hc/en-us/articles/4404456942738-FAQ#h_01FFHQFVBW0995G2MKZGCKQVEJ), SoloKeys [support 50](https://github.com/solokeys/solo1/issues/156#issuecomment-477645573), NitroKeys 3 [support 10](https://github.com/Nitrokey/nitrokey-3-firmware/blob/0e23c75318e2016ac1cfb8345de9279e3ad2eaf9/components/apps/src/lib.rs#L390). Non-discoverable credentials aren't subject to these limits because they work off of a single hardware secret.
+* Security keys can only allow clearing of individual slots if they support [CTAP 2.1](https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html). This is described in [this blog post](https://fy.blackhats.net.au/blog/2023-02-02-how-hype-will-turn-your-security-key-into-junk/). When security keys do not support CTAP 2.1, slots can only be freed up by resetting the hardware entirely, erasing all secrets at once.
+* Non-discoverable credentials take less space. This is important in some environments, but unlikely to be relevant if your users are storing passkeys in their Google or Apple accounts (plenty of space available there!)
+* Credential IDs have to be communicated during authentication (via the `allowCredentials` field). This allows browsers to offer better, more tailored prompts in some cases. For example: if the list contains a single authenticator with `"transports": ["AUTHENTICATOR_TRANSPORT_INTERNAL"]`, Chrome does “the right thing” by skipping the device selection popup: users go straight to the fingerprint popup, with no need to select “this device”!
The downside to this is, of course, that you need to store credential IDs, and you need to make sure you can retrieve credentials for each user. This can be done with a table of credentials keyed by user email, for example. Or if you have your own authentication already, a list of credentials can be returned when the user logs in.
diff --git a/docs/documentation/authentication/passkeys/integration.md b/authentication/passkeys/integration.mdx
similarity index 63%
rename from docs/documentation/authentication/passkeys/integration.md
rename to authentication/passkeys/integration.mdx
index fc5412f2..1e830faa 100644
--- a/docs/documentation/authentication/passkeys/integration.md
+++ b/authentication/passkeys/integration.mdx
@@ -1,22 +1,15 @@
---
-description: Integration guide to use passkeys in your application
-slug: /authentication/passkeys/integration
-sidebar_position: 2
+title: "Integrating Passkeys"
---
-# Integrating passkeys
-
## Passkey flow
A typical passkey flow is composed of 4 main steps, depicted below:
-
-
-
+
+
+
+
1. Your app frontend triggers a passkey prompt.
2. Your end-user uses their device to produce a signature with their passkey, and a signed request is produced.
@@ -25,57 +18,58 @@ A typical passkey flow is composed of 4 main steps, depicted below:
This flow happens once for **registration** and for each subsequent **authentication** or signature request. The main difference is the browser APIs used to trigger the passkey prompt in step (1):
-- **Passkey registration** uses `navigator.credentials.create`(as described in [this guide](https://web.dev/passkey-registration/)). `navigator.credentials.create` triggers the creation of a **new** passkey.
-- **Passkey authentication** uses `navigator.credentials.get`. See [this guide](https://web.dev/passkey-form-autofill/) for more information. `navigator.credentials.get` triggers a signature prompt for an **existing** passkey.
+* **Passkey registration** uses `navigator.credentials.create`(as described in [this guide](https://web.dev/passkey-registration/)). `navigator.credentials.create` triggers the creation of a **new** passkey.
+* **Passkey authentication** uses `navigator.credentials.get`. See [this guide](https://web.dev/passkey-form-autofill/) for more information. `navigator.credentials.get` triggers a signature prompt for an **existing** passkey.
## Our SDK can help
Our SDK has integrated passkey functionality, and we've built examples to help you get started.
-- [`@turnkey/http`](https://www.npmjs.com/package/@turnkey/http) has a helper to trigger passkey registration (`getWebAuthnAttestation`). You can see it in action in our [`with-federated-passkeys`](https://github.com/tkhq/sdk/tree/main/examples/with-federated-passkeys) example: [direct code link](https://github.com/tkhq/sdk/blob/a2bfbf3cbd6040902bbe4c247900ac560be42925/examples/with-federated-passkeys/src/pages/index.tsx#L88)
-- [`@turnkey/webauthn-stamper`](https://www.npmjs.com/package/@turnkey/webauthn-stamper) is a passkey-compatible stamper which integrates seamlessly with `TurnkeyClient`:
+* [`@turnkey/http`](https://www.npmjs.com/package/@turnkey/http) has a helper to trigger passkey registration (`getWebAuthnAttestation`). You can see it in action in our [`with-federated-passkeys`](https://github.com/tkhq/sdk/tree/main/examples/with-federated-passkeys) example: [direct code link](https://github.com/tkhq/sdk/blob/a2bfbf3cbd6040902bbe4c247900ac560be42925/examples/with-federated-passkeys/src/pages/index.tsx#L88)
+
+* [`@turnkey/webauthn-stamper`](https://www.npmjs.com/package/@turnkey/webauthn-stamper) is a passkey-compatible stamper which integrates seamlessly with `TurnkeyClient`:
- ```js
- import { WebauthnStamper } from "@turnkey/webauthn-stamper";
- import { TurnkeyClient, createActivityPoller } from "@turnkey/http";
+```ts
+import { WebauthnStamper } from "@turnkey/webauthn-stamper";
+import { TurnkeyClient, createActivityPoller } from "@turnkey/http";
- const stamper = new WebAuthnStamper({
- rpId: "your.app.xyz",
- });
+const stamper = new WebAuthnStamper({
+ rpId: "your.app.xyz",
+});
- // New HTTP client able to sign with passkeys
- const httpClient = new TurnkeyClient(
- { baseUrl: "https://api.turnkey.com" },
- stamper
- );
+// New HTTP client able to sign with passkeys
+const httpClient = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ stamper
+);
- // This will produce a signed request that can be POSTed from anywhere.
- // The `signedRequest` has a URL, a POST body, and a "stamp" (HTTP header name and value)
- const signedRequest = await httpClient.stampCreatePrivateKeys(...)
+// This will produce a signed request that can be POSTed from anywhere.
+// The `signedRequest` has a URL, a POST body, and a "stamp" (HTTP header name and value)
+const signedRequest = await httpClient.stampCreatePrivateKeys(...)
- // Alternatively, you can POST directly from your frontend.
- // Our HTTP client will use the webauthn stamper and the configured baseUrl automatically!
- const activityPoller = createActivityPoller({
- client: client,
- requestFn: client.createPrivateKeys,
- });
+// Alternatively, you can POST directly from your frontend.
+// Our HTTP client will use the webauthn stamper and the configured baseUrl automatically!
+const activityPoller = createActivityPoller({
+ client: client,
+ requestFn: client.createPrivateKeys,
+});
- // Contains the activity result; no backend proxy needed!
- const completedActivity = await activityPoller({
- type: "ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2",
- // (omitting the rest of this for brevity)
- })
- ```
+// Contains the activity result; no backend proxy needed!
+const completedActivity = await activityPoller({
+ type: "ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2",
+ // (omitting the rest of this for brevity)
+})
+```
-- [`@turnkey/viem`](https://www.npmjs.com/package/@turnkey/viem) is a package wrapping all of the above so that you work directly with Viem without worrying about passkeys. See [this demo](https://github.com/tkhq/sdk/tree/main/examples/with-viem-and-passkeys).
+* [`@turnkey/viem`](https://www.npmjs.com/package/@turnkey/viem) is a package wrapping all of the above so that you work directly with Viem without worrying about passkeys. See [this demo](https://github.com/tkhq/sdk/tree/main/examples/with-viem-and-passkeys).
Regardless of whether you use our helpers and abstractions, take a look at [our registration and authentication options guide](/authentication/passkeys/options). This will help you choose the right options for your passkey flow.
If you have questions, feedback, or find yourself in need of an abstraction or integration that doesn't exist yet, please get in touch with us! You can
-- Create an [issue on our SDK repo](https://github.com/tkhq/sdk/issues)
-- Join our slack community [here](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw)
-- Contact us at
+* Create an [issue on our SDK repo](https://github.com/tkhq/sdk/issues)
+* Join our slack community [here](https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ)
+* Contact us at [hello@turnkey.com](mailto:hello@turnkey.com)
We're here to make this as easy as possible for you and your team!
@@ -89,6 +83,6 @@ Turnkey has an open CORS policy for its public API. This means your frontend can
How should you decide what to do? Here are some considerations:
-- A backend proxy can be useful if you need to inspect and persist activity results. For example: if your users are creating wallets, you might want to persist the addresses. If your users are signing transactions, you might want to broadcast on their behalf.
-- Another reason why a backend server could be beneficial is monitoring, feature toggles, and validation: with a proxy you're able to control which requests are proxied and which aren't. You can also perform additional validation before signed requests are forwarded to Turnkey.
-- POSTing signed requests directly from your app frontend to Turnkey saves you the burden of running a proxy server, and takes you out of the loop so that your end-users interact directly with Turnkey. This is a "hands-off" approach that can work well if you want to give your end-users maximum flexibility and ownership over their sub-organization.
+* A backend proxy can be useful if you need to inspect and persist activity results. For example: if your users are creating wallets, you might want to persist the addresses. If your users are signing transactions, you might want to broadcast on their behalf.
+* Another reason why a backend server could be beneficial is monitoring, feature toggles, and validation: with a proxy you're able to control which requests are proxied and which aren't. You can also perform additional validation before signed requests are forwarded to Turnkey.
+* POSTing signed requests directly from your app frontend to Turnkey saves you the burden of running a proxy server, and takes you out of the loop so that your end-users interact directly with Turnkey. This is a "hands-off" approach that can work well if you want to give your end-users maximum flexibility and ownership over their sub-organization.
diff --git a/docs/documentation/authentication/passkeys/introduction.md b/authentication/passkeys/introduction.mdx
similarity index 66%
rename from docs/documentation/authentication/passkeys/introduction.md
rename to authentication/passkeys/introduction.mdx
index 6f667eb1..83e3089c 100644
--- a/docs/documentation/authentication/passkeys/introduction.md
+++ b/authentication/passkeys/introduction.mdx
@@ -1,28 +1,23 @@
---
-description: Learn about passkeys at a high-level
-slug: /authentication/passkeys/introduction
-sidebar_position: 1
+title: "Introduction to Passkeys"
+description: "Passkeys are born out of a new standard being pushed by major industry players: Apple and Google."
---
-# Introduction to passkeys
-
-Passkeys are born out of a new standard being pushed by major industry players: Apple and Google.
-
-Google has a great high-level introduction to passkeys at https://developers.google.com/identity/passkeys, and Apple has its own version here: https://developer.apple.com/passkeys
+Google has a great high-level introduction to passkeys at [https://developers.google.com/identity/passkeys](https://developers.google.com/identity/passkeys), and Apple has its own version here: [https://developer.apple.com/passkeys](https://developer.apple.com/passkeys)
## TLDR: what are passkeys?
From a technical point of view, passkeys are cryptographic key pairs created on end-user devices. Apple and Google have done a great job making these key pairs usable:
-- Key generation happens in secure end-user hardware.
-- Using passkeys is easy thanks to native browser UIs and cross-device syncing.
-- Passkey recovery for users is supported natively by Apple via iCloud Keychain and Google via the Google Password Manager.
+* Key generation happens in secure end-user hardware.
+* Using passkeys is easy thanks to native browser UIs and cross-device syncing.
+* Passkey recovery for users is supported natively by Apple via iCloud Keychain and Google via the Google Password Manager.
Passkeys come with big security upgrades compared to traditional passwords:
-- Access to passkeys is gated with OS-level biometrics: faceID, touchID, lock screen patterns, and so on.
-- Passkeys are bound to the web domain that creates them. This is important to thwart phishing attacks, where an attacker hosts a similar-looking website to steal user credentials. This is doable with passwords; impossible with passkeys.
-- Because passkeys rely on public key cryptography, passkeys have two components: a public key and a private key. Private keys are never disclosed to websites or apps, making them a lot harder to steal. Only public keys are sent. To authenticate, passkeys sign messages (with their private keys) and provide signatures as proofs, similar to crypto wallets.
+* Access to passkeys is gated with OS-level biometrics: faceID, touchID, lock screen patterns, and so on.
+* Passkeys are bound to the web domain that creates them. This is important to thwart phishing attacks, where an attacker hosts a similar-looking website to steal user credentials. This is doable with passwords; impossible with passkeys.
+* Because passkeys rely on public key cryptography, passkeys have two components: a public key and a private key. Private keys are never disclosed to websites or apps, making them a lot harder to steal. Only public keys are sent. To authenticate, passkeys sign messages (with their private keys) and provide signatures as proofs, similar to crypto wallets.
## Isn't this similar to Webauthn?
@@ -34,8 +29,8 @@ The difference? Passkeys are resident credentials and they can be synced between
Synchronization and recovery are both supported natively by Apple and Google:
-- With Apple, Passkeys created on one device are synced through [iCloud Keychain](https://support.apple.com/en-us/HT204085) as long as the user is logged in with their Apple ID. Apple covers both syncing and recovery in ["About the security of passkeys"](https://support.apple.com/en-us/102195). For some additional detail, see [this Q&A with the passkey team](https://developer.apple.com/news/?id=21mnmxow). Apple's account recovery process is documented in [this support page](https://support.apple.com/en-us/HT204921).
-- With Google, [Google Password Manager](https://passwords.google/) syncs passkeys across devices seamlessly. Google has plans to support syncing more broadly across different operating systems, see [this support summary](https://developers.google.com/identity/passkeys/supported-environments#chrome-passkey-support-summary). Recovery is covered in [this FAQ ("What happens if a user loses their device?")](https://developers.google.com/identity/passkeys/faq#what_happens_if_a_user_loses_their_device): it relies on Google's overall [account recovery process](https://support.google.com/accounts/answer/7682439?hl=en) because passkeys are attached to Google accounts.
+* With Apple, Passkeys created on one device are synced through [iCloud Keychain](https://support.apple.com/en-us/HT204085) as long as the user is logged in with their Apple ID. Apple covers both syncing and recovery in ["About the security of passkeys"](https://support.apple.com/en-us/102195). For some additional detail, see [this Q\&A with the passkey team](https://developer.apple.com/news/?id=21mnmxow). Apple's account recovery process is documented in [this support page](https://support.apple.com/en-us/HT204921).
+* With Google, [Google Password Manager](https://passwords.google/) syncs passkeys across devices seamlessly. Google has plans to support syncing more broadly across different operating systems, see [this support summary](https://developers.google.com/identity/passkeys/supported-environments#chrome-passkey-support-summary). Recovery is covered in [this FAQ ("What happens if a user loses their device?")](https://developers.google.com/identity/passkeys/faq#what_happens_if_a_user_loses_their_device): it relies on Google's overall [account recovery process](https://support.google.com/accounts/answer/7682439?hl=en) because passkeys are attached to Google accounts.
## OS and browser support
@@ -47,18 +42,15 @@ Support also varies by operating system: [this matrix](https://passkeys.dev/devi
We believe **it's time to move away from passwords** so we've built Turnkey without them. When you authenticate to Turnkey you'll be prompted to create a new passkey:
-
-
-
-
+
+
+
+
+
+
+
+
+
Authentication to Turnkey requires a passkey signature. No password needed!
diff --git a/docs/documentation/authentication/passkeys/native.md b/authentication/passkeys/native.mdx
similarity index 74%
rename from docs/documentation/authentication/passkeys/native.md
rename to authentication/passkeys/native.mdx
index 74d816d2..e6b44368 100644
--- a/docs/documentation/authentication/passkeys/native.md
+++ b/authentication/passkeys/native.mdx
@@ -1,24 +1,19 @@
---
-description: See how passkeys integrate into native applications on iOS and Android
-slug: /authentication/passkeys/native
-sidebar_position: 4
+title: "Native Passkeys"
+description: "If you're unfamiliar with passkeys broadly, head to for an overview. TL;DR: passkeys are cryptographic key pairs generated and stored on secure hardware. Typically this is your Mac's or iPhone's , your Android's , or an external security key plugged in via USB."
---
-# Native passkeys
-
-If you're unfamiliar with passkeys broadly, head to [this introduction](./introduction.md) for an overview. TL;DR: passkeys are cryptographic key pairs generated and stored on secure hardware. Typically this is your Mac's or iPhone's [secure enclave](https://support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web), your Android's [Titan M2 chip](https://security.googleblog.com/2021/10/pixel-6-setting-new-standard-for-mobile.html), or an external security key plugged in via USB.
-
-- Registration ("sign up") creates a new key pair: this is your passkey
-- Authentication ("sign in") uses an existing passkey to sign a message, proving ownership of the associated private key stored on your device.
+* Registration ("sign up") creates a new key pair: this is your passkey
+* Authentication ("sign in") uses an existing passkey to sign a message, proving ownership of the associated private key stored on your device.
## Passkeys on the Web
Creating and using passkeys on the web is straightforward: browsers offer APIs to do it!
-- `navigator.credentials.create` creates a passkey
-- `navigator.credentials.get` prompts the user to select a passkey to sign a message
+* `navigator.credentials.create` creates a passkey
+* `navigator.credentials.get` prompts the user to select a passkey to sign a message
-And this doesn't require a backend. Here's a demo proving it: https://passkeyapp.tkhqlabs.xyz/
+And this doesn't require a backend. Here's a demo proving it: [https://passkeyapp.tkhqlabs.xyz/](https://passkeyapp.tkhqlabs.xyz/)
An important security feature of passkeys: they're **domain-bound** to prevent phishing. In other words: passkeys created on `passkeyapp.tkhqlabs.xyz` won't be usable on `turnkey.com` for example. Browsers prevent this.
@@ -32,8 +27,8 @@ In the Android ecosystem the `CredentialManager` supports creating and using pas
iOS APIs to create and use passkeys are available as well:
-- `ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialRegistrationRequest` for passkey creation
-- `ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialAssertionRequest` for passkey usage
+* `ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialRegistrationRequest` for passkey creation
+* `ASAuthorizationPlatformPublicKeyCredentialProvider(…).createCredentialAssertionRequest` for passkey usage
See [these docs](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider) for more info. And [this app](https://github.com/r-n-o/shiny) for a mini demo.
@@ -59,10 +54,9 @@ If you're looking for a concrete example, head to [this repository](https://gith
Passkeys on native apps aren't app-bound, they're **domain** bound just like web passkeys. This may come as a surprise: you'll have to configure a web domain to use passkeys natively! Configuration is done separately per ecosystem, but the idea is the same:
-- iOS expects a JSON file at the domain root (`/.well-known
-/apple-app-site-association`) : [example](https://github.com/r-n-o/passkeyapp/blob/main/http/.well-known/apple-app-site-association)
-- Android expects a JSON file at the domain root (`/.well-known/assetlinks.json`): [example](https://github.com/r-n-o/passkeyapp/blob/main/http/.well-known/assetlinks.json)
+* iOS expects a JSON file at the domain root (`/.well-known /apple-app-site-association`) : [example](https://github.com/r-n-o/passkeyapp/blob/main/http/.well-known/apple-app-site-association)
+* Android expects a JSON file at the domain root (`/.well-known/assetlinks.json`): [example](https://github.com/r-n-o/passkeyapp/blob/main/http/.well-known/assetlinks.json)
-This unlocks interesting flows where users use their web-created passkeys in a "companion" native app, or vice-versa. For example: a native app linked to the wallet.tx.xyz domain would allow users to log into their account from a native mobile app _using their web-created passkey_ as long as they're synced properly.
+This unlocks interesting flows where users use their web-created passkeys in a "companion" native app, or vice-versa. For example: a native app linked to the wallet.tx.xyz domain would allow users to log into their account from a native mobile app *using their web-created passkey* as long as they're synced properly.
Note that these associations are "many-to-many": a website can link multiple associated apps, and a single native application can choose to create passkeys for multiple domains, via a dropdown for example. However (as far as we know) a single passkey is always bound to a single web domain: it can't be bound to multiple web domains.
diff --git a/docs/documentation/authentication/passkeys/options.md b/authentication/passkeys/options.mdx
similarity index 65%
rename from docs/documentation/authentication/passkeys/options.md
rename to authentication/passkeys/options.mdx
index 373d20e6..5af48974 100644
--- a/docs/documentation/authentication/passkeys/options.md
+++ b/authentication/passkeys/options.mdx
@@ -1,13 +1,8 @@
---
-description: Learn about registration and authentication options
-slug: /authentication/passkeys/options
-sidebar_position: 3
+title: "Passkey Options"
+description: "Whether you use the raw browser APIs or one of our helpers you'll have flexibility to set your own registration and authentication options. This page provides an overview and some recommendations related to these options."
---
-# Passkey options
-
-Whether you use the raw browser APIs or one of our helpers you'll have flexibility to set your own registration and authentication options. This page provides an overview and some recommendations related to these options.
-
## Registration options
Mozilla has good (but lengthy) documentation on each option: [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create). Below we detail the most relevant options you'll want to think about.
@@ -20,12 +15,9 @@ This is the challenge signed by the end-user for registration. During registrati
Number of seconds before "giving up". The browser will simply show a timeout popup:
-
-
-
+
+
+
This UI isn't very helpful, so we recommend making the timeout long (5 minutes). The less your users see this, the better.
@@ -35,28 +27,21 @@ The `rp` options is an object with 2 fields: `id` and `name`.
`rp.id` (aka RPID) should be your app top-level domain. For example, if your app is hosted on `https://your.app.xyz` the RPID should be `app.xyz` unless you have good reasons to do otherwise (see below).
-
- Reasons to set RPID to specific sub-domains
-
-`rp.id`, or RPID, is a way to identify the website a passkey is associated with. Once set at registration time, it **determines the set of origins on which the passkey may be be used**. The WebAuthn spec states that the RPID must be a “registrable domain suffix of, or equal to” the current domain. If the page creating a passkey is hosted at `https://your.app.xyz`, the RPID can thus be "your.app.xyz" or "app.xyz".
-
-A passkey with RPID "your.app.xyz" cannot be used on `https://www.app.xyz` or `https://foo.app.xyz`. However a passkey created with RPID "app.xyz" will be usable on all `https://*.app.xyz` sub-domains: `https://your.app.xyz`, `https://www.app.xyz`, `https://foo.app.xyz`, and so on. Hence our general recommendation above to set `app.xyz` (top-level domain) as the RPID to maximize flexibility.
+
+ `rp.id`, or RPID, is a way to identify the website a passkey is associated with. Once set at registration time, it **determines the set of origins on which the passkey may be be used**. The [WebAuthn spec](https://www.w3.org/TR/webauthn-2/#relying-party-identifier) states that the RPID must be a “registrable domain suffix of, or equal to” the current domain. If the page creating a passkey is hosted at `https://your.app.xyz`, the RPID can thus be "your.app.xyz" or "app.xyz".
-A reason why you might want to set the RPID to "your.app.xyz" instead of "app.xyz" like recommended above is extra security: if you are worried about user passkeys being usable across all your sub-domains, it makes sense to scope passkeys to the sub-domain they're meant to be used on, and only that sub-domain.
+ A passkey with RPID "your.app.xyz" **cannot** be used on `https://www.app.xyz` or `https://foo.app.xyz`. However a passkey created with RPID "app.xyz" **will** be usable on all `https://*.app.xyz` sub-domains: `https://your.app.xyz`, `https://www.app.xyz`, `https://foo.app.xyz`, and so on. Hence our general recommendation above to set `app.xyz` (top-level domain) as the RPID to maximize flexibility.
-If you scope passkeys to a specific sub-domain, be aware that migrating your app to a different sub-domain later will require a migration process where users have to re-enroll themselves by creating new passkeys on the new sub-domain. Passkeys cannot be transferred from one RPID to another.
+ A reason why you might want to set the RPID to "your.app.xyz" instead of "app.xyz" like recommended above is extra security: if you are worried about user passkeys being usable across all your sub-domains, it makes sense to scope passkeys to the sub-domain they're meant to be used on, and only that sub-domain.
-
+ If you scope passkeys to a specific sub-domain, be aware that migrating your app to a different sub-domain later will require a migration process where users have to re-enroll themselves by creating new passkeys on the new sub-domain. Passkeys cannot be transferred from one RPID to another.
+
`rp.id` will show up in the initial registration popup:
-
-
-
+
+
+
`rp.name` doesn't show up in the popup so can be set to anything. We recommend setting it to the correctly capitalized name of your app, in case browsers start showing it in their native UIs in the future.
@@ -74,12 +59,15 @@ The integers `-7` and `-257` are algorithm identifiers for ES256 (aka P256) and
The `user` field has three sub-fields:
-- `id`: also known as "user handle", isn't visible to the end-user. We **strongly recommend setting this to a random value** (e.g. `const id = new Uint8Array(32); crypto.getRandomValues(id)`) to make sure a new passkey is created. Be aware: **if you accidentally set this value to an existing user handle, the corresponding passkey will be overridden!** [This section of spec](https://www.w3.org/TR/webauthn-2/#dictionary-user-credential-params) is clear on the matter: "the user handle ought not be a constant value across different accounts, even for non-discoverable credentials".
-- `name`: this will show up in the passkey list modal (see screenshot below). We recommend setting this to something the user will recognize: their email, the name of your app, or potentially leave this up to the user:
-
-
-
-- `displayName`: as far as we can tell this doesn't show up in current browser UIs. It might show up in future iterations so it's best to populate this with the same value as `name`.
+* `id`: also known as "user handle", isn't visible to the end-user. We **strongly recommend setting this to a random value** (e.g. `const id = new Uint8Array(32); crypto.getRandomValues(id)`) to make sure a new passkey is created. Be aware: **if you accidentally set this value to an existing user handle, the corresponding passkey will be overridden!** [This section of spec](https://www.w3.org/TR/webauthn-2/#dictionary-user-credential-params) is clear on the matter: "the user handle ought not be a constant value across different accounts, even for non-discoverable credentials".
+
+* `name`: this will show up in the passkey list modal (see screenshot below). We recommend setting this to something the user will recognize: their email, the name of your app, or potentially leave this up to the user:
+
+
+
+
+
+* `displayName`: as far as we can tell this doesn't show up in current browser UIs. It might show up in future iterations so it's best to populate this with the same value as `name`.
### `authenticatorSelection`
@@ -89,10 +77,10 @@ This option has lots of consequences for UX, and it has many sub-options, outlin
This option, if set, restricts the type of authenticators that can be registered. See the table below for the values this option can take and their effect on registration prompts (captured via Chrome on a MacBook Pro).
-| Empty (default) | `platform` | `cross-platform` |
-| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
-| If you want broad compatibility, leave this option empty, and the browser UI will allow for both internal and external passkeys. | If set to `platform`, only internal authenticators (face ID, touch ID, and so on) can be registered. | If set to `cross-platform`, only passkeys from other devices or attached via USB are allowed. |
-| | | |
+| Empty (default) | `platform` | `cross-platform` |
+| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
+| If you want broad compatibility, leave this option empty, and the browser UI will allow for both internal and external passkeys. | If set to `platform`, only internal authenticators (face ID, touch ID, and so on) can be registered. | If set to `cross-platform`, only passkeys from other devices or attached via USB are allowed. |
+| | | |
#### `requireResidentKey` and `residentKey`
@@ -104,9 +92,9 @@ Important note: the default for `requireResidentKey` (`discouraged`) results in
"User verification" refers to mechanisms on the authenticators themselves such as PIN codes or biometric/fingerprint readers. This flag can be set to:
-- `discouraged`: yubikey PINs won't be required even if the device technically supports it. We've found that for TouchID/FaceID, authentication will still be required however.
-- `preferred`: yubikey PINs and other authentication mechanisms will be required if supported, but devices without them will be accepted.
-- `required`: authenticators without user verification support won't be accepted.
+* `discouraged`: yubikey PINs won't be required even if the device technically supports it. We've found that for TouchID/FaceID, authentication will still be required however.
+* `preferred`: yubikey PINs and other authentication mechanisms will be required if supported, but devices without them will be accepted.
+* `required`: authenticators without user verification support won't be accepted.
To maximize compatibility we recommend setting `userVerification` to "discouraged" or "preferred" because some authenticators do not support user verification.
@@ -132,30 +120,23 @@ List of objects restricting which credentials can be used during authentication.
Each object in this list has an ID (the credential ID) and a list of transports (e.g. "hybrid", "internal", "usb", etc). The `transports` list is **optional** but results in better, more targeted prompts. For example, here are screenshot of targeted prompts captured on Chrome, on a MacBook laptop:
-| `transports: ["internal"]` | `transports: ["usb"]` | `transports: ["hybrid"]` |
-| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
-| | | |
+| `transports: ["internal"]` | `transports: ["usb"]` | `transports: ["hybrid"]` |
+| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
+| | | |
The credential ID needs to be passed as a buffer but is returned from registration as a base64-encoded value: make sure to decode it (in JavaScript: `Buffer.from(storedCredentialId, "base64")`) to avoid issues.
If the wrong credential ID is specified, `transports: ["internal"]` is set, browsers error right away because they can enumerate internal credentials. Chrome, for example, displays the following error:
-
-
-
+
+
+
However, if the wrong credential ID is specified without `transports` set (or with other-than-internal `transports` set), browsers won't error right away because they can't enumerate external credentials. They will display an error once the user has pressed their security key or gone through the cross-device passkey flow:
-
-
-
+
+
+
### `attestation`
diff --git a/docs/documentation/authentication/sessions.md b/authentication/sessions.mdx
similarity index 53%
rename from docs/documentation/authentication/sessions.md
rename to authentication/sessions.mdx
index 45df9b38..caf81a8e 100644
--- a/docs/documentation/authentication/sessions.md
+++ b/authentication/sessions.mdx
@@ -1,17 +1,14 @@
---
-sidebar_position: 5
-description: Learn about user sessions on Turnkey
-slug: /authentication/sessions
+title: "Sessions"
+description: "Turnkey sessions allow a user to take multiple, contiguous actions in a defined period of time."
---
-# Sessions
-
## What is a session?
-Turnkey sessions allow a user to take multiple, contiguous actions in a defined period of time. Such actions can be divided into two buckets:
+Such actions can be divided into two buckets:
-- Read operations: Retrieving data (e.g., viewing wallet balances)
-- Write operations: Modifying data or performing sensitive actions (e.g., signing transactions)
+* Read operations: Retrieving data (e.g., viewing wallet balances)
+* Write operations: Modifying data or performing sensitive actions (e.g., signing transactions)
## How can I create a session?
@@ -25,9 +22,9 @@ By default, a parent organization has read access to all of its sub-organization
#### Client side access
-Separately, if you would like the client to have all read requests encapsulated (instead of reading data via a proxy like in the previous approach), the client can initiate a read-only session via a [CreateReadOnlySession activity](https://docs.turnkey.com/api#tag/Sessions/operation/CreateReadOnlySession). This activity returns a session string that, if passed into an HTTP request via `X-Session` header, gives permission to perform reads. Note that because this is an activity performed by an end-user, it requires authentication (e.g. via passkey).
+Separately, if you would like the client to have all read requests encapsulated (instead of reading data via a proxy like in the previous approach), the client can initiate a read-only session via a [CreateReadOnlySession activity](/api-reference/sessions/create-read-only-session). This activity returns a session string that, if passed into an HTTP request via `X-Session` header, gives permission to perform reads. Note that because this is an activity performed by an end-user, it requires authentication (e.g. via passkey).
-If you’d like to do this via our SDK abstractions, you can leverage the [login](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/sdk-client.ts#L231-L255)1 method, which creates a `CreateReadOnlySession` activity under the hood. It stores the resulting session string in [Local Storage](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/sdk-client.ts#L242-L252)2, and subsequent requests to fetch data from Turnkey injects the session stored here at [call time](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/__generated__/sdk-client-base.ts#L45-L47)3 within `@turnkey/sdk-browser`.
+If you’d like to do this via our SDK abstractions, you can leverage the [login](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/sdk-client.ts#L231-L255)1 method, which creates a `CreateReadOnlySession` activity under the hood. It stores the resulting session string in [Local Storage](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/sdk-client.ts#L242-L252)2, and subsequent requests to fetch data from Turnkey injects the session stored here at [call time](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/sdk-browser/src/__generated__/sdk-client-base.ts#L45-L47)3 within `@turnkey/sdk-browser`.
### Read-write sessions
@@ -35,11 +32,11 @@ In contrast to read-only sessions, a read-write session makes sense when a user
#### Creating a read-write session
-Developers can leverage the [CreateReadWriteSession](https://docs.turnkey.com/api#tag/Sessions/operation/CreateReadWriteSession) activity, which requires a target embedded key and returns a credential bundle. This is a pattern that many core, secure flows follow, including Email Auth, and OTP Auth. See [documentation](/authentication/email) for more details.
+Developers can leverage the [CreateReadWriteSession](/api-reference/sessions/create-read-write-session) activity, which requires a target embedded key and returns a credential bundle. This is a pattern that many core, secure flows follow, including Email Auth, and OTP Auth. See [documentation](/authentication/email#mechanism-and-cryptographic-details) for more details.
-
-
-
+
+
+
As illustrated above, once you have a target embedded key in place on the client side, you can call `CreateReadWriteSession`, get the resulting credential bundle, and decrypt it on the client side using the “TEK”. Upon decryption, the result is a usable Turnkey API key that can be used to make both read and write requests.
@@ -53,17 +50,17 @@ Your next question might be: where can I get the target embedded key? There are
#### iframes:
-Turnkey hosts an iframe for use cases such as this one at https://auth.turnkey.com (see [here](https://github.com/tkhq/frames/tree/main/auth) for code implementation). It’s to be used in conjunction with our [@turnkey/iframe-stamper](https://docs.turnkey.com/sdks/advanced/iframe-stamper), but note that some complexity can be abstracted away if using @turnkey/sdk-react. See this [code example](https://docs.turnkey.com/embedded-wallets/code-examples/create-passkey-session) for more.
+Turnkey hosts an iframe for use cases such as this one at [https://auth.turnkey.com](https://auth.turnkey.com) (see [here](https://github.com/tkhq/frames/tree/main/auth) for code implementation). It’s to be used in conjunction with our [@turnkey/iframe-stamper](/sdks/advanced/iframe-stamper), but note that some complexity can be abstracted away if using @turnkey/sdk-react. See this [code example](/embedded-wallets/code-examples/create-passkey-session) for more.
The iframe is responsible for holding the “TEK” mentioned in the previous diagram. In addition to housing the TEK, it exposes some methods to be able to interact with (but not extract!) the TEK. The end result is that the iframe can safely and securely sign requests to Turnkey. If you would like to take a look at lower level implementation details, see [here](https://github.com/tkhq/sdk/blob/main/packages/iframe-stamper/src/index.ts).
There are a few considerations to note when using sessions with iframes:
-- Sessions only last as long as the iframe is maintained on the browser
-- On desktop, this generally is as long as the user doesn’t completely quit the browser
-- On mobile, specifically iOS devices, this behavior is much more finicky as iOS is aggressive in clearing out data contained within iframes
-- In either case, sessions are indeed shared across different browser tabs and windows
-- May be more difficult to set up to use in conjunction with React Native or other frameworks
+* Sessions only last as long as the iframe is maintained on the browser
+* On desktop, this generally is as long as the user doesn’t completely quit the browser
+* On mobile, specifically iOS devices, this behavior is much more finicky as iOS is aggressive in clearing out data contained within iframes
+* In either case, sessions are indeed shared across different browser tabs and windows
+* May be more difficult to set up to use in conjunction with React Native or other frameworks
#### Local Storage:
@@ -71,9 +68,9 @@ Another potential host for a TEK is directly in Local Storage. We’ve created l
Here are some considerations for using Local Storage:
-- Generally more durable than using iframes in various contexts (i.e. web on both desktop and mobile)
-- Can be used in other settings such as React Native, Flutter, etc.
-- As mentioned, the developer has complete control over the target embedded key. As a result, it’s important to manage this credential with caution.
+* Generally more durable than using iframes in various contexts (i.e. web on both desktop and mobile)
+* Can be used in other settings such as React Native, Flutter, etc.
+* As mentioned, the developer has complete control over the target embedded key. As a result, it’s important to manage this credential with caution.
For an example that leverages Local Storage with Email Auth, see [here](https://github.com/tkhq/sdk/tree/main/examples/email-auth-local-storage).
@@ -81,53 +78,48 @@ For an example that leverages Local Storage with Email Auth, see [here](https://
Another option is to create an API key and store it directly within Local Storage. However, this is a riskier setup than via TEK (as mentioned in the above Local Storage section), as anyone who is able to access this client-side API key has full access to a User.
-
-
### Sessions FAQ
-
-#### How can I refresh a session?
-
-Once a user has a valid session, it is trivial to use that session to create a new session. It is possible to reuse the same loginWithReadWriteSession abstraction, as this will create a brand new session and automatically store the resulting credential bundle in local storage.
-
-#### How can I delete a session?
-
-In order to delete a session, simply remove all user-related artifacts from Local Storage. See implementation in context [here](https://github.com/tkhq/sdk/blob/9e9943387123d077fa3b7f38ef3be007291a2c8a/packages/sdk-browser/src/sdk-client.ts#L242-L255).
-
-```javascript
-/**
- * Clears out all data pertaining to a user session.
- *
- * @returns {Promise}
- */
-logoutUser = async (): Promise => {
- await removeStorageValue(StorageKeys.AuthBundle); // DEPRECATED
- await removeStorageValue(StorageKeys.CurrentUser);
- await removeStorageValue(StorageKeys.UserSession);
- await removeStorageValue(StorageKeys.ReadWriteSession);
-
- return true;
-};
-```
-
-#### How long are sessions?
-
-The expiration of session keys can be specified to any amount of time using the `expirationSeconds` parameter. The default length is 900 seconds (15 minutes).
-
-#### How many session keys can be active at once?
-
-A user can have up to 10 expiring API keys at any given time. If you create an expiring API key that exceeds that limit, Turnkey automatically deletes one of your existing keys using the following priority:
-
-- Expired API keys are deleted first
-- If no expired keys exist, the oldest unexpired key is deleted
-
+
+
+ Once a user has a valid session, it is trivial to use that session to create a new session. It is possible to reuse the same loginWithReadWriteSession abstraction, as this will create a brand new session and automatically store the resulting credential bundle in local storage.
+
+
+ In order to delete a session, simply remove all user-related artifacts from Local Storage. See implementation in context [here](https://github.com/tkhq/sdk/blob/9e9943387123d077fa3b7f38ef3be007291a2c8a/packages/sdk-browser/src/sdk-client.ts#L242-L255).
+
+ ```ts
+ /**
+ * Clears out all data pertaining to a user session.
+ *
+ * @returns {Promise}
+ */
+ logoutUser = async (): Promise => {
+ await removeStorageValue(StorageKeys.AuthBundle); // DEPRECATED
+ await removeStorageValue(StorageKeys.CurrentUser);
+ await removeStorageValue(StorageKeys.UserSession);
+ await removeStorageValue(StorageKeys.ReadWriteSession);
+
+ return true;
+ };
+ ```
+
+
+ The expiration of session keys can be specified to any amount of time using the `expirationSeconds` parameter. The default length is 900 seconds (15 minutes).
+
+
+ A user can have up to 10 expiring API keys at any given time. If you create an expiring API key that exceeds that limit, Turnkey automatically deletes one of your existing keys using the following priority:
+
+ * Expired API keys are deleted first
+ * If no expired keys exist, the oldest unexpired key is deleted
If you are looking to invalidate existing sessions, you can use the `invalidateExisting` parameter for Email Auth and OTP Auth activities. This will clear all existing session keys.
-
-#### Can I use the same sessions implementation for web and mobile?
-
+
+
+ This is not recommended, because:
This is not recommended, because:
-- iOS Safari handles iframes differently. Specifically, it very aggressively evicts data stored within the iframe's local storage.
-- React Native doesn’t support iframes natively at all
-- Mobile browsers have different storage behaviors generally
+* iOS Safari handles iframes differently. Specifically, it very aggressively evicts data stored within the iframe's local storage.
+* React Native doesn’t support iframes natively at all
+* Mobile browsers have different storage behaviors generally
Instead, implement platform-specific session management using Local Storage for mobile.
+
+
diff --git a/docs/documentation/authentication/sms.mdx b/authentication/sms.mdx
similarity index 69%
rename from docs/documentation/authentication/sms.mdx
rename to authentication/sms.mdx
index 41668dce..5b673895 100644
--- a/docs/documentation/authentication/sms.mdx
+++ b/authentication/sms.mdx
@@ -1,14 +1,9 @@
---
-title: SMS Authentication
-sidebar_label: SMS
-sidebar_position: 4
-slug: /authentication/sms
+title: "SMS Authentication"
+description: "SMS authentication enables users to authenticate their Turnkey account using their phone number via a 6-digit one-time password (OTP). When authenticated, users receive an expiring API key stored in memory within an iframe, which functions like a session key to access their wallet."
+sidebarTitle: "SMS"
---
-# SMS Authentication
-
-SMS authentication enables users to authenticate their Turnkey account using their phone number via a 6-digit one-time password (OTP). When authenticated, users receive an expiring API key stored in memory within an iframe, which functions like a session key to access their wallet.
-
## How It Works
SMS authentication uses two activities:
@@ -24,7 +19,9 @@ To start SMS authentication, create an activity with `ACTIVITY_TYPE_INIT_OTP_AUT
- `otpType`: must be set to `"OTP_TYPE_SMS"`
- `contact`: user's phone number (must be previously approved and attached to the user's organization data)
-- `userIdentifier`: optional parameter for rate limiting SMS OTP requests per user. We recommend generating this serverside based on the user's IP address or public key.
+- `userIdentifier`: optional parameter for rate limiting SMS OTP requests per user.
+ We recommend generating this server-side based on the user's IP address or public key.
+ See the [OTP Rate Limits](#otp-rate-limits) section below for more details.
### Verifying the OTP
@@ -41,9 +38,7 @@ Once the user receives their code, use `ACTIVITY_TYPE_OTP_AUTH` with these param
SMS authentication requires proper permissions through policies or parent organization status.
-:::note
-Non-paying accounts are limited to **50 SMS messages** per month.
-:::
+Non-paying accounts are limited to **50 SMS messages** per month.
## Enabling/Disabling SMS Auth
@@ -51,7 +46,7 @@ Non-paying accounts are limited to **50 SMS messages** per month.
SMS authentication is disabled by default. Enable it using `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE`:
-```sh
+```bash
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
"timestampMs": "'"$(date +%s)"'000",
"type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
@@ -72,3 +67,11 @@ turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization
- Users are limited to 10 long-lived API keys and 10 expiring API keys
- When the expiring API key limit is reached, the oldest key is automatically discarded
+
+## OTP Rate Limits
+
+In order to safeguard users, Turnkey enforces rate limits for OTP auth activities. If a `userIdentifier` parameter is provided, the following limits are enforced:
+
+- 3 requests per 3 minutes per unique `userIdentifier`
+- 3 retries max per code, after which point that code will be locked
+- 3 active codes per user, each with a 5 minute TTL
diff --git a/docs/documentation/authentication/social-logins.mdx b/authentication/social-logins.mdx
similarity index 67%
rename from docs/documentation/authentication/social-logins.mdx
rename to authentication/social-logins.mdx
index 7bcc0b0c..5c23a36c 100644
--- a/docs/documentation/authentication/social-logins.mdx
+++ b/authentication/social-logins.mdx
@@ -1,62 +1,54 @@
---
-title: Social Logins
-sidebar_label: Social Logins
-sidebar_position: 3
-slug: /authentication/social-logins
+title: "Social Logins"
+description: "Social logins provide a familiar and convenient way for users to access applications using their existing accounts from popular platforms like Google, Apple, or Facebook. Under the hood, this functionality is powered by OAuth - a robust authentication protocol that enables secure user verification through OpenID Connect ([OIDC](https://openid.net/specs/openid-connect-core-1_0.html)) tokens. This feature is available exclusively for sub-organization users."
---
-# Social Logins
+Similar to [email auth](/authentication/email), social login authentication is ideal for users who prefer not to manage API keys or [passkeys](/authentication/passkeys/introduction) directly. This makes it particularly well-suited for onboarding users who are comfortable with traditional web2-style accounts but may be unfamiliar with cryptographic keys and credentials. An example implementing social login authentication for an organization can be found in our SDK repo [here](https://github.com/tkhq/sdk/tree/main/examples/oauth).
-Social logins provide a familiar and convenient way for users to access applications using their existing accounts from popular platforms
-like Google, Apple, or Facebook. Under the hood, this functionality is powered by OAuth - a robust authentication protocol that enables
-secure user verification through OpenID Connect ([OIDC](https://openid.net/specs/openid-connect-core-1_0.html)) tokens. This feature is
-available exclusively for sub-organization users.
+## Roles and responsibilities
-Similar to [email auth](email), social login authentication is ideal for users who prefer not to manage API keys or
-[passkeys](/authentication/passkeys/introduction) directly. This makes it particularly well-suited for onboarding users who are comfortable
-with traditional web2-style accounts but may be unfamiliar with cryptographic keys and credentials. An example implementing
-social login authentication for an organization can be found in our SDK repo [here](https://github.com/tkhq/sdk/tree/main/examples/oauth).
+* **Turnkey**: runs verifiable infrastructure to create credentials and verify OIDC tokens
-## Roles and responsibilities
+* **Parent**: that's you! **For the rest of this guide we'll assume you, the reader, are a Turnkey customer**. We assume that you have:
+
+ * an existing Turnkey organization (we'll refer to this organization as "the parent organization")
+ * a web application frontend (we'll refer to this as just "app" or "web app")
+ * a backend able to sign and POST Turnkey activities ("backend" or "parent backend")
+
+* **End-User**: the end-user is a user of your web app. They have an account with Google.
-- **Turnkey**: runs verifiable infrastructure to create credentials and verify OIDC tokens
-- **Parent**: that's you! **For the rest of this guide we'll assume you, the reader, are a Turnkey customer**. We assume that you have:
- - an existing Turnkey organization (we'll refer to this organization as "the parent organization")
- - a web application frontend (we'll refer to this as just "app" or "web app")
- - a backend able to sign and POST Turnkey activities ("backend" or "parent backend")
-- **End-User**: the end-user is a user of your web app. They have an account with Google.
-- **OIDC Provider**: a provider able to authenticate your End-Users and provide OIDC tokens as proof. We'll use [Google](https://developers.google.com/identity/openid-connect/openid-connect) as an example.
+* **OIDC Provider**: a provider able to authenticate your End-Users and provide OIDC tokens as proof. We'll use [Google](https://developers.google.com/identity/openid-connect/openid-connect) as an example.
## OAuth step-by-step
### Registration (signup)
-
-
-
+
+
+
1. **End-User** enters the signup flow on the app, gets redirected to Google for authentication.
-1. Upon completion, the **Parent**'s backend receives the OIDC token authenticating **End-User**.
-1. This token is used inside of a `CREATE_SUB_ORGANIZATION` activity to register Google as the Oauth provider under the root user.
-1. **Turnkey** verifies the OIDC token and creates a new sub-organization.
+2. Upon completion, the **Parent**'s backend receives the OIDC token authenticating **End-User**.
+3. This token is used inside of a `CREATE_SUB_ORGANIZATION` activity to register Google as the Oauth provider under the root user.
+4. **Turnkey** verifies the OIDC token and creates a new sub-organization.
The user is now registered: a sub-organization under the parent organization has been created with a a root user, authenticated via an OAuth Provider. Concretely:
-- `issuer` is set to `https://accounts.google.com`
-- `audience` is set to `` (your Oauth app ID)
-- `subject` is set `` (the user ID of `End-User` on Google's side)
+* `issuer` is set to `https://accounts.google.com`
+* `audience` is set to `` (your Oauth app ID)
+* `subject` is set `` (the user ID of `End-User` on Google's side)
### Authentication (login)
-
-
-
+
+
+
1. **End-User** enters the login flow on the app, gets redirected to Google for authentication.
-1. Upon completion, `Parent backend` receives the OIDC token authenticating **End-User**.
-1. This token is used inside of an `OAUTH` activity, signed by the **Parent**'s backend.
-1. **Turnkey** verifies the OIDC token and encrypts an expiring API key credential to **End-User**.
-1. **End-User** decrypts the credential.
+2. Upon completion, `Parent backend` receives the OIDC token authenticating **End-User**.
+3. This token is used inside of an `OAUTH` activity, signed by the **Parent**'s backend.
+4. **Turnkey** verifies the OIDC token and encrypts an expiring API key credential to **End-User**.
+5. **End-User** decrypts the credential.
The user is now authenticated and able to perform Turnkey activities.
@@ -68,17 +60,17 @@ We've designed a new secure enclave to fetch TLS content securely and bring [non
To verify an OIDC token, other Turnkey enclaves receive the OIDC token as well as:
-- the signed content of the issuer's OpenId configuration. OpenId configuration **must** be hosted under `/.well-known/openid-configuration` for each domain. For Google for example, the issuer configuration is at [`accounts.google.com/.well-known/openid-configuration`](https://accounts.google.com/.well-known/openid-configuration). This JSON document contains, among other thing, a `jwksUri` key. The value for this key is a URL hosting the list of currently-valid OIDC token signers.
-- the signed content of the issuer's `jwksUri` (e.g., for Google, the `jwksUri` is [`googleapis.com/oauth2/v3/cert`](https://www.googleapis.com/oauth2/v3/certs)). This is a list of public keys against which the secure enclave can verify tokens. Note: **these public keys rotate periodically** (every ~6hrs), hence it's not possible to hardcode these public keys in our secure enclave code directly. We have to fetch them dynamically!
+* the signed content of the issuer's OpenId configuration. OpenId configuration **must** be hosted under `/.well-known/openid-configuration` for each domain. For Google for example, the issuer configuration is at [`accounts.google.com/.well-known/openid-configuration`](https://accounts.google.com/.well-known/openid-configuration). This JSON document contains, among other thing, a `jwksUri` key. The value for this key is a URL hosting the list of currently-valid OIDC token signers.
+* the signed content of the issuer's `jwksUri` (e.g., for Google, the `jwksUri` is [`googleapis.com/oauth2/v3/cert`](https://www.googleapis.com/oauth2/v3/certs)). This is a list of public keys against which the secure enclave can verify tokens. Note: **these public keys rotate periodically** (every \~6hrs), hence it's not possible to hardcode these public keys in our secure enclave code directly. We have to fetch them dynamically!
With all of that, an enclave can independently verify an OIDC token without making outbound requests. Once the token is parsed and considered authentic, our enclaves match the `iss`, `aud` and `sub` attributes against the registered OAuth providers on the Turnkey sub-organization. We also check `exp` to make sure the OIDC token is not expired, and the `nonce` attribute (see next section).
## Nonce restrictions in OIDC tokens
-Our [`OAUTH`](/api#tag/Users/operation/Oauth) activity requires 2 parameters minimum:
+Our [`OAUTH`](/api-reference/user-auth/oauth) activity requires 2 parameters minimum:
-- `oidcToken`: the base64 OIDC token
-- `targetPublicKey`: the client-side public key generated by the user
+* `oidcToken`: the base64 OIDC token
+* `targetPublicKey`: the client-side public key generated by the user
In order to prevent OIDC tokens from being used against multiple public keys, our enclaves parse the OIDC token and, as part of the validation logic, enforce that the `nonce` claim is set to `sha256(targetPublicKey)`.
@@ -92,8 +84,8 @@ If your OAuth provider does not allow you to customize `nonce` claims, Turnkey a
[OAuth2.0](https://datatracker.ietf.org/doc/html/rfc6749) is a separate protocol from [OIDC](https://openid.net/specs/openid-connect-core-1_0.html), with distinct goals:
-- "OAuth2.0" is an authorization framework
-- "OIDC" is an authentication framework
+* "OAuth2.0" is an authorization framework
+* "OIDC" is an authentication framework
We chose to name this feature "OAuth" because of the term familiarity: most Turnkey customers will have to setup an "OAuth" app with Google, and the user experience is often referred to as "OAuth" flows regardless of the protocol underneath.
@@ -103,11 +95,11 @@ Below, some details and pointers about specific providers we've worked with befo
### Google
-This provider is extensively tested and supported. We've integrated it in our demo wallet (hosted at https://wallet.tx.xyz), along with Apple and Facebook:
+This provider is extensively tested and supported. We've integrated it in our demo wallet (hosted at [https://wallet.tx.xyz](https://wallet.tx.xyz)), along with Apple and Facebook:
-
-
-
+
+
+
The code is open-source, feel free to [check it out](https://github.com/tkhq/demo-embedded-wallet) for reference. The exact line where the OAuth component is loaded is here: [ui/src/screens/LandingScreen.tsx](https://github.com/tkhq/demo-embedded-wallet/blob/d4ec308e9ce0bf0da7b64da2b39e1a80c077eb82/ui/src/screens/LandingScreen.tsx#L384).
@@ -115,7 +107,7 @@ The main documentation for Google OIDC is available [here](https://github.com/tk
### Apple
-Apple integration is also extensively tested and supported, and is integrated into our demo wallet (hosted at https://wallet.tx.xyz). The code provides an [example component](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/components/apple-auth.tsx) as well as an [example redirect handler]().
+Apple integration is also extensively tested and supported, and is integrated into our demo wallet (hosted at [https://wallet.tx.xyz](https://wallet.tx.xyz)). The code provides an [example component](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/components/apple-auth.tsx) as well as an [example redirect handler](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/app/\(landing\)/oauth-callback/apple/page.tsx).
Documentation for Apple OIDC can be found [here](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple).
@@ -123,17 +115,17 @@ Documentation for Apple OIDC can be found [here](https://developer.apple.com/doc
Facebook OIDC requires a [manual flow with PFKE](https://developers.facebook.com/docs/facebook-login/guides/advanced/oidc-token/) (Proof for Key Exchange). This flow requires a few extra steps compared with Apple or Google. Specifically:
-- You will need to generate a **code verifier** that can either be recalled (e.g. from a database) or reassembled in a later request.
-- You will need to provide a **code challenge** as a parameter of the OAuth redirect that is either the code verifier itself or the hash of the code verifier.
-- Instead of receiving the OIDC token after the OAuth flow, you will receive an **auth code** that must be exchanged for an OIDC token in a subsequent request. The code verifier and your app's ID are also required in this exchange.
+* You will need to generate a **code verifier** that can either be recalled (e.g. from a database) or reassembled in a later request.
+* You will need to provide a **code challenge** as a parameter of the OAuth redirect that is either the code verifier itself or the hash of the code verifier.
+* Instead of receiving the OIDC token after the OAuth flow, you will receive an **auth code** that must be exchanged for an OIDC token in a subsequent request. The code verifier and your app's ID are also required in this exchange.
In our example demo wallet, we opt to avoid using a database in the authentication process and instead generate our verification code serverside using the hash of a nonce and a secret salt value. The nonce is then passed to and returned from the Facebook API as a **state** parameter (see [the API spec](https://developers.facebook.com/docs/facebook-login/guides/advanced/oidc-token/) for details). Finally, the server reconstructs the verification code by re-hashing the nonce and the the salt. The full flow is displayed below:
-
-
-
+
+
+
-Code for the [redirect component](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/components/facebook-auth.tsx), [OAuth callback](), and [code exchange](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/actions/turnkey.ts#L54) are all available in the example wallet repo.
+Code for the [redirect component](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/components/facebook-auth.tsx), [OAuth callback](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/app/\(landing\)/oauth-callback/facebook/page.tsx), and [code exchange](https://github.com/tkhq/demo-embedded-wallet/blob/bf0e2292cbd2ee9cde6b241591b077fadf7ee71b/src/actions/turnkey.ts#L54) are all available in the example wallet repo.
If you prefer to use a database such as Redis instead of reassembling the verification code, you can store the verification code and retrieve it in the exchange stage using a lookup key either passed as **state** or stored in local browser storage.
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index bfd75dbd..00000000
--- a/babel.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
-};
diff --git a/category/advanced.mdx b/category/advanced.mdx
new file mode 100644
index 00000000..02494552
--- /dev/null
+++ b/category/advanced.mdx
@@ -0,0 +1,24 @@
+---
+title: "Advanced"
+description: "Use Turnkey's low-level http libraries directly"
+sidebarTitle: "Overview"
+---
+
+
+
+ Detailed guide on installing and initializing the TurnkeyClient
+
+
+ Guide on using the ApiKeyStamper
+
+
+
+ Guide on using the WalletStamper
+
+
+ Guide on using the WebauthnStamper
+
+
+ Guide on using the IframeStamper
+
+
diff --git a/category/code-examples-1.mdx b/category/code-examples-1.mdx
new file mode 100644
index 00000000..6d47205b
--- /dev/null
+++ b/category/code-examples-1.mdx
@@ -0,0 +1,11 @@
+---
+title: "Code Examples"
+sidebarTitle: "Overview"
+mode: wide
+---
+
+
+
+ Signing Transactions
+
+
diff --git a/category/code-examples.mdx b/category/code-examples.mdx
new file mode 100644
index 00000000..722a6806
--- /dev/null
+++ b/category/code-examples.mdx
@@ -0,0 +1,44 @@
+---
+title: "Code Examples"
+sidebarTitle: "Overview"
+mode: wide
+---
+
+
+
+ Create a Sub-Org with a Passkey User
+
+
+
+ Authenticate a User with a Passkey Credential
+
+
+
+ Create a User Passkey Session
+
+
+
+ Create a User with Email Only
+
+
+ Authenticate a User with Email
+
+
+ Recover a User with Email
+
+
+ Add an Additional Passkey
+
+
+ Authenticate a User with an Ethereum Wallet
+
+
+ Signing Transactions
+
+
+ Import Wallet or Private Key
+
+
+ Export Wallet or Private Key
+
+
diff --git a/category/security.mdx b/category/security.mdx
new file mode 100644
index 00000000..23a18ace
--- /dev/null
+++ b/category/security.mdx
@@ -0,0 +1,43 @@
+---
+title: Security
+sidebarTitle: Overview
+mode: wide
+description: "Learn how Turnkey achieves innovative, cloud scale, no single point of failure security."
+---
+
+
+
+ Learn about Turnkey's unique security framework
+
+
+
+ Learn how Turnkey handles private keys
+
+
+
+ Overview of secure enclaves and how we use them
+
+
+
+ Learn how we deploy our secure applications
+
+
+
+ Learn how we ensure an end-to-end audit trail
+
+
+ Turnkey's disaster recovery process
+
+
+
+ Learn about Turnkey's enclave to end-user secure channels
+
+
+
+ Read about Turnkey's ambitious foundations with the Turnkey Whitepaper
+
+
+
+ Overview of Turnkey's responsible disclosure program
+
+
diff --git a/category/web3-libraries.mdx b/category/web3-libraries.mdx
new file mode 100644
index 00000000..9cb4a9ba
--- /dev/null
+++ b/category/web3-libraries.mdx
@@ -0,0 +1,28 @@
+---
+title: Web3 Libraries
+description: "Turnkey Web3 Libraries"
+mode: wide
+sidebarTitle: "Overview"
+---
+
+
+
+ Ethers Wrapper
+
+
+
+ Viem Wrapper
+
+
+
+ CosmJS Wrapper
+
+
+
+ EIP 1193 Provider
+
+
+
+ Solana Web3 Wrapper
+
+
diff --git a/concepts/organizations.mdx b/concepts/organizations.mdx
new file mode 100644
index 00000000..69211320
--- /dev/null
+++ b/concepts/organizations.mdx
@@ -0,0 +1,54 @@
+---
+title: "Organizations"
+description: "An organization is a logical grouping of resources (e.g. users, policies, wallets). These resources can only be accessed by authorized and permissioned users within the organization. Resources are not shared between organizations."
+---
+
+## Root Quorum
+
+All organizations are controlled by a [Root Quorum](/concepts/users/root-quorum) which contains the root users and the required threshold of approvals to take any action. Only the root quorum can update the root quorum or feature set.
+
+## Features
+
+Organization features are Turnkey product offerings that organizations can opt-in to or opt-out of. Note that these features can be set and updated using the activities `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE` and `ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE`. The following is a list of such features:
+
+| Name | Description | Default | Notes |
+| -------------------------------- | --------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| FEATURE\_NAME\_EMAIL\_AUTH | Enables email authentication activities | Enabled | Can only be initiated by a parent organization for a sub-organization. |
+| FEATURE\_NAME\_EMAIL\_RECOVERY | Enables email recovery activities | Enabled | Can only be initiated by a parent organization for a sub-organization. |
+| FEATURE\_NAME\_WEBAUTHN\_ORIGINS | The origin Webauthn credentials are scoped to | Disabled | Parent organization feature applies to all sub-organizations. If not enabled, sub-organizations default to allowing all origins: "\*". For Passkey WaaS, we highly recommend enabling this feature. Example value: "[https://www.turnkey.com"](https://www.turnkey.com%22) |
+| FEATURE\_NAME\_WEBHOOK | A URL to receive activity notification events | Disabled | This feature is currently experimental. Example value: "[https://your.service.com/webhook"](https://your.service.com/webhook%22) |
+
+## Permissions
+
+All activity requests are subject to enforcement by Turnkey's policy engine. The policy engine determines if a request is allowed by checking the following:
+
+* Does this request violate our feature set?
+
+ * Email recovery cannot be initiated if disabled
+ * Email auth cannot be initiated if disabled
+
+* Should this request be denied by default?
+ * All import requests must target your own user
+
+* Does this request meet the root quorum threshold?
+
+* What is the outcome of evaluating this request against all organization policies? Outcomes include:
+
+ * `OUTCOME_ALLOW`: the request is allowed to process
+ * `OUTCOME_REQUIRES_CONSENSUS`: the request needs additional approvals
+ * `OUTCOME_REJECTED`: the request should be rejected
+ * `OUTCOME_DENY_EXPLICIT`: the request has been explicitly denied via policies
+ * `OUTCOME_DENY_IMPLICIT`: the request has been implicity denied as no policies grant the required permissions
+
+* Should this request be allowed by default?
+ * Users can manage their own credentials unless policies explicitly deny this
+
+## Resource Limits
+
+Organizations have [resource limits](/concepts/resource-limits) for performance and security considerations. If you're bumping into these limits, check out sub-organizations below.
+
+## Sub-Organizations
+
+A sub-organization is an isolated organization that has a pointer to a parent organization. The parent organization has **read** access to all sub-organizations, but no **write** access. This means users within the parent organization have no ability to use wallets or alter any resources in the sub-organization.
+
+For more information on sub-organizations and common use cases for this functionality, follow along in the next section .
diff --git a/docs/documentation/concepts/Introduction.md b/concepts/overview.mdx
similarity index 79%
rename from docs/documentation/concepts/Introduction.md
rename to concepts/overview.mdx
index 87d473fc..fa1eed35 100644
--- a/docs/documentation/concepts/Introduction.md
+++ b/concepts/overview.mdx
@@ -1,31 +1,19 @@
---
-sidebar_position: 1
-sidebar_label: Overview
-description: Understand Turnkey's core features and fundamentals.
-slug: /concepts/overview
+title: "Overview"
+description: "Turnkey is flexible, scalable, and secure wallet infrastructure that can be used for transaction automation (e.g., payments flows, smart contract management), or non-custodial embedded wallets. Turnkey offers low-level primitives that can be combined to accomplish a variety of goals."
---
-import DocCardList from '@theme/DocCardList';
-
-# Overview
-
-Turnkey is flexible, scalable, and secure wallet infrastructure that can be used for transaction automation (e.g., payments flows, smart contract management), or non-custodial embedded wallets. Turnkey offers low-level primitives that can be combined to accomplish a variety of goals.
-
When you sign up to Turnkey, you create an **[organization](/concepts/organizations)**, which is a segregated collection of users (including root users), wallets, and policies that are controlled by your business. This top level organization that is initially created, often referred to as your parent organization, is generally meant to represent an entire Turnkey-powered application. **[Users](/concepts/users/introduction)** can access Turnkey via their **[credentials](/concepts/users/credentials)** (e.g., API key, passkey). There are two primary ways for users to interact with Turnkey -- via the [Turnkey Dashboard](https://app.turnkey.com/dashboard), and by submitting activity requests via our public API. The Turnkey Dashboard, which is where you'll first create your Turnkey parent organization, is where root users of your parent organization will typically manage administrative activities. It supports passkey authentication only. On the other hand, interactions with Turnkey at scale (primarily, interactions initiated by end users) can be done via programmatically calling the Turnkey public API and submitting activity requests, with a variety of authentication methods supported. Users can submit **[activities](/developer-reference/api-overview/submissions)** (e.g. sign transaction, create user) based on the permissions granted by **[policies](/concepts/policies/overview)**. Root users are a special type of user that can bypass our policy engine and take any action if the threshold of **[root quorum](/concepts/users/root-quorum)** is met. Finally, **[wallets](/concepts/wallets)** are HD seed phrases that can derive many wallet accounts (i.e., individual addresses) which are used for signing operations.
Parent organizations can create **[sub-organizations](/concepts/sub-organizations)**, a segregated set of users, policies, and wallets to which the parent has read access, but not write access. These sub-organizations typically map to an end user in an embedded wallet setup, but can be used wherever you may need full segregation of Turnkey resources.
-
-
-
+
+
+
-# Concepts dictionary
+## Concepts dictionary
-## Organizations
+### Organizations
An organization is a logical grouping of resources like users, policies, and wallets. There are two types of organizations:
@@ -34,44 +22,44 @@ An organization is a logical grouping of resources like users, policies, and wal
| Parent Organization | When you first setup your implementation of Turnkey by signing up on the dashboard you create a parent organization controlled by your business. In most implementations, a top-level organization represents an entire Turnkey-powered implementation. For more information on Turnkey parent organizations [look here](/concepts/organizations). |
| Sub-Organization | A fully segregated organization nested under the parent organization. Parent organizations have read access to all their sub-organizations, but do not have write access. Each sub-organization typically maps to an individual end user in a Turnkey-powered application. Parent organizations can initiate limited actions for sub-organizations that then must be completed by the sub-organization, or without the need for completion by the sub-organization (e.g. `INIT_OTP_AUTH` or `INIT_USER_EMAIL_RECOVERY` require completion by sub-organization, `EMAIL_AUTH` does not). For more information on Turnkey sub-organizations [look here](/concepts/sub-organizations). |
-## Users
+### Users
-Turnkey users are resources within organizations or sub-organizations that can submit activities to Turnkey via a valid credential (e.g., API key, passkey). These requests can be made either by making direct API calls or through the Turnkey Dashboard. Users must be set up to authenticate to Turnkey with credentials (API keys, passkeys), or via other authentication methods such as OAuth, or email auth, with upper limits on credentials defined here in our [resource limits](/concepts/resource-limits). Users can also have associated "tags" which are logical groupings that can be referenced in policies. Users can only submit activities within their given organization — they cannot take action across organizations.
+Turnkey users are resources within organizations or sub-organizations that can submit activities to Turnkey via a valid credential (e.g., API key, passkey). These requests can be made either by making direct API calls or through the Turnkey Dashboard. Users must be set up to authenticate to Turnkey with credentials (API keys, passkeys), or via other authentication methods such as OAuth, or email auth, with upper limits on credentials defined here in our [resource limits](/concepts/resource-limits). Users can also have associated “tags” which are logical groupings that can be referenced in policies. Users can only submit activities within their given organization — they cannot take action across organizations.
There are two main types of users:
| User type | Description |
| :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Root Users | The first user(s) created in an organization will have root permissions, meaning they can bypass the policy engine to take any action within that specific organization. This ability can be limited via root quorum, which requires a threshold of root users to access root permissions. For example, if there are five root users and the threshold is three, at least three users must approve an activity for the root quorum threshold to be reached. When you first create a Turnkey organization, your user is automatically created as the sole member of the root quorum by default. |
-| Normal Users | Other than managing their own credentials, non-root users have no permissions unless explicitly granted by [policies](/concepts/policies/overview). By combining non-root users with policies granting permission for specific actions, you can build support for experiences providing [delegated access](/concepts/policies/delegated-access) to business controlled service account. |
+| Normal Users | Other than managing their own credentials, non-root users have no permissions unless explicitly granted by [policies](/concepts/policies/overview). By combining non-root users with policies granting permission for specific actions, you can build support for experiences providing [delegated access](/concepts/policies/delegated-access) to business controlled service account. |
In parent organizations, a user often maps to an individual from your team with administrative privileges and responsibilities. In sub-organizations, which are often used to manage an end user's resources, a user can represent an end user and their credentials. If there is only one user representing the end user with only end-user controlled credentials then this would be more akin to a standard non-custodial setup. However, this flexible primitive can often represent other aspects of your backend or application. For example, a Turnkey user might map to a:
-- Backend service used to automate certain transactions
-- Service with delegated access to take action on behalf of an end user
-- Required co-signer for all end user transactions
+* Backend service used to automate certain transactions
+* Service with delegated access to take action on behalf of an end user
+* Required co-signer for all end user transactions
For more information on Turnkey users [look here](/concepts/users/introduction).
-## Credentials
+### Credentials
Interacting with the Turnkey API requires each API call to be authenticated by cryptographically stamping it with a credential. This process is abstracted away in our SDKs and ensures that the request cannot be tampered with as it travels to the secure enclave. Credentials include API keys and passkeys / Webauthn devices for all Users, while sub-organization users can also use email or OAuth to authenticate. Email and OAuth leverage API keys under the hood.
For more information on Turnkey user credentials [look here](/concepts/users/credentials).
-## Activities
+### Activities
-Activities are specific actions taken by users, such as signing a transaction, adding a new user, or creating a sub-organization. Activity requests are always evaluated through our policy engine, and can evaluate to ALLOW, DENY, or REQUIRES_CONSENSUS (i.e., requires additional approvals before being allowed).
+Activities are specific actions taken by users, such as signing a transaction, adding a new user, or creating a sub-organization. Activity requests are always evaluated through our policy engine, and can evaluate to ALLOW, DENY, or REQUIRES\_CONSENSUS (i.e., requires additional approvals before being allowed).
For more information on Turnkey activities [look here](/developer-reference/api-overview/submissions).
-## Policies
+### Policies
-Policies, enforced by Turnkey's policy engine, grant users permissions to perform activities. These policies are a series of logical statements (e.g., User ID == 123 or ETH address == 0x543…9b34) that evaluate to either "ALLOW" or "DENY." Through these policies you can set granular controls on which users can take which actions with which wallets. Policies can also require multi-party approval / consensus, meaning a threshold of certain users will be required to approve the activity. As mentioned above, the root quorum will bypass the policy engine.
+Policies, enforced by Turnkey’s policy engine, grant users permissions to perform activities. These policies are a series of logical statements (e.g., User ID == 123 or ETH address == 0x543…9b34) that evaluate to either “ALLOW” or “DENY.” Through these policies you can set granular controls on which users can take which actions with which wallets. Policies can also require multi-party approval / consensus, meaning a threshold of certain users will be required to approve the activity. As mentioned above, the root quorum will bypass the policy engine.
For more information on Turnkey policies [look here](/concepts/policies/overview).
-## Wallets and Private Keys
+### Wallets and Private Keys
Resources used to generate crypto addresses and sign transactions or messages. We currently support secp256k1 and ed25519 curves and have two main types:
@@ -82,45 +70,65 @@ Resources used to generate crypto addresses and sign transactions or messages. W
Learn more about leveraging Wallets across different crypto ecosystems on our [Ecosystem Support](/ecosystems/framework) page.
-# Typical implementations
+## Typical implementations
-## Transaction Automation
+### Transaction Automation
Transaction automation entails a business signing transactions on its own behalf. For example, automating payments flows, managing smart contract deployment or programmatically trading in DeFi.
-
-
-
+
+
+
In this setup, the business is in full control of its wallets at all times. This use case typically does not require the use of sub-organizations and everything can be managed from the parent organization. We suggest the following setup:
-- **Root Users:** After initial setup of your parent organization, set a reasonable root quorum (e.g., 2 of 3), attach backup credentials to each user for safekeeping, and only use the root users in a "break glass" scenario.
-- **Service Users:** Create different users for separate services and/or approval workflows. For example, you might have user A that can automatically sign any transaction with Wallet X, but require both user A and user B to approve transactions with Wallet B.
-- **Service Policies:** Set appropriately restrictive policies based on your security needs.
-- **Wallets:** Create separate wallets where differentiated policies are needed, otherwise just leverage multiple wallet accounts within a single wallet.
+* **Root Users:** After initial setup of your parent organization, set a reasonable root quorum (e.g., 2 of 3), attach backup credentials to each user for safekeeping, and only use the root users in a “break glass” scenario.
+* **Service Users:** Create different users for separate services and/or approval workflows. For example, you might have user A that can automatically sign any transaction with Wallet X, but require both user A and user B to approve transactions with Wallet B.
+* **Service Policies:** Set appropriately restrictive policies based on your security needs.
+* **Wallets:** Create separate wallets where differentiated policies are needed, otherwise just leverage multiple wallet accounts within a single wallet.
-## Embedded Wallets
+### Embedded Wallets
Embedded wallets entail a business creating non-custodial wallets controlled by its end users. For example, allowing an end user to create and use a wallet via Web2 authentication methods like email or OAuth.
-
-
-
+
+
+
This is a non-custodial setup where the end user is in control of its wallet at all times. This use case requires the use of sub-organizations which map to an individual end user, and does not require any wallets in the parent organization. The parent organization will be used by your backend service for onboarding new users and initiating certain authentication methods (e.g., email, SMS), while the sub-organizations will be used by the end users for day-to-day signing. We suggest the following setup:
-- **Root Users:** After initial setup of your parent organization, set a reasonable root quorum (e.g., 2 of 3), attach backup credentials to each user for safekeeping, and only use the root users in a "break glass" scenario.
-- **Normal Users:** Create a single service user used for user onboarding and authentication.
-- **Policies:** Set a policy granting the user permission to `CREATE_SUB_ORGANIZATION`, `EMAIL_AUTH`, `OAUTH`, `OTP_AUTH`. For examples of how to create such policies [look here](/concepts/policies/examples).
-- **Sub-organizations:** Create individual sub-organizations for each user that contain a single root user with any relevant credentials, and a single wallet with any relevant wallet accounts.
+* **Root Users:** After initial setup of your parent organization, set a reasonable root quorum (e.g., 2 of 3), attach backup credentials to each user for safekeeping, and only use the root users in a “break glass” scenario.
+* **Normal Users:** Create a single service user used for user onboarding and authentication.
+* **Policies:** Set a policy granting the user permission to `CREATE_SUB_ORGANIZATION`, `EMAIL_AUTH`, `OAUTH`, `OTP_AUTH`. For examples of how to create such policies [look here](/concepts/policies/examples).
+* **Sub-organizations:** Create individual sub-organizations for each user that contain a single root user with any relevant credentials, and a single wallet with any relevant wallet accounts.
For more details on each individual concepts, refer to the pages below:
-
+
+
+
+ Understand Turnkey's core features and fundamentals.
+
+
+
+ Learn about Organizations on Turnkey
+
+
+ Learn about sub-organizations on Turnkey
+
+
+
+ 3 items
+
+
+
+ Learn about Wallets on Turnkey
+
+
+
+ Organization resource limits
+
+
+
+ 5 items
+
+
diff --git a/docs/documentation/concepts/policy-management/delegated-access.md b/concepts/policies/delegated-access.mdx
similarity index 68%
rename from docs/documentation/concepts/policy-management/delegated-access.md
rename to concepts/policies/delegated-access.mdx
index 608b2b53..712ca769 100644
--- a/docs/documentation/concepts/policy-management/delegated-access.md
+++ b/concepts/policies/delegated-access.mdx
@@ -1,23 +1,18 @@
---
-sidebar_position: 4
-description: Learn about setting up delegated access to user wallets on Turnkey.
-slug: /concepts/policies/delegated-access
-sidebar_label: Delegated Access
+title: "Delegated Access"
+description: "With Turnkey you can create multi-user accounts with flexible co-ownership controls. This primitive enables you to establish delegated access to a user’s wallet, reducing or removing the need for them to manually approve each action. You can provide a smoother user experience while ensuring that end-users maintain full control over their wallets."
---
-# Delegated access
-
-With Turnkey you can create multi-user accounts with flexible co-ownership controls. This primitive enables you to establish delegated access to a user’s wallet, reducing or removing the need for them to manually approve each action. You can provide a smoother user experience while ensuring that end-users maintain full control over their wallets.
-
To set up delegated access, here’s one example of how you can create a limited-permissions user within the end-user’s sub-organization:
-### Step 1: Create a sub-organization with two root users
+### Step 1: Create a sub-organization with two root users[](#step-1-create-a-sub-organization-with-two-root-users "Direct link to Step 1: Create a sub-organization with two root users")
+
+* Create your sub-organization with the two root users being:
-- Create your sub-organization with the two root users being:
- - The end-user
- - A user you control (let’s call it the ‘Service Account’)
+ * The end-user
+ * A user you control (let’s call it the ‘Service Account’)
-```sh
+```json
{
"type": "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7",
"timestampMs": "",
@@ -41,7 +36,7 @@ To set up delegated access, here’s one example of how you can create a limited
}
],
"apiKeys": [],
- "oidcProviders": []
+ "oidcProviders": []
},
{
"userName": "Service Account",
@@ -53,7 +48,7 @@ To set up delegated access, here’s one example of how you can create a limited
"publicKey": ""
}
],
- "oidcProviders": []
+ "oidcProviders": []
}
],
"rootQuorumThreshold": 1,
@@ -74,16 +69,18 @@ To set up delegated access, here’s one example of how you can create a limited
### Step 2: Create a new Delegated Access user using the Service Account
-- Create a new user, the ‘Delegated Account’
-- Create a custom policy granting the Delegated Account specific permissions. You might grant that user permissions to:
- - Sign any transaction
- - Sign only transactions to a specific address
- - Create new users in the sub-org
- - Or any other activity you want to be able to take using your Delegated Account
+* Create a new user, the ‘Delegated Account’
+
+* Create a custom policy granting the Delegated Account specific permissions. You might grant that user permissions to:
+
+ * Sign any transaction
+ * Sign only transactions to a specific address
+ * Create new users in the sub-org
+ * Or any other activity you want to be able to take using your Delegated Account
Here’s one example, creating a Delegated Account that only has permission to sign transactions to a specific receiver address:
-```sh
+```json
// 1. Create the Delegated Account
{
"type": "ACTIVITY_TYPE_CREATE_USER",
@@ -118,9 +115,10 @@ Here’s one example, creating a Delegated Account that only has permission to s
### Step 3: Remove the Service Account from the root quorum.
-- Using the Service Account:
- - Create a new policy that explicitly grants the Service Account permission to delete users. This is necessary because, once removed from the Root Quorum, the Service Account will have no permissions by default.
- - [Update the root quorum](https://docs.turnkey.com/api#tag/Organizations/operation/UpdateRootQuorum) to remove the Service Account from the root quorum
- - Delete the Service Account user from the organization
+* Using the Service Account:
+
+ * Create a new policy that explicitly grants the Service Account permission to delete users. This is necessary because, once removed from the Root Quorum, the Service Account will have no permissions by default.
+ * [Update the root quorum](/api-reference/organizations/update-root-quorum) to remove the Service Account from the root quorum
+ * Delete the Service Account user from the organization
After completing these steps, the sub-organization will have two users: the end-user (the only root-user) and the Delegated Account user, which has the permissions granted earlier.
diff --git a/docs/documentation/concepts/policy-management/Policy-examples.mdx b/concepts/policies/examples.mdx
similarity index 75%
rename from docs/documentation/concepts/policy-management/Policy-examples.mdx
rename to concepts/policies/examples.mdx
index de073bc7..119451f1 100644
--- a/docs/documentation/concepts/policy-management/Policy-examples.mdx
+++ b/concepts/policies/examples.mdx
@@ -1,17 +1,13 @@
---
-sidebar_position: 3
-description: Check out some example policies to help write your own
-slug: /concepts/policies/examples
-sidebar_label: Examples
+title: "Policy examples"
+sidebarTitle: "Examples"
---
-# Policy examples
-
## Access control
#### Allow a specific user to create wallets
-```json JSON
+```json
{
"policyName": "Allow user to create wallets",
"effect": "EFFECT_ALLOW",
@@ -22,7 +18,7 @@ sidebar_label: Examples
#### Allow users with a specific tag to create users
-```json JSON
+```json
{
"policyName": "Allow user_tag to create users",
"effect": "EFFECT_ALLOW",
@@ -33,7 +29,7 @@ sidebar_label: Examples
#### Require two users with a specific tag to add policies
-```json JSON
+```json
{
"policyName": "Require two users with user_tag to create policies",
"effect": "EFFECT_ALLOW",
@@ -44,7 +40,7 @@ sidebar_label: Examples
#### Deny all delete actions for users with a specific tag
-```json JSON
+```json
{
"policyName": "Only user_tag can take actions",
"effect": "EFFECT_DENY",
@@ -55,7 +51,7 @@ sidebar_label: Examples
#### Allow a specific user (e.g. API-only user) to create a sub-org
-```json JSON
+```json
{
"policyName": "Allow user to create a sub-org",
"effect": "EFFECT_ALLOW",
@@ -64,10 +60,11 @@ sidebar_label: Examples
}
```
-#### Allow a specific user to perform auth type activities (full list [here](Policy-language.md#activity-breakdown))
-Note: The activity.resource portion determines which activities can be performed. The activity.action determines what types of actions can be taken upon those resources.
+#### Allow a specific user to perform auth type activities (full list [here](/concepts/policies/language#activity-breakdown))
-```json JSON
+Note: The `activity.resource` portion determines which activities can be performed. The `activity.action` determines what types of actions can be taken upon those resources.
+
+```json
{
"policyName": "Allow user to initiate auth type activities",
"effect": "EFFECT_ALLOW",
@@ -76,8 +73,12 @@ Note: The activity.resource portion determines which activities can be performed
}
```
-#### Allow a specific user to perform a specific activity type (full list [here](Policy-language.md#activity-breakdown))
-Note: Activities may be upgraded over time, and thus new versions may be introduced. These policies will NOT be valid if an activity type is upgraded and requests are made on the new activity type. For example, if Turnkey introduces `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V3` (upgraded from `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2`) and a request is made with the newer `V3` version, this policy with not allow that user to perform `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V3` activities.
+#### Allow a specific user to perform a specific activity type (full list [here](/concepts/policies/language#activity-breakdown))
+
+Note: Activities may be upgraded over time, and thus new versions may be introduced.
+These policies will NOT be valid if an activity type is upgraded and requests are made on the new activity type.
+For example, if Turnkey introduces `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V3` (upgraded from `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2`)
+and a request is made with the newer `V3` version, this policy with not allow that user to perform `ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V3` activities.
```json JSON
{
@@ -92,7 +93,7 @@ Note: Activities may be upgraded over time, and thus new versions may be introdu
#### Allow a specific user to sign transactions with any account address within a specific wallet
-```json
+```JSON
{
"policyName": "Allow to sign transactions with ",
"effect": "EFFECT_ALLOW",
@@ -125,11 +126,11 @@ Note: Activities may be upgraded over time, and thus new versions may be introdu
### Ethereum (EVM)
-Note: see the [language section](Policy-language.md#appendix) for more details.
+Note: see the [language section](/concepts/policies/language#appendix) for more details.
#### Allow ERC-20 transfers for a specific token smart contract
-```json JSON
+```json
{
"policyName": "Enable ERC-20 transfers for ",
"effect": "EFFECT_ALLOW",
@@ -139,7 +140,7 @@ Note: see the [language section](Policy-language.md#appendix) for more details.
#### Allow anyone to sign transactions for testnet (Sepolia)
-```json JSON
+```json
{
"policyName": "Allow signing ethereum sepolia transactions",
"effect": "EFFECT_ALLOW",
@@ -149,7 +150,7 @@ Note: see the [language section](Policy-language.md#appendix) for more details.
#### Allow ETH transactions with a specific nonce range
-```json JSON
+```json
{
"policyName": "Allow signing Ethereum transactions with an early nonce",
"effect": "EFFECT_ALLOW",
@@ -159,11 +160,11 @@ Note: see the [language section](Policy-language.md#appendix) for more details.
### Solana
-Note: see the [language section](Policy-language.md#appendix) for various approaches on writing Solana policies.
+Note: see the [language section](/concepts/policies/language#appendix) for various approaches on writing Solana policies.
#### Allow Solana transactions that include a transfer from one specific sender
-```json JSON
+```json
{
"policyName": "Enable transactions with a transfer sent by ",
"effect": "EFFECT_ALLOW",
@@ -173,7 +174,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Allow Solana transactions that include a transfer to only one specific recipient
-```json JSON
+```json
{
"policyName": "Enable transactions with a single transfer sent to ",
"effect": "EFFECT_ALLOW",
@@ -183,7 +184,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Allow Solana transactions that have exactly one transfer, to one specific recipient
-```json JSON
+```json
{
"policyName": "Enable transactions with a transfer sent to ",
"effect": "EFFECT_ALLOW",
@@ -193,7 +194,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Allow Solana transactions that only use the Solana System Program
-```json JSON
+```json
{
"policyName": "Enable transactions that only use the system program",
"effect": "EFFECT_ALLOW",
@@ -203,7 +204,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Deny all Solana transactions transferring to an undesired address
-```json JSON
+```json
{
"policyName": "Reject transactions with a transfer sent to ",
"effect": "EFFECT_DENY",
@@ -213,7 +214,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Allow Solana transactions with specific expected instruction data
-```json JSON
+```json
{
"policyName": "Enable transactions where the first instruction has precisely ",
"effect": "EFFECT_ALLOW",
@@ -223,7 +224,7 @@ Note: see the [language section](Policy-language.md#appendix) for various approa
#### Allow Solana transactions whose first instruction involves a specific address
-```json JSON
+```json
{
"policyName": "Enable transactions where the first instruction has a first account involving ",
"effect": "EFFECT_ALLOW",
@@ -237,58 +238,51 @@ Turnkey’s policy engine supports policies for SPL token transfers. Specificall
Some important context for using SPL token policies with Turnkey:
-**Token Account Addresses**
-For context, Solana implements SPL token balances for a particular wallet address by creating a whole new account called a "token account" which has a pointer in its data field labeled "owner" that points back to the wallet address in question. So to hold a particular token in your Solana wallet, you have to have to create a new token account meant to hold that token, owned by your Solana wallet. For policies related to the receiving token address of an SPL transfer, the token address receiving the tokens will have to be used, NOT the wallet address that is the owner for the receiving token address. This is because, while both the owning wallet address and the receiving token address are specified in the transfer instruction, the owning wallet address of the recipient token address is not specified. For this we highly recommend using the convention of “associated token addresses” to set policies that, for example, allow SPL token transfers to a particular wallet address.
+**Token Account Addresses** For context, Solana implements SPL token balances for a particular wallet address by creating a whole new account called a "token account" which has a pointer in its data field labeled "owner" that points back to the wallet address in question. So to hold a particular token in your Solana wallet, you have to have to create a new token account meant to hold that token, owned by your Solana wallet. For policies related to the receiving token address of an SPL transfer, the token address receiving the tokens will have to be used, NOT the wallet address that is the owner for the receiving token address. This is because, while both the owning wallet address and the receiving token address are specified in the transfer instruction, the owning wallet address of the recipient token address is not specified. For this we highly recommend using the convention of “associated token addresses” to set policies that, for example, allow SPL token transfers to a particular wallet address.
-For further context on associated token addresses check out Solana’s documentation on it: https://spl.solana.com/associated-token-account
+For further context on associated token addresses check out Solana’s documentation on it: [https://spl.solana.com/associated-token-account](https://spl.solana.com/associated-token-account)
-:::info
+
+ An example implementation of using a policy to allow transfers to the associated token address of the intended recipient wallet address can be found in our SDK examples [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana#6-running-the-create-spl-token-transfer-with-policy-example).
+
-An example implementation of using a policy to allow transfers to the associated token address of the intended recipient wallet address can be found in our SDK examples [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana#6-running-the-create-spl-token-transfer-with-policy-example).
-
-:::
-
-**Mint Address Accessibility**
-The mint account address of the token will only be accessible when the transaction is constructed using instructions that specify the mint address – `TransferChecked` and `TransferCheckedWithFee`. For transactions constructed using the simple `Transfer` method, the mint account will be considered empty.
+**Mint Address Accessibility** The mint account address of the token will only be accessible when the transaction is constructed using instructions that specify the mint address – `TransferChecked` and `TransferCheckedWithFee`. For transactions constructed using the simple `Transfer` method, the mint account will be considered empty.
Here are some example policies for SPL transfers:
#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer from a particular sending token address
-```json JSON
+```json
{
"policyName": "Allow user to sign Solana transactions that include only a single SPL Transfer FROM ",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '')",
- "condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.from == '')"
-}
+ "condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.from == '')"}
```
#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL transfers TO a particular token address
-```json JSON
+```json
{
"policyName": "Allow user to sign Solana transactions only if ALL of the instructions are SPL transfers TO ",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '')",
- "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.to == '')"
-}
+ "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.to == '')"}
```
#### Allow users with a specific tag to sign Solana transactions only if ALL of the instructions are SPL token transfers with a specific address as the owner of the sending token address
-```json JSON
+```json
{
"policyName": "Allow users with to sign Solana transactions only if ALL of the instructions are SPL token transfers with as the owner of the sending token address",
"effect": "EFFECT_ALLOW",
- "consensus": "approvers.any(user, user.tags.contains(''))",
- "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.owner == '')"
-}
+ "consensus": "approvers.any(user, user.tags.contains('')"}
```
#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than a threshold amount
-```json Json
+```json
{
"policyName": "Allow user to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than ",
"effect": "EFFECT_ALLOW",
@@ -299,18 +293,17 @@ Here are some example policies for SPL transfers:
#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL token transfers where the token mint address is a particular address
-```json Json
+```json
{
"policyName": "Allow to sign a Solana transaction only if ALL of the instructions are SPL token transfers where the token mint address is ",
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '')",
- "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.token_mint == '')"
-}
+ "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.token_mint == '')"}
```
#### Allow a user to sign Solana transactions that includes a single instruction which is an SPL token transfer where one of the multisig signers of the owner is a particular address
-```json Json
+```json
{
"policyName": "Allow to sign a Solana transaction only if ALL of it's instructions are SPL token transfers where one of the multisig signers of the owner is ",
"effect": "EFFECT_ALLOW",
diff --git a/concepts/policies/language.mdx b/concepts/policies/language.mdx
new file mode 100644
index 00000000..291e82f7
--- /dev/null
+++ b/concepts/policies/language.mdx
@@ -0,0 +1,188 @@
+---
+title: "Policy language"
+description: "This page provides an overview of how to author policies using our policy language. To begin, we'll need to get familiar with the language's grammar, keywords, and types."
+sidebarTitle: "Language"
+---
+
+## Grammar
+
+The grammar has been designed for flexibility and expressiveness. We currently support the following operations:
+
+| Operation | Operators | Example | Types |
+| ---------- | ---------------------------- | ---------------------------- | ------------------------ |
+| logical | &&, \|\| | "true && false" | (bool, bool) -> bool |
+| comparison | ==, !=, \<, >, \<=, >= | "1 \< 2" | (int, int) -> bool |
+| comparison | ==, != | "'a' != 'b'" | (string, string) -> bool |
+| comparison | in | "1 in \[1, 2, 3]" | (T, list\) -> bool |
+| access | x\[\] | \[1,2,3]\[0] | (list\) -> T |
+| access | x\[\] | "'abc'\[0]" | (string) -> string |
+| access | x\[\..\] | \[1,2,3]\[0..2] | (list\) -> (list\) |
+| access | x\[\..\] | "'abc'\[0..2]" | (string) -> string |
+| access | x.\ | "user.tags" | (struct) -> T |
+| function | x.all(item, \) | "\[1,1,1].all(x, x == 1)" | (list\) -> bool |
+| function | x.any(item, \) | "\[1,2,3].any(x, x == 1)" | (list\) -> bool |
+| function | x.contains(\) | "\[1,2,3].contains(1)" | (list\) -> bool |
+| function | x.count() | "\[1,2,3].count()" | (list\) -> int |
+| function | x.filter(item, \) | "\[1,2,3].filter(x, x == 1)" | (list\) -> (list\) |
+
+## Keywords
+
+Keywords are reserved words that are dynamically interchanged for real values at evaluation time. Each field supports a different set of keywords.
+
+### Consensus
+
+| Keyword | Type | Description |
+| ------------- | ------------ | ---------------------------------------- |
+| **approvers** | list\ | The users that have approved an activity |
+
+### Condition
+
+| Keyword | Type | Description |
+| --------------- | ------------------- | ------------------------------------------------------------ |
+| **activity** | Activity | The activity metadata of the request |
+| **eth.tx** | EthereumTransaction | The parsed Ethereum transaction payload (see Appendix below) |
+| **solana.tx** | SolanaTransaction | The parsed Solana transaction payload (see Appendix below) |
+| **wallet** | Wallet | The target wallet used in sign requests |
+| **private_key** | PrivateKey | The target private key used in sign requests |
+
+## Types
+
+The language is strongly typed which makes policies easy to author and maintain.
+
+### Primitive
+
+| Type | Example | Notes |
+| ------------- | --------------- | ------------------------------------------------ |
+| **bool** | true | |
+| **int** | 256 | i128 |
+| **string** | 'a' | only single quotes are supported |
+| **list\** | \[1, 2, 3] | a list of type T |
+| **struct** | \{ id: 'abc' \} | a key-value map of \{ field:T \} (defined below) |
+
+### Struct
+
+| Struct | Field | Type | Description |
+| ----------------------- | ------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **User** | id | string | The identifier of the user |
+| | tags | list\ | The collection of tags for the user |
+| | email | string | The email address of the user |
+| | alias | string | The alias of the user |
+| **Activity** | type | string | The type of the activity (e.g. ACTIVITY_TYPE_SIGN_TRANSACTION_V2) |
+| | resource | string | The resource type the activity targets: `USER`, `PRIVATE_KEY`, `POLICY`, `WALLET`, `ORGANIZATION`, `INVITATION`, `CREDENTIAL`, `CONFIG`, `RECOVERY`, `AUTH`, `PAYMENT_METHOD`, `SUBSCRIPTION` |
+| | action | string | The action of the activity: `CREATE`, `UPDATE`, `DELETE`, `SIGN`, `EXPORT`, `IMPORT` |
+| **Wallet** | id | string | The identifier of the wallet |
+| **Wallet Account** | address | string | The wallet account address |
+| **PrivateKey** | id | string | The identifier of the private key |
+| | tags | list\ | The collection of tags for the private key |
+| **EthereumTransaction** | from | string | The sender address of the transaction |
+| | to | string | The receiver address of the transaction (can be an EOA or smart contract) |
+| | data | string | The arbitrary calldata of the transaction (hex-encoded) |
+| | value | int | The amount being sent (in wei) |
+| | gas | int | The maximum allowed gas for the transaction |
+| | gas_price | int | The price of gas for the transaction (Note: this field was used in legacy transactions and was replaced with max_fee_per_gas in EIP 1559 transactions, however when evaluating policies on EIP 1559 transactions, this field will be populated with the same value as max_fee_per_gas) |
+| | chain_id | int | The chain identifier for the transaction |
+| | nonce | int | The nonce for the transaction |
+| | max_fee_per_gas | int | EIP 1559 field specifying the max amount to pay per unit of gas for the transaction (Note: This is the sum of the gas for the transaction and the priority fee described below) |
+| | max_priority_fee_per_gas | int | EIP 1559 field specifying the max amount of the tip to be paid to miners for the transaction |
+| **SolanaTransaction** | account_keys | list\ | The accounts (public keys) involved in the transaction |
+| | program_keys | list\ | The programs (public keys) involved in the transaction |
+| | instructions | list\ | A list of Instructions (see below) |
+| | transfers | list\ | A list of Transfers (see below) |
+| | recent_blockhash | string | The recent blockhash specified in a transaction |
+
+#### Nested Structs
+
+| Struct | Field | Type | Description |
+| ---------------------- | --------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
+| **Instruction** | program_key | string | The program (public key) involved in the instruction |
+| | accounts | list\ | A list of Accounts involved in the instruction |
+| | instruction_data_hex | string | Raw hex bytes corresponding to instruction data |
+| | address_table_lookups | list\ | A list of AddressTableLookups used in the instruction. Learn more [here](https://solana.com/docs/advanced/lookup-tables) |
+| **Transfer** | sender | string | A Solana account (public key) |
+| | recipient | string | A Solana account (public key) |
+| | amount | string | The native SOL amount for the transfer (lamports) |
+| **Account** | account_key | string | A Solana account (public key) |
+| | signer | boolean | An indicator of whether or not the account is a signer |
+| | writable | boolean | An indicator of whether or not the account can perform a write operation |
+| **AddressTableLookup** | address_table_key | string | A Solana address (public key) corresponding to the address table |
+| | writable_indexes | list\ | Indexes corresponding to accounts that can perform writes |
+| | readonly_indexes | list\ | Indexes corresponding to accounts that can only perform reads |
+
+## Activity Breakdown
+
+| Resource Type | Action | Activity Type |
+| ------------------ | ------ | -----------------------------------------: |
+| **ORGANIZATION** | CREATE | ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7 |
+| | DELETE | ACTIVITY_TYPE_DELETE_ORGANIZATION |
+| | DELETE | ACTIVITY_TYPE_DELETE_SUB_ORGANIZATION |
+| **INVITATION** | CREATE | ACTIVITY_TYPE_CREATE_INVITATIONS |
+| | DELETE | ACTIVITY_TYPE_DELETE_INVITATION |
+| **POLICY** | CREATE | ACTIVITY_TYPE_CREATE_POLICY_V3 |
+| | CREATE | ACTIVITY_TYPE_CREATE_POLICIES |
+| | UPDATE | ACTIVITY_TYPE_UPDATE_POLICY |
+| | DELETE | ACTIVITY_TYPE_DELETE_POLICY |
+| **WALLET** | CREATE | ACTIVITY_TYPE_CREATE_WALLET |
+| | CREATE | ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS |
+| | EXPORT | ACTIVITY_TYPE_EXPORT_WALLET |
+| | EXPORT | ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT |
+| | IMPORT | ACTIVITY_TYPE_INIT_IMPORT_WALLET |
+| | IMPORT | ACTIVITY_TYPE_IMPORT_WALLET |
+| | DELETE | ACTIVITY_TYPE_DELETE_WALLETS |
+| | UPDATE | ACTIVITY_TYPE_UPDATE_WALLET |
+| **PRIVATE_KEY** | CREATE | ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2 |
+| | CREATE | ACTIVITY_TYPE_CREATE_PRIVATE_KEY_TAG |
+| | UPDATE | ACTIVITY_TYPE_UPDATE_PRIVATE_KEY_TAG |
+| | DELETE | ACTIVITY_TYPE_DISABLE_PRIVATE_KEY |
+| | DELETE | ACTIVITY_TYPE_DELETE_PRIVATE_KEY_TAGS |
+| | DELETE | ACTIVITY_TYPE_DELETE_PRIVATE_KEYS |
+| | EXPORT | ACTIVITY_TYPE_EXPORT_PRIVATE_KEY |
+| | IMPORT | ACTIVITY_TYPE_INIT_IMPORT_PRIVATE_KEY |
+| | IMPORT | ACTIVITY_TYPE_IMPORT_PRIVATE_KEY |
+| | SIGN | ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2 |
+| | SIGN | ACTIVITY_TYPE_SIGN_RAW_PAYLOADS |
+| | SIGN | ACTIVITY_TYPE_SIGN_TRANSACTION_V2 |
+| **USER** | CREATE | ACTIVITY_TYPE_CREATE_USERS_V2 |
+| | CREATE | ACTIVITY_TYPE_CREATE_USER_TAG |
+| | CREATE | ACTIVITY_TYPE_CREATE_API_ONLY_USERS |
+| | UPDATE | ACTIVITY_TYPE_UPDATE_USER |
+| | UPDATE | ACTIVITY_TYPE_UPDATE_USER_TAG |
+| | DELETE | ACTIVITY_TYPE_DELETE_USERS |
+| | DELETE | ACTIVITY_TYPE_DELETE_USER_TAGS |
+| **CREDENTIAL** | CREATE | ACTIVITY_TYPE_CREATE_API_KEYS_V2 |
+| | CREATE | ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2 |
+| | DELETE | ACTIVITY_TYPE_DELETE_API_KEYS |
+| | DELETE | ACTIVITY_TYPE_DELETE_AUTHENTICATORS |
+| | CREATE | ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS |
+| | DELETE | ACTIVITY_TYPE_DELETE_OAUTH_PROVIDERS |
+| **PAYMENT_METHOD** | UPDATE | ACTIVITY_TYPE_SET_PAYMENT_METHOD_V2 |
+| | DELETE | ACTIVITY_TYPE_DELETE_PAYMENT_METHOD |
+| **SUBSCRIPTION** | CREATE | ACTIVITY_TYPE_ACTIVATE_BILLING_TIER |
+| **CONFIG** | UPDATE | ACTIVITY_TYPE_UPDATE_ALLOWED_ORIGINS |
+| **RECOVERY** | CREATE | ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY |
+| **AUTH** | CREATE | ACTIVITY_TYPE_EMAIL_AUTH_V2 |
+| | CREATE | ACTIVITY_TYPE_INIT_OTP_AUTH |
+| | CREATE | ACTIVITY_TYPE_OTP_AUTH |
+| | CREATE | ACTIVITY_TYPE_OAUTH |
+| | CREATE | ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2 |
+
+## Appendix
+
+### Root quorum activities
+
+There are a select few activities that are not governed by policies, but rather by an organization's [root quorum](/concepts/users/root-quorum). These activities are: `ACTIVITY\_TYPE\_UPDATE\_ROOT\_QUORUM`, `ACTIVITY\_TYPE\_SET\_ORGANIZATION\_FEATURE`, `ACTIVITY\_TYPE\_REMOVE\_ORGANIZATION\_FEATURE`. For example, if a policy is added that allows a specific non-root user to perform `ACTIVITY\_TYPE\_SET\_ORGANIZATION\_FEATURE` activities, these requests will still fail as they are subject specifically to root quorum.
+
+### Ethereum
+
+Our Ethereum policy language (accessible via `eth.tx`) allows for the granular governance of signing Ethereum (EVM-compatible) transactions. Our policy engine exposes a [fairly standard set of properties](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope) belonging to a transaction.
+
+See the [Policy examples](/concepts/policies/examples) for sample scenarios.
+
+### Solana
+
+Similarly, our Solana policy language (accessible via `solana.tx`) allows for control over signing Solana transactions. Note that there are some fundamental differences between the architecture of the two types of transactions, hence the resulting differences in policy structure. Notably, within our policy engine, a Solana transaction contains a list of Transfers, currently corresponding to native SOL transfers. Each transfer within a transaction is considered a separate entity. Here are some approaches you might take to govern native SOL transfers:
+
+- _All_ transfers need to match the policy condition. Useful for allowlists ([example](/concepts/policies/examples#allow-solana-transactions-that-include-a-transfer-with-only-one-specific-recipient))
+- _Just one_ transfer needs to match the policy condition. Useful for blocklists ([example](/concepts/policies/examples#deny-all-solana-transactions-transferring-to-an-undesired-address))
+- Only match if there is a _single_ transfer in the transaction, _and_ that transfer meets the criteria ([example](/concepts/policies/examples#allow-solana-transactions-that-have-exactly-one-transfer-with-one-specific-recipient)). This is the most secure approach, and thus most restrictive.
+
+See the [Policy examples](/concepts/policies/examples) for sample scenarios.
diff --git a/docs/documentation/concepts/policy-management/Policy-overview.md b/concepts/policies/overview.mdx
similarity index 50%
rename from docs/documentation/concepts/policy-management/Policy-overview.md
rename to concepts/policies/overview.mdx
index 4573d947..7720f8ce 100644
--- a/docs/documentation/concepts/policy-management/Policy-overview.md
+++ b/concepts/policies/overview.mdx
@@ -1,14 +1,9 @@
---
-sidebar_position: 1
-description: Learn about Policies on Turnkey and how to manage them
-slug: /concepts/policies/overview
-sidebar_label: Overview
+title: "Policy overview"
+description: "Our policy engine is the foundation for flexible controls and permissions within your organization. This page provides an overview of how to author policies."
+sidebarTitle: "Overview"
---
-# Policy overview
-
-Our policy engine is the foundation for flexible controls and permissions within your organization. This page provides an overview of how to author policies.
-
## Policy structure
Our policies are defined using **JSON**. The `effect` determines if an activity should be allowed or denied based on the evaluation of the `consensus` and `condition` fields.
@@ -17,7 +12,7 @@ Our policies are defined using **JSON**. The `effect` determines if an activity
#### See below for an example policy that allows a single user to send transactions to a single address
-```json JSON
+```json
{
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '4b894565-fa11-42fc-b813-5bf4ea3d53f9')",
@@ -29,24 +24,30 @@ Our policies are defined using **JSON**. The `effect` determines if an activity
All policies defined within an Organization are evaluated on each request. The image below describes how an activity outcome is determined when resolving multiple policies. The rule follows the below steps:
-1. If a quorum of root users takes the action, the final outcome is `OUTCOME_ALLOW`
-2. Else if any applicable policy has `EFFECT_DENY`, the final outcome is `OUTCOME_DENY`. This is also referred to as "explicit deny."
-3. Else if at least one applicable policy has `EFFECT_ALLOW`, then the final outcome is `OUTCOME_ALLOW`
-4. Else the final outcome is `OUTCOME_DENY`. This is also referred to as "implicit deny." In cases of conflicts, `EFFECT_DENY` always wins.
+
+
+ If a quorum of root users takes the action, the final outcome is `OUTCOME_ALLOW`
+
+
+ Else if any applicable policy has `EFFECT_DENY`, the final outcome is `OUTCOME_DENY`. This is also referred to as "explicit deny."
+
+
+ Else if at least one applicable policy has `EFFECT_ALLOW`, then the final outcome is `OUTCOME_ALLOW`
+
+
+ Else the final outcome is `OUTCOME_DENY`. This is also referred to as "implicit deny." In cases of conflicts, `EFFECT_DENY` always wins.
+
+
Stated differently:
-
-
-
+
+
+
Almost all actions on Turnkey are implicitly denied by default. There are a few exceptions, however:
-- Root users bypass any policies.
-- All users have implicit GET (read) permissions in their own Organization and any associated Sub-Organizations.
-- All users have implicit permission to change their own credentials.
-- All users have implicit permission to approve an activity if they were included in consensus (i.e., a user specified as part of the consensus required to approve a SIGN_TRANSACTION activity does not need separate, explicit permission to sign transactions).
+* Root users bypass any policies.
+* All users have implicit GET (read) permissions in their own Organization and any associated Sub-Organizations.
+* All users have implicit permission to change their own credentials.
+* All users have implicit permission to approve an activity if they were included in consensus (i.e., a user specified as part of the consensus required to approve a SIGN\_TRANSACTION activity does not need separate, explicit permission to sign transactions).
diff --git a/docs/documentation/concepts/policy-management/Policy-quickstart.md b/concepts/policies/quickstart.mdx
similarity index 64%
rename from docs/documentation/concepts/policy-management/Policy-quickstart.md
rename to concepts/policies/quickstart.mdx
index 648025e7..9d39b53c 100644
--- a/docs/documentation/concepts/policy-management/Policy-quickstart.md
+++ b/concepts/policies/quickstart.mdx
@@ -1,47 +1,48 @@
---
-sidebar_position: 2
-description: Add a new user to your Org and try out the policy engine
-slug: /concepts/policies/quickstart
-sidebar_label: Quickstart
+title: "Policy quickstart"
+description: "This guide will help you add an additional user to your Turnkey organization and set permissions for that user through Policies. Specifically, we will create an API-only user with permissions to sign transactions to an allowlisted address."
+sidebarTitle: "Quickstart"
---
-# Policy quickstart
-
-This guide will help you add an additional user to your Turnkey organization and set permissions for that user through Policies. Specifically, we will create an API-only user with permissions to sign transactions to an allowlisted address.
-
This assumes that you previously completed the [Sign a transaction](/getting-started/quickstart) guide, and thus have already set up:
-- Your Turnkey organization
-- An API key for the Root User
-- A Wallet with an Ethereum account
+* Your Turnkey organization
+* An API key for the Root User
+* A Wallet with an Ethereum account
## Create your new users
New users in your Turnkey organization can be created by navigating to the "Users" tab and clicking "Add User".
-
+
+
+
In the create user flow, you have the option to grant API key or web access to your new user. For this example, we're going to create an API-only user.
-
+
+
+
Under access types, select "API key". Enter the user name "Policy Test". This will be an API-only user, and therefore an email is not required. Click continue and create a new API key to associate with the user using the following command:
-```shell
+```bash
turnkey generate api-key --organization $ORGANIZATION_ID --key-name policy_test
```
-This will create 2 files, "policy_test.public" and "policy_test.private". Copy the contents of the ".public" file and paste it into "API public key". Finish the create user flow and authenticate. Your new user will appear in the Users table. Note down the user ID as you will use it in the next step.
+This will create 2 files, "policy\_test.public" and "policy\_test.private". Copy the contents of the ".public" file and paste it into "API public key". Finish the create user flow and authenticate. Your new user will appear in the Users table. Note down the user ID as you will use it in the next step.
## Create policies for your new users.
Next we will create a policy to grant permissions to the new user. Navigate to the "Policies" tab and click on "Add new policy".
-
+
+
+
Choose a name and note to describe your new policy. Next, enter the following policy, making sure to replace `` with an Ethereum address of your choosing and `` with the user ID of your recently created API user.
-```json JSON
+```json
{
"effect": "EFFECT_ALLOW",
"consensus": "approvers.any(user, user.id == '')",
@@ -55,7 +56,7 @@ Generate sample transactions using our [transaction tool](https://build.tx.xyz).
Next, try signing these two different transactions by replacing `` in the code snippet below. As a reminder, this guide assumes you've completed the [Quickstart](/getting-started/quickstart) guide, and have set `$ORGANIZATION_ID` as an environment variable.
-```shell
+```json
turnkey request --path /public/v1/submit/sign_transaction --body '{
"timestampMs": "'"$(date +%s)"'000",
"type": "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
@@ -72,6 +73,6 @@ You'll see that the activity to allowlisted address comes back as `COMPLETED`, w
## Extra credit
-- Try out some of our [policy examples](/concepts/policies/examples)
-- Check out the [policy overview](/concepts/policies/overview)
-- Learn how to author policies with our [policy language](/concepts/policies/overview)
+* Try out some of our [policy examples](/concepts/policies/examples)
+* Check out the [policy overview](/concepts/policies/overview)
+* Learn how to author policies with our [policy language](/concepts/policies/overview)
diff --git a/concepts/resource-limits.mdx b/concepts/resource-limits.mdx
new file mode 100644
index 00000000..5e1b33bd
--- /dev/null
+++ b/concepts/resource-limits.mdx
@@ -0,0 +1,29 @@
+---
+title: "Resource Limits"
+description: "We have limits on the number of resources within a single organization to avoid performance slowdowns and overly complex permission models. You can scale your organizational resources beyond these limits via . You can create an unlimited number of sub-organizations within a single organization."
+mode: wide
+---
+
+Currently, the resource limits within a single organization are as follows:
+
+| Resource | Maximum parent org allowance | Maximum sub-org allowance |
+| :----------------------------- | :--------------------------: | :-----------------------: |
+| Private keys | 1,000 | 1,000 |
+| HD Wallets | 100 | 100 |
+| HD Wallet Accounts | unlimited | unlimited |
+| Users | 100 | 100 |
+| Policies | 100 | 100 |
+| Invitations | 100 | 100 |
+| Tags | 100 | 10 |
+| Authenticators per user | 10 | 10 |
+| API keys per user (long-lived) | 10 | 10 |
+| API keys per user (expiring) | 10 | 10 |
+| Sub-Organizations | unlimited | 0 |
+| OAuth providers per user | 10 | 10 |
+
+Note that if you create an expiring API key that would exceed the limit above, Turnkey automatically deletes one of your existing keys using the following priority:
+
+1. Expired API keys are deleted first
+2. If no expired keys exist, the oldest unexpired key is deleted
+
+If you are approaching any of these limits in your implementation and require support, reach out to the Turnkey team ([help@turnkey.com](mailto:help@turnkey.com)).
diff --git a/concepts/sub-organizations.mdx b/concepts/sub-organizations.mdx
new file mode 100644
index 00000000..07b7db1b
--- /dev/null
+++ b/concepts/sub-organizations.mdx
@@ -0,0 +1,34 @@
+---
+title: "Sub-Organizations"
+description: "Using Turnkey's flexible infrastructure, you can programmatically create and manage sub-organizations for your end-users. sub-organizations aren't subject to size limits: you can create as many sub-organizations as needed. The parent organization has **read-only** visibility into all of its sub-organizations, and activities performed in sub-organizations roll up to the parent for billing purposes."
+---
+
+We envision sub-organizations being very useful to model your End-Users if you're a business using Turnkey for key management. Let's explore how.
+
+## Creating Sub-Organizations
+
+Creating a new sub-organization is an activity performed by the parent organization. The activity itself takes the following attributes as inputs:
+
+- organization name
+- a list of root users
+- a root quorum threshold
+- \[optional] a wallet (note: in versions prior to V4, this was a private key)
+
+Root users can be programmatic or human, with one or many credentials attached.
+
+## Using Sub-Organizations
+
+[Sub-Organizations as Wallets](/embedded-wallets/sub-organizations-as-wallets) explains how you might want to use this primitive as a way to model end-user controlled wallets, or custodial wallets. If you have another use-case in mind, or questions/feedback on this page, reach out to [welcome@turnkey.com](mailto:welcome@turnkey.com)!
+
+## Deleting Sub-Organizations
+
+To delete a sub-organization, you can use the [delete sub-organization activity](/api-reference/organizations/delete-sub-organization).
+Before proceeding, ensure that all private keys and wallets within the sub-organization have been exported to prevent any loss of funds.
+Alternatively, you can set the `deleteWithoutExport` parameter to `true` to bypass this requirement.
+By default, the `deleteWithoutExport` parameter is set to `false`.
+
+
+ This activity must be initiated by a root user in the sub-organization that is
+ to be deleted. A parent org cannot delete a sub-organization without its
+ participation.
+
diff --git a/docs/documentation/concepts/user-management/Best-practices.md b/concepts/users/best-practices.mdx
similarity index 75%
rename from docs/documentation/concepts/user-management/Best-practices.md
rename to concepts/users/best-practices.mdx
index d2ea90ae..be625e3a 100644
--- a/docs/documentation/concepts/user-management/Best-practices.md
+++ b/concepts/users/best-practices.mdx
@@ -1,52 +1,54 @@
---
-sidebar_position: 5
-description: Best practices as you set up users and policies
-slug: /concepts/users/best-practices
+title: "Best Practices"
+description: "This page describes some best practices to consider as you set up users and policies while getting ready for production."
---
-# Best practices
-
-This page describes some best practices to consider as you set up users and policies while getting ready for production.
-
## Managing users
-**Enforce a security policy of least privilege for your users**
-Users on Turnkey should have the minimum required privilege to accomplish their job. When setting up users, consider this for their access type and policies that will grant the user permissions.
+**Enforce a security policy of least privilege for your users**
+ Users on Turnkey should have the minimum required privilege to accomplish their job. When setting up users, consider this for their access type and policies that will grant the user permissions.
-**Use user tags to create groups of users with equal permissions**
+**Use user tags to create groups of users with equal permissions**
Referencing user tags in policies instead of individual users allows for clearer management of permissions.
-**When creating new users, consider verifying onboarding before adding tags**
+**When creating new users, consider verifying onboarding before adding tags**
When inviting a web user to your Turnkey organization, you should consider real-life verification to confirm that they have onboarded correctly before granting that user permissions via tags. Granting tags prior to verification could provide an attacker permissions in your Turnkey organization if they are able to access the signup link.
-**Regularly review and remove unused users, user tags, and policies**
+**Regularly review and remove unused users, user tags, and policies**
If a user is unused or the user has left your company, you should remove them from your Turnkey organization to avoid compromise.
-**Attach multiple authenticators to web users**
+**Attach multiple authenticators to web users**
+
This ensures you don't lose access to the user. If an authenticator is lost or stolen, log in immediately to remove that authenticator from the user, or notify someone in your organization with permissions to delete the user. It's best to use multiple types of authenticators to ensure the security of your account if one fails.
## Protecting API keys
API keys allow programmatic access to Turnkey, and thus anyone with access to your API key has the same level of access to your Turnkey organization as you do. Consider the following to better protect your API keys and Turnkey organization.
-**Don't embed API keys directly in your code**
+**Don't embed API keys directly in your code**
+
This reduces the ways that a hacker could acquire your API key. Our SDKs and CLI enable you to reference your API keys so you don't have to put them directly in your code.
-**Use different API-only users for different applications**
+**Use different API-only users for different applications**
+
This allows you to isolate permissions and differentiate activities between those applications. In the case that an API key is lost or stolen, it also allows you to revoke access solely for the affected application.
-**Use a secret management system to protect your API keys**
+**Use a secret management system to protect your API keys**
+
Tools like Hashicorp Vault or AWS KMS can help you protect your API key from malicious access.
-**Regularly remove any unused API keys**
+**Regularly remove any unused API keys**
+
This reduces the chance that an old key can be used to access your Turnkey organization.
## Setting up policies
-**Apply least-privilege permissions**
+**Apply least-privilege permissions**
+
Turnkey's policy engine allows you to enforce permissions at a fine-grained level. When setting up your account, we suggest you use the principle of least privilege, meaning that a user only has the minimum permissions that are necessary to perform their job. Create policies that ensure that users have least-privilege permissions.
-**Apply consensus to sensitive actions**
+**Apply consensus to sensitive actions**
+
Sensitive actions like changing policies or signing transactions should be carefully controlled as they can lead to funds being moved off of the platform. You can apply consensus to actions like this to ensure that multiple approvals are required. For example, the policy below specifies that 2 total approvals, including the initiating approval, are required to create a new policy.
```json
@@ -58,15 +60,17 @@ Sensitive actions like changing policies or signing transactions should be caref
}
```
-**Be especially careful with the ability to add policies**
+**Be especially careful with the ability to add policies**
+
Within this principle of least privilege,
Some actions should be treated more sensitively:
-- Adding policies
-- Signing transactions
+* Adding policies
+* Signing transactions
+
+**Use allowlisting if you only send to a set of addresses**
-**Use allowlisting if you only send to a set of addresses**
If your use case for Turnkey only requires you to send funds to a certain set of crypto addresses, you should set a policy that allowlists those addresses. See below for an example policy.
```json
diff --git a/docs/documentation/concepts/user-management/credentials.md b/concepts/users/credentials.mdx
similarity index 71%
rename from docs/documentation/concepts/user-management/credentials.md
rename to concepts/users/credentials.mdx
index c947d9a9..daf4ddf1 100644
--- a/docs/documentation/concepts/user-management/credentials.md
+++ b/concepts/users/credentials.mdx
@@ -1,15 +1,11 @@
---
-sidebar_position: 2
-description: Learn about user credentials and authentication on Turnkey
-slug: /concepts/users/credentials
+title: "Credentials"
+description: "Credentials represent ways for Users to authenticate to Turnkey. All Turnkey Credentials are held by you, the end-user. Turnkey only keeps **public keys**."
---
+At the moment, Turnkey supports 2 types of Credentials:
-# Credentials
-
-Credentials represent ways for Users to authenticate to Turnkey. All Turnkey Credentials are held by you, the end-user. Turnkey only keeps **public keys**. At the moment, Turnkey supports 2 types of Credentials:
-
-- Authenticators
-- API Keys
+* Authenticators
+* API Keys
Note that every Turnkey user needs at least one long-lived credential (a passkey, or non-expiring API key). This is to prevent users from getting locked out of their accounts. The exception is if the user belongs to a suborg, and [Email Auth](/authentication/email) is enabled for that sub-organization.
@@ -25,4 +21,4 @@ Turnkey API requests are authenticated with API key signatures. When you generat
Requests made via SDK or CLI use the private API key to sign requests. Turnkey's public API expects all requests (e.g. to get data or to submit activities) to be signed.
-See our [API reference](../../api/#tag/API-Keys/operation/CreateApiKeys) for how to programmatically create API keys.
+See our [API reference](/api-reference/api-keys/create-api-keys) for how to programmatically create API keys.
diff --git a/concepts/users/introduction.mdx b/concepts/users/introduction.mdx
new file mode 100644
index 00000000..1fda70e9
--- /dev/null
+++ b/concepts/users/introduction.mdx
@@ -0,0 +1,18 @@
+---
+title: "Introduction to users"
+description: "Turnkey users are resources within organizations or sub-organizations that can submit activities to Turnkey via a valid credential (e.g., API key, passkey)."
+mode: wide
+sidebarTitle: "Overview"
+---
+
+ These requests can be made either by making direct API calls or through the Turnkey Dashboard. Users must have at least one valid credential (one of API key, passkey), with upper limits on credentials defined here in our [resource limits](/concepts/resource-limits). Users can also have associated “tags” which are logical groupings that can be referenced in policies. Users can only submit activities within their given organization — they cannot take action across organizations.
+
+A User's attributes are:
+
+* UUID: a globally unique ID (e.g. `fc6372d1-723d-4f7e-8554-dc3a212e4aec`), used as a unique identifier for a User in the context of Policies or User Tags, or Quorums.
+* Name and email
+* Authenticators: a list of authenticators (see below for information)
+* API key: a list of API keys (see below for information)
+* User tags: a list of User Tag UUIDs
+
+A **user belongs to one organization**, and one organization can have many (**up to 100**) users. If you need to create more users, consider using Sub-Organizations.
diff --git a/docs/documentation/concepts/user-management/Root-quorum.md b/concepts/users/root-quorum.mdx
similarity index 80%
rename from docs/documentation/concepts/user-management/Root-quorum.md
rename to concepts/users/root-quorum.mdx
index 6fd77fd8..c35482cd 100644
--- a/docs/documentation/concepts/user-management/Root-quorum.md
+++ b/concepts/users/root-quorum.mdx
@@ -1,21 +1,16 @@
---
-sidebar_position: 4
-description: Learn about the root quorum and how to manage it
-slug: /concepts/users/root-quorum
+title: "Root Quorum"
+description: "When you create a Turnkey organization, your user is created and will default to being the sole member of the root quorum. Because of the wide scope of permissions, it is important to take care when using any users in the root quorum. The following offers a technical overview and some best practices."
---
-# Root quorum
-
-When you create a Turnkey organization, your user is created and will default to being the sole member of the root quorum. Because of the wide scope of permissions, it is important to take care when using any users in the root quorum. The following offers a technical overview and some best practices.
-
## Technical overview
The root quorum is a group of users who can execute any action and bypass the policy engine.
The root quorum is defined by
-- `userIds`: the Ids of users who compose the quorum set
-- `threshold`: the number of quorum members required to execute an action as root
+* `userIds`: the Ids of users who compose the quorum set
+* `threshold`: the number of quorum members required to execute an action as root
Actions approved by the root quorum do not go through the policy engine; thus it is impossible to limit root quorum actions with any policies.
@@ -47,25 +42,25 @@ Ensure that you have scoped policies for day-to-day actions that you expect to c
There are primarily two factors to consider when setting the root quorum
-- how hard is to get locked out of root? I.E. how many authenticators need to be lost/destroyed so the threshold cannot be met.
-- how many authenticators need to be compromised for an attacker to take root actions?
+* how hard is to get locked out of root? I.E. how many authenticators need to be lost/destroyed so the threshold cannot be met.
+* how many authenticators need to be compromised for an attacker to take root actions?
For example, if a quorum is configured as 2/5, then
-- if 4 users lost all their authenticators, no root actions could be taken (including updating the quorum itself).
-- if 2 different users authenticators are compromised, an attacker could steal all the organizations funds.
+* if 4 users lost all their authenticators, no root actions could be taken (including updating the quorum itself).
+* if 2 different users authenticators are compromised, an attacker could steal all the organizations funds.
**Example Setups**
The below examples are provided as a convenience only. It is up to you to ensure that the root quorum setup you design is appropriate for your particular circumstances in order to secure your organization and minimize the risk of lockout of root functionality. Failure to properly configure your root quorom setup could result in complete loss of funds.
-_High Value Organization_
+*High Value Organization*
Special users should be created that are only used for root actions. Those users' authenticators should be stored in geographically distributed locations that have personal access controls, are natural disaster resistant, and have redundancy in case of hardware failure. These would only be used in the case of a disaster.
For day to day admin operations, admin policies that use consensus can be put in place. These can be a set of finely scoped policies.
-_Low Value, End-User Directed Organization_
+*Low Value, End-User Directed Organization*
The end-user and the business both have one user in the organization. The root quorum would be configured as a 1/2, which includes the business and end-users' Users. This allows the business support channel to unbrick the user if they lose access to their account or otherwise add overly restrictive policies.
diff --git a/concepts/wallets.mdx b/concepts/wallets.mdx
new file mode 100644
index 00000000..57cf1707
--- /dev/null
+++ b/concepts/wallets.mdx
@@ -0,0 +1,124 @@
+---
+title: "Wallets"
+description: "A [hierarchical deterministic (HD) Wallet](https://learnmeabitcoin.com/technical/hd-wallets) is a collection of cryptographic private/public key pairs that share a common seed. A Wallet is used to generate Accounts."
+---
+
+```json
+{
+ "walletId": "eb98ae4c-07eb-4117-9b2d-8a453c0e1e64",
+ "walletName": "default"
+}
+```
+
+#### Configuration
+
+Wallet seeds are generated with a default mnemonic length of 12 words. The [BIP-39 specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports mnemonic lengths of 12, 15, 18, 21, and 24 words. To enhance your Wallet's security, you may consider opting for a longer mnemonic length. This optional `mnemonicLength` field can be set when creating a Wallet. It's important to note that once the Wallet seed is generated, the mnemonic is permanent and cannot be altered.
+
+## Accounts
+
+An account contains the directions for deriving a cryptographic key pair and corresponding address from a Wallet. In practice, this looks like:
+
+* The Wallet seed and Account curve are used to create a root key pair
+* The Account path format and path are used to derive an extended key pair from the root key pair
+* The Account address format is used to derive the address from the extended public key
+
+```json
+{
+ "address": "0x7aAE6F67798D1Ea0b8bFB5b64231B2f12049DB5e",
+ "addressFormat": "ADDRESS_FORMAT_ETHEREUM",
+ "curve": "CURVE_SECP256K1",
+ "path": "m/44'/60'/0'/0/0",
+ "pathFormat": "PATH_FORMAT_BIP32",
+ "walletId": "eb98ae4c-07eb-4117-9b2d-8a453c0e1e64"
+}
+```
+
+**The account address is used to sign with the underlying extended private key.**
+
+#### HD Wallet Default Paths
+
+HD wallets use standardized derivation paths to generate multiple accounts from a single seed. These paths follow a specific structure that allows for consistent address generation across different wallet implementations. Here are common default paths for some of the ecosystems supported by Turnkey:
+
+* Ethereum: `m/44'/60'/0'/0/0`
+* Cosmos: `m/44'/118'/0'/0/0`
+* Solana: `m/44'/501'/0'/0'`
+
+For a complete list of coin types and possible HD paths, refer to the [SLIP-0044 specification](https://github.com/satoshilabs/slips/blob/master/slip-0044.md).
+
+#### Address formats and curves
+
+See below for specific address formats that you can currently derive on Turnkey:
+
+| Type | Address Format | Curve | Default HD Path |
+| -------- | ----------------------------------------- | ---------------- | ------------------ |
+| n/a | ADDRESS\_FORMAT\_COMPRESSED | CURVE\_SECP256K1 | m/0'/0 |
+| n/a | ADDRESS\_FORMAT\_UNCOMPRESSED | CURVE\_SECP256K1 | m/0'/0 |
+| Ethereum | ADDRESS\_FORMAT\_ETHEREUM | CURVE\_SECP256K1 | m/44'/60'/0'/0/0 |
+| Cosmos | ADDRESS\_FORMAT\_COSMOS | CURVE\_SECP256K1 | m/44'/118'/0'/0/0 |
+| Solana | ADDRESS\_FORMAT\_SOLANA | CURVE\_ED25519 | m/44'/501'/0'/0 |
+| Tron | ADDRESS\_FORMAT\_TRON | CURVE\_SECP256K1 | m/44'/195'/0'/0/0 |
+| Sui | ADDRESS\_FORMAT\_SUI | CURVE\_ED25519 | m/44'/784'/0'/0/0 |
+| Aptos | ADDRESS\_FORMAT\_APTOS | CURVE\_ED25519 | m/44'/637'/0'/0'/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_MAINNET\_P2PKH | CURVE\_SECP256K1 | m/44'/0'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_MAINNET\_P2SH | CURVE\_SECP256K1 | m/49'/0'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_MAINNET\_P2WPKH | CURVE\_SECP256K1 | m/84'/0'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_MAINNET\_P2WSH | CURVE\_SECP256K1 | m/48'/0'/0'/2'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_MAINNET\_P2TR | CURVE\_SECP256K1 | m/86'/0'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_TESTNET\_P2PKH | CURVE\_SECP256K1 | m/44'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_TESTNET\_P2SH | CURVE\_SECP256K1 | m/49'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_TESTNET\_P2WPKH | CURVE\_SECP256K1 | m/84'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_TESTNET\_P2WSH | CURVE\_SECP256K1 | m/48'/1'/0'/2'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_TESTNET\_P2TR | CURVE\_SECP256K1 | m/86'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_SIGNET\_P2PKH | CURVE\_SECP256K1 | m/44'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_SIGNET\_P2SH | CURVE\_SECP256K1 | m/49'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_SIGNET\_P2WPKH | CURVE\_SECP256K1 | m/84'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_SIGNET\_P2WSH | CURVE\_SECP256K1 | m/48'/1'/0'/2'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_SIGNET\_P2TR | CURVE\_SECP256K1 | m/86'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_REGTEST\_P2PKH | CURVE\_SECP256K1 | m/44'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_REGTEST\_P2SH | CURVE\_SECP256K1 | m/49'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_REGTEST\_P2WPKH | CURVE\_SECP256K1 | m/84'/1'/0'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_REGTEST\_P2WSH | CURVE\_SECP256K1 | m/48'/1'/0'/2'/0/0 |
+| Bitcoin | ADDRESS\_FORMAT\_BITCOIN\_REGTEST\_P2TR | CURVE\_SECP256K1 | m/86'/1'/0'/0/0 |
+| Sei | ADDRESS\_FORMAT\_SEI | CURVE\_ED25519 | m/44'/118'/0'/0/0 |
+| Stellar | ADDRESS\_FORMAT\_XLM | CURVE\_ED25519 | m/44'/148'/0'/0'/0 |
+| Dogecoin | ADDRESS\_FORMAT\_DOGE\_MAINNET | CURVE\_SECP256K1 | m/44'/3'/0'/0/0 |
+| Dogecoin | ADDRESS\_FORMAT\_DOGE\_TESTNET | CURVE\_SECP256K1 | m/44'/1'/0'/0/0 |
+| TON | ADDRESS\_FORMAT\_TON\_V3R2 | CURVE\_ED25519 | m/44'/607'/0'/0/0 |
+| TON | ADDRESS\_FORMAT\_TON\_V4R2 | CURVE\_ED25519 | m/44'/607'/0'/0/0 |
+| XRP | ADDRESS\_FORMAT\_XRP | CURVE\_SECP256K1 | m/44'/144'/0'/0/0 |
+
+#### Where can I learn more?
+
+In addition to the guide mentioned above on [HD Wallets](https://learnmeabitcoin.com/technical/hd-wallets), there is also a page specifically on [Derivation Paths](https://learnmeabitcoin.com/technical/derivation-paths).
+
+#### What if I don't see the address format for my network?
+
+You can use `ADDRESS_FORMAT_COMPRESSED` to generate a public key which can be used to sign with.
+
+#### What if I don't see the curve for my network?
+
+Contact us at [hello@turnkey.com](mailto:hello@turnkey.com).
+
+## Delete wallets
+
+To delete wallets you can call the [delete wallets activity](/api-reference/wallets/delete-wallets). Before deleting a wallet it must have been exported to prevent loss of funds, or you can pass in the `deleteWithoutExport` parameter with the value `true` to override this. The `deleteWithoutExport` parameter, if not passed in, is default `false`. Note that this activity must be initiated by the wallet owner.
+
+## Private Keys
+
+Turnkey also supports raw private keys, but we recommend using Wallets since they offer several advantages:
+
+* Wallets can be used across various cryptographic curves
+* Wallets can generate millions of addresses for various digital assets
+* Wallets can be represented by a checksummed, mnemonic phrase making them easier to backup and recover
+
+## Export keys
+
+Exporting on Turnkey enables you or your end users to export a copy of a Wallet or Private Key from our system at any time. While most Turnkey users opt to keep Wallets within Turnkey's secure infrastructure, the export functionality means you are never locked into Turnkey, and gives you the freedom to design your own backup processes as you see fit. Check out our [Export Wallet guide](/wallets/export-wallets) to allow your users to securely export their wallets.
+
+## Import keys
+
+Importing on Turnkey enables you or your end users to import a Wallet or Private Key to our system. Check out our [Import Wallet guide](/wallets/import-wallets) to allow your users to securely import their wallets.
+
+## Delete keys
+
+To delete prviate keys you can call the [delete private keys activity](/api-reference/private-keys/delete-private-keys). Before deleting a private key it must have been exported to prevent loss of funds, or you can pass in the `deleteWithoutExport` parameter with the value `true` to override this. The `deleteWithoutExport` parameter, if not passed in, is default `false`. Note that this activity must be initiated by the private key owner.
diff --git a/docs/documentation/developer-reference/api-overview/errors.md b/developer-reference/api-overview/errors.mdx
similarity index 71%
rename from docs/documentation/developer-reference/api-overview/errors.md
rename to developer-reference/api-overview/errors.mdx
index a52be9f3..595b3e35 100644
--- a/docs/documentation/developer-reference/api-overview/errors.md
+++ b/developer-reference/api-overview/errors.mdx
@@ -1,26 +1,21 @@
---
-sidebar_position: 5
-description: Enumerating all errors received using the Turnkey API
-slug: /developer-reference/api-overview/errors
+title: "Errors"
+description: "An error returned by the Turnkey API might look something like this:"
---
-# Errors
-
-An error returned by the Turnkey API might look something like this:
-
-```
+```bash
Turnkey error 3: organization mismatch: request is targeting organization ("USER SUB ORG"), but voters are in organization ("OUR MAIN ORG")
```
Within this error message there are a few different parts that are worth breaking down. First the GRPC Error code. This looks like this:
-```
+```bash
Turnkey error 3:
```
This GRPC error wraps what we call a Turnkey Error which looks something like:
-```
+```bash
organization mismatch: request is targeting organization ("USER SUB ORG"), but voters are in organization ("OUR MAIN ORG")
```
@@ -88,37 +83,37 @@ The below table enumerates all errors across different actions that can be taken
## GRPC Error Codes
-Turnkey uses GRPC internally to communicate with our internal services whenever an API request is made. Due to this some errors will be wrapped with GRPC error messages. These error codes are listed below for your convenience, however these will not remain in Turnkey error messages forever and you should **not** do error handling based on these codes as these could be removed at any time. In the following example `Turnkey error 3:` represents a grpc error (error code 3, INVALID_ARGUMENT) wrapping a Turnkey error.
+Turnkey uses GRPC internally to communicate with our internal services whenever an API request is made. Due to this some errors will be wrapped with GRPC error messages. These error codes are listed below for your convenience, however these will not remain in Turnkey error messages forever and you should **not** do error handling based on these codes as these could be removed at any time. In the following example `Turnkey error 3:` represents a grpc error (error code 3, INVALID\_ARGUMENT) wrapping a Turnkey error.
Example
-```
+```bash
Turnkey error 3: organization mismatch: request is targeting organization ("USER SUB ORG"), but voters are in organization ("OUR MAIN ORG")
```
### GRPC Status Codes Reference
-| Code | Number | Description |
-| ------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| OK | 0 | Not an error; returned on success. |
-| CANCELLED | 1 | The operation was cancelled, typically by the caller. |
-| UNKNOWN | 2 | Unknown error. For example, this error may be returned when a `Status` value received from another address space belongs to an error space that is not known in this address space. Also errors raised by APIs that do not return enough error information may be converted to this error. |
-| INVALID_ARGUMENT | 3 | The client specified an invalid argument. Note that this differs from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments that are problematic regardless of the state of the system (e.g., a malformed file name). |
-| DEADLINE_EXCEEDED | 4 | The deadline expired before the operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long |
-| NOT_FOUND | 5 | Some requested entity (e.g., file or directory) was not found. Note to server developers: if a request is denied for an entire class of users, such as gradual feature rollout or undocumented allowlist, `NOT_FOUND` may be used. If a request is denied for some users within a class of users, such as user-based access control, `PERMISSION_DENIED` must be used. |
-| ALREADY_EXISTS | 6 | The entity that a client attempted to create (e.g., file or directory) already exists. |
-| PERMISSION_DENIED | 7 | The caller does not have permission to execute the specified operation. `PERMISSION_DENIED` must not be used for rejections caused by exhausting some resource (use `RESOURCE_EXHAUSTED` instead for those errors). `PERMISSION_DENIED` must not be used if the caller can not be identified (use `UNAUTHENTICATED` instead for those errors). This error code does not imply the request is valid or the requested entity exists or satisfies other pre-conditions. |
-| RESOURCE_EXHAUSTED | 8 | Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space. |
-| FAILED_PRECONDITION | 9 | The operation was rejected because the system is not in a state required for the operation's execution. For example, the directory to be deleted is non-empty, an rmdir operation is applied to a non-directory, etc. Service implementors can use the following guidelines to decide between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: (a) Use `UNAVAILABLE` if the client can retry just the failing call. (b) Use `ABORTED` if the client should retry at a higher level (e.g., when a client-specified test-and-set fails, indicating the client should restart a read-modify-write sequence). (c) Use `FAILED_PRECONDITION` if the client should not retry until the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory is non-empty, `FAILED_PRECONDITION` should be returned since the client should not retry unless the files are deleted from the directory. |
-| ABORTED | 10 | The operation was aborted, typically due to a concurrency issue such as a sequencer check failure or transaction abort. See the guidelines above for deciding between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`. |
-| OUT_OF_RANGE | 11 | The operation was attempted past the valid range. E.g., seeking or reading past end-of-file. Unlike `INVALID_ARGUMENT`, this error indicates a problem that may be fixed if the system state changes. For example, a 32-bit file system will generate `INVALID_ARGUMENT` if asked to read at an offset that is not in the range [0,2^32-1], but it will generate `OUT_OF_RANGE` if asked to read from an offset past the current file size. There is a fair bit of overlap between `FAILED_PRECONDITION` and `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific error) when it applies so that callers who are iterating through a space can easily look for an `OUT_OF_RANGE` error to detect when they are done. |
-| UNIMPLEMENTED | 12 | The operation is not implemented or is not supported/enabled in this service. |
-| INTERNAL | 13 | Internal errors. This means that some invariants expected by the underlying system have been broken. This error code is reserved for serious errors. |
-| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. Note that it is not always safe to retry non-idempotent operations. |
-| DATA_LOSS | 15 | Unrecoverable data loss or corruption. |
-| UNAUTHENTICATED | 16 | The request does not have valid authentication credentials for the operation. |
-
-Source: https://grpc.io/docs/guides/status-codes/
+| Code | Number | Description |
+| -------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| OK | 0 | Not an error; returned on success. |
+| CANCELLED | 1 | The operation was cancelled, typically by the caller. |
+| UNKNOWN | 2 | Unknown error. For example, this error may be returned when a `Status` value received from another address space belongs to an error space that is not known in this address space. Also errors raised by APIs that do not return enough error information may be converted to this error. |
+| INVALID\_ARGUMENT | 3 | The client specified an invalid argument. Note that this differs from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments that are problematic regardless of the state of the system (e.g., a malformed file name). |
+| DEADLINE\_EXCEEDED | 4 | The deadline expired before the operation could complete. For operations that change the state of the system, this error may be returned even if the operation has completed successfully. For example, a successful response from a server could have been delayed long |
+| NOT\_FOUND | 5 | Some requested entity (e.g., file or directory) was not found. Note to server developers: if a request is denied for an entire class of users, such as gradual feature rollout or undocumented allowlist, `NOT_FOUND` may be used. If a request is denied for some users within a class of users, such as user-based access control, `PERMISSION_DENIED` must be used. |
+| ALREADY\_EXISTS | 6 | The entity that a client attempted to create (e.g., file or directory) already exists. |
+| PERMISSION\_DENIED | 7 | The caller does not have permission to execute the specified operation. `PERMISSION_DENIED` must not be used for rejections caused by exhausting some resource (use `RESOURCE_EXHAUSTED` instead for those errors). `PERMISSION_DENIED` must not be used if the caller can not be identified (use `UNAUTHENTICATED` instead for those errors). This error code does not imply the request is valid or the requested entity exists or satisfies other pre-conditions. |
+| RESOURCE\_EXHAUSTED | 8 | Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space. |
+| FAILED\_PRECONDITION | 9 | The operation was rejected because the system is not in a state required for the operation's execution. For example, the directory to be deleted is non-empty, an rmdir operation is applied to a non-directory, etc. Service implementors can use the following guidelines to decide between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: (a) Use `UNAVAILABLE` if the client can retry just the failing call. (b) Use `ABORTED` if the client should retry at a higher level (e.g., when a client-specified test-and-set fails, indicating the client should restart a read-modify-write sequence). (c) Use `FAILED_PRECONDITION` if the client should not retry until the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory is non-empty, `FAILED_PRECONDITION` should be returned since the client should not retry unless the files are deleted from the directory. |
+| ABORTED | 10 | The operation was aborted, typically due to a concurrency issue such as a sequencer check failure or transaction abort. See the guidelines above for deciding between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`. |
+| OUT\_OF\_RANGE | 11 | The operation was attempted past the valid range. E.g., seeking or reading past end-of-file. Unlike `INVALID_ARGUMENT`, this error indicates a problem that may be fixed if the system state changes. For example, a 32-bit file system will generate `INVALID_ARGUMENT` if asked to read at an offset that is not in the range \[0,2^32-1], but it will generate `OUT_OF_RANGE` if asked to read from an offset past the current file size. There is a fair bit of overlap between `FAILED_PRECONDITION` and `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific error) when it applies so that callers who are iterating through a space can easily look for an `OUT_OF_RANGE` error to detect when they are done. |
+| UNIMPLEMENTED | 12 | The operation is not implemented or is not supported/enabled in this service. |
+| INTERNAL | 13 | Internal errors. This means that some invariants expected by the underlying system have been broken. This error code is reserved for serious errors. |
+| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. Note that it is not always safe to retry non-idempotent operations. |
+| DATA\_LOSS | 15 | Unrecoverable data loss or corruption. |
+| UNAUTHENTICATED | 16 | The request does not have valid authentication credentials for the operation. |
+
+Source: [https://grpc.io/docs/guides/status-codes/](https://grpc.io/docs/guides/status-codes/)
## Troubleshooting
@@ -126,37 +121,35 @@ Source: https://grpc.io/docs/guides/status-codes/
Common causes:
-- An unknown organization ID was passed in a request made to the Turnkey API
+* An unknown organization ID was passed in a request made to the Turnkey API
Troubleshooting tips:
-- Confirm that you are using the proper Organization ID. All Turnkey resources are identified with a UUID, so confirm you are not passing a different resource's UUID as the organization ID in your request.
+* Confirm that you are using the proper Organization ID. All Turnkey resources are identified with a UUID, so confirm you are not passing a different resource's UUID as the organization ID in your request.
----
### malformed organization ID provided
Common causes:
-- An improperly formatted organization ID UUID was passed in a request made to the Turnkey API
+* An improperly formatted organization ID UUID was passed in a request made to the Turnkey API
Troubleshooting tips:
-- Confirm the the UUID conforms to the UUID standard `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX`
+* Confirm the the UUID conforms to the UUID standard `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX`
----
### bad request body
Common causes:
-- A malformed request body was passed in a request made to the Turnky API
+* A malformed request body was passed in a request made to the Turnky API
Troubleshooting tips:
-- A typical activity request has the `type`, `timestampMS`, and `organizationId` parameters at the top level and then a `parameters` parameter with more specific parameters based on the request type. For example a CREATE_WALLET activity request body might look something like this:
+* A typical activity request has the `type`, `timestampMS`, and `organizationId` parameters at the top level and then a `parameters` parameter with more specific parameters based on the request type. For example a CREATE\_WALLET activity request body might look something like this:
-```
+```json
{
"type": "ACTIVITY_TYPE_CREATE_WALLET",
"timestampMs": "",
@@ -176,653 +169,556 @@ Troubleshooting tips:
}
```
-- A get resource request body might look slightly different with less fields. An example of a GET_WALLET request body looks something like this:
+* A get resource request body might look slightly different with less fields. An example of a GET\_WALLET request body looks something like this:
-```
+```json
{
"organizationId": "string",
"walletId": "string"
}
```
----
-
### api operations disabled
Common causes:
-- Turnkey has disabled API operations globally.
+* Turnkey has disabled API operations globally.
Troubleshooting tips:
-- This situation will only happen in the most extreme case and should not be something you need to worry about.
+* This situation will only happen in the most extreme case and should not be something you need to worry about.
----
-### this organization cannot execute activities because it is over its allotted quota. Please reach out to the Turnkey team (help@turnkey.com) for more information.
+### this organization cannot execute activities because it is over its allotted quota. Please reach out to the Turnkey team ([help@turnkey.com](mailto:help@turnkey.com)) for more information.
Common causes:
-- You have exceeded your monthly signing quota. The first 25 signatures a month are free for "free" users.
-- You have reached a resource limit on a particular resource. You can find out about our resource limits [here](/concepts/resource-limits).
+* You have exceeded your monthly signing quota. The first 25 signatures a month are free for "free" users.
+* You have reached a resource limit on a particular resource. You can find out about our resource limits [here](/concepts/resource-limits).
Troubleshooting tips:
-- If you need to increase your signature limit take a look at our [pricing page](https://www.turnkey.com/pricing) and contact us at help@turnkey.com!
-- Resource limits are imposed globally and cannot be increased, speak with our team at help@turnkey.com to understand how to better integrate Turnkey with your system to utilize Turnkey to its highest potential.
+* If you need to increase your signature limit take a look at our [pricing page](https://www.turnkey.com/pricing) and contact us at [help@turnkey.com](mailto:help@turnkey.com)!
+* Resource limits are imposed globally and cannot be increased, speak with our team at [help@turnkey.com](mailto:help@turnkey.com) to understand how to better integrate Turnkey with your system to utilize Turnkey to its highest potential.
----
-
-### this sub-organization cannot execute activities because its parent is over its allotted quota. Please reach out to the Turnkey team (help@turnkey.com) for more information.
+### this sub-organization cannot execute activities because its parent is over its allotted quota. Please reach out to the Turnkey team ([help@turnkey.com](mailto:help@turnkey.com)) for more information.
Common causes:
-- You have exceeded your monthly signing quota. The first 25 signatures a month are free for "free" users.
-- You have reached a resource limit on a particular resource. You can find out about our resource limits [here](/concepts/resource-limits).
+* You have exceeded your monthly signing quota. The first 25 signatures a month are free for "free" users.
+* You have reached a resource limit on a particular resource. You can find out about our resource limits [here](/concepts/resource-limits).
Troubleshooting tips:
-- If you need to increase your signature limit take a look at our [pricing page](https://www.turnkey.com/pricing) and contact us at help@turnkey.com!
-- Resource limits are imposed globally and cannot be increased, speak with our team at help@turnkey.com to understand how to better integrate Turnkey with your system to utilize Turnkey to its highest potential.
-
----
-
-### this organization cannot execute activities because it has been rate limited. Please reach out to the Turnkey team (help@turnkey.com) for more information.
+* If you need to increase your signature limit take a look at our [pricing page](https://www.turnkey.com/pricing) and contact us at [help@turnkey.com](mailto:help@turnkey.com)!
+* Resource limits are imposed globally and cannot be increased, speak with our team at [help@turnkey.com](mailto:help@turnkey.com) to understand how to better integrate Turnkey with your system to utilize Turnkey to its highest potential.
+### this organization cannot execute activities because it has been rate limited. Please reach out to the Turnkey team ([help@turnkey.com](mailto:help@turnkey.com)) for more information.
Common causes:
-- You have exceeded your rate limit. We need to maintain a per-customer rate limit to ensure that the service we provide to all of our customers service can be exceptional.
+* You have exceeded your rate limit. We need to maintain a per-customer rate limit to ensure that the service we provide to all of our customers service can be exceptional.
Troubleshooting tips:
-- If you are interested in increasing your rate limit reach out to us at help@turnkey.com!
+* If you are interested in increasing your rate limit reach out to us at [help@turnkey.com](mailto:help@turnkey.com)!
----
-### this sub-organization cannot execute activities because its parent has been rate limited. Please reach out to the Turnkey team (help@turnkey.com) for more information.
+### this sub-organization cannot execute activities because its parent has been rate limited. Please reach out to the Turnkey team ([help@turnkey.com](mailto:help@turnkey.com)) for more information.
Common causes:
-- You have exceeded your rate limit. We need to maintain a per-customer rate limit to ensure that the service we provide to all of our customers service can be exceptional.
+* You have exceeded your rate limit. We need to maintain a per-customer rate limit to ensure that the service we provide to all of our customers service can be exceptional.
Troubleshooting tips:
-- If you are interested in increasing your rate limit reach out to us at help@turnkey.com!
+* If you are interested in increasing your rate limit reach out to us at [help@turnkey.com](mailto:help@turnkey.com)!
----
### request not authorized
-
Common causes:
-- A user that created a request is not allowed to complete the action that was requested.
-- For example a parent-organization trying to create a wallet within a sub-organization that does not have a delegated access API key.
+* A user that created a request is not allowed to complete the action that was requested.
+* For example a parent-organization trying to create a wallet within a sub-organization that does not have a delegated access API key.
Troubleshooting tips:
-- Confirm that you are using the correct credentials for the request you are making.
-- Confirm that all necessary [policies](/concepts/policies/overview) are in place so that the action that is requested can be performed.
+* Confirm that you are using the correct credentials for the request you are making.
+* Confirm that all necessary [policies](/concepts/policies/overview) are in place so that the action that is requested can be performed.
----
### no valid authentication signature found for request
Common causes:
-- No signature, or [stamp](/developer-reference/api-overview/stamps), is attached to a request. All requests made to Turnkey's api must be stamped so that Turnkey can authenticate and authorize the user who performed the request.
+* No signature, or [stamp](/developer-reference/api-overview/stamps), is attached to a request. All requests made to Turnkey's api must be stamped so that Turnkey can authenticate and authorize the user who performed the request.
Troubleshooting tips:
-- Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
-- At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
-
----
+* Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
+* At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
### could not find public key in organization
Common causes:
-- The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
+* The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
----
### failed while looking up public key in parent organization
Common causes:
-- The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
+* The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### could not find public key in organization or its parent organization
Common causes:
-- The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
+* The public key corresponding to the signature in a stamp is not found in the organization the request is targeting. This means that a request was formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
----
### could not verify WebAuthN signature
Common causes:
-- The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. Again this means the request is formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
+* The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. Again this means the request is formatted properly, but the authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### credential ID could not be found in organization or its parent organization
Common causes:
-- Turnkey cannot translate a public key obtained from a stamp that was created with a WebAuthn authenticator to a parent organization or one of its corresponding sub-organizations that the request was made for.
+* Turnkey cannot translate a public key obtained from a stamp that was created with a WebAuthn authenticator to a parent organization or one of its corresponding sub-organizations that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### public key could not be found in organization or its parent organization
Common causes:
-- Turnkey cannot translate a public key obtained from a stamp to a parent organization or one of its corresponding sub-organizations that the request was made for.
+* Turnkey cannot translate a public key obtained from a stamp to a parent organization or one of its corresponding sub-organizations that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticators to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticators to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### more than one suborg associated with a credential ID
-
Common causes:
-- This error occurs for requests like [whoami](https://docs.turnkey.com/api#tag/Sessions/operation/GetWhoami). In particular this request tries to go backwards from the stamp to the public key then to a corresponding sub-orgnaization under a parent organization. If there are multiple sub-organizations with the same public key corresponding to an authenticator it is unknown who is initiating that particular request without more context.
+* This error occurs for requests like [whoami](/api-reference/sessions/who-am-i). In particular this request tries to go backwards from the stamp to the public key then to a corresponding sub-orgnaization under a parent organization. If there are multiple sub-organizations with the same public key corresponding to an authenticator it is unknown who is initiating that particular request without more context.
Troubleshooting tips:
-- Inlcude the sub-organization ID in the whoami request body.
-- Avoid including the same authenticator in multiple sub-organizations
-
----
+* Inlcude the sub-organization ID in the whoami request body.
+* Avoid including the same authenticator in multiple sub-organizations
### more than one suborg associated with a public key
Common causes:
-- This error occurs for requests like [whoami](https://docs.turnkey.com/api#tag/Sessions/operation/GetWhoami). In particular this request tries to go backwards from the stamp to the public key then to a corresponding sub-orgnaization under a parent organization. If there are multiple sub-organizations with the same public key it is unknown who is initiating that particular request without more context.
+* This error occurs for requests like [whoami](/api-reference/sessions/who-am-i). In particular this request tries to go backwards from the stamp to the public key then to a corresponding sub-orgnaization under a parent organization. If there are multiple sub-organizations with the same public key it is unknown who is initiating that particular request without more context.
Troubleshooting tips:
-- Inlcude the sub-organization ID in the whoami request body.
-- Avoid including the same authenticator in multiple sub-organizations
-
----
+* Inlcude the sub-organization ID in the whoami request body.
+* Avoid including the same authenticator in multiple sub-organizations
### could not verify api key signature
Common causes:
-- The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the api-key used to create the request is not associated with the organization that the request was made for.
+* The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the api-key used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper api-keys to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper api-keys to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### expired api key
Common causes:
-- The API key used for the request has expired
+* The API key used for the request has expired
Troubleshooting tips:
-- Create a new API key to use for the request
-- Create an API key that doesn't expire
+* Create a new API key to use for the request
+* Create an API key that doesn't expire
----
### malformed activity stamp
Common causes:
-- The stamp attached to a request is not formatted properly.
+* The stamp attached to a request is not formatted properly.
Troubleshooting tips:
-- Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
-- At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
-
----
+* Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
+* At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
### could not extract webauthn stamp
Common causes:
-- A stamp is not attached to a request.
+* A stamp is not attached to a request.
Troubleshooting tips:
-- Take a look at the page on [stamps](../api-overview/stamps.md) to get some inforamtion about stamps, what they are, and how they are created.
-- At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
-
----
+* Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
+* At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
### could not extract api key stamp
Common causes:
-- A stamp is not attached to a request.
+* A stamp is not attached to a request.
Troubleshooting tips:
-- Take a look at the page on [stamps](../api-overview/stamps.md) to get some inforamtion about stamps, what they are, and how they are created.
-- At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
+* Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
+* At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
----
### cannot authenticate public API activity request without a stamp (X-Stamp/X-Stamp-Webauthn header)
Common causes:
-- A stamp is not attached to a request.
+* A stamp is not attached to a request.
Troubleshooting tips:
-- Take a look at the page on [stamps](../api-overview/stamps.md) to get some inforamtion about stamps, what they are, and how they are created.
-- At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
-
----
+* Take a look at the page on [stamps](/developer-reference/api-overview/stamps) to get some inforamtion about stamps, what they are, and how they are created.
+* At a base level our SDK's abstract away the complicated stamping process for you. [Here](https://github.com/tkhq/sdk/tree/main/examples) are some example projects with our JS/TS SDK to get you started!
### webauthn authenticator not found in organization
Common causes:
-- The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the webauthn authenticator used to create the request is not associated with the organization that the request was made for.
+* The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the webauthn authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticator to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticator to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### webauthn authenticator not found in organization or parent organization
Common causes:
-- The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the webauthn authenticator used to create the request is not associated with the organization that the request was made for.
+* The signature used to create a stamp for a request cannot be verified for the organization the request is targeting. This means the request is formatted properly, but the webauthn authenticator used to create the request is not associated with the organization that the request was made for.
Troubleshooting tips:
-- Ensure that you have added the proper authenticator to the organization you are targeting.
-- Ensure that you are targeting the proper organization.
-
----
+* Ensure that you have added the proper authenticator to the organization you are targeting.
+* Ensure that you are targeting the proper organization.
### invalid payload encoding
Common causes:
-- This error is specific to the [sign_raw_payload](https://docs.turnkey.com/api#tag/Signing/operation/SignRawPayload) endpoint. A valid encoding needs to be passed so that Turnkey can properly sign the requested message.
+* This error is specific to the [sign\_raw\_payload](/api-reference/signing/sign-raw-payload) endpoint. A valid encoding needs to be passed so that Turnkey can properly sign the requested message.
Troubleshooting tips:
-- Use a valid encoding scheme from the following: `PAYLOAD_ENCODING_HEXADECIMAL`, `PAYLOAD_ENCODING_TEXT_UTF8`
-
----
+* Use a valid encoding scheme from the following: `PAYLOAD_ENCODING_HEXADECIMAL`, `PAYLOAD_ENCODING_TEXT_UTF8`
### invalid hash function
Common causes:
-- This error is specific to the [sign_raw_payload](https://docs.turnkey.com/api#tag/Signing/operation/SignRawPayload) endpoint. A valid hash function needs to be passed so that Turnkey can properly hash and sign the requested message.
+* This error is specific to the [sign\_raw\_payload](/api-reference/signing/sign-raw-payload) endpoint. A valid hash function needs to be passed so that Turnkey can properly hash and sign the requested message.
Troubleshooting tips:
-- Use a valid hash function scheme from the following: `HASH_FUNCTION_NO_OP`, `HASH_FUNCTION_SHA256`, `HASH_FUNCTION_KECCAK256`, `HASH_FUNCTION_NOT_APPLICABLE`
-- More information about `HASH_FUNCTION_NO_OP` [here](/faq#what-does-hash_function_no_op-mean)
-- More information about `HASH_FUNCTION_NOT_APPLICABLE` [here](/faq#what-is-hash_function_not_applicable-and-how-does-it-differ-from-hash_function_no_op)
-
----
+* Use a valid hash function scheme from the following: `HASH_FUNCTION_NO_OP`, `HASH_FUNCTION_SHA256`, `HASH_FUNCTION_KECCAK256`, `HASH_FUNCTION_NOT_APPLICABLE`
+* More information about `HASH_FUNCTION_NO_OP` [here](/faq#what-does-hash_function_no_op-mean)
+* More information about `HASH_FUNCTION_NOT_APPLICABLE` [here](/faq#what-is-hash_function_not_applicable-and-how-does-it-differ-from-hash_function_no_op)
### invalid magic link template
Common causes:
-- The email template provided for specific activities is invalid.
+* The email template provided for specific activities is invalid.
Troubleshooting tips:
-- Read more about [bespoke email templates](/embedded-wallets/sub-organization-auth#bespoke-email-templates)
-- Reach out to Turnkey at help@turnkey.com!
-
----
+* Read more about [bespoke email templates](/embedded-wallets/sub-organization-auth#bespoke-email-templates)
+* Reach out to Turnkey at [help@turnkey.com](mailto:help@turnkey.com)!
### failed to get email template contents
Common causes:
-- There was an error getting the email template for an associated activity
+* There was an error getting the email template for an associated activity
Troubleshooting tips:
-- Reach out to Turnkey at help@turnkey.com!
-
----
+* Reach out to Turnkey at [help@turnkey.com](mailto:help@turnkey.com)!
### failed to unmarshal template variables
Common causes:
-- There are invalid template variables used in your email template.
+* There are invalid template variables used in your email template.
Troubleshooting tips:
-- Read more about [bespoke email templates](/embedded-wallets/sub-organization-auth#bespoke-email-templates)
-- Reach out to Turnkey at help@turnkey.com!
-
----
+* Read more about [bespoke email templates](/embedded-wallets/sub-organization-auth#bespoke-email-templates)
+* Reach out to Turnkey at [help@turnkey.com](mailto:help@turnkey.com)!
### authentication failed
Common causes:
-- Turnkey was unable to authenticate the user based on the stamp provided.
+* Turnkey was unable to authenticate the user based on the stamp provided.
Troubleshooting tips:
-- Ensure that all proper authenticators and api-keys have been added to the organization.
-- Read more about how to create a stamp for a request [here](/developer-reference/api-overview/stamps)
-
----
+* Ensure that all proper authenticators and api-keys have been added to the organization.
+* Read more about how to create a stamp for a request [here](/developer-reference/api-overview/stamps)
### failed to load organizations
Common causes:
-- A request is targeting an unknown organization ID.
+* A request is targeting an unknown organization ID.
Troubleshooting tips:
-- Ensure that the passed organization ID in the request is valid.
-
----
+* Ensure that the passed organization ID in the request is valid.
### policy label must be unique
Common causes:
-- A new policy that is to be created shares the same name as a different policy. Policy names must be unique, and names in general must be unique per resource, so that they can be properly identified.
+* A new policy that is to be created shares the same name as a different policy. Policy names must be unique, and names in general must be unique per resource, so that they can be properly identified.
Troubleshooting tips:
-- Change the label/name that will be used for the new policy.
-- Delete the old policy.
-- Update the old policy to have a new name.
-
----
+* Change the label/name that will be used for the new policy.
+* Delete the old policy.
+* Update the old policy to have a new name.
### invalid policy consensus
Common causes:
-- An invalid consensus expression is passed.
+* An invalid consensus expression is passed.
Troubleshooting tips:
-- Read more about policy structure [here](/concepts/policies/overview#policy-structure)
-
----
+* Read more about policy structure [here](/concepts/policies/overview#policy-structure)
### invalid policy condition
Common causes:
-- An invalid condition expression is passed.
+* An invalid condition expression is passed.
Troubleshooting tips:
-- Read more about policy structure [here](/concepts/policies/overview#policy-structure)
-
----
+* Read more about policy structure [here](/concepts/policies/overview#policy-structure)
### quorum threshold must be non-zero integer
Common causes:
-- Quorum is the required amount of approvals by [root quorum members](/concepts/users/root-quorum) needed for an action to take place within an organization.
+* Quorum is the required amount of approvals by [root quorum members](/concepts/users/root-quorum) needed for an action to take place within an organization.
Troubleshooting tips:
-- When creating a sub-organization or updating the root quroum amount, use a non-zero positive integer.
-
----
+* When creating a sub-organization or updating the root quroum amount, use a non-zero positive integer.
### quorum users missing
Common causes:
-- A user marked as part of the root quorum is missing from the set of users within an organization. This is a validation error that can occur when trying to delete a user that is part of the root quorum.
+* A user marked as part of the root quorum is missing from the set of users within an organization. This is a validation error that can occur when trying to delete a user that is part of the root quorum.
Troubleshooting tips:
-- Before deleting the user, remove them from the root quroum using [Update Root Quorum](https://docs.turnkey.com/api#tag/Organizations/operation/UpdateRootQuorum)
-
----
+* Before deleting the user, remove them from the root quroum using [Update Root Quorum](/api-reference/organizations/update-root-quorum)
### invalid api key expiration
Common causes:
-- An invalid expiration time was passed in for an api key's expiration time parameter when using [Create API Key](https://docs.turnkey.com/api#tag/API-Keys/operation/CreateApiKeys)
+* An invalid expiration time was passed in for an api key's expiration time parameter when using [Create API Key](/api-reference/api-keys/create-api-keys)
Troubleshooting tips:
-- The `expirationSeconds` parameter is passed as string of seconds of how long the key should last. Any non-positive non-integer string will be considered invalid.
-
----
+* The `expirationSeconds` parameter is passed as string of seconds of how long the key should last. Any non-positive non-integer string will be considered invalid.
### missing parameter: user authenticator attestation
Common causes:
-- An attestation parameter is not passed when performing a request regarding an authenticator. For example [Create Authenticators](https://docs.turnkey.com/api#tag/Authenticators/operation/CreateAuthenticators)
+* An attestation parameter is not passed when performing a request regarding an authenticator. For example [Create Authenticators](/api-reference/authenticators/create-authenticators)
Troubleshooting tips:
-- The attestation generated by the authenticator includes a new key pair, the challenge, and device metadata that is signed, read more about attestations [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion).
-- An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
-
----
+* The attestation generated by the authenticator includes a new key pair, the challenge, and device metadata that is signed, read more about attestations [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion).
+* An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
### invalid authenticator attestation
Common causes:
-- An attestation parameter is not valid when performing a request regarding an authenticator. For example [Create Authenticators](https://docs.turnkey.com/api#tag/Authenticators/operation/CreateAuthenticators)
+* An attestation parameter is not valid when performing a request regarding an authenticator. For example [Create Authenticators](/api-reference/authenticators/create-authenticators)
Troubleshooting tips:
-- The attestation generated by the authenticator includes a new key pair, the challenge, and device metadata that is signed, read more about attestations [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion).
-- An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
-
----
+* The attestation generated by the authenticator includes a new key pair, the challenge, and device metadata that is signed, read more about attestations [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion).
+* An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
### missing parameter: user authenticator attestation auth data
Common causes:
-- The attestation auth data parameter is not valid when performing a request regarding an authenticator. For example [Create Authenticators](https://docs.turnkey.com/api#tag/Authenticators/operation/CreateAuthenticators). This parameter is obtained as part of the attestation object.
+* The attestation auth data parameter is not valid when performing a request regarding an authenticator. For example [Create Authenticators](/api-reference/authenticators/create-authenticators). This parameter is obtained as part of the attestation object.
Troubleshooting tips:
-- An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
-
----
+* An example of getting the correct parameters needed to use the Create Authenticators endpoint can be found within our [react-components](https://github.com/tkhq/sdk/blob/main/examples/react-components/src/app/dashboard/page.tsx#L246-L276) SDK example
### user has exceeded maximum authenticators
Common causes:
-- Turnkey allows up to 10 authenticators per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits).
+* Turnkey allows up to 10 authenticators per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits).
Troubleshooting Tips:
-- Delete any unecessary authenticators attached to a user.
-- Create a new user within the same organization and attach the authenicator to that user.
-
----
+* Delete any unecessary authenticators attached to a user.
+* Create a new user within the same organization and attach the authenicator to that user.
### user has exceeded maximum long-lived api keys
Common causes:
-- Turnkey allows up to 10 long-lived api keys per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits).
+* Turnkey allows up to 10 long-lived api keys per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits).
Troubleshooting Tips:
-- Delete any unecessary long-lived API keys attached to a user.
-- Create a new user within the same organization and attach the API key to that user.
-
----
+* Delete any unecessary long-lived API keys attached to a user.
+* Create a new user within the same organization and attach the API key to that user.
### user has exceeded maximum short-lived api keys
Common causes:
-- Turnkey allows up to 10 short-lived api keys per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits). Short-lived API keys will automatically be deleted from an organization when they are expired.
+* Turnkey allows up to 10 short-lived api keys per user. This is a hard resource limit. More information on resource limits [here](/concepts/resource-limits). Short-lived API keys will automatically be deleted from an organization when they are expired.
Troubleshooting Tips:
-- Delete any unecessary short-lived API keys attached to a user.
-- Create a new user within the same organization and attach the API key to that user.
-
----
+* Delete any unecessary short-lived API keys attached to a user.
+* Create a new user within the same organization and attach the API key to that user.
### missing wallet params
Common causes:
-- Some wallet/wallet account parameters have been omitted when creating a sub-organization
+* Some wallet/wallet account parameters have been omitted when creating a sub-organization
Troubleshooting tips:
-- Include all of the required parameters when creating a wallet during sub-organization creation. More info on the parameters [here](https://docs.turnkey.com/api#tag/Organizations/operation/CreateSubOrganization).
-
----
+* Include all of the required parameters when creating a wallet during sub-organization creation. More info on the parameters [here](/api-reference/organizations/create-sub-organization).
### invalid path format
Common causes:
-- This error occurs when an invalid path format parameter is passed to a request like [Create Wallet Accounts](https://docs.turnkey.com/api#tag/Wallets/operation/CreateWalletAccounts).
+* This error occurs when an invalid path format parameter is passed to a request like [Create Wallet Accounts](/api-reference/wallets/create-wallet-accounts).
Troubleshooting tips:
-- For now the path format must be: `PATH_FORMAT_BIP32`.
-
----
+* For now the path format must be: `PATH_FORMAT_BIP32`.
### invalid path
Common causes:
-- An invalid path parameter is passed to a request like [Create Wallet Accounts](https://docs.turnkey.com/api#tag/Wallets/operation/CreateWalletAccounts). Paths cannot be reused within the same HD wallet.
+* An invalid path parameter is passed to a request like [Create Wallet Accounts](/api-reference/wallets/create-wallet-accounts). Paths cannot be reused within the same HD wallet.
Troubleshooting tips:
-- The path is a string that is used to derive a new account within an HD wallet. A list of default paths per address format can be found [here](/concepts/wallets#hd-wallet-default-paths)
-- Paths cannot be reused within the same HD wallet.
-
----
+* The path is a string that is used to derive a new account within an HD wallet. A list of default paths per address format can be found [here](/concepts/wallets#hd-wallet-default-paths)
+* Paths cannot be reused within the same HD wallet.
### invalid address format
Common causes:
-- An invalid address format parameter is passed to a request like [Create Wallet Accounts](https://docs.turnkey.com/api#tag/Wallets/operation/CreateWalletAccounts).
+* An invalid address format parameter is passed to a request like [Create Wallet Accounts](/api-reference/wallets/create-wallet-accounts).
Troubleshooting tips:
-- Turnkey offers a wide range of support for many ecosystems. A list of valid address formats can be found in the table [here](/concepts/wallets#address-formats-and-curves).
-- More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
-
----
+* Turnkey offers a wide range of support for many ecosystems. A list of valid address formats can be found in the table [here](/concepts/wallets#address-formats-and-curves).
+* More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
### invalid curve
Common causes:
-- An invalid curve parameter is passed to a request like [Create Wallet Accounts](https://docs.turnkey.com/api#tag/Wallets/operation/CreateWalletAccounts).
+* An invalid curve parameter is passed to a request like [Create Wallet Accounts](/api-reference/wallets/create-wallet-accounts).
Troubleshooting tips:
-- Before ecosystem level integrations Turnkey offers support on a curve level. This makes us extendable to any ecosystem that is based on a curve we support. A list of valid curve parameters can be found in the table [here](/concepts/wallets#address-formats-and-curves).
-- More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
-
----
+* Before ecosystem level integrations Turnkey offers support on a curve level. This makes us extendable to any ecosystem that is based on a curve we support. A list of valid curve parameters can be found in the table [here](/concepts/wallets#address-formats-and-curves).
+* More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
### curve required
Common causes:
-- The curve parameter is not passed to a request like [Create Wallet Accounts](https://docs.turnkey.com/api#tag/Wallets/operation/CreateWalletAccounts).
+* The curve parameter is not passed to a request like [Create Wallet Accounts](/api-reference/wallets/create-wallet-accounts).
Troubleshooting tips:
-- Before ecosystem level integrtaions Turnkey offers support on a curve level. This makes us extendable to any ecosystem that is based on a curve we support. A list of valid curve parameters can be found in the table [here](/concepts/wallets#address-formats-and-curves).
-- More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
-
----
+* Before ecosystem level integrtaions Turnkey offers support on a curve level. This makes us extendable to any ecosystem that is based on a curve we support. A list of valid curve parameters can be found in the table [here](/concepts/wallets#address-formats-and-curves).
+* More about Turnkey and general ecosystem support can be found [here](/ecosystems/framework).
### No activity found with fingerprint. Consensus activities must target an existing activity by fingerprint
Common causes:
-- This error occurs during the [Approve/Reject Activity](https://docs.turnkey.com/api#tag/Consensus/operation/ApproveActivity) activity. The fingerprint parameter must be a fingerprint of a valid activity.
+* This error occurs during the [Approve/Reject Activity](/api-reference/consensus/approve-activity) activity. The fingerprint parameter must be a fingerprint of a valid activity.
Troubleshooting tips:
-- Confirm that a valid fingerprint of an activity that requires approval or rejection is passed as part of this activity.
-
----
+* Confirm that a valid fingerprint of an activity that requires approval or rejection is passed as part of this activity.
### internal server error
Common causes:
-- This error is thrown for a variety of internal server errors that are not due to user error. These activities will have an error id passed with them like: `internal server error (9fbfda54-7141-4192-ae72-8bac3512149a)` that can be used for troubleshooting.
+* This error is thrown for a variety of internal server errors that are not due to user error. These activities will have an error id passed with them like: `internal server error (9fbfda54-7141-4192-ae72-8bac3512149a)` that can be used for troubleshooting.
Troubleshooting tips:
-- Retry the activity. This could be a fluke case and the following activity could pass without failure.
-- If you think there is problem or if your service is degraded, please reach out to Turnkey help@turnkey.com and provide the error id in the error message.
-
----
+* Retry the activity. This could be a fluke case and the following activity could pass without failure.
+* If you think there is problem or if your service is degraded, please reach out to Turnkey [help@turnkey.com](mailto:help@turnkey.com) and provide the error id in the error message.
diff --git a/developer-reference/api-overview/intro.mdx b/developer-reference/api-overview/intro.mdx
new file mode 100644
index 00000000..1d68b594
--- /dev/null
+++ b/developer-reference/api-overview/intro.mdx
@@ -0,0 +1,29 @@
+---
+title: "Introduction"
+description: "Turnkey's API is a remote procedure call (RPC) API."
+---
+
+## RPC/HTTP
+
+We chose RPC-over-HTTP for convenience and ease-of-use. Most of our users should be able to integrate with our API without a major re-architecture of their existing systems.
+
+Many client libraries are available to make requests to a RPC/HTTP API, across many languages. Turnkey will provide SDKs for the most popular programming languages. For other languages, a RPC/HTTP API ensures there is an easy integration path available via raw http clients.
+
+## POST-only
+
+If you look at the [API reference](/api-reference/overview) you'll notice that all API calls to Turnkey are HTTP POST requests. Requests contain a POST body and a header with a digital signature over the POST body. We call this digitial signature a [Stamp](/developer-reference/api-overview/stamps).
+
+Requests must be stamped by registered user credentials and verified by Turnkey's secure enclaves before they are processed. This ensures cryptographic integrity end-to-end which eliminates the ability for any party to modify a user's request.
+
+### Queries and Submissions
+
+Turnkey's API is divided into 2 broad categories: queries and submissions.
+
+* Queries are read requests (e.g. `get_users`, `list_users`)
+* Submissions are requests to execute a workload (e.g. `create_policy`, `sign_transaction`, `delete_user`)
+
+## Dive Deeper
+
+* Creating your first [Stamp](/developer-reference/api-overview/stamps)
+* Fetching data with [Queries](/developer-reference/api-overview/queries)
+* Executing workloads with [Submissions](/developer-reference/api-overview/submissions)
diff --git a/developer-reference/api-overview/queries.mdx b/developer-reference/api-overview/queries.mdx
new file mode 100644
index 00000000..a70479f3
--- /dev/null
+++ b/developer-reference/api-overview/queries.mdx
@@ -0,0 +1,7 @@
+---
+title: "Queries"
+description: "Queries are read requests to Turnkey's API. Query URL paths are prefixed with `/public/v1/query`. Queries are not subject to enforcement of the policy engine. All users within an organization can read any data within the organization."
+mode: wide
+---
+
+Additionally, parent organizations have the ability to query data for all of their sub-organizations.
diff --git a/developer-reference/api-overview/stamps.mdx b/developer-reference/api-overview/stamps.mdx
new file mode 100644
index 00000000..547fc526
--- /dev/null
+++ b/developer-reference/api-overview/stamps.mdx
@@ -0,0 +1,101 @@
+---
+title: "Stamps"
+description: "Every request made to Turnkey must include a signature over the POST body attached as a HTTP header. Our secure enclave applications use this signature to verify the integrity and authenticity of the request."
+---
+
+### API Keys
+
+To create a valid, API key stamped request follow these steps:
+
+
+ Sign the JSON-encoded POST body with your API key to produce a `signature` (DER-encoded)
+
+
+ Hex encode the `signature`
+
+
+
+ Create a JSON-encoded stamp:
+
+ * `publicKey`: the public key of API key
+ * `signature`: the signature produced by the API key
+ * `scheme`: `SIGNATURE_SCHEME_TK_API_P256`
+
+
+ Base64URL encode the stamp
+
+
+ Attach the encoded string to your request as a `X-Stamp` header
+
+
+ Submit the stamped request to Turnkey's API
+
+
+
+### WebAuthn
+
+To create a valid, Webauthn authenticator stamped request follow these steps:
+
+
+ Compute the webauthn challenge by hashing the POST body bytes (JSON encoded) with SHA256. For example, if the POST body is `{"organization_id": "1234", "type": "ACTIVITY_TYPE_CREATE_API_KEYS", "params": {"for": "example"}`, the webauthn challenge is the string `7e8b4653fc7e51dc119cea031942f4693b4742ceca4dda269b925802b38b2147`
+
+
+ Include the challenge amongst WebAuthn signing options. Refer to the existing stamper implementations in the [following section](#stampers)) for examples
+
+
+ * Note that if you need to pass the challenge as bytes, you'll need to utf8-encode the challenge string (in JS, the challenge bytes will be `TextEncoder().encode("7e8b4653fc7e51dc119cea031942f4693b4742ceca4dda269b925802b38b2147")`)
+ * Additional note for React Native contexts: the resulting string should then additionally be base64-encoded. See [implementation](https://github.com/tkhq/sdk/blob/b52db566e79a65eec8d8e7066053d6a3ac5f3943/packages/react-native-passkey-stamper/src/util.ts#L5-L10)
+
+
+
+
+ Create a JSON-encoded stamp:
+
+ * `credentialId`: the id of the webauthn authenticator
+ * `authenticatorData`: the authenticator data produced by Webauthn assertion
+ * `clientDataJson`: the client data produced by the Webauthn assertion
+ * `signature`: the signature produced by the Webauthn assertion
+
+
+
+ Attach the JSON-encoded stamp to your request as a `X-Stamp-Webauthn` header
+
+ * Header names are case-insensitive (so `X-Stamp-Webauthn` and `X-Stamp-WebAuthn` are considered equivalent)
+
+ * Unlike API key stamps, the format is just JSON; no base64URL encoding necessary! For example: `X-Stamp-Webauthn: {"authenticatorData":"UaQZ...","clientDataJson":"eyJ0...","credentialId":"Grf...","signature":"MEQ..."}`
+
+
+
+ Submit the stamped request to Turnkey's API. If you would like your client request to be proxied through a backend, refer to the patterns mentioned [here](/authentication/passkeys/integration#proxying-signed-requests). An example application that uses this pattern can be found at wallet.tx.xyz (code [here](https://github.com/tkhq/demo-embedded-wallet/))
+
+
+
+### Stampers
+
+Our [JS SDK](https://github.com/tkhq/sdk) and [CLI](https://github.com/tkhq/tkcli) abstract request stamping for you. If you choose to use an independent client, you will need to implement this yourself. For reference, check out our implementations:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Our CLI has a `--no-post` option to generate stamps without sending anything over the network. This is a useful tool should you have trouble with debugging stamping-related logic. A sample command might look something like:
+
+```json
+turnkey request --no-post --host api.turnkey.com --path /api/v1/sign --body '{"payload": "hello from TKHQ"}'
+{
+ "curlCommand": "curl -X POST -d'{\"payload\": \"hello from TKHQ\"}' -H'X-Stamp: eyJwdWJsaWNLZXkiOiIwMzI3YTUwMDMyZTZmMDYzMWQ1NjA1YjZhZGEzMmI3NzkwNzRmMzQ2ZTgxYjY4ZTEyODAxNjQwZjFjOWVlMDNkYWUiLCJzaWduYXR1cmUiOiIzMDQ0MDIyMDM2MjNkZWZkNjE4ZWIzZTIxOTk3MDQ5NjQwN2ViZTkyNDQ3MzE3ZGFkNzVlNDEyYmQ0YTYyNjdjM2I1ZTIyMjMwMjIwMjQ1Yjc0MDg0OGE3MmQwOGI2MGQ2Yzg0ZjMzOTczN2I2M2RiM2JjYmFkYjNiZDBkY2IxYmZiODY1NzE1ZDhiNSIsInNjaGVtZSI6IlNJR05BVFVSRV9TQ0hFTUVfVEtfQVBJX1AyNTYifQ' -v 'https://api.turnkey.com/api/v1/sign'",
+ "message": "{\"payload\": \"hello from TKHQ\"}",
+ "stamp": "eyJwdWJsaWNLZXkiOiIwMzI3YTUwMDMyZTZmMDYzMWQ1NjA1YjZhZGEzMmI3NzkwNzRmMzQ2ZTgxYjY4ZTEyODAxNjQwZjFjOWVlMDNkYWUiLCJzaWduYXR1cmUiOiIzMDQ0MDIyMDM2MjNkZWZkNjE4ZWIzZTIxOTk3MDQ5NjQwN2ViZTkyNDQ3MzE3ZGFkNzVlNDEyYmQ0YTYyNjdjM2I1ZTIyMjMwMjIwMjQ1Yjc0MDg0OGE3MmQwOGI2MGQ2Yzg0ZjMzOTczN2I2M2RiM2JjYmFkYjNiZDBkY2IxYmZiODY1NzE1ZDhiNSIsInNjaGVtZSI6IlNJR05BVFVSRV9TQ0hFTUVfVEtfQVBJX1AyNTYifQ"
+}
+```
diff --git a/docs/documentation/developer-reference/api-overview/submissions.md b/developer-reference/api-overview/submissions.mdx
similarity index 61%
rename from docs/documentation/developer-reference/api-overview/submissions.md
rename to developer-reference/api-overview/submissions.mdx
index 1dde2a39..6a74dcb9 100644
--- a/docs/documentation/developer-reference/api-overview/submissions.md
+++ b/developer-reference/api-overview/submissions.mdx
@@ -1,26 +1,21 @@
---
-sidebar_position: 4
-description: Secure execution with Turnkey
-slug: /developer-reference/api-overview/submissions
+title: "Submissions"
+description: "Submissions are requests to securely execute a workload. Submission URL paths are prefixed with `/public/v1/submit`. Submissions requests, if valid, produce an `Activity`."
---
-# Submissions
-
-Submissions are requests to securely execute a workload. Submission URL paths are prefixed with `/public/v1/submit`. Submissions requests, if valid, produce an `Activity`.
-
### Activites
Activities typically create, modify, or utilize a resource within Turnkey and are subject to consensus or condition enforcement via the policy engine. Activities are executed optimistically synchronous. This means that if we can process the request synchronously, we will. Otherwise, we'll fallback to asynchronous processing. Your services or applications should account for this by checking the response for the activity state:
-- If `activity.status == ACTIVITY_STATUS_COMPLETED`, `activity.result` field will be populated with a successful response.
-- If `activity.status == ACTIVITY_STATUS_FAILED`, `activity.failure` field will be populated with a failure reason.
-- If `activity.status == ACTIVITY_STATUS_CONSENSUS_NEEDED`, additional signatures are required to process the request.
-- If `activity.status == ACTIVITY_STATUS_PENDING`, the request is processing asynchronously.
+* If `activity.status == ACTIVITY_STATUS_COMPLETED`, `activity.result` field will be populated with a successful response.
+* If `activity.status == ACTIVITY_STATUS_FAILED`, `activity.failure` field will be populated with a failure reason.
+* If `activity.status == ACTIVITY_STATUS_CONSENSUS_NEEDED`, additional signatures are required to process the request.
+* If `activity.status == ACTIVITY_STATUS_PENDING`, the request is processing asynchronously.
You can get activity status updates by:
-- Re-submitting the request. See the notes on idempotency below.
-- Polling `get_activity` with the `activity.id`
+* Re-submitting the request. See the notes on idempotency below.
+* Polling `get_activity` with the `activity.id`
### Idempotency
diff --git a/docs/documentation/developer-reference/webhooks.md b/developer-reference/webhooks.mdx
similarity index 70%
rename from docs/documentation/developer-reference/webhooks.md
rename to developer-reference/webhooks.mdx
index a0b1ee8f..fa81c5e1 100644
--- a/docs/documentation/developer-reference/webhooks.md
+++ b/developer-reference/webhooks.mdx
@@ -1,13 +1,8 @@
---
-sidebar_position: 1
-description: Activity webhooks
-slug: /developer-reference/webhooks
+title: "Activity Webhooks"
+description: "Webhooks provide a powerful mechanism to receive real-time notifications about activity requests in your Turnkey organization. Additionally, you'll be able to receive all activity requests for both the parent organization and all its child organizations. This functionality can be enabled via the organization feature capabilities of our platform, as detailed in the section on [organization features](/concepts/organizations#features)."
---
-# Activity Webhooks
-
-Webhooks provide a powerful mechanism to receive real-time notifications about activity requests in your Turnkey organization. Additionally, you'll be able to receive all activity requests for both the parent organization and all its child organizations. This functionality can be enabled via the organization feature capabilities of our platform, as detailed in the section on [organization features](/concepts/organizations#features).
-
This guide is designed to walk you through the process of setting up webhooks, from environment preparation to verification of successful event capturing.
## Prerequisites
@@ -18,31 +13,35 @@ Before diving into webhook configuration, ensure you have completed the necessar
Begin by setting the necessary environment variables:
-```shell
-ORGANIZATION_ID=
-KEY_NAME=webhook-test
+```bash
+ORGANIZATION_ID=KEY_NAME=webhook-test
```
### API Key Generation
Generate an new API key using the Turnkey CLI with the following command:
-```shell
+```bash
turnkey generate api-key --organization $ORGANIZATION_ID --key-name $KEY_NAME
```
### Ngrok Installation and Setup
Ngrok is a handy tool that allows you to expose your local server to the internet. Follow these steps to set it up:
-
-1. Download Ngrok from [their website](https://ngrok.com/download).
-2. Follow the provided instructions to install Ngrok and configure your auth token.
+
+
+ Download Ngrok from [their website](https://ngrok.com/download).
+
+
+ Follow the provided instructions to install Ngrok and configure your auth token.
+
+
### Local Server Setup
Open a new terminal window and set up a local server to listen for incoming webhook events:
-```shell
+```bash
nc -l 8000
```
@@ -50,13 +49,13 @@ nc -l 8000
In another terminal, initiate Ngrok to forward HTTP requests to your local server:
-```shell
+```bash
ngrok http 8000
```
Here's an output of the above command:
-```
+```bash
Session Status online
Account Satoshi Nakamoto (Plan: Free)
Update update available (version 3.7.0, Ctrl-U to update)
@@ -72,7 +71,7 @@ Connections ttl opn rt1 rt5 p50 p90
Save the ngrok URL as an environment variable:
-```shell
+```bash
WEBHOOK_URL=https://04•••35.ngrok-free.app # Replace with the URL provided by ngrok
```
@@ -80,13 +79,13 @@ WEBHOOK_URL=https://04•••35.ngrok-free.app # Replace with the URL provided
To ensure Ngrok is correctly forwarding requests, perform a test using curl:
-```shell
+```bash
curl -X POST $WEBHOOK_URL -d "{}"
```
Example output:
-```shell
+```bash
POST / HTTP/1.1
Host:04b2-121-74-183-35.ngrok-free.app
User-Agent: curl/8.4.0
@@ -100,14 +99,13 @@ Accept-Encoding: gzip
{}
```
-After executing this command, you should see the request appear in the terminal where `nc` is running.
-Terminate the `nc` session by pressing CTRL+C and restart it by rerunning the `nc` command.
+After executing this command, you should see the request appear in the terminal where `nc` is running. Terminate the `nc` session by pressing CTRL+C and restart it by rerunning the `nc` command.
## Configuring the Webhook URL
Set your webhook URL using the Turnkey CLI with the following command:
-```shell
+```bash
turnkey request --path /public/v1/submit/set_organization_feature --body '{
"timestampMs": "'"$(date +%s)"'000",
"type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
@@ -121,11 +119,8 @@ turnkey request --path /public/v1/submit/set_organization_feature --body '{
### Testing Your Webhook
-Assuming the previous request executed successfully it's time to test out your webhook!
-In order to verify that your webhook is correctly configured and receiving data,
-we can simply execute the previous turnkey request command again which creates a new activity request that will be captured by your webhook.
-Monitor the terminal with `nc` running to observe the incoming webhook data.
+Assuming the previous request executed successfully it's time to test out your webhook! In order to verify that your webhook is correctly configured and receiving data, we can simply execute the previous turnkey request command again which creates a new activity request that will be captured by your webhook. Monitor the terminal with `nc` running to observe the incoming webhook data.
## Conclusion
-By following these steps, you should now have a functioning webhook setup that captures all activity requests for your organization and its sub-organizations. If you encounter any issues or have feedback about this feature, reach out on [slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw)!
+By following these steps, you should now have a functioning webhook setup that captures all activity requests for your organization and its sub-organizations. If you encounter any issues or have feedback about this feature, reach out on [slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ)!
diff --git a/docs.json b/docs.json
new file mode 100644
index 00000000..0459881a
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,653 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Turnkey",
+ "colors": {
+ "primary": "#6a5bf5",
+ "light": "#6a5bf5",
+ "dark": "#050a0b"
+ },
+ "favicon": "/favicon.svg",
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Documentation",
+ "groups": [
+ {
+ "group": "Documentation",
+ "pages": [
+ "home",
+ {
+ "group": "Getting Started",
+ "pages": [
+ "getting-started",
+ {
+ "group": "Quickstart",
+ "pages": [
+ "getting-started/quickstart",
+ "getting-started/embedded-wallet-quickstart",
+ "getting-started/signing-automation-quickstart"
+ ]
+ },
+ "getting-started/examples",
+ "getting-started/launch-checklist"
+ ]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "concepts/overview",
+ "concepts/organizations",
+ "concepts/sub-organizations",
+ {
+ "group": "Users",
+ "pages": [
+ "concepts/users/introduction",
+ "concepts/users/credentials",
+ "concepts/users/root-quorum",
+ "concepts/users/best-practices"
+ ]
+ },
+ "concepts/wallets",
+ "concepts/resource-limits",
+ {
+ "group": "Policies",
+ "pages": [
+ "concepts/policies/overview",
+ "concepts/policies/quickstart",
+ "concepts/policies/examples",
+ "concepts/policies/delegated-access",
+ "concepts/policies/language"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Authentication",
+ "pages": [
+ {
+ "group": "Passkeys",
+ "pages": [
+ "authentication/passkeys/introduction",
+ "authentication/passkeys/integration",
+ "authentication/passkeys/options",
+ "authentication/passkeys/native",
+ "authentication/passkeys/discoverable-vs-non-discoverable"
+ ]
+ },
+ "authentication/email",
+ "authentication/social-logins",
+ "authentication/sms",
+ "authentication/sessions"
+ ]
+ },
+ {
+ "group": "Wallets",
+ "pages": [
+ "wallets/pregenerated-wallets",
+ "wallets/import-wallets",
+ "wallets/export-wallets"
+ ]
+ },
+ {
+ "group": "Ecosystem",
+ "pages": [
+ "ecosystems/framework",
+ "ecosystems/ethereum",
+ "ecosystems/solana",
+ "ecosystems/bitcoin",
+ "ecosystems/others"
+ ]
+ },
+ {
+ "group": "Developer Reference",
+ "pages": [
+ {
+ "group": "API Overview",
+ "pages": [
+ "developer-reference/api-overview/intro",
+ "developer-reference/api-overview/stamps",
+ "developer-reference/api-overview/queries",
+ "developer-reference/api-overview/submissions",
+ "developer-reference/api-overview/errors"
+ ]
+ },
+ "developer-reference/webhooks"
+ ]
+ },
+ {
+ "group": "Security",
+ "pages": [
+ "category/security",
+ "security/our-approach",
+ "security/non-custodial-key-mgmt",
+ "security/secure-enclaves",
+ "security/quorum-deployments",
+ "security/verifiable-data",
+ "security/disaster-recovery",
+ "security/enclave-secure-channels",
+ "security/whitepaper",
+ "security/reporting-a-vulnerability"
+ ]
+ },
+ "faq"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Solutions",
+ "groups": [
+ {
+ "group": "Solutions",
+ "pages": [
+ {
+ "group": "Embedded Wallets",
+ "pages": [
+ "embedded-wallets/overview",
+ "embedded-wallets/sub-organizations-as-wallets",
+ "embedded-wallets/sub-organization-auth",
+ "embedded-wallets/sub-organization-recovery",
+ "reference/aa-wallets",
+ "reference/embedded-wallet-kit",
+ {
+ "group": "Code Examples",
+ "pages": [
+ "category/code-examples",
+ "embedded-wallets/code-examples/create-sub-org-passkey",
+ "embedded-wallets/code-examples/authenticate-user-passkey",
+ "embedded-wallets/code-examples/create-passkey-session",
+ "embedded-wallets/code-examples/create-user-email",
+ "embedded-wallets/code-examples/authenticate-user-email",
+ "embedded-wallets/code-examples/email-recovery",
+ "embedded-wallets/code-examples/add-credential",
+ "embedded-wallets/code-examples/wallet-auth",
+ "embedded-wallets/code-examples/signing-transactions",
+ "embedded-wallets/code-examples/import",
+ "embedded-wallets/code-examples/export"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Signing Automation",
+ "pages": [
+ "signing-automation/overview",
+ {
+ "group": "Code Examples",
+ "pages": [
+ "category/code-examples-1",
+ "signing-automation/code-examples/signing-transactions"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "SDK Reference",
+ "groups": [
+ {
+ "group": "SDK Reference",
+ "pages": [
+ "sdks/introduction",
+ "sdks/javascript-browser",
+ "sdks/javascript-server",
+ "sdks/react",
+ "sdks/react-native",
+ "sdks/golang",
+ "sdks/rust",
+ "sdks/Ruby",
+ "sdks/cli",
+ "sdks/flutter",
+ {
+ "group": "Swift",
+ "pages": [
+ "sdks/swift",
+ "sdks/swift/proxy-middleware",
+ "sdks/swift/register-passkey"
+ ]
+ },
+ {
+ "group": "Web3 Libraries",
+ "pages": [
+ "category/web3-libraries",
+ "sdks/web3/ethers",
+ "sdks/web3/viem",
+ "sdks/web3/cosmjs",
+ "sdks/web3/eip-1193",
+ "sdks/web3/solana"
+ ]
+ },
+ {
+ "group": "Advanced",
+ "pages": [
+ "category/advanced",
+ "sdks/advanced/turnkey-client",
+ "sdks/advanced/api-key-stamper",
+ "sdks/advanced/wallet-stamper",
+ "sdks/advanced/webauthn-stamper",
+ "sdks/advanced/iframe-stamper"
+ ]
+ },
+ "sdks/migration-path"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "API Reference",
+ "pages": [
+ {
+ "group": "API Reference",
+ "pages": [
+ "api-reference/overview",
+ {
+ "group": "Organizations",
+ "pages": [
+ "api-reference/organizations/overview",
+ "api-reference/organizations/get-configs",
+ "api-reference/organizations/get-suborgs",
+ "api-reference/organizations/get-verified-suborgs",
+ "api-reference/organizations/create-sub-organization",
+ "api-reference/organizations/delete-sub-organization",
+ "api-reference/organizations/update-root-quorum"
+ ]
+ },
+ {
+ "group": "Invitations",
+ "pages": [
+ "api-reference/invitations/overview",
+ "api-reference/invitations/create-invitations",
+ "api-reference/invitations/delete-invitation"
+ ]
+ },
+ {
+ "group": "Policies",
+ "pages": [
+ "api-reference/policies/overview",
+ "api-reference/policies/get-policy",
+ "api-reference/policies/list-policies",
+ "api-reference/policies/create-policies",
+ "api-reference/policies/create-policy",
+ "api-reference/policies/delete-policy",
+ "api-reference/policies/update-policy"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "api-reference/features/remove-organization-feature",
+ "api-reference/features/set-organization-feature"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Wallets and Private Keys",
+ "pages": [
+ {
+ "group": "Wallets",
+ "pages": [
+ "api-reference/wallets/overview",
+ "api-reference/wallets/get-wallet",
+ "api-reference/wallets/get-wallet-account",
+ "api-reference/wallets/list-wallets-accounts",
+ "api-reference/wallets/list-wallets",
+ "api-reference/wallets/create-wallet",
+ "api-reference/wallets/create-wallet-accounts",
+ "api-reference/wallets/delete-wallets",
+ "api-reference/wallets/export-wallet",
+ "api-reference/wallets/export-wallet-account",
+ "api-reference/wallets/import-wallet",
+ "api-reference/wallets/init-import-wallet",
+ "api-reference/wallets/update-wallet"
+ ]
+ },
+ {
+ "group": "Signing",
+ "pages": [
+ "api-reference/signing/overview",
+ "api-reference/signing/sign-raw-payload",
+ "api-reference/signing/sign-raw-payloads",
+ "api-reference/signing/sign-transaction"
+ ]
+ },
+ {
+ "group": "Private Keys",
+ "pages": [
+ "api-reference/private-keys/overview",
+ "api-reference/private-keys/get-private-key",
+ "api-reference/private-keys/list-private-keys",
+ "api-reference/private-keys/create-private-keys",
+ "api-reference/private-keys/delete-private-keys",
+ "api-reference/private-keys/export-private-key",
+ "api-reference/private-keys/import-private-key",
+ "api-reference/private-keys/init-import-private-key"
+ ]
+ },
+ {
+ "group": "Private Key Tags",
+ "pages": [
+ "api-reference/private-key-tags/overview",
+ "api-reference/private-key-tags/list-private-key-tags",
+ "api-reference/private-key-tags/create-private-key-tag",
+ "api-reference/private-key-tags/delete-private-key-tags",
+ "api-reference/private-key-tags/update-private-key-tag"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Users",
+ "pages": [
+ {
+ "group": "Users",
+ "pages": [
+ "api-reference/users/overview",
+ "api-reference/users/get-user",
+ "api-reference/users/list-users",
+ "api-reference/users/create-users",
+ "api-reference/users/delete-users",
+ "api-reference/users/update-user"
+ ]
+ },
+ {
+ "group": "User Tags",
+ "pages": [
+ "api-reference/user-tags/list-user-tags",
+ "api-reference/user-tags/create-user-tag",
+ "api-reference/user-tags/delete-user-tags",
+ "api-reference/user-tags/update-user-tag"
+ ]
+ },
+ {
+ "group": "User Recovery",
+ "pages": [
+ "api-reference/user-recovery/init-email-recovery",
+ "api-reference/user-recovery/recover-a-user"
+ ]
+ },
+ {
+ "group": "User Auth",
+ "pages": [
+ "api-reference/user-auth/get-oauth-providers",
+ "api-reference/user-auth/create-oauth-providers",
+ "api-reference/user-auth/delete-oauth-providers",
+ "api-reference/user-auth/perform-email-auth",
+ "api-reference/user-auth/init-otp-auth",
+ "api-reference/user-auth/oauth",
+ "api-reference/user-auth/otp-auth"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Credentials",
+ "pages": [
+ {
+ "group": "Authenticators",
+ "pages": [
+ "api-reference/authenticators/overview",
+ "api-reference/authenticators/get-authenticator",
+ "api-reference/authenticators/get-authenticators",
+ "api-reference/authenticators/create-authenticators",
+ "api-reference/authenticators/delete-authenticators"
+ ]
+ },
+ {
+ "group": "API keys",
+ "pages": [
+ "api-reference/api-keys/overview",
+ "api-reference/api-keys/get-api-key",
+ "api-reference/api-keys/get-api-key-1"
+ ]
+ },
+ {
+ "group": "Sessions",
+ "pages": [
+ "api-reference/sessions/who-am-i",
+ "api-reference/sessions/create-read-only-session",
+ "api-reference/sessions/create-read-write-session"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Activities",
+ "pages": [
+ {
+ "group": "Activities",
+ "pages": [
+ "api-reference/activities/overview",
+ "api-reference/activities/get-activity",
+ "api-reference/activities/list-activities"
+ ]
+ },
+ {
+ "group": "Consensus",
+ "pages": [
+ "api-reference/consensus/overview",
+ "api-reference/consensus/approve-activity",
+ "api-reference/consensus/reject-activity"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "Community",
+ "href": "https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ",
+ "icon": "slack"
+ },
+ {
+ "anchor": "Blog",
+ "href": "https://mirror.xyz/turnkeyhq.eth/",
+ "icon": "newspaper"
+ },
+ {
+ "anchor": "Contact Us",
+ "href": "https://www.turnkey.com/contact-us",
+ "icon": "headset"
+ }
+ ]
+ }
+ },
+ "logo": {
+ "light": "/logo/light.svg",
+ "dark": "/logo/dark.svg",
+ "href": "https://www.turnkey.com/"
+ },
+ "styling": {
+ "codeblocks": "system"
+ },
+ "navbar": {
+ "links": [
+ {
+ "label": "Login",
+ "href": "https://app.turnkey.com/dashboard"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Get Started",
+ "href": "https://app.turnkey.com/dashboard/auth/initial"
+ }
+ },
+ "footer": {
+ "socials": {
+ "x": "https://x.com/turnkeyhq",
+ "github": "https://github.com/tkhq",
+ "slack": "https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ",
+ "linkedin": "https://www.linkedin.com/company/turnkeyhq/"
+ }
+ },
+ "redirects": [
+ {
+ "source": "/users/sessions",
+ "destination": "/features/sessions",
+ "permanent": true
+ },
+ {
+ "source": "/users/:slug*",
+ "destination": "/concepts/users/:slug*",
+ "permanent": true
+ },
+ {
+ "source": "/api-introduction",
+ "destination": "/developer-reference/api-overview/intro",
+ "permanent": true
+ },
+ {
+ "source": "/api-design/:slug*",
+ "destination": "/api-overview/:slug*",
+ "permanent": true
+ },
+ {
+ "source": "/managing-policies/:slug*",
+ "destination": "/concepts/policies/:slug*",
+ "permanent": true
+ },
+ {
+ "source": "/managing-users/:slug*",
+ "destination": "/concepts/users/:slug*",
+ "permanent": true
+ },
+ {
+ "source": "/getting-started/email-auth",
+ "destination": "/features/email-auth",
+ "permanent": true
+ },
+ {
+ "source": "/concepts/email-auth",
+ "destination": "/features/email-auth",
+ "permanent": true
+ },
+ {
+ "source": "/concepts/email-recovery",
+ "destination": "/features/email-recovery",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/export-wallets",
+ "destination": "/features/export-wallets",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/import-wallets",
+ "destination": "/features/import-wallets",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/aa-wallets",
+ "destination": "/reference/aa-wallets",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/sub-organization-auth",
+ "destination": "/embedded-wallets/sub-organization-auth",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/sub-organization-recovery",
+ "destination": "/embedded-wallets/sub-organization-recovery",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/sub-organizations-as-wallets",
+ "destination": "/embedded-wallets/sub-organizations-as-wallets",
+ "permanent": true
+ },
+ {
+ "source": "/integration-guides/webhooks",
+ "destination": "/features/webhooks",
+ "permanent": true
+ },
+ {
+ "source": "/reference/react-components",
+ "destination": "/reference/embedded-wallet-kit",
+ "permanent": true
+ },
+ {
+ "source": "/getting-started/resource-limits",
+ "destination": "/concepts/resource-limits",
+ "permanent": true
+ },
+ {
+ "source": "/ecosystem-integrations/ethereum",
+ "destination": "/ecosystems/ethereum",
+ "permanent": true
+ },
+ {
+ "source": "/ecosystem-integrations/solana",
+ "destination": "/ecosystems/solana",
+ "permanent": true
+ },
+ {
+ "source": "/ecosystem-integrations/bitcoin",
+ "destination": "/ecosystems/bitcoin",
+ "permanent": true
+ },
+ {
+ "source": "/ecosystem-integrations/index",
+ "destination": "/ecosystems/framework",
+ "permanent": true
+ },
+ {
+ "source": "/passkeys/discoverable-vs-non-discoverable",
+ "destination": "/authentication/passkeys/discoverable-vs-non-discoverable",
+ "permanent": true
+ },
+ {
+ "source": "/passkeys/integration",
+ "destination": "/authentication/passkeys/integration",
+ "permanent": true
+ },
+ {
+ "source": "/passkeys/introduction",
+ "destination": "/authentication/passkeys/introduction",
+ "permanent": true
+ },
+ {
+ "source": "/passkeys/native",
+ "destination": "/authentication/passkeys/native",
+ "permanent": true
+ },
+ {
+ "source": "/passkeys/options",
+ "destination": "/authentication/passkeys/options",
+ "permanent": true
+ },
+ {
+ "source": "/features/oauth",
+ "destination": "/authentication/social-logins",
+ "permanent": true
+ },
+ {
+ "source": "/concepts/introduction",
+ "destination": "/concepts/overview",
+ "permanent": true
+ },
+ {
+ "source": "/features/email-auth",
+ "destination": "/authentication/email",
+ "permanent": true
+ },
+ {
+ "source": "/features/email-recovery",
+ "destination": "/authentication/email",
+ "permanent": true
+ },
+ {
+ "source": "/api-overview/:slug*",
+ "destination": "/developer-reference/api-overview/:slug*",
+ "permanent": true
+ }
+ ]
+}
diff --git a/docs/documentation/_category_.json b/docs/documentation/_category_.json
deleted file mode 100644
index 94e9cfac..00000000
--- a/docs/documentation/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Documentation",
- "position": 3,
- "collapsed": false
-}
diff --git a/docs/documentation/authentication/_category_.json b/docs/documentation/authentication/_category_.json
deleted file mode 100644
index f1f310b8..00000000
--- a/docs/documentation/authentication/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Authentication",
- "position": 4,
- "collapsed": true
-}
diff --git a/docs/documentation/authentication/passkeys/_category_.json b/docs/documentation/authentication/passkeys/_category_.json
deleted file mode 100644
index 40480212..00000000
--- a/docs/documentation/authentication/passkeys/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Passkeys",
- "position": 1,
- "collapsed": true
-}
diff --git a/docs/documentation/concepts/Wallets.md b/docs/documentation/concepts/Wallets.md
deleted file mode 100644
index a455855f..00000000
--- a/docs/documentation/concepts/Wallets.md
+++ /dev/null
@@ -1,130 +0,0 @@
----
-sidebar_position: 5
-sidebar_label: Wallets
-description: Learn about Wallets on Turnkey
-slug: /concepts/wallets
----
-
-# Wallets
-
-A [hierarchical deterministic (HD) Wallet](https://learnmeabitcoin.com/technical/hd-wallets) is a collection of cryptographic private/public key pairs that share a common seed. A Wallet is used to generate Accounts.
-
-```json
-{
- "walletId": "eb98ae4c-07eb-4117-9b2d-8a453c0e1e64",
- "walletName": "default"
-}
-```
-
-#### Configuration
-
-Wallet seeds are generated with a default mnemonic length of 12 words. The [BIP-39 specification](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports mnemonic lengths of 12, 15, 18, 21, and 24 words. To enhance your Wallet's security, you may consider opting for a longer mnemonic length. This optional `mnemonicLength` field can be set when creating a Wallet. It's important to note that once the Wallet seed is generated, the mnemonic is permanent and cannot be altered.
-
-## Accounts
-
-An account contains the directions for deriving a cryptographic key pair and corresponding address from a Wallet. In practice, this looks like:
-
-- The Wallet seed and Account curve are used to create a root key pair
-- The Account path format and path are used to derive an extended key pair from the root key pair
-- The Account address format is used to derive the address from the extended public key
-
-```json
-{
- "address": "0x7aAE6F67798D1Ea0b8bFB5b64231B2f12049DB5e",
- "addressFormat": "ADDRESS_FORMAT_ETHEREUM",
- "curve": "CURVE_SECP256K1",
- "path": "m/44'/60'/0'/0/0",
- "pathFormat": "PATH_FORMAT_BIP32",
- "walletId": "eb98ae4c-07eb-4117-9b2d-8a453c0e1e64"
-}
-```
-
-**The account address is used to sign with the underlying extended private key.**
-
-#### HD Wallet Default Paths
-
-HD wallets use standardized derivation paths to generate multiple accounts from a single seed. These paths follow a specific structure that allows for consistent address generation across different wallet implementations. Here are common default paths for some of the ecosystems supported by Turnkey:
-
-- Ethereum: `m/44'/60'/0'/0/0`
-- Cosmos: `m/44'/118'/0'/0/0`
-- Solana: `m/44'/501'/0'/0'`
-
-For a complete list of coin types and possible HD paths, refer to the [SLIP-0044 specification](https://github.com/satoshilabs/slips/blob/master/slip-0044.md).
-
-#### Address formats and curves
-
-See below for specific address formats that you can currently derive on Turnkey:
-
-| Type | Address Format | Curve | Default HD Path |
-| -------- | ------------------------------------- | --------------- | ------------------ |
-| n/a | ADDRESS_FORMAT_COMPRESSED | CURVE_SECP256K1 | m/0'/0 |
-| n/a | ADDRESS_FORMAT_UNCOMPRESSED | CURVE_SECP256K1 | m/0'/0 |
-| Ethereum | ADDRESS_FORMAT_ETHEREUM | CURVE_SECP256K1 | m/44'/60'/0'/0/0 |
-| Cosmos | ADDRESS_FORMAT_COSMOS | CURVE_SECP256K1 | m/44'/118'/0'/0/0 |
-| Solana | ADDRESS_FORMAT_SOLANA | CURVE_ED25519 | m/44'/501'/0'/0 |
-| Tron | ADDRESS_FORMAT_TRON | CURVE_SECP256K1 | m/44'/195'/0'/0/0 |
-| Sui | ADDRESS_FORMAT_SUI | CURVE_ED25519 | m/44'/784'/0'/0/0 |
-| Aptos | ADDRESS_FORMAT_APTOS | CURVE_ED25519 | m/44'/637'/0'/0'/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_MAINNET_P2PKH | CURVE_SECP256K1 | m/44'/0'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_MAINNET_P2SH | CURVE_SECP256K1 | m/49'/0'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_MAINNET_P2WPKH | CURVE_SECP256K1 | m/84'/0'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_MAINNET_P2WSH | CURVE_SECP256K1 | m/48'/0'/0'/2'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_MAINNET_P2TR | CURVE_SECP256K1 | m/86'/0'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_TESTNET_P2PKH | CURVE_SECP256K1 | m/44'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_TESTNET_P2SH | CURVE_SECP256K1 | m/49'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_TESTNET_P2WPKH | CURVE_SECP256K1 | m/84'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_TESTNET_P2WSH | CURVE_SECP256K1 | m/48'/1'/0'/2'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_TESTNET_P2TR | CURVE_SECP256K1 | m/86'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_SIGNET_P2PKH | CURVE_SECP256K1 | m/44'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_SIGNET_P2SH | CURVE_SECP256K1 | m/49'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_SIGNET_P2WPKH | CURVE_SECP256K1 | m/84'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_SIGNET_P2WSH | CURVE_SECP256K1 | m/48'/1'/0'/2'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_SIGNET_P2TR | CURVE_SECP256K1 | m/86'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_REGTEST_P2PKH | CURVE_SECP256K1 | m/44'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_REGTEST_P2SH | CURVE_SECP256K1 | m/49'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_REGTEST_P2WPKH | CURVE_SECP256K1 | m/84'/1'/0'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_REGTEST_P2WSH | CURVE_SECP256K1 | m/48'/1'/0'/2'/0/0 |
-| Bitcoin | ADDRESS_FORMAT_BITCOIN_REGTEST_P2TR | CURVE_SECP256K1 | m/86'/1'/0'/0/0 |
-| Sei | ADDRESS_FORMAT_SEI | CURVE_SECP256K1 | m/44'/118'/0'/0/0 |
-| Stellar | ADDRESS_FORMAT_XLM | CURVE_ED25519 | m/44'/148'/0'/0'/0 |
-| Dogecoin | ADDRESS_FORMAT_DOGE_MAINNET | CURVE_SECP256K1 | m/44'/3'/0'/0/0 |
-| Dogecoin | ADDRESS_FORMAT_DOGE_TESTNET | CURVE_SECP256K1 | m/44'/1'/0'/0/0 |
-| TON | ADDRESS_FORMAT_TON_V3R2 | CURVE_ED25519 | m/44'/607'/0'/0/0 |
-| TON | ADDRESS_FORMAT_TON_V4R2 | CURVE_ED25519 | m/44'/607'/0'/0/0 |
-| XRP | ADDRESS_FORMAT_XRP | CURVE_SECP256K1 | m/44'/144'/0'/0/0 |
-
-#### Where can I learn more?
-
-In addition to the guide mentioned above on [HD Wallets](https://learnmeabitcoin.com/technical/hd-wallets), there is also a page specifically on [Derivation Paths](https://learnmeabitcoin.com/technical/derivation-paths).
-
-#### What if I don't see the address format for my network?
-
-You can use `ADDRESS_FORMAT_COMPRESSED` to generate a public key which can be used to sign with.
-
-#### What if I don't see the curve for my network?
-
-Contact us at hello@turnkey.com.
-
-## Delete wallets
-
-To delete wallets you can call the [delete wallets activity](https://docs.turnkey.com/api#tag/Wallets/operation/DeleteWallets). Before deleting a wallet it must have been exported to prevent loss of funds, or you can pass in the `deleteWithoutExport` parameter with the value `true` to override this. The `deleteWithoutExport` parameter, if not passed in, is default `false`. Note that this activity must be initiated by the wallet owner.
-
-## Private Keys
-
-Turnkey also supports raw private keys, but we recommend using Wallets since they offer several advantages:
-
-- Wallets can be used across various cryptographic curves
-- Wallets can generate millions of addresses for various digital assets
-- Wallets can be represented by a checksummed, mnemonic phrase making them easier to backup and recover
-
-## Export keys
-
-Exporting on Turnkey enables you or your end users to export a copy of a Wallet or Private Key from our system at any time. While most Turnkey users opt to keep Wallets within Turnkey's secure infrastructure, the export functionality means you are never locked into Turnkey, and gives you the freedom to design your own backup processes as you see fit. Check out our [Export Wallet guide](/wallets/export-wallets) to allow your users to securely export their wallets.
-
-## Import keys
-
-Importing on Turnkey enables you or your end users to import a Wallet or Private Key to our system. Check out our [Import Wallet guide](/wallets/import-wallets) to allow your users to securely import their wallets.
-
-## Delete keys
-
-To delete prviate keys you can call the [delete private keys activity](https://docs.turnkey.com/api#tag/Private-Keys/operation/DeletePrivateKeys). Before deleting a private key it must have been exported to prevent loss of funds, or you can pass in the `deleteWithoutExport` parameter with the value `true` to override this. The `deleteWithoutExport` parameter, if not passed in, is default `false`. Note that this activity must be initiated by the private key owner.
diff --git a/docs/documentation/concepts/_category_.json b/docs/documentation/concepts/_category_.json
deleted file mode 100644
index 6e81e196..00000000
--- a/docs/documentation/concepts/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Concepts",
- "position": 3,
- "collapsed": true
-}
diff --git a/docs/documentation/concepts/organizations.md b/docs/documentation/concepts/organizations.md
deleted file mode 100644
index 0d6bd18b..00000000
--- a/docs/documentation/concepts/organizations.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-sidebar_position: 2
-sidebar_label: Organizations
-description: Understanding Turnkey organizations
-slug: /concepts/organizations
----
-
-# Organizations
-
-An organization is a logical grouping of resources (e.g. users, policies, wallets). These resources can only be accessed by authorized and permissioned users within the organization. Resources are not shared between organizations.
-
-## Root Quorum
-
-All organizations are controlled by a [Root Quorum](/concepts/users/root-quorum) which contains the root users and the required threshold of approvals to take any action. Only the root quorum can update the root quorum or feature set.
-
-## Features
-
-Organization features are Turnkey product offerings that organizations can opt-in to or opt-out of. Note that these features can be set and updated using the activities `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE` and `ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE`. The following is a list of such features:
-
-| Name | Description | Default | Notes |
-| ----------------------------- | --------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| FEATURE_NAME_EMAIL_AUTH | Enables email authentication activities | Enabled | Can only be initiated by a parent organization for a sub-organization. |
-| FEATURE_NAME_EMAIL_RECOVERY | Enables email recovery activities | Enabled | Can only be initiated by a parent organization for a sub-organization. |
-| FEATURE_NAME_WEBAUTHN_ORIGINS | The origin Webauthn credentials are scoped to | Disabled | Parent organization feature applies to all sub-organizations.
If not enabled, sub-organizations default to allowing all origins: "\*". For Passkey WaaS, we highly recommend enabling this feature.
Example value: "https://www.turnkey.com" |
-| FEATURE_NAME_WEBHOOK | A URL to receive activity notification events | Disabled | This feature is currently experimental.
Example value: "https://your.service.com/webhook" |
-
-## Permissions
-
-All activity requests are subject to enforcement by Turnkey's policy engine. The policy engine determines if a request is allowed by checking the following:
-
-- Does this request violate our feature set?
- - Email recovery cannot be initiated if disabled
- - Email auth cannot be initiated if disabled
-- Should this request be denied by default?
- - All import requests must target your own user
-- Does this request meet the root quorum threshold?
-- What is the outcome of evaluating this request against all organization policies? Outcomes include:
- - `OUTCOME_ALLOW`: the request is allowed to process
- - `OUTCOME_REQUIRES_CONSENSUS`: the request needs additional approvals
- - `OUTCOME_REJECTED`: the request should be rejected
- - `OUTCOME_DENY_EXPLICIT`: the request has been explicitly denied via policies
- - `OUTCOME_DENY_IMPLICIT`: the request has been implicity denied as no policies grant the required permissions
-- Should this request be allowed by default?
- - Users can manage their own credentials unless policies explicitly deny this
-
-## Resource Limits
-
-Organizations have [resource limits](/concepts/resource-limits) for performance and security considerations. If you're bumping into these limits, check out sub-organizations below.
-
-## Sub-Organizations
-
-A sub-organization is an isolated organization that has a pointer to a parent organization. The parent organization has **read** access to all sub-organizations, but no **write** access. This means users within the parent organization have no ability to use wallets or alter any resources in the sub-organization.
-
-For more information on sub-organizations and common use cases for this functionality, follow along in the next section 👉.
diff --git a/docs/documentation/concepts/policy-management/Policy-language.md b/docs/documentation/concepts/policy-management/Policy-language.md
deleted file mode 100644
index e8e71d27..00000000
--- a/docs/documentation/concepts/policy-management/Policy-language.md
+++ /dev/null
@@ -1,192 +0,0 @@
----
-description: A guide to authoring policies with our policy lanugage
-slug: /concepts/policies/language
-sidebar_label: Language
----
-
-# Policy language
-
-This page provides an overview of how to author policies using our policy language. To begin, we'll need to get familiar with the language's grammar, keywords, and types.
-
-## Grammar
-
-The grammar has been designed for flexibility and expressiveness. We currently support the following operations:
-
-| Operation | Operators | Example | Types |
-| ---------- | ---------------------------- | --------------------------- | ------------------------ |
-| logical | &&, \|\| | "true && false" | (bool, bool) -> bool |
-| comparison | ==, !=, <, >, <=, >= | "1 < 2" | (int, int) -> bool |
-| comparison | ==, != | "'a' != 'b'" | (string, string) -> bool |
-| comparison | in | "1 in [1, 2, 3]" | (T, list) -> bool |
-| access | x[] | \[1,2,3\]\[0\] | (list) -> T |
-| access | x[] | "'abc'[0]" | (string) -> string |
-| access | x[..] | \[1,2,3\]\[0..2\] | (list) -> (list) |
-| access | x[..] | "'abc'[0..2]" | (string) -> string |
-| access | x. | "user.tags" | (struct) -> T |
-| function | x.all(item, ) | "[1,1,1].all(x, x == 1)" | (list) -> bool |
-| function | x.any(item, ) | "[1,2,3].any(x, x == 1)" | (list) -> bool |
-| function | x.contains() | "[1,2,3].contains(1)" | (list) -> bool |
-| function | x.count() | "[1,2,3].count()" | (list) -> int |
-| function | x.filter(item, ) | "[1,2,3].filter(x, x == 1)" | (list) -> (list) |
-
-## Keywords
-
-Keywords are reserved words that are dynamically interchanged for real values at evaluation time. Each field supports a different set of keywords.
-
-### Consensus
-
-| Keyword | Type | Description |
-| ------------- | ----------- | ---------------------------------------- |
-| **approvers** | list | The users that have approved an activity |
-
-### Condition
-
-| Keyword | Type | Description |
-| --------------- | ------------------- | ------------------------------------------------------------ |
-| **activity** | Activity | The activity metadata of the request |
-| **eth.tx** | EthereumTransaction | The parsed Ethereum transaction payload (see Appendix below) |
-| **solana.tx** | SolanaTransaction | The parsed Solana transaction payload (see Appendix below) |
-| **wallet** | Wallet | The target wallet used in sign requests |
-| **private_key** | PrivateKey | The target private key used in sign requests |
-
-## Types
-
-The language is strongly typed which makes policies easy to author and maintain.
-
-### Primitive
-
-| Type | Example | Notes |
-| ------------ | ------------- | ---------------------------------------------- |
-| **bool** | true | |
-| **int** | 256 | i128 |
-| **string** | 'a' | only single quotes are supported |
-| **list** | [1, 2, 3] | a list of type T |
-| **struct** | { id: 'abc' } | a key-value map of { field:T } (defined below) |
-
-### Struct
-
-| Struct | Field | Type | Description |
-| ----------------------- | ---------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **User** | id | string | The identifier of the user |
-| | tags | list | The collection of tags for the user |
-| | email | string | The email address of the user |
-| | alias | string | The alias of the user |
-| **Activity** | type | string | The type of the activity (e.g. ACTIVITY_TYPE_SIGN_TRANSACTION_V2) |
-| | resource | string | The resource type the activity targets: `USER`, `PRIVATE_KEY`, `POLICY`, `WALLET`, `ORGANIZATION`, `INVITATION`, `CREDENTIAL`, `CONFIG`, `RECOVERY`, `AUTH`, `PAYMENT_METHOD`, `SUBSCRIPTION` |
-| | action | string | The action of the activity: `CREATE`, `UPDATE`, `DELETE`, `SIGN`, `EXPORT`, `IMPORT` |
-| **Wallet** | id | string | The identifier of the wallet |
-| **Wallet Account** | address | string | The wallet account address |
-| **PrivateKey** | id | string | The identifier of the private key |
-| | tags | list | The collection of tags for the private key |
-| **EthereumTransaction** | from | string | The sender address of the transaction |
-| | to | string | The receiver address of the transaction (can be an EOA or smart contract) |
-| | data | string | The arbitrary calldata of the transaction (hex-encoded) |
-| | value | int | The amount being sent (in wei) |
-| | gas | int | The maximum allowed gas for the transaction |
-| | gas_price | int | The price of gas for the transaction (Note: this field was used in legacy transactions and was replaced with max_fee_per_gas in EIP 1559 transactions, however when evaluating policies on EIP 1559 transactions, this field will be populated with the same value as max_fee_per_gas) |
-| | chain_id | int | The chain identifier for the transaction |
-| | nonce | int | The nonce for the transaction |
-| | max_fee_per_gas | int | EIP 1559 field specifying the max amount to pay per unit of gas for the transaction (Note: This is the sum of the gas for the transaction and the priority fee described below) |
-| | max_priority_fee_per_gas | int | EIP 1559 field specifying the max amount of the tip to be paid to miners for the transaction |
-| **SolanaTransaction** | account_keys | list | The accounts (public keys) involved in the transaction |
-| | program_keys | list | The programs (public keys) involved in the transaction |
-| | instructions | list | A list of Instructions (see below) |
-| | transfers | list | A list of Transfers (see below) |
-| | recent_blockhash | string | The recent blockhash specified in a transaction |
-
-#### Nested Structs
-
-| Struct | Field | Type | Description |
-| ---------------------- | --------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
-| **Instruction** | program_key | string | The program (public key) involved in the instruction |
-| | accounts | list | A list of Accounts involved in the instruction |
-| | instruction_data_hex | string | Raw hex bytes corresponding to instruction data |
-| | address_table_lookups | list | A list of AddressTableLookups used in the instruction. Learn more [here](https://solana.com/docs/advanced/lookup-tables) |
-| **Transfer** | sender | string | A Solana account (public key) |
-| | recipient | string | A Solana account (public key) |
-| | amount | string | The native SOL amount for the transfer (lamports) |
-| **Account** | account_key | string | A Solana account (public key) |
-| | signer | boolean | An indicator of whether or not the account is a signer |
-| | writable | boolean | An indicator of whether or not the account can perform a write operation |
-| **AddressTableLookup** | address_table_key | string | A Solana address (public key) corresponding to the address table |
-| | writable_indexes | list | Indexes corresponding to accounts that can perform writes |
-| | readonly_indexes | list | Indexes corresponding to accounts that can only perform reads |
-
-## Activity Breakdown
-
-| Resource Type | Action | Activity Type |
-|--------------------|--------|-:------------------------------------------|
-| **ORGANIZATION** | CREATE | ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7 |
-| | DELETE | ACTIVITY_TYPE_DELETE_ORGANIZATION |
-| | DELETE | ACTIVITY_TYPE_DELETE_SUB_ORGANIZATION |
-| **INVITATION** | CREATE | ACTIVITY_TYPE_CREATE_INVITATIONS |
-| | DELETE | ACTIVITY_TYPE_DELETE_INVITATION |
-| **POLICY** | CREATE | ACTIVITY_TYPE_CREATE_POLICY_V3 |
-| | CREATE | ACTIVITY_TYPE_CREATE_POLICIES |
-| | UPDATE | ACTIVITY_TYPE_UPDATE_POLICY |
-| | DELETE | ACTIVITY_TYPE_DELETE_POLICY |
-| **WALLET** | CREATE | ACTIVITY_TYPE_CREATE_WALLET |
-| | CREATE | ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS |
-| | EXPORT | ACTIVITY_TYPE_EXPORT_WALLET |
-| | EXPORT | ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT |
-| | IMPORT | ACTIVITY_TYPE_INIT_IMPORT_WALLET |
-| | IMPORT | ACTIVITY_TYPE_IMPORT_WALLET |
-| | DELETE | ACTIVITY_TYPE_DELETE_WALLETS |
-| | UPDATE | ACTIVITY_TYPE_UPDATE_WALLET |
-| **PRIVATE_KEY** | CREATE | ACTIVITY_TYPE_CREATE_PRIVATE_KEYS_V2 |
-| | CREATE | ACTIVITY_TYPE_CREATE_PRIVATE_KEY_TAG |
-| | UPDATE | ACTIVITY_TYPE_UPDATE_PRIVATE_KEY_TAG |
-| | DELETE | ACTIVITY_TYPE_DISABLE_PRIVATE_KEY |
-| | DELETE | ACTIVITY_TYPE_DELETE_PRIVATE_KEY_TAGS |
-| | DELETE | ACTIVITY_TYPE_DELETE_PRIVATE_KEYS |
-| | EXPORT | ACTIVITY_TYPE_EXPORT_PRIVATE_KEY |
-| | IMPORT | ACTIVITY_TYPE_INIT_IMPORT_PRIVATE_KEY |
-| | IMPORT | ACTIVITY_TYPE_IMPORT_PRIVATE_KEY |
-| | SIGN | ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2 |
-| | SIGN | ACTIVITY_TYPE_SIGN_RAW_PAYLOADS |
-| | SIGN | ACTIVITY_TYPE_SIGN_TRANSACTION_V2 |
-| **USER** | CREATE | ACTIVITY_TYPE_CREATE_USERS_V2 |
-| | CREATE | ACTIVITY_TYPE_CREATE_USER_TAG |
-| | CREATE | ACTIVITY_TYPE_CREATE_API_ONLY_USERS |
-| | UPDATE | ACTIVITY_TYPE_UPDATE_USER |
-| | UPDATE | ACTIVITY_TYPE_UPDATE_USER_TAG |
-| | DELETE | ACTIVITY_TYPE_DELETE_USERS |
-| | DELETE | ACTIVITY_TYPE_DELETE_USER_TAGS |
-| **CREDENTIAL** | CREATE | ACTIVITY_TYPE_CREATE_API_KEYS_V2 |
-| | CREATE | ACTIVITY_TYPE_CREATE_AUTHENTICATORS_V2 |
-| | DELETE | ACTIVITY_TYPE_DELETE_API_KEYS |
-| | DELETE | ACTIVITY_TYPE_DELETE_AUTHENTICATORS |
-| | CREATE | ACTIVITY_TYPE_CREATE_OAUTH_PROVIDERS |
-| | DELETE | ACTIVITY_TYPE_DELETE_OAUTH_PROVIDERS |
-| **PAYMENT_METHOD** | UPDATE | ACTIVITY_TYPE_SET_PAYMENT_METHOD_V2 |
-| | DELETE | ACTIVITY_TYPE_DELETE_PAYMENT_METHOD |
-| **SUBSCRIPTION** | CREATE | ACTIVITY_TYPE_ACTIVATE_BILLING_TIER |
-| **CONFIG** | UPDATE | ACTIVITY_TYPE_UPDATE_ALLOWED_ORIGINS |
-| **RECOVERY** | CREATE | ACTIVITY_TYPE_INIT_USER_EMAIL_RECOVERY |
-| **AUTH** | CREATE | ACTIVITY_TYPE_EMAIL_AUTH_V2 |
-| | CREATE | ACTIVITY_TYPE_INIT_OTP_AUTH |
-| | CREATE | ACTIVITY_TYPE_OTP_AUTH |
-| | CREATE | ACTIVITY_TYPE_OAUTH |
-| | CREATE | ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2 |
-
-## Appendix
-
-### Root quorum activities
-
-There are a select few activities that are not governed by policies, but rather by an organization's [root quorum](../../concepts/user-management/Root-quorum.md). These activities are: `ACTIVITY_TYPE_UPDATE_ROOT_QUORUM`, `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE`, `ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE`. For example, if a policy is added that allows a specific non-root user to perform `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE` activities, these requests will still fail as they are subject specifically to root quorum.
-
-### Ethereum
-
-Our Ethereum policy language (accessible via `eth.tx`) allows for the granular governance of signing Ethereum (EVM-compatible) transactions. Our policy engine exposes a [fairly standard set of properties](https://ethereum.org/en/developers/docs/transactions/#typed-transaction-envelope) belonging to a transaction.
-
-See the [Policy examples](./Policy-examples.mdx) for sample scenarios.
-
-### Solana
-
-Similarly, our Solana policy language (accessible via `solana.tx`) allows for control over signing Solana transactions. Note that there are some fundamental differences between the architecture of the two types of transactions, hence the resulting differences in policy structure. Notably, within our policy engine, a Solana transaction contains a list of Transfers, currently corresponding to native SOL transfers. Each transfer within a transaction is considered a separate entity. Here are some approaches you might take to govern native SOL transfers:
-
-- _All_ transfers need to match the policy condition. Useful for allowlists ([example](./Policy-examples.mdx#allow-solana-transactions-that-include-a-transfer-with-only-one-specific-recipient))
-- _Just one_ transfer needs to match the policy condition. Useful for blocklists ([example](./Policy-examples.mdx#deny-all-solana-transactions-transferring-to-an-undesired-address))
-- Only match if there is a _single_ transfer in the transaction, _and_ that transfer meets the criteria ([example](./Policy-examples.mdx#allow-solana-transactions-that-have-exactly-one-transfer-with-one-specific-recipient)). This is the most secure approach, and thus most restrictive.
-
-See the [Policy examples](./Policy-examples.mdx) for sample scenarios.
diff --git a/docs/documentation/concepts/policy-management/_category_.json b/docs/documentation/concepts/policy-management/_category_.json
deleted file mode 100644
index 049be11d..00000000
--- a/docs/documentation/concepts/policy-management/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Policies",
- "position": 7,
- "collapsed": true
-}
diff --git a/docs/documentation/concepts/resource-limits.md b/docs/documentation/concepts/resource-limits.md
deleted file mode 100644
index 404a1625..00000000
--- a/docs/documentation/concepts/resource-limits.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-sidebar_position: 6
-sidebar_label: Resource Limits
-description: Organization resource limits
-slug: /concepts/resource-limits
----
-
-# Resource Limits
-
-We have limits on the number of resources within a single organization to avoid performance slowdowns and overly complex permission models. You can scale your organizational resources beyond these limits via [sub-organizations](/concepts/Sub-Organizations). You can create an unlimited number of sub-organizations within a single organization.
-
-Currently, the resource limits within a single organization are as follows:
-
-| Resource | Maximum parent org allowance | Maximum sub-org allowance |
-| :----------------------------- | :--------------------------: | :-----------------------: |
-| Private keys | 1,000 | 1,000 |
-| HD Wallets | 100 | 100 |
-| HD Wallet Accounts | unlimited | unlimited |
-| Users | 100 | 100 |
-| Policies | 100 | 100 |
-| Invitations | 100 | 100 |
-| Tags | 100 | 10 |
-| Authenticators per user | 10 | 10 |
-| API keys per user (long-lived) | 10 | 10 |
-| API keys per user (expiring) | 10 | 10 |
-| Sub-Organizations | unlimited | 0 |
-| OAuth providers per user | 10 | 10 |
-
-Note that if you create an expiring API key that would exceed the limit above, Turnkey automatically deletes one of your existing keys using the following priority:
-
-1. Expired API keys are deleted first
-2. If no expired keys exist, the oldest unexpired key is deleted
-
-If you are approaching any of these limits in your implementation and require support, reach out to the Turnkey team ().
diff --git a/docs/documentation/concepts/sub-organizations.md b/docs/documentation/concepts/sub-organizations.md
deleted file mode 100644
index 880babc3..00000000
--- a/docs/documentation/concepts/sub-organizations.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-sidebar_position: 3
-sidebar_label: Sub-Organizations
-description: Learn about sub-orgs and how you can use them
-slug: /concepts/sub-organizations
----
-
-# Sub-Organizations
-
-Using Turnkey's flexible infrastructure, you can programmatically create and manage sub-organizations for your end-users. sub-organizations aren't subject to size limits: you can create as many sub-organizations as needed. The parent organization has **read-only** visibility into all of its sub-organizations, and activities performed in sub-organizations roll up to the parent for billing purposes.
-
-We envision sub-organizations being very useful to model your End-Users if you're a business using Turnkey for key management. Let's explore how.
-
-## Creating Sub-Organizations
-
-Creating a new sub-organization is an activity performed by the parent organization. The activity itself takes the following attributes as inputs:
-
-- organization name
-- a list of root users
-- a root quorum threshold
-- [optional] a wallet (note: in versions prior to V4, this was a private key)
-
-Root users can be programmatic or human, with one or many credentials attached.
-
-## Using Sub-Organizations
-
-[Sub-Organizations as Wallets](/embedded-wallets/sub-organizations-as-wallets) explains how you might want to use this primitive as a way to model end-user controlled wallets, or custodial wallets. If you have another use-case in mind, or questions/feedback on this page, reach out to [welcome@turnkey.com](mailto:welcome@turnkey.com)!
-
-## Deleting Sub-Organizations
-
-To delete sub-organizations you can call the [delete sub-organization activity](https://docs.turnkey.com/api#tag/Organizations/operation/DeleteSubOrganization). Before deleting a sub-organization all private keys and wallets within the sub-organization must have been exported to prevent loss of funds, or you can pass in the `deleteWithoutExport` parameter with the value `true` to override this. The `deleteWithoutExport` parameter, if not passed in, is default `false`.
-
-Note that this activity must be initiated by a root user in the sub-organization that is to be deleted. A parent org cannot delete a sub-organization without its participation.
diff --git a/docs/documentation/concepts/user-management/Introduction.md b/docs/documentation/concepts/user-management/Introduction.md
deleted file mode 100644
index 01e07809..00000000
--- a/docs/documentation/concepts/user-management/Introduction.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-sidebar_position: 1
-description: Learn about Users on Turnkey
-slug: /concepts/users/introduction
----
-
-# Introduction to users
-
-Turnkey users are resources within organizations or sub-organizations that can submit activities to Turnkey via a valid credential (e.g., API key, passkey). These requests can be made either by making direct API calls or through the Turnkey Dashboard. Users must have at least one valid credential (one of API key, passkey), with upper limits on credentials defined here in our [resource limits](/concepts/resource-limits). Users can also have associated “tags” which are logical groupings that can be referenced in policies. Users can only submit activities within their given organization — they cannot take action across organizations.
-
-A User's attributes are:
-
-- UUID: a globally unique ID (e.g. `fc6372d1-723d-4f7e-8554-dc3a212e4aec`), used as a unique identifier for a User in the context of Policies or User Tags, or Quorums.
-- Name and email
-- Authenticators: a list of authenticators (see below for information)
-- API key: a list of API keys (see below for information)
-- User tags: a list of User Tag UUIDs
-
-A **user belongs to one organization**, and one organization can have many (**up to 100**) users. If you need to create more users, consider using Sub-Organizations.
diff --git a/docs/documentation/concepts/user-management/_category_.json b/docs/documentation/concepts/user-management/_category_.json
deleted file mode 100644
index d84639e0..00000000
--- a/docs/documentation/concepts/user-management/_category_.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "label": "Users",
- "position": 4,
- "collapsed": true,
- "link": {
- "type": "doc",
- "id": "documentation/concepts/user-management/Introduction"
- }
-}
diff --git a/docs/documentation/developer-reference/_category_.json b/docs/documentation/developer-reference/_category_.json
deleted file mode 100644
index fe657389..00000000
--- a/docs/documentation/developer-reference/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Developer Reference",
- "position": 7,
- "collapsed": true
-}
diff --git a/docs/documentation/developer-reference/api-overview/_category_.json b/docs/documentation/developer-reference/api-overview/_category_.json
deleted file mode 100644
index 2cadcc0b..00000000
--- a/docs/documentation/developer-reference/api-overview/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "API Overview",
- "position": 1,
- "collapsed": true
-}
diff --git a/docs/documentation/developer-reference/api-overview/intro.md b/docs/documentation/developer-reference/api-overview/intro.md
deleted file mode 100644
index 3d2e8556..00000000
--- a/docs/documentation/developer-reference/api-overview/intro.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-sidebar_position: 1
-description: Getting started with the Turnkey API
-slug: /developer-reference/api-overview/intro
----
-
-# Introduction
-
-## RPC/HTTP
-
-Turnkey's API is a remote procedure call (RPC) API. We chose RPC-over-HTTP for convenience and ease-of-use. Most of our users should be able to integrate with our API without a major re-architecture of their existing systems.
-
-Many client libraries are available to make requests to a RPC/HTTP API, across many languages. Turnkey will provide SDKs for the most popular programming languages. For other languages, a RPC/HTTP API ensures there is an easy integration path available via raw http clients.
-
-## POST-only
-
-If you look at the [API reference](/api) you'll notice that all API calls to Turnkey are HTTP POST requests. Requests contain a POST body and a header with a digital signature over the POST body. We call this digitial signature a [Stamp](./stamps.md).
-
-Requests must be stamped by registered user credentials and verified by Turnkey's secure enclaves before they are processed. This ensures cryptographic integrity end-to-end which eliminates the ability for any party to modify a user's request.
-
-### Queries and Submissions
-
-Turnkey's API is divided into 2 broad categories: queries and submissions.
-
-- Queries are read requests (e.g. `get_users`, `list_users`)
-- Submissions are requests to execute a workload (e.g. `create_policy`, `sign_transaction`, `delete_user`)
-
-## Dive Deeper
-
-- Creating your first [Stamp](./stamps.md)
-- Fetching data with [Queries](./queries.md)
-- Executing workloads with [Submissions](./submissions.md)
diff --git a/docs/documentation/developer-reference/api-overview/queries.md b/docs/documentation/developer-reference/api-overview/queries.md
deleted file mode 100644
index 639c11fd..00000000
--- a/docs/documentation/developer-reference/api-overview/queries.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-sidebar_position: 3
-description: Fetching data from Turnkey
-slug: /developer-reference/api-overview/queries
----
-
-# Queries
-
-Queries are read requests to Turnkey's API. Query URL paths are prefixed with `/public/v1/query`. Queries are not subject to enforcement of the policy engine. All users within an organization can read any data within the organization.
-
-Additionally, parent organizations have the ability to query data for all of their sub-organizations.
diff --git a/docs/documentation/developer-reference/api-overview/stamps.md b/docs/documentation/developer-reference/api-overview/stamps.md
deleted file mode 100644
index c982f3fb..00000000
--- a/docs/documentation/developer-reference/api-overview/stamps.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-sidebar_position: 2
-description: Creating your first signed request
-slug: /developer-reference/api-overview/stamps
----
-
-# Stamps
-
-Every request made to Turnkey must include a signature over the POST body attached as a HTTP header. Our secure enclave applications use this signature to verify the integrity and authenticity of the request.
-
-### API Keys
-
-To create a valid, API key stamped request follow these steps:
-
-1. Sign the JSON-encoded POST body with your API key to produce a `signature` (DER-encoded)
-2. Hex encode the `signature`
-3. Create a JSON-encoded stamp:
- - `publicKey`: the public key of API key
- - `signature`: the signature produced by the API key
- - `scheme`: `SIGNATURE_SCHEME_TK_API_P256`
-4. Base64URL encode the stamp
-5. Attach the encoded string to your request as a `X-Stamp` header
-6. Submit the stamped request to Turnkey's API
-
-### WebAuthn
-
-To create a valid, Webauthn authenticator stamped request follow these steps:
-
-1. Compute the webauthn challenge by hashing the POST body bytes (JSON encoded) with SHA256. For example, if the POST body is `{"organization_id": "1234", "type": "ACTIVITY_TYPE_CREATE_API_KEYS", "params": {"for": "example"}`, the webauthn challenge is the string `7e8b4653fc7e51dc119cea031942f4693b4742ceca4dda269b925802b38b2147`
-2. Include the challenge amongst WebAuthn signing options. Refer to the existing stamper implementations in the [following section](#stampers)) for examples
-
-- Note that if you need to pass the challenge as bytes, you'll need to utf8-encode the challenge string (in JS, the challenge bytes will be `TextEncoder().encode("7e8b4653fc7e51dc119cea031942f4693b4742ceca4dda269b925802b38b2147")`)
-- Additional note for React Native contexts: the resulting string should then additionally be base64-encoded. See [implementation](https://github.com/tkhq/sdk/blob/b52db566e79a65eec8d8e7066053d6a3ac5f3943/packages/react-native-passkey-stamper/src/util.ts#L5-L10)
-
-3. Create a JSON-encoded stamp:
- - `credentialId`: the id of the webauthn authenticator
- - `authenticatorData`: the authenticator data produced by Webauthn assertion
- - `clientDataJson`: the client data produced by the Webauthn assertion
- - `signature`: the signature produced by the Webauthn assertion
-4. Attach the JSON-encoded stamp to your request as a `X-Stamp-Webauthn` header
- - Header names are case-insensitive (so `X-Stamp-Webauthn` and `X-Stamp-WebAuthn` are considered equivalent)
- - Unlike API key stamps, the format is just JSON; no base64URL encoding necessary! For example: `X-Stamp-Webauthn: {"authenticatorData":"UaQZ...","clientDataJson":"eyJ0...","credentialId":"Grf...","signature":"MEQ..."}`
-5. Submit the stamped request to Turnkey's API. If you would like your client request to be proxied through a backend, refer to the patterns mentioned [here](/authentication/passkeys/integration#proxying-signed-requests). An example application that uses this pattern can be found at wallet.tx.xyz (code [here](https://github.com/tkhq/demo-embedded-wallet/))
-
-### Stampers
-
-Our [JS SDK](https://github.com/tkhq/sdk) and [CLI](https://github.com/tkhq/tkcli) abstract request stamping for you. If you choose to use an independent client, you will need to implement this yourself. For reference, check out our implementations:
-
-- [API Key Stamper](https://github.com/tkhq/sdk/blob/main/packages/api-key-stamper)
-- [WebAuthn Stamper](https://github.com/tkhq/sdk/blob/main/packages/webauthn-stamper)
-- [React Native Stamper](https://github.com/tkhq/sdk/tree/main/packages/react-native-passkey-stamper)
-- [iFrame Stamper](https://github.com/tkhq/sdk/tree/main/packages/iframe-stamper)
-- [Telegram Cloud Storage Stamper](https://github.com/tkhq/sdk/tree/main/packages/telegram-cloud-storage-stamper)
-- [CLI](https://github.com/tkhq/tkcli/blob/main/src/cmd/turnkey/pkg/request.go)
-
-Our CLI has a `--no-post` option to generate stamps without sending anything over the network. This is a useful tool should you have trouble with debugging stamping-related logic. A sample command might look something like:
-
-```
-turnkey request --no-post --host api.turnkey.com --path /api/v1/sign --body '{"payload": "hello from TKHQ"}'
-{
- "curlCommand": "curl -X POST -d'{\"payload\": \"hello from TKHQ\"}' -H'X-Stamp: eyJwdWJsaWNLZXkiOiIwMzI3YTUwMDMyZTZmMDYzMWQ1NjA1YjZhZGEzMmI3NzkwNzRmMzQ2ZTgxYjY4ZTEyODAxNjQwZjFjOWVlMDNkYWUiLCJzaWduYXR1cmUiOiIzMDQ0MDIyMDM2MjNkZWZkNjE4ZWIzZTIxOTk3MDQ5NjQwN2ViZTkyNDQ3MzE3ZGFkNzVlNDEyYmQ0YTYyNjdjM2I1ZTIyMjMwMjIwMjQ1Yjc0MDg0OGE3MmQwOGI2MGQ2Yzg0ZjMzOTczN2I2M2RiM2JjYmFkYjNiZDBkY2IxYmZiODY1NzE1ZDhiNSIsInNjaGVtZSI6IlNJR05BVFVSRV9TQ0hFTUVfVEtfQVBJX1AyNTYifQ' -v 'https://api.turnkey.com/api/v1/sign'",
- "message": "{\"payload\": \"hello from TKHQ\"}",
- "stamp": "eyJwdWJsaWNLZXkiOiIwMzI3YTUwMDMyZTZmMDYzMWQ1NjA1YjZhZGEzMmI3NzkwNzRmMzQ2ZTgxYjY4ZTEyODAxNjQwZjFjOWVlMDNkYWUiLCJzaWduYXR1cmUiOiIzMDQ0MDIyMDM2MjNkZWZkNjE4ZWIzZTIxOTk3MDQ5NjQwN2ViZTkyNDQ3MzE3ZGFkNzVlNDEyYmQ0YTYyNjdjM2I1ZTIyMjMwMjIwMjQ1Yjc0MDg0OGE3MmQwOGI2MGQ2Yzg0ZjMzOTczN2I2M2RiM2JjYmFkYjNiZDBkY2IxYmZiODY1NzE1ZDhiNSIsInNjaGVtZSI6IlNJR05BVFVSRV9TQ0hFTUVfVEtfQVBJX1AyNTYifQ"
-}
-```
diff --git a/docs/documentation/ecosystems/_category_.json b/docs/documentation/ecosystems/_category_.json
deleted file mode 100644
index 7246522f..00000000
--- a/docs/documentation/ecosystems/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Ecosystem",
- "position": 6,
- "collapsed": true
-}
diff --git a/docs/documentation/ecosystems/framework.mdx b/docs/documentation/ecosystems/framework.mdx
deleted file mode 100644
index 297dbc05..00000000
--- a/docs/documentation/ecosystems/framework.mdx
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: Overview
-sidebar_label: Overview
-sidebar_position: 1
-slug: /ecosystems/framework
----
-
-import DocCardList from "@theme/DocCardList";
-
-# Overview
-
-## Introduction
-
-Turnkey operates at the **cryptographic curve** level rather that specific assets. As a result Turnkey is asset
-agnostic and can be used with any type of asset as long as we support the underlying curve.
-
-We are continuously evaluating and adding support for emerging assets and protocols. If there are specific
-cryptocurrencies you'd like to see us offer deeper support for, please let us know by contacting us at
-[hello@turnkey.com](mailto:hello@turnkey.com), on [X](https://x.com/turnkeyhq/), or
-[on Slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw).
-
-## What is Turnkey's approach to supporting crypto assets?
-
-Turnkey follows a tiered approach to supporting digital assets, ranging from supporting cryptographic curve support,
-to advanced transaction parsing and policy management. Each tier deepens the level of functionality, as outlined below:
-
-**Tier 1: Curve-level support**
-
-Cryptographic curves are our fundamental primitive, allowing Turnkey private keys to store and sign for any
-cryptocurrency that uses a supported curve. We currently support SECP256k1 and Ed25519 curves.
-
-**Tier 2: Address derivation**
-
-Turnkey abstracts address generation, automatically deriving addresses for supported cryptocurrencies.
-
-For a full list of address formats you can derive on Turnkey, refer to [Address formats and Curves](/concepts/wallets).
-
-**Tier 3: SDK for transaction construction and signing**
-
-Our SDK provides tools and scripts to help in constructing and signing basic transactions, enabling an even smoother integration.
-
-**Tier 4: Transaction parsing and policy creation**
-
-At our highest level of support, Turnkey offers the ability to parse transactions and define custom policies based on transaction parameters.
-
-| Tier | Depth of support | EVM | SVM | BTC | ATOM | TRON | SUI | APT | MOVE |
-| ----------- | :------------------------------- | --------------- | --------------- | --------------- | ---- | ---- | --------------------- | --------------- | ---- |
-| Tier 1 | Curve-level | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| Tier 2 | Address derivation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| Tier 3 | SDK construction and signing | ✅ | ✅ | | | | | | |
-| Tier 4 | Transaction parsing and policies | ✅ | ✅ | | | | | | |
-
-For more details about each ecosystem, refer to the pages below:
-
-
diff --git a/docs/documentation/ecosystems/others.md b/docs/documentation/ecosystems/others.md
deleted file mode 100644
index bebd77b8..00000000
--- a/docs/documentation/ecosystems/others.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-sidebar_position: 1000
-title: Others
-description: "Specific support for other ecosystems on Turnkey"
-slug: /ecosystems/others
----
-
-# What other ecosystems and chains does Turnkey support?
-
-## Cosmos
-
-We support address derivation and have published a package on NPM to help with transaction construction: [`@turnkey/cosmjs`](https://www.npmjs.com/package/@turnkey/cosmjs). See it in action in [`examples/with-cosmjs`](https://github.com/tkhq/sdk/tree/main/examples/with-cosmjs).
-
-## Tron, Sui, Aptos, Movement
-
-We support address derivation for these ecosystems. We also have two SDK examples:
-
-- [`examples/with-sui`](https://github.com/tkhq/sdk/tree/main/examples/with-sui) demonstrates transaction construction and broadcast on Sui.
-- [`examples/with-aptos`](https://github.com/tkhq/sdk/tree/main/examples/with-aptos) demonstrates transaction construction and broadcast on Aptos.
-
-## Ed25519 and SECP256k1 Signing
-
-Turnkey supports bare Ed25519 and SECP256k1 signing which covers most ecosystems and blockchains out there. If your ecosystem of choice isn't covered adequately we're open to feedback and feature requests. Give us a shout on [X](https://x.com/turnkeyhq/) or [Slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw)!
diff --git a/docs/documentation/getting-started/_category_.json b/docs/documentation/getting-started/_category_.json
deleted file mode 100644
index 289872bf..00000000
--- a/docs/documentation/getting-started/_category_.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "label": "Getting Started",
- "position": 1,
- "collapsed": false,
- "link": {
- "type": "generated-index",
- "description": "Get started with Turnkey.",
- "slug": "/getting-started"
- }
-}
diff --git a/docs/documentation/getting-started/quickstart/_category_.json b/docs/documentation/getting-started/quickstart/_category_.json
deleted file mode 100644
index 20ebbd99..00000000
--- a/docs/documentation/getting-started/quickstart/_category_.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "label": "Quickstart",
- "position": 1,
- "collapsed": false,
- "link": {
- "type": "doc",
- "id": "quickstart"
- }
-}
diff --git a/docs/documentation/getting-started/quickstart/embedded-wallet-quickstart.mdx b/docs/documentation/getting-started/quickstart/embedded-wallet-quickstart.mdx
deleted file mode 100644
index 6b035a84..00000000
--- a/docs/documentation/getting-started/quickstart/embedded-wallet-quickstart.mdx
+++ /dev/null
@@ -1,641 +0,0 @@
----
-id: embedded-wallet-quickstart
-sidebar_position: 2
-description: Quickstart guide to building Embedded Wallets with Turnkey
-slug: /getting-started/embedded-wallet-quickstart
-sidebar_label: "Embedded Wallets"
-title: "Embedded Wallets"
----
-
-import DocCardList from "@theme/DocCardList";
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import { Step, Steps } from "@site/src/components/step";
-
-# Embedded Wallets Quickstart
-
-Turnkey's Embedded Wallets enable you to integrate secure, custom wallet experiences directly
-into your product. With features like advanced security, seamless authentication, and flexible
-UX options, you can focus on building great products while we handle the complexities of private key management.
-
-## Prerequisites
-
-This guide assumes you've completed the steps to create an account, organization, and API keypair as described in the [Quickstart](/getting-started/quickstart) section.
-
-## Installation
-
-Create a new Next.js app via `npx create-next-app@latest`. Or install into an existing project.
-
-
-
-
-```bash
-npm install @turnkey/sdk-react
-```
-
-
-
-
-```bash
-pnpm add @turnkey/sdk-react
-```
-
-
-
-
-```bash
-yarn add @turnkey/sdk-react
-```
-
-
-
-
-:::note
-
-**React 19 Users**
-
-If you're using Next.js 15 with React 19 you may encounter an installation error with `@turnkey/sdk-react`. Consider:
-
-- Downgrading React to `18.x.x`
-- Using `npm install --force` or `--legacy-peer-deps`
-
-You may learn more about this [here](https://ui.shadcn.com/docs/react-19).
-
-:::
-
-## Setup
-
-
-
-Environment
-
-The following environment variables are necessary to use the Turnkey SDK.
-
-```bash title=".env"
-NEXT_PUBLIC_ORGANIZATION_ID=
-TURNKEY_API_PUBLIC_KEY=
-TURNKEY_API_PRIVATE_KEY=
-NEXT_PUBLIC_BASE_URL=https://api.turnkey.com
-```
-
-Configure
-
-Fill in with your Organization ID and API Base URL.
-
-```tsx title="src/app/layout.tsx"
-const config = {
- apiBaseUrl: "https://api.turnkey.com",
- defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID,
-};
-```
-
-Provider
-
-Wrap your layout with the `TurnkeyProvider` component, and import styles from sdk-react.
-
-```tsx title="src/app/layout.tsx"
-import { TurnkeyProvider } from "@turnkey/sdk-react";
-import "@turnkey/sdk-react/styles"; // required to render auth component styles properly
-
-export default function RootLayout({ children }) {
- return (
-
-
- {children}
-
-
- );
-}
-```
-
-:::note
-
-**React 19 Users**
-
-`@turnkey/sdk-react` is built with React 18. If you're using React 19 you'll find a type mismatch on the children type.
-
-To fix this, you can use the `@ts-ignore` directive to suppress the error.
-
-```tsx title="src/app/layout.tsx"
-
- {/* @ts-ignore */}
- {children}
-
-```
-
-We're actively working towards React 19 compatibility.
-
-:::
-
-
-
-## Authenticate
-
-
-
-
-
-The auth component contains the UI and logic to handle the authentication flow.
-
-
-
-Configure
-
-For simplicity, this app will only support email authentication. We have other guides on additional authentication methods.
-Additionally, you can customize the order in which the auth methods are displayed.
-
-```tsx title="src/app/page.tsx"
-"use client";
-
-export default function Home() {
- // The auth methods to display in the UI
- const config = {
- authConfig: {
- emailEnabled: true,
- // Set the rest to false to disable them
- passkeyEnabled: false,
- phoneEnabled: false,
- appleEnabled: false,
- facebookEnabled: false,
- googleEnabled: false,
- },
- // The order of the auth methods to display in the UI
- configOrder: ["email" /* "passkey", "phone", "socials" */],
- };
-
- return ;
-}
-```
-
-
-Auth Config Options
-
-```ts
-type AuthConfig = {
- emailEnabled: boolean;
- passkeyEnabled: boolean;
- phoneEnabled: boolean;
- appleEnabled: boolean;
- googleEnabled: boolean;
- facebookEnabled: boolean;
-};
-```
-
-
-
-Import
-
-Import the auth component into your app and pass in the config object.
-
-```tsx title="src/app/page.tsx"
-"use client";
-
-import { Auth } from "@turnkey/sdk-react";
-
-export default function Home() {
- const config = {
- authConfig: {
- emailEnabled: true,
- passkeyEnabled: false,
- phoneEnabled: false,
- appleEnabled: false,
- facebookEnabled: false,
- googleEnabled: false,
- },
- configOrder: ["email"],
- };
-
- return (
-
-
-
- );
-}
-```
-
-Handlers
-
-Define two functions to handle the "success" and "error" states. Initially, the `onError` function will set an `errorMessage`
-state variable which will be used to display an error message to the user. The `onAuthSuccess`
-function will route the user to the dashboard after successful authentication.
-
-A new [sub-organization](/concepts/sub-organizations) and [wallet](/concepts/wallets) is created for each new user during the authentication flow.
-
-```tsx title="src/app/page.tsx"
-"use client";
-
-import { useState } from "react";
-import { Auth } from "@turnkey/sdk-react";
-
-export default function Home() {
- const [errorMessage, setErrorMessage] = useState("");
- const router = useRouter();
-
- const onAuthSuccess = async () => {
- // We'll add the dashboard route in the next step
- router.push("/dashboard");
- };
-
- const onError = (errorMessage: string) => {
- setErrorMessage(errorMessage);
- };
-
- // Add the handlers to the config object
- const config = {
- // ...
- onAuthSuccess: onAuthSuccess,
- onError: onError,
- };
-
- return (
-
-
-
- );
-}
-```
-
-Dashboard: User Session
-
-Add a dashboard route to the app where the user will be able to view their account and sign messages.
-
-```tsx title="src/app/dashboard/page.tsx"
-export default function Dashboard() {
- return
Dashboard
;
-}
-```
-
-Since the app is wrapped with the `TurnkeyProvider` component, the `useTurnkey` hook is available to all child components.
-Calling `turnkey.getCurrentUser()` will return the current user's session information from local storage.
-
-Add a state variable to store the user:
-
-```tsx title="src/app/dashboard/page.tsx" {2,8}
-import { useState, useEffect } from "react";
-import { useTurnkey } from "@turnkey/sdk-react";
-
-export default function Dashboard() {
- const { turnkey } = useTurnkey();
- const [user, setUser] = useState(null);
-
- useEffect(() => {
- if (turnkey) {
- const user = turnkey.getCurrentUser();
- setUser(user);
- }
- }, [turnkey]);
-
- return
Dashboard
;
-}
-```
-
-
-User Session
-
-```ts
-export interface User {
- // Unique identifier for the user.
- userId: string;
- // Username of the user.
- username: string;
- organization: {
- // Unique identifier for the organization.
- organizationId: string;
- // Name of the organization.
- organizationName: string;
- };
- session:
- | {
- // read-only session .
- read?: ReadOnlySession;
- // read-write session details.
- write?: ReadWriteSession;
- // Authenticated client associated with the session.
- authClient: AuthClient;
- }
- | undefined;
-}
-
-export interface ReadOnlySession {
- // Read-only session token for `X-Session` header
- token: string;
- // Expiry time in seconds since Unix epoch.
- expiry: number;
-}
-
-export interface ReadWriteSession {
- // Credential bundle for iFrame client, generated by `createReadWriteSession` or `createApiKeys`.
- credentialBundle: string;
- // Expiry time in seconds since Unix epoch.
- expiry: number;
-}
-```
-
-
-
-
-
-## Sign Message
-
-Turnkey supports signing arbitrary messages with the [`signRawPayload`](/api#tag/Signing/operation/SignRawPayload) method.
-
-The `signRawPayload` method requires these parameters:
-
-- `payload`: The raw unsigned payload to sign
-- `signWith`: The signing address (wallet account, private key address, or private key ID)
-- `encoding`: The message encoding format
-- `hashFunction`: The selected hash algorithm
-
-
-
-The Payload
-
-For simplicity, a human readable string, `message`, will be the payload to sign. Add a state variable to store the message and an input field to allow the user to enter the message:
-
-```tsx title="src/app/dashboard/page.tsx" {6}
-import { useState, useEffect } from "react";
-
-export default function Dashboard() {
- //...
-
- const [message, setMessage] = useState("");
-
- //...
-
- return (
-
- setMessage(e.target.value)}
- placeholder="Enter message to sign"
- />
-
- );
-}
-```
-
-The Signer
-
-Signing messages requires a signer e.g. a Turnkey wallet address to sign with and a payload or message to sign. A new wallet is created for each user during the authentication flow.
-
-Create a function called `getSignWith`, to get the user's wallet account address which will be used to sign the message.
-
-Use the `getActiveClient` method from the `useTurnkey` hook to get the client authenticated with the user's read-write session:
-
-```tsx title="src/app/dashboard/page.tsx" {5,7-32}
-import { useState, useEffect } from "react";
-import { useTurnkey } from "@turnkey/sdk-react";
-
-export default function Dashboard() {
- const { turnkey, getActiveClient } = useTurnkey();
- const [user, setUser] = useState(null);
-
- const getSignWith = async () => {
- // This will return the authIframeClient with the credential bundle injected
- const client = await getActiveClient();
-
- // The user's sub-organization id
- const organizationId = user?.organization.organizationId;
-
- // Get the user's wallets
- const wallets = await client?.getWallets({
- organizationId,
- });
-
- // Get the first wallet of the user
- const walletId = wallets?.wallets[0].walletId ?? "";
-
- // Use the `walletId` to get the accounts associated with the wallet
- const accounts = await client?.getWalletAccounts({
- organizationId,
- walletId,
- });
-
- const signWith = accounts?.accounts[0].address ?? "";
-
- return signWith;
- };
-
- useEffect(/* ... */*/);
-
- return (/*
...
*/*/);
-}
-```
-
-The Signing Function
-
-Create a function called `signMessage`. This function will:
-
-- Get the user's wallet account for signing the message
-- Compute the keccak256 hash of the message
-- Call the `signRawPayload` method
-
-Note: To compute the `keccak256` hash of the message, this example uses the `hashMessage` function from `viem`. However, any other hashing library can be used.
-
-```tsx
-const signMessage = async () => {
- const payload = await hashMessage(message);
- const signWith = await getSignWith();
-
- const signature = await client?.signRawPayload({
- payload,
- signWith,
- // The message encoding format
- encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
- // The hash function used to hash the message
- hashFunction: "HASH_FUNCTION_KECCAK256",
- });
-};
-```
-
-Display
-
-Add a button to the UI to trigger the `signMessage` function.
-
-```tsx title="src/app/dashboard/page.tsx" {2,8}
-import { useState, useEffect } from "react";
-import { useTurnkey } from "@turnkey/sdk-react";
-import { hashMessage } from "viem";
-
-export default function Dashboard() {
- //...
-
- const [message, setMessage] = useState("");
-
- const signMessage = async () => {
- const payload = await hashMessage(message);
- const signWith = await getSignWith();
-
- const signature = await client?.signRawPayload({
- payload,
- signWith,
- // The message encoding format
- encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
- // The hash function used to hash the message
- hashFunction: "HASH_FUNCTION_KECCAK256",
- });
- };
-
- return (
-
- );
-}
-```
-
-
-
-## Next Steps
-
-
diff --git a/docs/documentation/getting-started/quickstart/quickstart.mdx b/docs/documentation/getting-started/quickstart/quickstart.mdx
deleted file mode 100644
index 77fc34c1..00000000
--- a/docs/documentation/getting-started/quickstart/quickstart.mdx
+++ /dev/null
@@ -1,128 +0,0 @@
----
-id: quickstart
-title: "Quickstart"
-sidebar_position: 1
-description: Get started with Turnkey by creating an organization and obtaining your keys.
-slug: /getting-started/quickstart
----
-
-import DocCardList from "@theme/DocCardList";
-
-import { Step, Steps } from "@site/src/components/step";
-
-# Quickstart
-
-Before diving into the code, let's set up your organization and conjure up an API keypair to unlock the full potential of Turnkey!
-
-## Create an Account
-
-Navigate to the [Turnkey Dashboard](https://app.turnkey.com/dashboard/auth/initial) to create an account and setup your organization:
-
-
-
-
-
-## Get Your Organization ID
-
-Once logged in, open the user dropdown at the top right. Your Organization ID is listed there. Copy it for use in your code or environment variables.
-
-
-
-
-
-## Create an API Key
-
-The API keypair is used to authenticate requests to Turnkey. We'll create one now.
-
-
- Navigate to the User Details page.
-
-
-
-Click "Create an API key".
-
-
-
-Choose a key generation method
-
-For this guide, we'll use the in-browser method.
-
-
-
-
-
-Optionally, you may also generate keys using the Turnkey CLI.
-
-
-
-Name your keypair
-
-
-
-Approve & Create
-
-You'll be prompted to authenticate with the authenticator setup during account creation.
-Save the private key in a secure location — **it won't be visible after this step**.
-
-**Important**: Both the public and private keys are required for signing requests to the Turnkey API. Keep these keys secure and out of reach of end-users.
-
-
-
-API Key Created 🎉
-
-
-
-
-
-## Next Steps
-
-Now that you've created an organization and API keypair, you're ready to start developing with Turnkey!
-
-
diff --git a/docs/documentation/security/Verifiable-data.md b/docs/documentation/security/Verifiable-data.md
deleted file mode 100644
index f1eb2377..00000000
--- a/docs/documentation/security/Verifiable-data.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-description: Learn how we ensure an end-to-end audit trail
-sidebar_position: 5
-slug: /security/verifiable-data
----
-
-# Verifiable data
-
-Enclave applications in Turnkey’s infrastructure are stateless meaning, there is no persistent data held behind the enclave boundary. Instead, data is held in a PostgreSQL instance in our primary AWS account. Before any enclave applications operate on the data in a Turnkey account, it first verifies that that data has been recently notarized by Turnkey’s notarizer. A recent stamp could be the result of an update or initiated by the heartbeat service.
-
-By verifying the authenticity of data using cryptographic signatures (no passwords!) and timestamping, we enable zero-risk data sharing between these apps and block man-in-the-middle or downgrade attacks. The combination of these features results in a system and an audit trail that is verifiable end-to-end.
-
-The entire Turnkey architecture including this verifiable data flow is described below:
-
-
-
-
diff --git a/docs/documentation/security/_category_.json b/docs/documentation/security/_category_.json
deleted file mode 100644
index 0d0b0f17..00000000
--- a/docs/documentation/security/_category_.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "label": "Security",
- "position": 8,
- "collapsed": true,
- "link": {
- "type": "generated-index",
- "description": "Learn how Turnkey achieves innovative, cloud scale, no single point of failure security."
- }
-}
diff --git a/docs/documentation/security/disaster-recovery.md b/docs/documentation/security/disaster-recovery.md
deleted file mode 100644
index 254a983c..00000000
--- a/docs/documentation/security/disaster-recovery.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-sidebar_position: 6
-description: Turnkey's disaster recovery process
-slug: /security/disaster-recovery
----
-
-# Disaster recovery
-
-We have a comprehensive disaster recovery process in place for all critical Turnkey data. In particular, there are two main categories of data that we consider to be critical:
-
-- Organization data: Core data within your organization, including details for users, encrypted private key material, policies, tags, activity history, etc.
-- Quorum Keys: Keys used by members of the Quorum Set to boot secure applications, and perform sensitive actions within an enclave like decrypting private keys or making policy decisions.
-
-For organization data, because all enclave applications are stateless, our persistence strategy is very similar to a traditional web application. Data is encrypted, stored redundantly across geographies, and consistently backed up and exported to our disaster recovery accounts.
-
-For Quorum Keys, as described in [Quorum deployments](./Quorum-deployment.md), we split the key between members of the Quorum set and have a level of redundancy in those shards. In the unlikely event that all members of the Quorum Set were to lose their active shares, we have a set of offline backup shares securely stored across geographically distributed locations.
diff --git a/docs/documentation/security/whitepaper.md b/docs/documentation/security/whitepaper.md
deleted file mode 100644
index e9c8d89d..00000000
--- a/docs/documentation/security/whitepaper.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-sidebar_position: 9
-description: Read about Turnkey's ambitious foundations with the Turnkey Whitepaper
-slug: /security/whitepaper
----
-
-# The Turnkey Whitepaper
-
-We have published an in-depth whitepaper describing the principles with which we've built Turnkey and explaining in great detail the infrastructure foundations as well as the system architecture underpinning our product.
-
-Our whitepaper is available online at **[whitepaper.turnkey.com](https://whitepaper.turnkey.com)**.
diff --git a/docs/documentation/wallets/_category_.json b/docs/documentation/wallets/_category_.json
deleted file mode 100644
index ff28ed05..00000000
--- a/docs/documentation/wallets/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Wallets",
- "position": 5,
- "collapsed": true
-}
diff --git a/docs/documentation/wallets/export-wallets.md b/docs/documentation/wallets/export-wallets.md
deleted file mode 100644
index 16a11b8f..00000000
--- a/docs/documentation/wallets/export-wallets.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-sidebar_position: 5
-description: Learn about Wallet and Key Export on Turnkey
-slug: /wallets/export-wallets
----
-
-# Export Wallets and Keys
-
-Turnkey's export functionality allows your end users to backup or transfer a [Wallet](/concepts/Wallets) by securely viewing the wallet's [mnemonic phrase](https://learnmeabitcoin.com/technical/mnemonic). We engineered this feature to ensure that the user can export their mnemonic without exposing the mnemonic itself to Turnkey or your application.
-
-The process of exporting wallets or private keys from Turnkey is broken up into two primary steps:
-
-1. Export the wallet or private key via Turnkey. You must specify the wallet or private key ID, as well as a target public key, which the wallet or private key will be encrypted to. Encryption ensures that the key material is only accessible by the client, and cannot be extracted by any man-in-the-middle (MITM)
-2. Decrypt the resulting bundle returned by Turnkey
-
-See the [Enclave to end-user secure channel](../security/enclave-secure-channels.md) for more technical details.
-
-# Implementation Guides
-
-See [Code Examples](../../../embedded-wallets/code-examples/export) for more details.
diff --git a/docs/documentation/wallets/import-wallets.md b/docs/documentation/wallets/import-wallets.md
deleted file mode 100644
index b240855a..00000000
--- a/docs/documentation/wallets/import-wallets.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-sidebar_position: 4
-description: Learn about Wallet and Key Import on Turnkey
-slug: /wallets/import-wallets
----
-
-# Import Wallets and Keys
-
-Turnkey's import functionality allows your end users to securely transfer a [Wallet](/concepts/Wallets) or a [Private Key](/concepts/Wallets#private-keys) onto the Turnkey platform via CLI or an embedded iframe. We engineered this feature to ensure that the user can import their mnemonic or private key into a Turnkey secure enclave without exposing it to Turnkey or your application.
-
-The process of importing wallets or private keys into Turnkey is broken up into three primary steps:
-
-1. Initialize the import process. This produces an import bundle, containing a public key and signature. These artifacts will be used in the next step to ensure that key material is only accessible by Turnkey, and cannot be extracted by any man-in-the-middle (MITM)
-2. Encrypt the key material to the artifacts from the previous step
-3. Import the encrypted bundle to Turnkey
-
-See the [Enclave to end-user secure channel](../security/enclave-secure-channels.md) for more technical details.
-
-# Implementation Guides
-
-See [Code Examples](../../../embedded-wallets/code-examples/import) for more details.
diff --git a/docs/documentation/wallets/pregenerated-wallets.md b/docs/documentation/wallets/pregenerated-wallets.md
deleted file mode 100644
index ec076917..00000000
--- a/docs/documentation/wallets/pregenerated-wallets.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-sidebar_position: 2
-description: Learn about pre-generating wallets for users on Turnkey.
-slug: /wallets/pregenerated-wallets
----
-
-# Pre-generated wallets
-
-Turnkey allows you to pre-generate wallets for your user before they authenticate.
-This is helpful if you already know the users email or phone number, and want to
-create a deposit address for them or airdrop a reward before they authenticate to Turnkey.
-
-To accomplish this, create a new sub-org for that user with a single root user.
-This root user should only have the end user’s email or phone number associated with it,
-and no other authenticators, which ensures that only the end user can claim the pre-generated wallet.
-When the end user wants to claim the wallet, they can complete the [Email Auth](/authentication/email)
-flow to authenticate and sign a transaction or add a new authenticator.
diff --git a/docs/sdks/_category_.json b/docs/sdks/_category_.json
deleted file mode 100644
index d43a0507..00000000
--- a/docs/sdks/_category_.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "label": "SDKs",
- "position": 2,
- "collapsed": false,
- "link": {
- "type": "generated-index",
- "description": "Discover how to effectively utilize the Turnkey SDKs in your applications, with comprehensive information on setup, usage, and best practices for integrating Turnkey's capabilities seamlessly."
- }
-}
diff --git a/docs/sdks/advanced/_category_.json b/docs/sdks/advanced/_category_.json
deleted file mode 100644
index 44cc24dc..00000000
--- a/docs/sdks/advanced/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Advanced",
- "position": 13,
- "link": {
- "type": "generated-index",
- "description": "Use Turnkey's low-level http libraries directly"
- }
-}
diff --git a/docs/sdks/advanced/api-key-stamper.mdx b/docs/sdks/advanced/api-key-stamper.mdx
deleted file mode 100644
index f7613c8a..00000000
--- a/docs/sdks/advanced/api-key-stamper.mdx
+++ /dev/null
@@ -1,178 +0,0 @@
----
-title: "ApiKeyStamper"
-sidebar_position: 2
-description: Guide on using the ApiKeyStamper.
-slug: /sdks/advanced/api-key-stamper
----
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-## Introduction
-
-The [`@turnkey/api-key-stamper`](https://www.npmjs.com/package/@turnkey/api-key-stamper) package simplifies the process of using your public/private API keys and passkeys to stamp and approve activity requests for Turnkey's API.
-This stamping mechanism is central to the API's security, ensuring that each request is authenticated and authorized.
-For an in-depth understanding of API keys see [this section](/faq#why-do-you-require-a-public--private-key-pair-to-access-turnkey-api).
-
-## Installing
-
-To get started install the [`@turnkey/api-key-stamper`](https://www.npmjs.com/package/@turnkey/api-key-stamper) client.
-
-
-
-
-```bash
-npm i @turnkey/api-key-stamper
-```
-
-
-
-
-```bash
-pnpm i @turnkey/api-key-stamper
-```
-
-
-
-
-```bash
-yarn add @turnkey/api-key-stamper
-```
-
-
-
-
-## Initializing
-
-The `ApiKeyStamper` class implements the `TStamper` interface used by the [TurnkeyClient](./turnkey-client.mdx) in the `@turnkey/http` module.
-It encapsulates the logic necessary to sign activity requests and generates the appropriate HTTP headers for authentication.
-To get started with an `ApiKeyStamper`, you can initialize it using its constructor:
-
-### `constructor(config: TApiKeyStamperConfig): TStamper`
-
-#### Parameters
-
-
-
-An object containing configuration settings for the stamper.
-
-
-
-
-
-Your Turnkey API private key.
-
-
-
-
-
-Your Turnkey API public key.
-
-
-
-#### Types
-
-##### `TApiKeyStamperConfig`
-
-```ts
-type TApiKeyStamperConfig = {
- apiPublicKey: string;
- apiPrivateKey: string;
-};
-```
-
-##### `TStamper`
-
-```ts
-interface TStamper {
- stamp: (input: string) => Promise;
-}
-```
-
-#### Example
-
-The example below shows how to initialize and use the `ApiKeyStamper` with the `TurnkeyClient` to make a request
-to Turnkey's [`/public/v1/query/whoami`](https://docs.turnkey.com/api#tag/Sessions/operation/GetWhoami) endpoint:
-
-```ts
-import { TurnkeyClient } from "@turnkey/http";
-import { ApiKeyStamper } from "@turnkey/api-key-stamper";
-
-// Following best practices, define parameters in your .env file
-const baseUrl = process.env.TURNKEY_BASE_URL || "https://api.turnkey.com";
-const apiPublicKey = process.env.TURNKEY_API_PUBLIC_KEY;
-const apiPrivateKey = process.env.TURNKEY_API_PRIVATE_KEY;
-
-// Initialize the API key stamper
-const stamper = new ApiKeyStamper({ apiPublicKey, apiPrivateKey });
-
-// Initialize the Turnkey client
-const tk = new TurnkeyClient({ baseUrl }, stamper);
-
-// Now you can make authenticated requests using the APIKeyStamper
-const whoami = await tk.getWhoami({
- organizationId: "",
-});
-```
-
-## Methods
-
-### `stamp: (input: string) => Promise`
-
-Creates a digital stamp which includes the public key, signature scheme, and a signature.
-
-#### Parameters
-
-
-
-The payload that needs to be stamped.
-
-
-
-#### Types
-
-##### `TStamp`
-
-```ts
-type TStamp = {
- stampHeaderName: string;
- stampHeaderValue: string;
-};
-```
diff --git a/docs/sdks/golang.mdx b/docs/sdks/golang.mdx
deleted file mode 100644
index 44e53584..00000000
--- a/docs/sdks/golang.mdx
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: "Golang"
-sidebar_position: 7
-description: Golang SDK
-slug: /sdks/golang
----
-
-Turnkey offers native tooling for interacting with the API using Golang. See https://github.com/tkhq/go-sdk for more details.
diff --git a/docs/sdks/introduction.mdx b/docs/sdks/introduction.mdx
deleted file mode 100644
index 6f7b3834..00000000
--- a/docs/sdks/introduction.mdx
+++ /dev/null
@@ -1,200 +0,0 @@
----
-title: "Introduction"
-sidebar_position: 1
-description: Introduction to the SDK
-slug: /sdks/introduction
----
-
-import Table from "@site/src/components/table/table";
-import { CheckmarkCircleIcon } from "@site/src/components/icons";
-
-# SDK Reference
-
-Turnkey provides a variety of client and server SDKs which simplify interacting with Turnkey's API. The SDKs offer methods, utilities, and helper functions to quickly implement features and perform common workflows.
-
-The following SDK reference tables separate our SDKs by Client and Server. The column headers indicate the specific languages or frameworks for which we have an SDK. The rows indicate a specific feature or capability that Turnkey provides.
-
-A green checkmark in the table indicates that the SDK provides either a complete implementation for a feature or helper methods exist and can be composed in a few lines of code to implement a workflow. If no checkmark is present it means the SDK does not offer support for that feature.
-
-Turnkey also has several [wrappers for popular web3 libraries](https://docs.turnkey.com/category/web3-libraries) to streamline integration into existing dApps.
-
-## Client Side SDKs
-
-
,
- ,
- ,
- ,
- ,
- ],
- [
- "Wallet Management",
- ,
- ,
- ,
- "",
- "",
- ],
- [
- "Policy Management",
- ,
- ,
- ,
- "",
- "",
- ],
- ]}
-/>
diff --git a/docs/sdks/javascript-server.mdx b/docs/sdks/javascript-server.mdx
deleted file mode 100644
index 89d059be..00000000
--- a/docs/sdks/javascript-server.mdx
+++ /dev/null
@@ -1,175 +0,0 @@
----
-title: "JavaScript Server"
-sidebar_position: 3
-description: JavaScript Server SDK
-slug: /sdks/javascript-server
----
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-## Overview
-
-The [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server) package exposes functionality that lets developers build server-side functionality for applications that interact with the Turnkey API. It exposes a ready-made API client class which manages the process of constructing requests to the Turnkey API and authenticating them with a valid API key. Furthermore, it exposes API proxies that forward requests from your application's client that need to be signed by parent organizations API key.
-
-Use the [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server) package to handle server-side interactions for applications that interact with the Turnkey API.
-
-## Installation
-
-
-
-
-```bash
-npm install @turnkey/sdk-server
-```
-
-
-
-
-```bash
-yarn add @turnkey/sdk-server
-```
-
-
-
-
-
-## Initializing
-
-```typescript
-import { Turnkey } from "@turnkey/sdk-server";
-
-const turnkey = new Turnkey({
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
- apiBaseUrl: "https://api.turnkey.com",
- apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
- apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
-});
-```
-
-#### Parameters
-
-
-
-An object containing configuration settings for the Server Client.
-
-
-
-
-
-The root organization that requests will be made from unless otherwise specified
-
-
-
-
-
-The base URL that API requests will be sent to (use https://api.turnkey.com when making requests to Turnkey's API)
-
-
-
-
-
-The API Private Key to sign requests with (this will normally be the API Private Key to your root organization)
-
-
-
-
-
-The API Public Key associated with the configured API Private Key above
-
-
-
-## Creating Clients
-
-Calls to Turnkey's API must be signed with a valid credential (often referred to in the docs as [stamping](/developer-reference/api-overview/stamps)) from the user initiating the API call. When using the Server SDK, the user initiating the API call is normally your root organization, and the API call is authenticated with the API keypair you create on the Turnkey dashboard.
-
-#### `apiClient()`
-
-The `apiClient` method returns an instance of the `TurnkeyApiClient` which will sign requests with the injected `apiPrivateKey`, and `apiPublicKey` credentials.
-
-```typescript
-const apiClient = turnkey.apiClient();
-const walletsResponse = await apiClient.getWallets();
-
-// this will sign the request with the configured api credentials
-```
-
-## Creating API Proxies
-
-There are certain actions that are initiated by users, but require the activity to be signed by the root organization itself. Examples of this include the initial creation of the user `subOrganization` or sending an email to a user with a login credential as part of an `emailAuth` flow.
-
-These can be implemented in your backend by creating an `apiClient` and handling requests from your browser application at different routes, but we have also provided a convenience method for doing this by having allowing a single `apiProxy` to handle requests at a single route and automatically sign specific user actions with the root organization's credentials.
-
-#### expressProxyHandler()
-
-The `expressProxyHandler()` method creates a proxy handler designed as a middleware for Express applications. It provides an API endpoint that forwards requests to the Turnkey API server.
-
-```typescript
-const turnkeyProxyHandler = turnkey.expressProxyHandler({
- allowedMethods: ["createSubOrganization", "emailAuth", "getSubOrgIds"],
-});
-
-app.post("/apiProxy", turnkeyProxyHandler);
-
-// this will sign requests made with the client-side `serverSign` function with the root organization's API key for the allowedMethods in the config
-```
-
-#### 2. nextProxyHandler() [WIP]
-
-The `nextProxyHandler()` method creates a proxy handler designed as a middleware for Next.js applications. It provides an API endpoint that forwards requests to the Turnkey API server.
-
-```typescript
-// Configure the Next.js handler with allowed methods
-const turnkeyProxyHandler = turnkey.nextProxyHandler({
- allowedMethods: ["createSubOrganization", "emailAuth", "getSubOrgIds"],
-});
-
-export default turnkeyProxyHandler;
-
-// this will sign requests made with the client-side `serverSign` function with the root organization's API key for the allowedMethods in the config
-```
diff --git a/docs/sdks/ruby.mdx b/docs/sdks/ruby.mdx
deleted file mode 100644
index 51c447de..00000000
--- a/docs/sdks/ruby.mdx
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: "Ruby"
-sidebar_position: 9
-description: Ruby SDK
-slug: /sdks/Ruby
----
-
-Turnkey offers native tooling for interacting with the API using Ruby. See https://github.com/tkhq/ruby-sdk for more details.
diff --git a/docs/sdks/rust.mdx b/docs/sdks/rust.mdx
deleted file mode 100644
index 6e4b5ee1..00000000
--- a/docs/sdks/rust.mdx
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: "Rust"
-sidebar_position: 8
-description: Rust SDK
-slug: /sdks/rust
----
-
-Turnkey offers native tooling for interacting with the API using Rust. See https://github.com/tkhq/rust-sdk for more details.
diff --git a/docs/sdks/swift/_category_.json b/docs/sdks/swift/_category_.json
deleted file mode 100644
index 7444705d..00000000
--- a/docs/sdks/swift/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Swift",
- "position": 12,
- "link": {
- "type": "doc",
- "id": "sdks/swift/index"
- }
-}
diff --git a/docs/sdks/swift/index.mdx b/docs/sdks/swift/index.mdx
deleted file mode 100644
index d2df1790..00000000
--- a/docs/sdks/swift/index.mdx
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: Turnkey Swift SDK
-description: Guides to using the Turnkey Swift SDK
----
-
-import DocCardList from "@theme/DocCardList";
-
-This documentation contains guides for using the [Turnkey Swift SDK](https://github.com/tkhq/swift-sdk).
-
-
diff --git a/docs/sdks/web3-libraries/_category_.json b/docs/sdks/web3-libraries/_category_.json
deleted file mode 100644
index ef28c752..00000000
--- a/docs/sdks/web3-libraries/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Web3 Libraries",
- "position": 12,
- "link": {
- "type": "generated-index",
- "description": "Turnkey Web3 Libraries"
- }
-}
diff --git a/docs/sdks/web3-libraries/cosmjs.mdx b/docs/sdks/web3-libraries/cosmjs.mdx
deleted file mode 100644
index 6916c4cf..00000000
--- a/docs/sdks/web3-libraries/cosmjs.mdx
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: "CosmJS"
-sidebar_position: 3
-description: CosmJS Wrapper
-slug: /sdks/web3/cosmjs
----
-
-## CosmJS
-
-Similarly, [`@turnkey/cosmjs`](https://www.npmjs.com/package/@turnkey/cosmjs) exports a `TurnkeyDirectWallet` that serves as a drop-in replacement for a CosmJS direct wallet. It includes support for `signDirect`. See full implementation [here](https://github.com/tkhq/sdk/tree/main/packages/cosmjs) for more details and examples.
-
-```node
-// Initialize a Turnkey Signer
-const turnkeySigner = await TurnkeyDirectWallet.init({
- config: {
- ...
- },
- prefix: "celestia", // can be replaced with other Cosmos chains
-});
-
-const account = refineNonNull((await turnkeySigner.getAccounts())[0]);
-const compressedPublicKey = toHex(account.pubkey);
-const selfAddress = account.address;
-```
diff --git a/docs/sdks/web3-libraries/eip-1193.mdx b/docs/sdks/web3-libraries/eip-1193.mdx
deleted file mode 100644
index 84b9895d..00000000
--- a/docs/sdks/web3-libraries/eip-1193.mdx
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: "EIP 1193"
-sidebar_position: 4
-description: EIP 1193 Provider
-slug: /sdks/web3/eip-1193
----
-
-## EIP-1193
-
-[`@turnkey/eip-1193-provider`](https://www.npmjs.com/package/@turnkey/eip-1193-provider) is a Turnkey-compatible Ethereum provider that conforms to the [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) standard. It's built to seamlessly integrate with a broad spectrum of EVM-compatible chains, offering capabilities like account management, transaction signing, and blockchain interaction.
-
-See [`with-eip-1193-provider`](https://github.com/tkhq/sdk/tree/main/examples/with-eip-1193-provider) for an example.
diff --git a/docs/sdks/web3-libraries/ethers.mdx b/docs/sdks/web3-libraries/ethers.mdx
deleted file mode 100644
index adc03387..00000000
--- a/docs/sdks/web3-libraries/ethers.mdx
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: "Ethers"
-sidebar_position: 1
-description: Ethers Wrapper
-slug: /sdks/web3/ethers
----
-
-## Ethers
-
-[`@turnkey/ethers`](https://www.npmjs.com/package/@turnkey/ethers) exports a `TurnkeySigner` that serves as a drop-in replacement for an Ethers signer. Out of the box, it supports `{ signTransaction | signMessage | signTypedData }`. See full implementation [here](https://github.com/tkhq/sdk/tree/main/packages/ethers) for more details and examples. Note that you must **bring your own provider and connect it** to the TurnkeySigner.
-
-```node
-// Initialize a Turnkey Signer
-const turnkeySigner = new TurnkeySigner({
- ...
-});
-
-// Bring your own provider (such as Alchemy or Infura: https://docs.ethers.org/v6/api/providers/)
-const network = "goerli";
-const provider = new ethers.providers.InfuraProvider(network);
-const connectedSigner = turnkeySigner.connect(provider);
-```
diff --git a/docs/sdks/web3-libraries/solana.mdx b/docs/sdks/web3-libraries/solana.mdx
deleted file mode 100644
index 6bb0652a..00000000
--- a/docs/sdks/web3-libraries/solana.mdx
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: "Solana"
-sidebar_position: 4
-description: Solana Web3 Wrapper
-slug: /sdks/web3/solana
----
-
-## @solana/web3
-
-We have released a package that you can use to sign transactions and messages: [`@turnkey/solana`](https://www.npmjs.com/package/@turnkey/solana). See [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana) for an example.
diff --git a/docs/sdks/web3-libraries/viem.mdx b/docs/sdks/web3-libraries/viem.mdx
deleted file mode 100644
index acf10d84..00000000
--- a/docs/sdks/web3-libraries/viem.mdx
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: "Viem"
-sidebar_position: 2
-description: Viem Wrapper
-slug: /sdks/web3/viem
----
-
-## Viem
-
-[`@turnkey/viem`](https://www.npmjs.com/package/@turnkey/viem) provides a Turnkey [Custom Account](https://viem.sh/docs/accounts/custom.html#custom-account) (signer) which implements the signing APIs expected by Viem clients.
-
-See [`with-viem`](https://github.com/tkhq/sdk/tree/main/examples/with-viem) and [`with-viem-and-passkeys`](https://github.com/tkhq/sdk/tree/main/examples/with-viem-and-passkeys) for examples.
diff --git a/docs/solutions/_category_.json b/docs/solutions/_category_.json
deleted file mode 100644
index b69bf317..00000000
--- a/docs/solutions/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Solutions",
- "position": 3,
- "collapsed": false
-}
diff --git a/docs/solutions/embedded-wallets/_category_.json b/docs/solutions/embedded-wallets/_category_.json
deleted file mode 100644
index d6b121e1..00000000
--- a/docs/solutions/embedded-wallets/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Embedded Wallets",
- "position": 1,
- "collapsed": false
-}
diff --git a/docs/solutions/embedded-wallets/code-examples/_category_.json b/docs/solutions/embedded-wallets/code-examples/_category_.json
deleted file mode 100644
index b416d083..00000000
--- a/docs/solutions/embedded-wallets/code-examples/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Code Examples",
- "position": 7,
- "link": {
- "type": "generated-index",
- "description": "Code Examples"
- }
-}
diff --git a/docs/solutions/embedded-wallets/code-examples/create-sub-org-passkey.mdx b/docs/solutions/embedded-wallets/code-examples/create-sub-org-passkey.mdx
deleted file mode 100644
index 5b4b9a18..00000000
--- a/docs/solutions/embedded-wallets/code-examples/create-sub-org-passkey.mdx
+++ /dev/null
@@ -1,575 +0,0 @@
----
-title: "Create a Sub-Org with a Passkey User"
-sidebar_position: 1
-description: Create a Sub-Org with a Passkey User
-slug: /embedded-wallets/code-examples/create-sub-org-passkey
----
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-## Overview
-
-In this guide, we'll walk through the process of creating a new end user with a passkey. Generally, these new users will take the form of a [Turnkey Sub-Organization](../../../concepts/Sub-Organizations). This process involves using the following Turnkey SDK packages:
-
-1. [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server): Used on the server-side to leverage the parent organization's public/private API key pair to create the new user's sub-organization.
-2. [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser): Used on the client-side to complete the email recovery process by adding an end-user passkey.
-3. [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react): Used for Next.js applications to initialize the Turnkey SDK.
-
-The process of creating a new sub-organization is split between client-side and server-side operations to prevent exposing the parent organization's private API key.
-
-:::info
-
-For a refresher on the relationship between your application's end users and Turnkey Sub-Organizations, see [this page](../overview#how-it-works) for more.
-
-:::
-
-## Implementation
-
-### Initialize the Turnkey SDK on the Browser
-
-
-
-
-Wrap the root layout of your application with the `TurnkeyProvider` providing the required configuration options.
-This allows you to use the Turnkey client throughout your app via the `useTurnkey()` hook.
-
-```tsx title="app/layout.tsx"
-import { TurnkeyProvider } from "@turnkey/sdk-react";
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
-
-
-
- {children}
-
-
-
- );
-}
-```
-
-:::info
-
-The `NEXT_PUBLIC_ORGANIZATION_ID` should be set to the parent
-organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
-
-The `NEXT_PUBLIC_TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](../../../authentication/passkeys/options#rp) for more details.
-
-:::
-
-
-
-
-```typescript title="src/turnkey.ts"
-import { Turnkey } from "@turnkey/sdk-browser";
-
-// Initialize the Turnkey SDK with your organization ID and API base URL
-const turnkeyBrowser = new Turnkey({
- rpId: process.env.TURNKEY_RP_ID,
- apiBaseUrl: "https://api.turnkey.com",
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
-});
-```
-
-:::info
-
-The `TURNKEY_ORGANIZATION_ID` should be set to the parent
-organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
-
-The `TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](../../../authentication/passkeys/options#rp) for more details.
-
-:::
-
-
-
-
-### Initialize the Passkey Client
-
-Next, we'll initialize the `passkeyClient`, which will enable your application to interact with passkeys.
-
-
-
-
-We add the `"use client"` directive to the Recovery component to as react hooks can only be used client-side.
-
-```tsx title="app/create-suborg.tsx"
-"use client";
-
-import { useTurnkey } from "@turnkey/sdk-react";
-
-export default function CreateSubOrganization() {
- const { passkeyClient } = useTurnkey();
-
- return
{/* ... rest of the code */}
;
-}
-```
-
-
-
-
-```typescript title="src/create-suborg.ts"
-import { Turnkey } from "@turnkey/sdk-browser";
-
-// Initialize the Turnkey SDK with your organization credentials
-const turnkey = new Turnkey({
- rpId: process.env.TURNKEY_RP_ID, // Your relying party ID
- apiBaseUrl: process.env.TURNKEY_API_BASE_URL, // Turnkey API base URL
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, // Your parent organization ID
-});
-
-// Initialize the Passkey Client
-const passkeyClient = turnkey.passkeyClient();
-
-// We'll add more functionality here in the following steps
-```
-
-
-
-
-### Create User Passkey
-
-In order to create a new passkey for a user, you can call the `createUserPasskey` SDK function.
-Calling this method will prompt the user to create a passkey, which will be securely stored by their browser.
-This credential will be associated with the user's account (sub-organization) and used for future authentication.
-Once the credential is created, we'll use it in the next step to create a new sub-organization that corresponds to the user.
-
-:::info
-
-The result of `createUserPasskey` includes an encoded challenge and attestation.
-The encoded challenge ensures the request is fresh and legitimate, while the attestation verifies
-the authenticity of the device creating the credential. For more information on how passkeys work,
-including details on the challenge and attestation objects,
-you can refer to the [Passkeys Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#passkeys).
-
-:::
-
-
-
-
-```tsx title="app/create-suborg.tsx"
-// ... previous code
-
-export default function CreateSubOrganization() {
- const { passkeyClient } = useTurnkey();
-
- const createNewPasskey = async () => {
- const credential = await passkeyClient?.createUserPasskey({
- publicKey: {
- // This is the name of the passkey that will be displayed to the user
- rp: {
- name: "Wallet Passkey",
- },
- user: {
- // We can use the username as the name and display name
- name: "Default User Name",
- displayName: "Default User Name",
- },
- },
- });
-
- // we'll use this credential in the next step to create a new sub-organization
- return credential;
- };
-
- // ... rest of the code
-
- return (/* ... */);
-}
-```
-
-
-
-
-```typescript title="src/create-suborg.ts"
-// ... previous code
-
-const createNewPasskey = async () => {
- const credential = await passkeyClient?.createUserPasskey({
- publicKey: {
- // This is the name of the passkey that will be displayed to the user
- rp: {
- name: "Wallet Passkey",
- },
- user: {
- // We can use the username as the name and display name
- name: "Default User Name",
- displayName: "Default User Name",
- },
- },
- });
-
- // we'll use this credential in the next step to create a new sub-organization
- return credential;
-};
-```
-
-
-
-
-### Initialize the Turnkey SDK on the Server
-
-Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package.
-This allows you to use the parent organization's public/private API key pair to create sub-organizations.
-
-
-
-
-For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client.
-This will ensure that the function is executed on the server-side and will have access to the
-server-side environment variables e.g. your parent organization's public/private API key pair.
-For more information on Next.js server actions, see the Next.js documentation on
-[Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
-
-```ts title="app/actions.ts"
-"use server";
-
-import { Turnkey } from "@turnkey/sdk-server";
-
-// Initialize the Turnkey Server Client on the server-side
-const turnkeyServer = new Turnkey({
- baseUrl: "https://api.turnkey.com",
- apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
- apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
-}).apiClient();
-```
-
-
-
-
-```typescript title="src/turnkey.ts"
-import { Turnkey } from "@turnkey/sdk-server";
-
-// Initialize the Turnkey Server Client on the server-side
-const turnkeyServer = new Turnkey({
- baseUrl: "https://api.turnkey.com",
- apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
- apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
-}).apiClient();
-```
-
-
-
-
-### Create a Function for Sub-Org Creation
-
-Next we'll create a new function called `createSubOrganization` that will be used to create a new sub-organization from the server-side.
-This method will be called from the client-side with the end-user's details.
-
-
-
-
-We export the `createSubOrganization` server action to be called from the client-side.
-
-```tsx title="app/actions.tsx"
-import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser";
-
-// ... previous code
-
-type TAttestation = {
- credentialId: string;
- clientDataJson: string;
- attestationObject: string;
- transports: (
- | "AUTHENTICATOR_TRANSPORT_BLE"
- | "AUTHENTICATOR_TRANSPORT_INTERNAL"
- | "AUTHENTICATOR_TRANSPORT_NFC"
- | "AUTHENTICATOR_TRANSPORT_USB"
- | "AUTHENTICATOR_TRANSPORT_HYBRID"
- )[];
-};
-
-export const createSubOrganization = async (
- email: string,
- credential: string,
- attestation: string
-) => {
- const createSubOrgResponse = await turnkeyServer.createSubOrganization({
- subOrganizationName: "My New Suborg",
- rootUsers: [
- {
- userName: "Default User Name",
- userEmail: email,
- apiKeys: [],
- authenticators: [
- {
- authenticatorName: "Default Passkey",
- challenge: challenge,
- attestation: attestation,
- },
- ],
- oauthProviders: [],
- },
- ],
- rootQuorumThreshold: 1,
- wallet: {
- walletName: "Default Wallet",
- accounts: DEFAULT_ETHEREUM_ACCOUNTS,
- },
- });
-
- return createSubOrgResponse;
-};
-```
-
-
-
-
-```typescript title="src/turnkey-server.ts"
-// ... previous code
-
-type TAttestation = {
- credentialId: string;
- clientDataJson: string;
- attestationObject: string;
- transports: (
- | "AUTHENTICATOR_TRANSPORT_BLE"
- | "AUTHENTICATOR_TRANSPORT_INTERNAL"
- | "AUTHENTICATOR_TRANSPORT_NFC"
- | "AUTHENTICATOR_TRANSPORT_USB"
- | "AUTHENTICATOR_TRANSPORT_HYBRID"
- )[];
-};
-
-export const createSubOrganization = async (
- email: string,
- credential: string,
- attestation: string
-) => {
- const createSubOrgResponse = await turnkeyServer.createSubOrganization({
- subOrganizationName: "My New Suborg",
- rootUsers: [
- {
- userName: "Default User Name",
- userEmail: email,
- apiKeys: [],
- authenticators: [
- {
- authenticatorName: "Default Passkey",
- challenge: challenge,
- attestation: attestation,
- },
- ],
- oauthProviders: [],
- },
- ],
- rootQuorumThreshold: 1,
- wallet: {
- walletName: "Default Wallet",
- accounts: DEFAULT_ETHEREUM_ACCOUNTS,
- },
- });
-
- return createSubOrgResponse;
-};
-```
-
-
-
-
-### Complete Create Sub-Organization
-
-At this stage, we create the sub-organization using the **server-side** function we created in the previous step.
-
-
-
-
-##### 1. Import the server action
-
-```tsx title="app/create-suborg.tsx"
-import { createSubOrganization } from "./actions";
-```
-
-##### 2. Call `createSubOrganization` with the end-user's details
-
-```tsx title="app/create-suborg.tsx"
-// ...
-
-import { useForm } from "react-hook-form";
-
-type TSubOrgFormData = {
- email: string;
-};
-
-export default function CreateSubOrganization() {
- // ...
-
- // Use form handler for suborg creation
- const { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } =
- useForm();
-
- // Maintain state
- const [createSubOrganizationResponse, setCreateSubOrganizationResponse] =
- useState(null);
-
- const createSubOrg = async (data: TSubOrgFormData) => {
- const { encodedChallenge: challenge, attestation } =
- await createNewPasskey();
-
- const createSubOrganizationResponse = await createSubOrganization(
- data.email,
- challenge,
- attestation
- );
-
- setCreateSubOrganizationResponse(createSubOrganizationResponse);
- };
-
- return (
-
- {createSubOrganizationResponse ? (
-
You've created a sub-organization!
- ) : (
-
- )}
-
- );
-}
-```
-
-
- create-suborg.tsx
-
-```tsx
-"use client";
-
-import { useState } from "react";
-import { useTurnkey } from "@turnkey/sdk-react";
-import { useForm } from "react-hook-form";
-
-// Import the createSubOrganization server action
-import { createSubOrganization } from "./actions";
-
-type TSubOrgFormData = {
- email: string;
-};
-
-type TAttestation = {
- credentialId: string;
- clientDataJson: string;
- attestationObject: string;
- transports: (
- | "AUTHENTICATOR_TRANSPORT_BLE"
- | "AUTHENTICATOR_TRANSPORT_INTERNAL"
- | "AUTHENTICATOR_TRANSPORT_NFC"
- | "AUTHENTICATOR_TRANSPORT_USB"
- | "AUTHENTICATOR_TRANSPORT_HYBRID"
- )[];
-};
-
-export default function CreateSubOrganization() {
- const { passkeyClient } = useTurnkey();
-
- // Use form handler for suborg creation
- const { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } =
- useForm();
-
- // Maintain state
- const [createSubOrganizationResponse, setCreateSubOrganizationResponse] =
- useState(null);
-
- const createNewPasskey = async () => {
- const credential = await passkeyClient?.createUserPasskey({
- publicKey: {
- // This is the name of the passkey that will be displayed to the user
- rp: {
- name: "Wallet Passkey",
- },
- user: {
- // We can use the username as the name and display name
- name: "Default User Name",
- displayName: "Default User Name",
- },
- },
- });
-
- // we'll use this credential in the next step to create a new sub-organization
- return credential;
- };
-
- const createSubOrg = async (data: TSubOrgFormData) => {
- const { encodedChallenge: challenge, attestation } =
- await createNewPasskey();
-
- const createSubOrganizationResponse = await createSubOrganization(
- data.email,
- challenge,
- attestation
- );
-
- setCreateSubOrganizationResponse(createSubOrganizationResponse);
- };
-
- return (
-
- {createSubOrganizationResponse ? (
-
You've created a sub-organization!
- ) : (
-
- )}
-
- );
-}
-```
-
-
-
-
-
-
-##### 1. Import the server action
-
-```tsx title="app/create-suborg.tsx"
-import { createSubOrganization } from "./turnkey-server";
-```
-
-##### 2. Call `createSubOrganization` with the end-user's details
-
-```typescript title="src/turnkey.ts"
-import { createSubOrganization } from "./turnkey-server";
-
-// ... rest of the code
-
-const createSubOrganizationResponse = await createSubOrganization(
- email,
- attestation,
- challenge
-);
-```
-
-
-
-
-## Examples
-
-A few mini examples where sub-orgs are created with passkeys, see the following:
-
-- https://github.com/tkhq/sdk/tree/main/examples/with-solana-passkeys
-- https://github.com/tkhq/sdk/tree/main/examples/with-eth-passkeys-galore
-- https://github.com/tkhq/sdk/tree/main/examples/with-federated-passkeys
diff --git a/docs/solutions/embedded-wallets/email-auth-for-sub-organizations.md b/docs/solutions/embedded-wallets/email-auth-for-sub-organizations.md
deleted file mode 100644
index 4710e4f4..00000000
--- a/docs/solutions/embedded-wallets/email-auth-for-sub-organizations.md
+++ /dev/null
@@ -1,198 +0,0 @@
----
-sidebar_position: 3
-description: Learn about Email Auth on Turnkey
-slug: /embedded-wallets/sub-organization-auth
----
-
-# Email Authentication
-
-Email auth is a powerful feature to couple with [sub-organizations](/concepts/Sub-Organizations) for your users. This approach empowers your users to authenticate their Turnkey in a simple way (via email!), while minimizing your involvement: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to.
-
-Our [Demo Embedded Wallet](https://wallet.tx.xyz) application serves an example of how email auth functionality might be integrated. We encourage you to try it (and check out the [code](https://github.com/tkhq/demo-embedded-wallet)) before diving into your own implementation.
-
-## Prerequisites
-
-Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth on behalf of suborgs. Check out our [Quickstart guide](/getting-started/quickstart) if you need help getting started. To allow an API user to initiate email auth, you'll need the following policy in your main organization:
-
-```json JSON
-{
- "effect": "EFFECT_ALLOW",
- "consensus": "approvers.any(user, user.id == '')",
- "condition": "activity.resource == 'AUTH' && activity.action == 'CREATE'"
-}
-```
-
-## Helper packages
-
-- We have released open-source code to create target encryption keys, decrypt auth credentials, and sign Turnkey activities. We've deployed this as a static HTML page hosted on `auth.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the auth credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
-- We have also built a package to help you insert this iframe and interact with it in the context of email auth: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
-
-In the rest of this guide we'll assume you are using these helpers.
-
-## Email Auth step-by-step
-
-Here's a diagram summarizing the email auth flow step-by-step ([direct link](/img/email_auth_steps.png)):
-
-
-
-Let's review these steps in detail:
-
-1. User on `yoursite.xyz` clicks "auth", and a new auth UI is shown. We recommend this auth UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to successfully authenticate. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
-
- ```js
- const iframeStamper = new IframeStamper({
- iframeUrl: "https://auth.turnkey.com",
- // Configure how the iframe element is inserted on the page
- iframeContainerId: "your-container",
- iframeElementId: "turnkey-iframe",
- });
-
- // Inserts the iframe in the DOM. This creates the new encryption target key
- const publicKey = await iframeStamper.init();
- ```
-
-2. Your code receives the iframe public key and shows the auth form, and the user enters their email address.
-3. Your app can now create and sign a new `EMAIL_AUTH` activity with the user email and the iframe public key in the parameters. Optional arguments include a custom name for the API key, and a specific duration (denoted in seconds) for it. Note: you'll need to retrieve the sub-organization ID based on the user email.
-4. Email is received by the user.
-5. User copies and pastes their auth code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe. In order to enable persistent sessions, save the auth code in local storage:
-
- ```js
- window.localStorage.setItem("BUNDLE", bundle);
- ```
-
- See [Email Customization](#email-customization) below to use a magic link instead of a one time code.
-
-6. Your app injects the auth code into the iframe for decryption:
-
- ```js
- await iframeStamper.injectCredentialBundle(code);
- ```
-
-7. At this point, the user is authenticated!
-
-8. Your app should use [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new activity, e.g. `CREATE_WALLET`:
-
- ```js
- // New client instantiated with our iframe stamper
- const client = new TurnkeyClient(
- { baseUrl: "https://api.turnkey.com" },
- iframeStamper,
- );
-
- // Sign and submits the CREATE_WALLET activity
- const response = await client.createWallet({
- type: "ACTIVITY_TYPE_CREATE_WALLET",
- timestampMs: String(Date.now()),
- organizationId: authResponse.organizationId,
- parameters: {
- walletName: "Default Wallet",
- accounts: [
- {
- curve: "CURVE_SECP256K1",
- pathFormat: "PATH_FORMAT_BIP32",
- path: "m/44'/60'/0'/0/0",
- addressFormat: "ADDRESS_FORMAT_ETHEREUM",
- },
- ],
- },
- });
- ```
-
-9. User navigates to a new tab.
-
-10. Because the code was also saved in local storage (step 6), it can be injected into the iframe across different tabs, resulting in a persistent session. See our [Demo Embedded Wallet](https://wallet.tx.xyz) for a [sample implementation](https://github.com/tkhq/demo-embedded-wallet/blob/942ccc97de7f9289892b1714b10f3a21afec71b3/src/providers/auth-provider.tsx#L150-L171), specifically dealing with sharing the iframeStamper across components.
-
- ```js
- const code = window.localStorage.getItem("BUNDLE");
- await iframeStamper.injectCredentialBundle(code);
- ```
-
-11. Again, the user is authenticated, and able to initiate activities!
-
-12. Just like step 8, the iframeStamper can be used to sign another activity.
-
- ```js
- const client = new TurnkeyClient(
- { baseUrl: "https://api.turnkey.com" },
- iframeStamper,
- );
-
- // Sign and submits a SIGN_TRANSACTION activity
- const response = await client.signTransaction({
- type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
- timestampMs: String(Date.now()),
- organizationId: authResponse.organizationId,
- parameters: {
- signWith: "0x...",
- type: "TRANSACTION_TYPE_ETHEREUM",
- unsignedTransaction: "unsigned-tx",
- },
- });
- ```
-
-Congrats! You've succcessfully implemented Email Auth! 🥳
-
-## Integration notes
-
-### Email customization
-
-We offer customization for the following:
-
-- `appName`: the name of the application. This will be used in the email's subject, e.g. `Sign in to ${appName}`
-- `logoUrl`: a link to a PNG with a max width of 340px and max height of 124px
-- `magicLinkTemplate`: a template for the URL to be used in the magic link button, e.g. `https://dapp.xyz/%s`. The auth bundle will be interpolated into the `%s`
-
-```js
-// Sign and submits the EMAIL_AUTH activity
-const response = await client.emailAuth({
- type: "ACTIVITY_TYPE_EMAIL_AUTH",
- timestampMs: String(Date.now()),
- organizationId: ,
- parameters: {
- email: ,
- targetPublicKey: ,
- apiKeyName: ,
- expirationSeconds: ,
- emailCustomization: {
- appName: ,
- logoUrl: ,
- magicLinkTemplate:
- }
- },
-});
-```
-
-### Bespoke email templates
-
-We also support custom HTML email templates for [Enterprise](https://www.turnkey.com/pricing) clients. This allows you to inject arbitrary data from a JSON string containing key-value pairs. In this case, the `emailCustomization` variable may look like:
-
-```js
-...
-emailCustomization: {
- templateId: ,
- templateVariables: "{\"username\": \"alice and bob\"}"
-}
-...
-```
-
-In this specific example, the value `alice and bob` can be interpolated into the email template using the key `username`. The use of such template variables is purely optional.
-
-Here's an example of a custom HTML email containing an email auth bundle:
-
-
-
-
-
-If you are interested in implementing bespoke, fully-customized email templates, please reach out to .
-
-### Credential validity checks
-
-By default, if a Turnkey request is signed with an expired credential, the API will return a 401 error. If you'd like to validate an injected credential, you can specifically use the `whoami` endpoint:
-
-```js
-const whoamiResponse = await client.getWhoami({
- organizationId,
-});
-```
-
-A valid response indicates the credential is still live; otherwise, an error including `unable to authenticate: api key expired` will be thrown.
diff --git a/docs/solutions/embedded-wallets/email-recovery-in-sub-organizations.md b/docs/solutions/embedded-wallets/email-recovery-in-sub-organizations.md
deleted file mode 100644
index 7653c2f6..00000000
--- a/docs/solutions/embedded-wallets/email-recovery-in-sub-organizations.md
+++ /dev/null
@@ -1,102 +0,0 @@
----
-sidebar_position: 4
-description: Learn about Email Recovery on Turnkey
-slug: /embedded-wallets/sub-organization-recovery
----
-
-# Email Recovery
-
-:::info
-
-Email Recovery is a legacy flow, now superseded by [Email Auth](./email-auth-for-sub-organizations.md), which can used to implement recovery flows and more.
-
-:::
-
-Email recovery shines if you are leveraging [sub-organizations](/concepts/Sub-Organizations) to create embedded wallets for your users. This allows your users to recover their Turnkey account if something goes wrong with their passkeys, and keeps you out of the loop: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to.
-
-A simple example demonstrating email recovery end-to-end can be found [here](https://github.com/tkhq/sdk/tree/main/examples/email-recovery).
-
-
-
-## Prerequisites
-
-Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email recovery on behalf of suborgs. Check out our [Quickstart guide](/getting-started/quickstart) if you need help getting started. To allow an API user to initiate email recovery, you'll need the following policy in your main organization:
-
-```json JSON
-{
- "effect": "EFFECT_ALLOW",
- "consensus": "approvers.any(user, user.id == '')",
- "condition": "activity.resource == 'RECOVERY' && activity.action == 'CREATE'"
-}
-```
-
-## Helper packages
-
-- We have released open-source code to create target encryption keys, decrypt recovery credentials, and sign Turnkey activities. We've deployed this a static HTML page hosted on `recovery.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the recovery credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
-- We have also built a package to help you insert this iframe and interact with it in the context of email recovery: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
-
-In the rest of this guide we'll assume you are using these helpers.
-
-## Email Recovery step-by-step
-
-Here's a diagram summarizing the email recovery flow step-by-step ([direct link](/img/email_recovery_steps.png)):
-
-
-
-
-
-Let's review these steps in detail:
-
-1. User on `yoursite.xyz` clicks "recovery", and a new recovery UI is shown. We recommend this recovery UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to complete recovery. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
-
- ```js
- const iframeStamper = new IframeStamper({
- iframeUrl: "https://recovery.turnkey.com",
- // Configure how the iframe element is inserted on the page
- iframeContainerId: "your-container",
- iframeElementId: "turnkey-iframe",
- });
-
- // Inserts the iframe in the DOM. This creates the new encryption target key
- const publicKey = await iframeStamper.init();
- ```
-
-2. Your code receives the iframe public key and shows the recovery form, and the user enters their email address.
-3. Your app can now create and sign a new `INIT_USER_EMAIL_RECOVERY` activity with the user email and the iframe public key in the parameters. Note: you'll need to retrieve the sub-organization ID based on the user email.
-4. Email is received by the user.
-5. User copies and pastes their recovery code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe.
-6. Your app injects the recovery code into the iframe for decryption:
- ```js
- await iframeStamper.injectCredentialBundle(code);
- ```
-7. Your app prompts the user to create a new passkey (using our SDK functionality):
- ```js
- // Creates a new passkey
- let attestation = await getWebAuthnAttestation(...params...)
- ```
-8. Your app uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new `RECOVER_USER` activity:
-
- ```js
- // New client instantiated with our iframe stamper
- const client = new TurnkeyClient(
- { baseUrl: "https://api.turnkey.com" },
- iframeStamper,
- );
-
- // Sign and submits the RECOVER_USER activity
- const response = await client.recoverUser({
- type: "ACTIVITY_TYPE_RECOVER_USER",
- timestampMs: String(Date.now()),
- organizationId: initRecoveryResponse.organizationId,
- parameters: {
- userId: initRecoveryResponse.userId,
- authenticator: {
- authenticatorName: data.authenticatorName,
- challenge: base64UrlEncode(challenge),
- attestation: attestation,
- },
- },
- });
- ```
-
-Once the `RECOVER_USER` activity is successfully posted, the recovery is complete! If this activity succeeds, your frontend can redirect to login/sign-in or perform crypto signing with the new passkey.
diff --git a/docs/solutions/embedded-wallets/overview.md b/docs/solutions/embedded-wallets/overview.md
deleted file mode 100644
index 09180d8a..00000000
--- a/docs/solutions/embedded-wallets/overview.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-sidebar_position: 1
-description: Intro to embedded wallets on Turnkey
-slug: /embedded-wallets/overview
-sidebar_label: Introduction
----
-
-# Embedded Wallets
-
-## Seamless, secure wallet infrastructure
-
-With embedded wallets on Turnkey, you can create custom wallet experiences that are seamlessly integrated into your product, without compromising on security. Whether you need custodial or non-custodial wallets, our infrastructure provides the foundation for building innovative, user-friendly crypto products.
-
-### Why embedded wallets?
-
-Embedded wallets give you the freedom to design and control the entire user experience, while offloading the complexity and risk of private key management to Turnkey. As one of our customers put it:
-
-> "The ability for us to build our own stack and control the entire user experience without worrying about security has been one of the best things about Turnkey."
-
-With Embedded wallets, you can:
-
-- Create custom wallet flows that match your product's look and feel
-- Choose between custodial or non-custodial models based on your use case
-- Leverage advanced security and UX features like multi-sig and session keys
-- Support various authentication methods, from passkeys to email-based flows
-- Focus on building great products, not managing private keys
-
-### How it works
-
-Turnkey's Embedded Wallets are built on top of our [Sub-Organizations](/concepts/sub-organizations) primitive. Each wallet is represented by a sub-organization, which can be configured with different security settings and access controls.
-
-##### Custodial vs non-custodial
-
-- For custodial wallets, your application holds the master key and can initiate transactions on behalf of users.
-- For non-custodial wallets, users hold their own private keys and must approve each transaction, whether it's via their own passkey, API key, or iframe session.
-
-##### Advanced features
-
-- Multi-sig: Require multiple approvals for sensitive transactions
-- Session Keys: Grant temporary, limited access to wallets for improved UX
-- Flexible Authentication: Use passkeys, email auth, or other methods to secure user access
-
-##### Getting started
-
-To start building with embedded wallets, check out our detailed guides and API references:
-
-- [Sub-Organizations as Wallets](/embedded-wallets/sub-organizations-as-wallets)
-- [Sub-Organization Email Auth](/embedded-wallets/sub-organization-auth)
-- [Sub-Organization Recovery](/embedded-wallets/sub-organization-recovery)
-- [Turnkey API Reference](/api)
-
-Or, if you’re looking to create smart wallets for your users, using Turnkey as a user-friendly, recoverable EOA, check out our [guide](/reference/aa-wallets) on integrating Account Abstraction (AA) wallets with Turnkey.
diff --git a/docs/solutions/signing-automation/_category_.json b/docs/solutions/signing-automation/_category_.json
deleted file mode 100644
index a68d9bbe..00000000
--- a/docs/solutions/signing-automation/_category_.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "label": "Signing Automation",
- "position": 2,
- "collapsed": false
-}
diff --git a/docs/solutions/signing-automation/code-examples/_category_.json b/docs/solutions/signing-automation/code-examples/_category_.json
deleted file mode 100644
index f87dc892..00000000
--- a/docs/solutions/signing-automation/code-examples/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Code Examples",
- "position": 2,
- "link": {
- "type": "generated-index",
- "description": "Code Examples"
- }
-}
diff --git a/docs/solutions/signing-automation/code-examples/signing-transactions.mdx b/docs/solutions/signing-automation/code-examples/signing-transactions.mdx
deleted file mode 100644
index 9dfde097..00000000
--- a/docs/solutions/signing-automation/code-examples/signing-transactions.mdx
+++ /dev/null
@@ -1,51 +0,0 @@
----
-title: "Signing Transactions"
-sidebar_position: 2
-description: Signing Transactions
-slug: /signing-automation/code-examples/signing-transactions
----
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-This is a guide to signing transactions in a server context. While these snippets leverage Ethers, it can be swapped out for other signers in the Viem or Solana contexts. An example for Ethers can be found [here](https://github.com/tkhq/sdk/tree/main/examples/with-ethers), and [here](https://github.com/tkhq/sdk/tree/main/examples/with-viem) for Viem in the server context. A similar example with Solana can be found [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana).
-
-#### 1. Initialize the Server Client
-
-```typescript
-import { Turnkey } from "@turnkey/sdk-browser";
-
-const turnkeyClient = new Turnkey({
- apiBaseUrl: "https://api.turnkey.com",
- defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
- apiPrivateKey: process.env.API_PRIVATE_KEY,
- apiPublicKey: process.env.API_PUBLIC_KEY,
-});
-```
-
-#### 2. Initialize an Ethers Provider and Turnkey Signer
-
-```typescript
-import { ethers } from "ethers";
-import { TurnkeySigner } from "@turnkey/ethers";
-
-const provider = new ethers.JsonRpcProvider();
-const turnkeySigner = new TurnkeySigner({
- client: turnkeyClient.apiClient(),
- organizationId: process.env.ORGANIZATION_ID!,
- signWith: process.env.SIGN_WITH!,
- });
-```
-
-#### 3. Call `sendTransaction` with the Turnkey Signer
-
-```typescript
-const transactionRequest = {
- to: "",
- value: ethers.parseEther(""),
- type: 2,
-};
-const sendTransaction =
- await connectedSigner.sendTransaction(transactionRequest);
-```
diff --git a/docs/welcome.mdx b/docs/welcome.mdx
deleted file mode 100644
index 3ece0783..00000000
--- a/docs/welcome.mdx
+++ /dev/null
@@ -1,346 +0,0 @@
----
-title: Welcome
-sidebar_position: 1
-slug: /
----
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import { Dropdown } from "@site/src/components/Dropdown";
-
-# Getting started with Turnkey
-
-
-
-
-
-Welcome to Turnkey! To make the most out of Turnkey's wallet infrastructure, we've compiled a list of helpful resources for you below.
-
-:::note
-
-Getting ready to launch? Ensure you **double check our [resource limits](/concepts/resource-limits) and [rate limits](/faq#do-you-have-any-rate-limits-in-place-in-your-public-api)** to ensure your implementation will not trigger these limits at production scale.
-For additional pre-launch guidance, **refer to our [Launch Checklist](/getting-started/launch-checklist)** to make sure you're ready to launch safely.
-
-:::
-
-## About
-
-[Turnkey](https://turnkey.com) is highly flexible key management infrastructure,
-purpose-built for security and scale. Our API and open-source SDKs make it easy for you to take your
-product from 0 to 1, and enable developers to create millions of embedded wallets and automate complex onchain transactions.
-
-Whether you're building a DeFi platform, a payments app, an AI agent, or anything requiring a private key,
-Turnkey offers the building blocks to bring your ideas to life.
-
-Our solution covers two main use cases:
-
-
-
-
Embedded Wallets
-
-
Create millions of embedded wallets on behalf of your users for a flawless onboarding and in-app experience.
- Read our concept overview below for a smooth implementation.
-
-
-
-
-
-## Concepts Overview
-
-:::note
-
-Before getting started, we highly recommend
-familiarizing yourself with Turnkey's core concepts below to ensure a smooth implementation.
-
-:::
-
----
-
-At the core of Turnkey is an important concept:
-instead of directly managing private keys, wallets
-are accessed through authenticators like passkeys, social logins,
-or API keys:
-
-
-
-
-
-### Here's how that works:
-
-- **Organizations (parent orgs)** in Turnkey are top-level
- entities that contain users, wallets, and policies for a business,
- with the initial "parent organization" _typically representing an
- entire Turnkey-powered application._
-- Parent organizations can create **sub-organizations (sub-orgs)**,
- which are fully segregated organizations nested under the parent organization.
- Parent orgs cannot modify the contents of a sub-org, and sub-orgs and _typically represent an end user_.
-- Both parent organizations and sub-organizations contain a set of
- **resources and authenticators** that you can configure, including their own users, wallets, API keys, private keys, and policies.
-- **Activities** (like signing transactions or creating users)
- are governed by **policies** created via Turnkey's policy engine,
- though root users can bypass the policy engine when meeting root quorum requirements.
-- **Wallets** in Turnkey are HD seed phrases that can generate
- multiple wallet accounts (addresses) for signing operations.
-
-:::note
-
-There is no set relationship between organizations, sub-organizations,
-activities, wallets, and resources.
-This makes Turnkey highly flexible and configurable to any use case.
-
-:::
-
-For a more in-depth overview, [access our documentation here](/concepts/overview).
-
-## Demos And Examples
-
----
-
-1. [Embedded wallet demo app (web)](https://wallet.tx.xyz/)
-2. [Flutter (mobile) demo app](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app)
-3. [Telegram mini-app demo](https://github.com/tkhq/demo-telegram-mini-app)
-4. [Pop-up wallet demo](https://github.com/tkhq/popup-wallet-demo)
-5. [React native demo wallet (mobile, web)](https://github.com/tkhq/react-native-demo-wallet/tree/1f2a37d42ac448e388e80d54a349c5387f532de1)
-6. [Wallet Kit (pre-generated UI components for user authentication)](https://wallets.turnkey.com/)
-7. [Building a trading bot on Solana: Guide](https://www.helius.dev/blog/how-to-build-a-secure-ai-agent-on-solana)
-
-### **Feature spotlight: Send crypto via a URL**
-
-Moonshot users can now send crypto to their friends via a URL.
-Under the hood, Turnkey pre-generates a wallet for the new user and
-loads it with the specified amount of crypto — all authenticated via a biometric passkey (e.g. Face ID).
-
-
;
}
```
+
-
-
-
+
Create a new file `src/add-passkey.ts`:
-```typescript title="src/add-passkey.ts"
+```ts src/add-passkey.ts
import { Turnkey } from "@turnkey/sdk-browser";
// Initialize the Turnkey SDK with your organization credentials
@@ -101,64 +86,58 @@ const passkeyClient = turnkey.passkeyClient();
// We'll add more functionality here in the following steps
```
-
-
+
### Authenticate the User
-Now that that the Passkey Client is initialized, we'll call the `login` function which will prompt the
-user to authenticate with their passkey. Additionally, this function will set the current user in
-local storage upon successful authentication, which will be used later when creating an additional authenticator.
-
-:::note
+Now that that the Passkey Client is initialized, we'll call the `login` function which will prompt the user to authenticate with their passkey. Additionally, this function will set the current user in local storage upon successful authentication, which will be used later when creating an additional authenticator.
-The user object which gets stored in local storage is defined as follows:
+
+ The user object which gets stored in local storage is defined as follows:
-```typescript
-export interface User {
- userId: string;
- username: string;
- organization: SubOrganization;
- readOnlySession?: ReadOnlySession;
-}
-```
-
-:::
-
-
-
+ ```
+ export interface User {
+ userId: string;
+ username: string;
+ organization: SubOrganization;
+ readOnlySession?: ReadOnlySession;
+ }
+ ```
+
-```tsx title="app/add-passkey.tsx"
+
+
+```tsx app/add-passkey.tsx
// ... previous code
export default function AddPasskey() {
- const { passkeyClient } = useTurnkey();
+ // We'll need the base Turnkey client to get the current user
+ const { passkeyClient, turnkey } = useTurnkey();
- const login = async () => {
- const response = await passkeyClient?.login();
- if (response.organizationId) {
- console.log("User authenticated successfully");
- } else {
- console.log("User authentication failed");
+ // ... previous code
+
+ const getUser = async () => {
+ // Get the current user from local storage,
+ // we'll need the `userId` to create the authenticator in the next step
+ const user = await turnkey?.getCurrentUser();
+ if (user) {
+ console.log("User retrieved successfully");
}
+ // return the user to be used in the next step
+ return user;
};
- // We'll add more functionality here in the following steps
-
return (
- {/* We'll more add UI elements here */}
);
}
```
-
-
-
-
-```typescript title="src/add-passkey.ts"
+
+
+```ts src/add-passkey.ts
// ... previous code
const login = async () => {
@@ -170,20 +149,16 @@ const login = async () => {
}
};
```
-
-
-
+
### Get the current user
-Before creating a new passkey, we'll get the current user. This function will retrieve the user from local storage,
-which was set after calling the `login` function. We'll need the `userId` to create the authenticator in the final step.
-
-
-
+Before creating a new passkey, we'll get the current user. This function will retrieve the user from local storage, which was set after calling the `login` function. We'll need the `userId` to create the authenticator in the final step.
-```tsx title="app/add-passkey.tsx"
+
+
+```ts app/add-passkey.tsx
// ... previous code
export default function AddPasskey() {
@@ -210,11 +185,9 @@ export default function AddPasskey() {
);
}
```
-
-
-
-
-```typescript title="src/add-passkey.ts"
+
+
+```ts src/add-passkey.ts
// ... previous code
const getCurrentUser = async () => {
@@ -228,31 +201,21 @@ const getCurrentUser = async () => {
return user;
};
```
-
-
+
### Create User Passkey
-Now that you have authenticated the user, you can call the `createUserPasskey` function to create a new user passkey credential.
-Calling this method will prompt the user to create a passkey, which will be securely stored by their browser.
-This credential will be associated with the user's account and used for future authentication.
-Once the credential is created, we'll use it in the next step to create a new authenticator for the user.
-
-:::info
+Now that you have authenticated the user, you can call the `createUserPasskey` function to create a new user passkey credential. Calling this method will prompt the user to create a passkey, which will be securely stored by their browser. This credential will be associated with the user's account and used for future authentication. Once the credential is created, we'll use it in the next step to create a new authenticator for the user.
-The credential includes an encoded challenge and attestation.
-The encoded challenge ensures the request is fresh and legitimate, while the attestation verifies
-the authenticity of the device creating the credential. For more information on how passkeys work,
-including details on the challenge and attestation objects,
-you can refer to the [Passkeys Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#passkeys).
+
+ The credential includes an encoded challenge and attestation. The encoded challenge ensures the request is fresh and legitimate, while the attestation verifies the authenticity of the device creating the credential. For more information on how passkeys work, including details on the challenge and attestation objects, you can refer to the [Passkeys Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#passkeys).
+
-:::
+
+
-
-
-
-```tsx title="app/add-passkey.tsx"
+```tsx app/add-passkey.tsx
// ... previous code
export default function AddPasskey() {
@@ -283,11 +246,9 @@ export default function AddPasskey() {
return (/* ... */);
}
```
-
-
-
-
-```typescript title="src/add-passkey.ts"
+
+
+```ts src/add-passkey.ts
// ... previous code
// We'll pass the user object returned from `getUser` to this function
@@ -309,62 +270,56 @@ const createNewPasskey = async (user: User) => {
return credential;
};
```
-
-
+
### Add the credential to the wallet
-Now that you have created a new user passkey credential, we'll use this credential to create a new passkey authenticator for the user.
-We'll need the userId to create the authenticator, so we'll get the current user first. This value comes from local storage which was
-set in the previous step when the user successfully authenticated via the `login` function.
+Now that you have created a new user passkey credential, we'll use this credential to create a new passkey authenticator for the user. We'll need the userId to create the authenticator, so we'll get the current user first. This value comes from local storage which was set in the previous step when the user successfully authenticated via the `login` function.
-
-
-
-```tsx title="app/add-passkey.tsx"
-// ... previous code
-
-export default function AddPasskey() {
- const { passkeyClient, turnkey } = useTurnkey();
+
+
+ ```tsx app/add-passkey.tsx
// ... previous code
- const addPasskey = async () => {
- const user = await getUser();
- const credential = await createNewPasskey(user);
-
- const authenticatorsResponse = await passkeyClient.createAuthenticators({
- authenticators: [
- {
- authenticatorName: "New Passkey Authenticator",
- challenge: credential.encodedChallenge,
- attestation: credential.attestation,
- },
- ],
- userId: user.userId,
- });
-
- // Check if the authenticator was created successfully
- if (authenticatorsResponse?.activity.id) {
- console.log("Authenticator created successfully");
- }
- };
-
- return (
-
- {/* Add a button to add the passkey to the wallet */}
-
-
-
+ {/* Add a button to add the passkey to the wallet */}
+
+
+
+ );
+ }
+ ```
+
+ ```tsx app/add-passkey.tsx
"use client";
import { useState } from "react";
@@ -433,14 +388,12 @@ export default function AddPasskey() {
);
}
-```
+ ```
+
+
+
-
-
-
-
-
-```typescript title="src/add-passkey.ts"
+```ts src/add-passkey.ts
// ... previous code
const addPasskey = async () => {
@@ -470,14 +423,12 @@ const addPasskey = async () => {
}
};
```
-
-
+
### Optional: Read/Write Sessions
-In some cases, you may want to create a read/write session for the user to reduce the number of passkey prompts.
-This session can be used instead of the passkey to sign requests to Turnkey's API to improve the user experience.
+In some cases, you may want to create a read/write session for the user to reduce the number of passkey prompts. This session can be used instead of the passkey to sign requests to Turnkey's API to improve the user experience.
In the this tutorial we used the passkey to authenticate the request to create a new authenticator. The result is that the user will be prompted 3 times:
@@ -492,7 +443,7 @@ By creating a read/write session, we can reduce the number of passkey prompts to
To create a read/write session, we simply replace `passkeyClient.login()` with `passkeyClient.loginWithReadwriteSession()`:
-```typescript title="src/add-passkey.ts"
+```ts src/add-passkey.ts
// ... previous code
const login = async () => {
@@ -501,16 +452,14 @@ const login = async () => {
};
```
-Assuming the login is successful, a read/write session object will be stored in local storage.
-We'll use the stored session in conjunction with the iframe client to authenticate the create authenticator request.
+Assuming the login is successful, a read/write session object will be stored in local storage. We'll use the stored session in conjunction with the iframe client to authenticate the create authenticator request.
-
-
+
+
-We'll use the active client returned from the `useTurnkey` hook which will be initialized with the read/write session.
-The rest of the code remains the same.
+We'll use the active client returned from the `useTurnkey` hook which will be initialized with the read/write session. The rest of the code remains the same.
-```tsx title="app/add-passkey.tsx"
+```tsx app/add-passkey.tsx
// ... previous code
export default function AddPasskey() {
@@ -536,15 +485,14 @@ export default function AddPasskey() {
return (/* ... */);
}
```
-
-
-
+
+
##### 1. Initialize the iframe client
We'll create a new function to initialize the iframe client and inject the read/write session.
-```typescript title="src/add-passkey.ts"
+```ts src/add-passkey.ts
// ... previous code
const getIframeClient = async () => {
@@ -566,21 +514,19 @@ const getIframeClient = async () => {
};
```
-:::note
-
-When using the TypeScript SDK, you'll need to ensure that the HTML element exists somewhere in the rendered DOM.
+
+ When using the TypeScript SDK, you'll need to ensure that the HTML element exists somewhere in the rendered DOM.
-```html
-
-```
-
-:::
+ ```
+
+ ```
+
##### 2. Update the `addPasskey` function
We'll update the `addPasskey` function to use the iframe client to authenticate the request to create a new authenticator.
-```typescript title="src/add-passkey.ts"
+```ts src/add-passkey.ts
// ... previous code
const addPasskey = async () => {
@@ -594,15 +540,11 @@ const addPasskey = async () => {
// ... rest of the code remains the same
};
```
-
-
+
## Conclusion
-In this guide, we've walked through the process of adding a new credential to an existing wallet using the Turnkey SDK.
-By following these steps, you can improve the usability of your application by allowing users to create multiple authentication methods.
-This flexibility enables users to add a hardware security device like a Yubikey, or a native passkey via providers like iCloud keychain or 1Password,
-enhancing their overall experience with your application.
+In this guide, we've walked through the process of adding a new credential to an existing wallet using the Turnkey SDK. By following these steps, you can improve the usability of your application by allowing users to create multiple authentication methods. This flexibility enables users to add a hardware security device like a Yubikey, or a native passkey via providers like iCloud keychain or 1Password, enhancing their overall experience with your application.
For a complete example, check out our [demo embedded wallet](https://github.com/tkhq/demo-embedded-wallet/blob/main/src/components/add-passkey.tsx).
diff --git a/docs/solutions/embedded-wallets/code-examples/authenticate-user-email.mdx b/embedded-wallets/code-examples/authenticate-user-email.mdx
similarity index 77%
rename from docs/solutions/embedded-wallets/code-examples/authenticate-user-email.mdx
rename to embedded-wallets/code-examples/authenticate-user-email.mdx
index e4e71afd..021c1c8b 100644
--- a/docs/solutions/embedded-wallets/code-examples/authenticate-user-email.mdx
+++ b/embedded-wallets/code-examples/authenticate-user-email.mdx
@@ -1,17 +1,12 @@
---
title: "Authenticate a User with Email"
-sidebar_position: 4
-description: Authenticate a User with a Email
-slug: /embedded-wallets/code-examples/authenticate-user-email
+mode: wide
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
+
+
-#### 1. Initialize Turnkey
-
-```typescript
+```JavaScript
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
@@ -19,12 +14,13 @@ const turnkey = new Turnkey({
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
});
```
+
-#### 2. Initialize the Iframe Client
+
Note that the iframe client must be initialized with the dom element where the iframe will be injected. If you are using the [react-sdk](/sdks/react) you can import the `iframeClient` from the `useTurnkey()` hook without this step and the iframe dom element will be managed for you. Note that the `iframeClient` must be initialized before calling `emailAuth` because you need the `iframePublicKey` as a parameter to the `emailAuth` call.
-```typescript
+```JavaScript
import { Turnkey, TurnkeySDKBrowserConfig } from "@turnkey/sdk-browser";
const turnkeyConfig: TurnkeySDKBrowserConfig = {...};
@@ -37,10 +33,11 @@ const iframeContainerId = "turnkey-auth-iframe-container-id";
const iframeClient = await turnkey.iframeClient(document.getElementById(iframeContainerId))
```
+
-#### 3. Call `emailAuth` from your backend
+
-```typescript
+```JavaScript
await turnkey.serverSign(
"emailAuth",
[{
@@ -53,7 +50,7 @@ await turnkey.serverSign(
If you need to lookup the user `subOrganizationId` by email, you can call the `getSubOrgIds` method with the `filterType` parameter set to `"EMAIL"`
-```typescript
+```JavaScript
const subOrgIds = await turnkey.serverSign(
"getSubOrgIds",
[{
@@ -64,10 +61,11 @@ const subOrgIds = await turnkey.serverSign(
const userSubOrganizationId = subOrgIds.organizationIds[0];
```
+
-#### 4. Inject the emailed `credentialBundle` into the iframe to authenticate the user
+
-```typescript
+```JavaScript
const authenticationResponse =
await iframeClient.injectCredentialBundle(credentialBundle);
if (authenticationResponse) {
@@ -80,20 +78,27 @@ if (authenticationResponse) {
}
```
-#### 5. Make read requests on behalf of the authenticated user from the `currentUserSession`
+
+
+
-```typescript
+```JavaScript
const currentUserSession = await turnkey.currentUserSession();
const walletsResponse = await currentUserSession.getWallets();
const walletName = walletsResponse.wallets[0].walletName;
```
-#### 6. Call the `iframeClient` directly for write requests
+
-```typescript
+
+
+```JavaScript
import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser";
const newWalletResponse = await iframeClient.createWallet({
walletName: "New Wallet for User",
accounts: DEFAULT_ETHEREUM_ACCOUNTS,
});
```
+
+
+
diff --git a/docs/solutions/embedded-wallets/code-examples/authenticate-user-passkey.mdx b/embedded-wallets/code-examples/authenticate-user-passkey.mdx
similarity index 63%
rename from docs/solutions/embedded-wallets/code-examples/authenticate-user-passkey.mdx
rename to embedded-wallets/code-examples/authenticate-user-passkey.mdx
index 59a2cf51..1c80f48c 100644
--- a/docs/solutions/embedded-wallets/code-examples/authenticate-user-passkey.mdx
+++ b/embedded-wallets/code-examples/authenticate-user-passkey.mdx
@@ -1,17 +1,11 @@
---
title: "Authenticate a User with a Passkey Credential"
-sidebar_position: 2
-description: Authenticate a User with a Passkey Credential
-slug: /embedded-wallets/code-examples/authenticate-user-passkey
+mode: wide
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-#### 1. Initialize the Passkey Client
-
-```typescript
+
+
+```tsx
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
@@ -20,10 +14,10 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-#### 2. Call the `login` function
-
-```typescript
+
+```tsx
const response = await passkeyClient.login();
if (response.organizationId) {
navigate("/authenticated-route");
@@ -31,23 +25,27 @@ if (response.organizationId) {
navigate("/not-authenticated-route");
}
```
+
-#### 3. Make read requests on behalf of the authenticated user from the `currentUserSession`
+
-```typescript
+```tsx
const currentUserSession = await turnkey.currentUserSession();
const walletsResponse = await currentUserSession.getWallets();
const walletName = walletsResponse.wallets[0].walletName;
```
+
-#### 4. Call the `passkeyClient` directly for write requests
+
This will always prompt a user to confirm the action with their passkey credential
-```typescript
+```tsx
import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser";
const newWalletResponse = await passkeyClient.createWallet({
walletName: "New Wallet for User",
accounts: DEFAULT_ETHEREUM_ACCOUNTS,
});
```
+
+
diff --git a/docs/solutions/embedded-wallets/code-examples/create-passkey-session.mdx b/embedded-wallets/code-examples/create-passkey-session.mdx
similarity index 70%
rename from docs/solutions/embedded-wallets/code-examples/create-passkey-session.mdx
rename to embedded-wallets/code-examples/create-passkey-session.mdx
index 234369b0..c769205b 100644
--- a/docs/solutions/embedded-wallets/code-examples/create-passkey-session.mdx
+++ b/embedded-wallets/code-examples/create-passkey-session.mdx
@@ -1,21 +1,18 @@
---
title: "Create a User Passkey Session"
-sidebar_position: 3
-description: Create a User Passkey Session
-slug: /embedded-wallets/code-examples/create-passkey-session
+description: "A passkey session is an expiring session enabled by an initial passkey authentication. You could think of this as a "lightning mode" of sorts: creating a passkey session allows users to authenticate subsequent requests touch-free. Under the hood, this is powered by our [iframeStamper](/sdks/advanced/iframe-stamper). These sessions are enabled by creating a short-lived embedded API key in the browser, and encrypting it to our [auth iframe](https://github.com/tkhq/frames/blob/main/auth/index.html). This is largely similar to our [Email Auth setup](/authentication/email)."
---
-A passkey session is an expiring session enabled by an initial passkey authentication. You could think of this as a "lightning mode" of sorts: creating a passkey session allows users to authenticate subsequent requests touch-free. Under the hood, this is powered by our [iframeStamper](../../../sdks/advanced/iframe-stamper). These sessions are enabled by creating a short-lived embedded API key in the browser, and encrypting it to our [auth iframe](https://github.com/tkhq/frames/blob/main/auth/index.html). This is largely similar to our [Email Auth setup](/authentication/email).
-
By calling `createPasskeySession()`, the SDK stores the resulting auth bundle in local storage for later retrieval, and also returns it to the caller. If you don't want to rely on `getActiveClient()` (a helper method within `@turnkey/sdk-react` to retrieve active Turnkey clients) and instead want to manage the stampers yourself, you can inject the auth bundle into the iframe — see the [code](https://github.com/tkhq/sdk/blob/b04de6258b2617b4c303c2b9797d4b30322461a3/packages/sdk-react/src/contexts/TurnkeyContext.tsx#L47-L74) for specifics.
## Steps using `@turnkey/sdk-react`
-This process is made the most seamless by leveraging our [React package](../../../sdks/react). Read on for a non-React implementation.
+This process is made the most seamless by leveraging our [React package](/sdks/react). Read on for a non-React implementation.
-#### 1. Initialize the React Provider
+
+
-```typescript
+```js
import { TurnkeyProvider } from "@turnkey/sdk-react";
const turnkeyConfig = {
apiBaseUrl: "https://api.turnkey.com",
@@ -32,23 +29,25 @@ const turnkeyConfig = {
```
+
-#### 2. Call the `createPasskeySession` function
+
-```typescript
+```js
import { useTurnkey } from "@turnkey/sdk-react";
const { turnkey, passkeyClient, authIframeClient } = useTurnkey();
const currentUser = await turnkey?.getCurrentUser(); // this assumes the user has already logged in via passkeyClient?.login()`}
const authBundle = await passkeyClient?.createPasskeySession(
currentUser?.userId!,
authIframeClient?.iframePublicKey!,
- "900" // seconds. 900 == 15 minutes, but can be configured to any value
+ "900", // seconds. 900 == 15 minutes, but can be configured to any value
);
```
+
-#### 3. Use the passkey session for read and write requests
+
-```typescript
+```js
const { getActiveClient } = useTurnkey();
const currentClient = await getActiveClient();
// make arbitrary requests
@@ -56,12 +55,15 @@ const whoami = await currentClient.getWhoami({ organizationId: })
```
Note: `getActiveClient()` is a helper method designed to abstract away the need to check if a passkey session is still valid. If it has expired, this will default to the `passkeyClient`, in which case users will sign each request using their passkey.
+
+
## Alternative Steps (non-React)
-#### 1. Initialize the Passkey Client
+
+
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
apiBaseUrl: "https://api.turnkey.com",
@@ -69,10 +71,11 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-#### 2. Call the `createPasskeySession` function
+
-```typescript
+```js
const currentUser = await turnkey.getCurrentUser(); // this assumes the user has already logged in via passkeyClient.login()`}
const authIframeClient = await turnkey.iframeClient({
iframeContainer: document.getElementById("
-#### 3. Use the passkey session for read and write requests
+
-```typescript
+```js
if (authBundle) { // authBundle was generated in Step 2
const injected = await authIframeClient.injectCredentialBundle(
authBundle
@@ -103,3 +107,5 @@ if (authBundle) { // authBundle was generated in Step 2
}
}
```
+
+
diff --git a/embedded-wallets/code-examples/create-sub-org-passkey.mdx b/embedded-wallets/code-examples/create-sub-org-passkey.mdx
new file mode 100644
index 00000000..e9ddce6d
--- /dev/null
+++ b/embedded-wallets/code-examples/create-sub-org-passkey.mdx
@@ -0,0 +1,533 @@
+---
+title: "Create a Sub-Org with a Passkey User"
+description: "In this guide, we'll walk through the process of creating a new end user with a passkey."
+---
+
+## Overview
+
+Generally, these new users will take the form of a [Turnkey Sub-Organization](/concepts/sub-organizations). This process involves using the following Turnkey SDK packages:
+
+1. [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server): Used on the server-side to leverage the parent organization's public/private API key pair to create the new user's sub-organization.
+2. [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser): Used on the client-side to complete the email recovery process by adding an end-user passkey.
+3. [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react): Used for Next.js applications to initialize the Turnkey SDK.
+
+The process of creating a new sub-organization is split between client-side and server-side operations to prevent exposing the parent organization's private API key.
+
+
+ For a refresher on the relationship between your application's end users and Turnkey Sub-Organizations, see [this page](/embedded-wallets/overview#how-it-works) for more.
+
+
+## Implementation
+
+### Initialize the Turnkey SDK on the Browser
+
+
+
+Wrap the root layout of your application with the `TurnkeyProvider` providing the required configuration options. This allows you to use the Turnkey client throughout your app via the `useTurnkey()` hook.
+```tsx app/layout.tsx
+import { TurnkeyProvider } from "@turnkey/sdk-react";
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+```
+
+ The `NEXT_PUBLIC_ORGANIZATION_ID` should be set to the parent organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
+
+ The `NEXT_PUBLIC_TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](/authentication/passkeys/options#rp) for more details.
+
+
+
+```tsx src/turnkey.ts
+import { Turnkey } from "@turnkey/sdk-browser";
+
+// Initialize the Turnkey SDK with your organization ID and API base URL
+const turnkeyBrowser = new Turnkey({
+ rpId: process.env.TURNKEY_RP_ID,
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+});
+```
+
+ The `TURNKEY_ORGANIZATION_ID` should be set to the parent organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
+
+ The `TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](/authentication/passkeys/options#rp) for more details.
+
+
+
+
+
+
+### Initialize the Passkey Client
+
+Next, we'll initialize the `passkeyClient`, which will enable your application to interact with passkeys.
+
+
+
+We add the `"use client"` directive to the Recovery component to as react hooks can only be used client-side.
+
+```tsx app/create-suborg.tsx
+"use client";
+
+import { useTurnkey } from "@turnkey/sdk-react";
+
+export default function CreateSubOrganization() {
+ const { passkeyClient } = useTurnkey();
+
+ return
{/* ... rest of the code */}
;
+}
+```
+
+
+
+```tsx src/create-suborg.ts
+import { Turnkey } from "@turnkey/sdk-browser";
+
+// Initialize the Turnkey SDK with your organization credentials
+const turnkey = new Turnkey({
+ rpId: process.env.TURNKEY_RP_ID, // Your relying party ID
+ apiBaseUrl: process.env.TURNKEY_API_BASE_URL, // Turnkey API base URL
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, // Your parent organization ID
+});
+
+// Initialize the Passkey Client
+const passkeyClient = turnkey.passkeyClient();
+
+// We'll add more functionality here in the following steps
+```
+
+
+
+### Create User Passkey
+
+In order to create a new passkey for a user, you can call the `createUserPasskey` SDK function. Calling this method will prompt the user to create a passkey, which will be securely stored by their browser. This credential will be associated with the user's account (sub-organization) and used for future authentication. Once the credential is created, we'll use it in the next step to create a new sub-organization that corresponds to the user.
+
+
+ The result of `createUserPasskey` includes an encoded challenge and attestation. The encoded challenge ensures the request is fresh and legitimate, while the attestation verifies the authenticity of the device creating the credential. For more information on how passkeys work, including details on the challenge and attestation objects, you can refer to the [Passkeys Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#passkeys).
+
+
+
+
+
+```tsx app/create-suborg.tsx
+// ... previous code
+
+export default function CreateSubOrganization() {
+ const { passkeyClient } = useTurnkey();
+
+ const createNewPasskey = async () => {
+ const credential = await passkeyClient?.createUserPasskey({
+ publicKey: {
+ // This is the name of the passkey that will be displayed to the user
+ rp: {
+ name: "Wallet Passkey",
+ },
+ user: {
+ // We can use the username as the name and display name
+ name: "Default User Name",
+ displayName: "Default User Name",
+ },
+ },
+ });
+
+ // we'll use this credential in the next step to create a new sub-organization
+ return credential;
+ };
+
+ // ... rest of the code
+
+ return (/* ... */);
+}
+```
+
+
+
+```tsx src/create-suborg.ts
+// ... previous code
+
+const createNewPasskey = async () => {
+ const credential = await passkeyClient?.createUserPasskey({
+ publicKey: {
+ // This is the name of the passkey that will be displayed to the user
+ rp: {
+ name: "Wallet Passkey",
+ },
+ user: {
+ // We can use the username as the name and display name
+ name: "Default User Name",
+ displayName: "Default User Name",
+ },
+ },
+ });
+
+ // we'll use this credential in the next step to create a new sub-organization
+ return credential;
+};
+```
+
+
+
+### Initialize the Turnkey SDK on the Server
+
+Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package. This allows you to use the parent organization's public/private API key pair to create sub-organizations.
+
+
+
+
+For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client. This will ensure that the function is executed on the server-side and will have access to the server-side environment variables e.g. your parent organization's public/private API key pair. For more information on Next.js server actions, see the Next.js documentation on [Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
+
+```tsx app/actions.ts
+"use server";
+
+import { Turnkey } from "@turnkey/sdk-server";
+
+// Initialize the Turnkey Server Client on the server-side
+const turnkeyServer = new Turnkey({
+ apiBaseUrl: "https://api.turnkey.com",
+ apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
+ apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+}).apiClient();
+```
+
+
+ ```tsx src/turnkey.ts
+ import { Turnkey } from "@turnkey/sdk-server";
+
+ // Initialize the Turnkey Server Client on the server-side
+ const turnkeyServer = new Turnkey({
+ apiBaseUrl: "https://api.turnkey.com",
+ apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
+ apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+ }).apiClient();
+ ```
+
+
+
+### Create a Function for Sub-Org Creation
+
+Next we'll create a new function called `createSubOrganization` that will be used to create a new sub-organization from the server-side. This method will be called from the client-side with the end-user's details.
+
+
+
+
+We export the `createSubOrganization` server action to be called from the client-side.
+
+```tsx app/actions.tsx
+import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser";
+
+// ... previous code
+
+type TAttestation = {
+ credentialId: string;
+ clientDataJson: string;
+ attestationObject: string;
+ transports: (
+ | "AUTHENTICATOR_TRANSPORT_BLE"
+ | "AUTHENTICATOR_TRANSPORT_INTERNAL"
+ | "AUTHENTICATOR_TRANSPORT_NFC"
+ | "AUTHENTICATOR_TRANSPORT_USB"
+ | "AUTHENTICATOR_TRANSPORT_HYBRID"
+ )[];
+};
+
+export const createSubOrganization = async (
+ email: string,
+ credential: string,
+ attestation: string,
+) => {
+ const createSubOrgResponse = await turnkeyServer.createSubOrganization({
+ subOrganizationName: "My New Suborg",
+ rootUsers: [
+ {
+ userName: "Default User Name",
+ userEmail: email,
+ apiKeys: [],
+ authenticators: [
+ {
+ authenticatorName: "Default Passkey",
+ challenge: challenge,
+ attestation: attestation,
+ },
+ ],
+ oauthProviders: [],
+ },
+ ],
+ rootQuorumThreshold: 1,
+ wallet: {
+ walletName: "Default Wallet",
+ accounts: DEFAULT_ETHEREUM_ACCOUNTS,
+ },
+ });
+
+ return createSubOrgResponse;
+};
+```
+
+
+```tsx src/turnkey-server.ts
+/// ... previous code
+
+type TAttestation = {
+ credentialId: string;
+ clientDataJson: string;
+ attestationObject: string;
+ transports: (
+ | "AUTHENTICATOR_TRANSPORT_BLE"
+ | "AUTHENTICATOR_TRANSPORT_INTERNAL"
+ | "AUTHENTICATOR_TRANSPORT_NFC"
+ | "AUTHENTICATOR_TRANSPORT_USB"
+ | "AUTHENTICATOR_TRANSPORT_HYBRID"
+ )[];
+};
+
+export const createSubOrganization = async (
+ email: string,
+ credential: string,
+ attestation: string,
+) => {
+ const createSubOrgResponse = await turnkeyServer.createSubOrganization({
+ subOrganizationName: "My New Suborg",
+ rootUsers: [
+ {
+ userName: "Default User Name",
+ userEmail: email,
+ apiKeys: [],
+ authenticators: [
+ {
+ authenticatorName: "Default Passkey",
+ challenge: challenge,
+ attestation: attestation,
+ },
+ ],
+ oauthProviders: [],
+ },
+ ],
+ rootQuorumThreshold: 1,
+ wallet: {
+ walletName: "Default Wallet",
+ accounts: DEFAULT_ETHEREUM_ACCOUNTS,
+ },
+ });
+
+ return createSubOrgResponse;
+};
+```
+
+
+
+### Complete Create Sub-Organization
+
+At this stage, we create the sub-organization using the **server-side** function we created in the previous step.
+
+
+
+
+
+
+ ```tsx app/create-suborg.tsx
+ import { createSubOrganization } from "./actions";
+ ```
+
+
+
+
+ ```tsx app/create-suborg.tsx
+ // ...
+
+ import { useForm } from "react-hook-form";
+
+ type TSubOrgFormData = {
+ email: string;
+ };
+
+ export default function CreateSubOrganization() {
+ // ...
+
+ // Use form handler for suborg creation
+ const { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } =
+ useForm();
+
+ // Maintain state
+ const [createSubOrganizationResponse, setCreateSubOrganizationResponse] =
+ useState(null);
+
+ const createSubOrg = async (data: TSubOrgFormData) => {
+ const { encodedChallenge: challenge, attestation } =
+ await createNewPasskey();
+
+ const createSubOrganizationResponse = await createSubOrganization(
+ data.email,
+ challenge,
+ attestation,
+ );
+
+ setCreateSubOrganizationResponse(createSubOrganizationResponse);
+ };
+
+ return (
+
+ {createSubOrganizationResponse ? (
+
You've created a sub-organization!
+ ) : (
+
+ )}
+
+ );
+ }
+ ```
+
+ ```tsx
+ "use client";
+
+ import { useState } from "react";
+ import { useTurnkey } from "@turnkey/sdk-react";
+ import { useForm } from "react-hook-form";
+
+ // Import the createSubOrganization server action
+ import { createSubOrganization } from "./actions";
+
+ type TSubOrgFormData = {
+ email: string;
+ };
+
+ type TAttestation = {
+ credentialId: string;
+ clientDataJson: string;
+ attestationObject: string;
+ transports: (
+ | "AUTHENTICATOR_TRANSPORT_BLE"
+ | "AUTHENTICATOR_TRANSPORT_INTERNAL"
+ | "AUTHENTICATOR_TRANSPORT_NFC"
+ | "AUTHENTICATOR_TRANSPORT_USB"
+ | "AUTHENTICATOR_TRANSPORT_HYBRID"
+ )[];
+ };
+
+ export default function CreateSubOrganization() {
+ const { passkeyClient } = useTurnkey();
+
+ // Use form handler for suborg creation
+ const { register: subOrgFormRegister, handleSubmit: subOrgFormSubmit } =
+ useForm();
+
+ // Maintain state
+ const [createSubOrganizationResponse, setCreateSubOrganizationResponse] =
+ useState(null);
+
+ const createNewPasskey = async () => {
+ const credential = await passkeyClient?.createUserPasskey({
+ publicKey: {
+ // This is the name of the passkey that will be displayed to the user
+ rp: {
+ name: "Wallet Passkey",
+ },
+ user: {
+ // We can use the username as the name and display name
+ name: "Default User Name",
+ displayName: "Default User Name",
+ },
+ },
+ });
+
+ // we'll use this credential in the next step to create a new sub-organization
+ return credential;
+ };
+
+ const createSubOrg = async (data: TSubOrgFormData) => {
+ const { encodedChallenge: challenge, attestation } =
+ await createNewPasskey();
+
+ const createSubOrganizationResponse = await createSubOrganization(
+ data.email,
+ challenge,
+ attestation,
+ );
+
+ setCreateSubOrganizationResponse(createSubOrganizationResponse);
+ };
+
+ return (
+
+ {createSubOrganizationResponse ? (
+
You've created a sub-organization!
+ ) : (
+
+ )}
+
+ );
+ }
+ ```
+
+
+
+
+
+
+
+
+
+```tsx app/create-suborg.tsx
+import { createSubOrganization } from "./turnkey-server";
+```
+
+
+
+
+```tsx src/turnkey.ts
+import { createSubOrganization } from "./turnkey-server";
+
+// ... rest of the code
+
+const createSubOrganizationResponse = await createSubOrganization(
+ email,
+ attestation,
+ challenge,
+);
+```
+
+
+
+
+
+## Examples
+
+A few mini examples where sub-orgs are created with passkeys, see the following:
+
+
+
+
+
+
diff --git a/docs/solutions/embedded-wallets/code-examples/create-user-email.mdx b/embedded-wallets/code-examples/create-user-email.mdx
similarity index 52%
rename from docs/solutions/embedded-wallets/code-examples/create-user-email.mdx
rename to embedded-wallets/code-examples/create-user-email.mdx
index ed9ae10c..9dd3ac34 100644
--- a/docs/solutions/embedded-wallets/code-examples/create-user-email.mdx
+++ b/embedded-wallets/code-examples/create-user-email.mdx
@@ -1,21 +1,13 @@
---
title: "Create a User with Email Only"
-sidebar_position: 3
-description: Create a User with Email Only
-slug: /embedded-wallets/code-examples/create-user-email
+description: "This example demonstrates how to create a sub organization using just an end-user's email: passkeys not required! Note that this flow does not require emails to be verified."
+mode: wide
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
+
+
-This example demonstrates how to create a sub organization using just an end-user's email: passkeys not required! Note that this flow does not require emails to be verified.
-
-
-
-#### 1. Initialize Turnkey
-
-```typescript
+```JavaScript
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
@@ -23,10 +15,11 @@ const turnkey = new Turnkey({
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
});
```
+
-#### 2. Configure the Sub Organization for the User
+
-```typescript
+```JavaScript
import { DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-browser;"
const subOrganizationConfig = {
@@ -45,11 +38,14 @@ const subOrganizationConfig = {
}
};
```
+
-#### 3. Call `createSubOrganization` from your backend
+
-```typescript
+```JavaScript
await turnkey.serverSign("createSubOrganization", [subOrganizationConfig]);
```
-This is all that is needed to create a user without any authentication credential other than their email address, in the [login](authenticate-user-email) flow you can see how to then authenticate the user after their `subOrganization` is created.
+This is all that is needed to create a user without any authentication credential other than their email address, in the [login](/embedded-wallets/code-examples/authenticate-user-email) flow you can see how to then authenticate the user after their `subOrganization` is created.
+
+
diff --git a/docs/solutions/embedded-wallets/code-examples/email-recovery.mdx b/embedded-wallets/code-examples/email-recovery.mdx
similarity index 71%
rename from docs/solutions/embedded-wallets/code-examples/email-recovery.mdx
rename to embedded-wallets/code-examples/email-recovery.mdx
index f341edad..5cda01a9 100644
--- a/docs/solutions/embedded-wallets/code-examples/email-recovery.mdx
+++ b/embedded-wallets/code-examples/email-recovery.mdx
@@ -1,23 +1,15 @@
---
title: "Recover a User with Email"
-sidebar_position: 5
-description: Recover a User with Email
-slug: /embedded-wallets/code-examples/email-recovery
+description: "In this guide, we'll walk through the process of recovering a user using their email."
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import CodeBlock from "@theme/CodeBlock";
-
-:::info
-
-Email Recovery is a legacy flow, now superseded by [Email Auth](/embedded-wallets/code-examples/authenticate-user-email), which can used to implement recovery flows and more.
-
-:::
+
+ Email Recovery is a legacy flow, now superseded by [Email Auth](/embedded-wallets/code-examples/authenticate-user-email), which can used to implement recovery flows and more.
+
## Overview
-In this guide, we'll walk through the process of recovering a user using their email. This process involves using the following Turnkey SDK packages:
+This process involves using the following Turnkey SDK packages:
1. [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server): Used on the server-side to leverage the parent organization's public/private API key pair for initializing the email recovery.
2. [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser): Used on the client-side to complete the email recovery process by adding an end-user passkey.
@@ -25,7 +17,7 @@ In this guide, we'll walk through the process of recovering a user using their e
The email recovery process is split between client-side and server-side operations to prevent exposing the parent organization's private API key.
-For an in-depth understanding of the email recovery process at Turnkey, refer to our docs on [email Recovery](/authentication/email#recovery-flow).
+For an in-depth understanding of the email recovery process at Turnkey, refer to our docs on [email recovery](/authentication/email#recovery-flow).
## Implementation
@@ -33,13 +25,12 @@ For an in-depth understanding of the email recovery process at Turnkey, refer to
Begin by initializing the Turnkey SDK with your organization ID and the Turnkey API's base URL on the **client-side**.
-
-
+
+
-Wrap the root layout of your application with the `TurnkeyProvider` providing the required configuration options.
-This allows you to use the Turnkey client throughout your app via the `useTurnkey()` hook.
+Wrap the root layout of your application with the `TurnkeyProvider` providing the required configuration options. This allows you to use the Turnkey client throughout your app via the `useTurnkey()` hook.
-```tsx title="app/layout.tsx"
+```tsx app/layout.tsx
import { TurnkeyProvider } from "@turnkey/sdk-react";
export default function RootLayout({
@@ -54,7 +45,8 @@ export default function RootLayout({
config={{
rpId: process.env.NEXT_PUBLIC_TURNKEY_RP_ID,
apiBaseUrl: "https://api.turnkey.com",
- defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID,
+ defaultOrganizationId:
+ process.env.NEXT_PUBLIC_ORGANIZATION_ID,
}}
>
{children}
@@ -64,20 +56,14 @@ export default function RootLayout({
);
}
```
-
-:::info
-
-The `NEXT_PUBLIC_ORGANIZATION_ID` should be set to the parent
-organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
-
-The `NEXT_PUBLIC_TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](../../../authentication/passkeys/options#rp) for more details.
-
-:::
-
-
-
-
-```typescript title="src/turnkey.ts"
+
+ The `NEXT_PUBLIC_ORGANIZATION_ID` should be set to the parent organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
+
+ The `NEXT_PUBLIC_TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](/authentication/passkeys/options#rp) for more details.
+
+
+
+```tsx src/turnkey.ts
import { Turnkey } from "@turnkey/sdk-browser";
// Initialize the Turnkey SDK with your organization ID and API base URL
@@ -87,77 +73,63 @@ const turnkeyBrowser = new Turnkey({
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
});
```
+
+ The `TURNKEY_ORGANIZATION_ID` should be set to the parent organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
-:::info
-
-The `TURNKEY_ORGANIZATION_ID` should be set to the parent
-organization ID which can be found in the [Turnkey Dashboard](https://app.turnkey.com/dashboard).
-
-The `TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](../../../authentication/passkeys/options#rp) for more details.
-
-:::
-
-
+ The `TURNKEY_RP_ID` should be set to your application's desired relying party ID; this is typically your domain, or localhost if developing locally. See [this page](/authentication/passkeys/options#rp) for more details.
+
+
-#### Server-side Initialization
-Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package.
-This allows you to use the parent organization's public/private API key pair to initialize the email recovery process securely.
+#### Server-side Initialization
-
-
+Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package. This allows you to use the parent organization's public/private API key pair to initialize the email recovery process securely.
-For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client.
-This will ensure that the function is executed on the server-side and will have access to the
-server-side environment variables e.g. your parent organization's public/private API key pair.
-For more information on Next.js server actions, see the Next.js documentation on
-[Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
+
+
+For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client. This will ensure that the function is executed on the server-side and will have access to the server-side environment variables e.g. your parent organization's public/private API key pair. For more information on Next.js server actions, see the Next.js documentation on [Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
-```ts title="app/actions.ts"
+```tsx app/actions.ts
"use server";
import { Turnkey } from "@turnkey/sdk-server";
// Initialize the Turnkey Server Client on the server-side
const turnkeyServer = new Turnkey({
- baseUrl: "https://api.turnkey.com",
+ apiBaseUrl: "https://api.turnkey.com",
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
}).apiClient();
```
+
-
-
-
-```typescript title="src/turnkey.ts"
+
+```tsx src/turnkey.ts
import { Turnkey } from "@turnkey/sdk-server";
// Initialize the Turnkey Server Client on the server-side
const turnkeyServer = new Turnkey({
- baseUrl: "https://api.turnkey.com",
+ apiBaseUrl: "https://api.turnkey.com",
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
}).apiClient();
```
-
-
+
#### Initialize the Iframe Client
-Next, we'll initialize the `iframeClient` which will create a secure iframe within your application.
-The `iframeClient` must be initialized before beginning the user recovery process, as we'll need the
-iframe's public key as a parameter for the `initEmailRecovery` method.
+Next, we'll initialize the `iframeClient` which will create a secure iframe within your application. The `iframeClient` must be initialized before beginning the user recovery process, as we'll need the iframe's public key as a parameter for the `initEmailRecovery` method.
-
-
+
+
We add the `"use client"` directive to the Recovery component to as react hooks can only be used client-side.
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
"use client";
import { useTurnkey } from "@turnkey/sdk-react";
@@ -168,39 +140,37 @@ export default function Recovery() {
return
{/* ... rest of the code */}
;
}
```
+
-
-
-
-```typescript title="src/turnkey.ts"
+
+```ts src/turnkey.ts
const iframeContainerId = "turnkey-recovery-iframe-container-id";
const authIframeClient = await turnkey.iframeClient(
- document.getElementById(iframeContainerId)
+ document.getElementById(iframeContainerId),
);
```
When using the TypeScript SDK, you'll need to ensure that the HTML element exists somewhere in the rendered DOM.
-```html title="index.html"
+
+
+```html index.html
```
-
-
+
### Create a Recovery Function
-Next we'll create a new function called `initEmailRecovery` that will be used to initialize the email recovery process on the server-side.
-This method will be called from the client-side with the user's email and the target public key from the iframe client. Calling the `initEmailRecovery`
-method will trigger an email sent to the user containing a credential bundle which will be used to authenticate the authIframeClient in the next step.
+Next we'll create a new function called `initEmailRecovery` that will be used to initialize the email recovery process on the server-side. This method will be called from the client-side with the user's email and the target public key from the iframe client. Calling the `initEmailRecovery` method will trigger an email sent to the user containing a credential bundle which will be used to authenticate the authIframeClient in the next step.
-
-
+
+
We export the `initEmailRecovery` server action to be called from the client-side.
-```ts title="app/actions.ts"
+```tsx app/actions.ts
// ... previous code
export const initEmailRecovery = async ({
@@ -217,11 +187,10 @@ export const initEmailRecovery = async ({
return recoveryResponse;
};
```
+
-
-
-
-```typescript title="src/turnkey-server.ts"
+
+```tsx src/turnkey-server.ts
export const initEmailRecovery = async ({
email,
targetPublicKey,
@@ -236,28 +205,26 @@ export const initEmailRecovery = async ({
return recoveryResponse;
};
```
-
-
+
### Initialize Email Recovery
-At this stage, we initialize the email recovery process using the **server-side** function we created in the previous step.
-The user will need to paste the credential bundle they receive in their email into your app, which is then used to
-authenticate the `authIframeClient` via the `injectCredentialBundle` method.
-
-
-
+At this stage, we initialize the email recovery process using the **server-side** function we created in the previous step. The user will need to paste the credential bundle they receive in their email into your app, which is then used to authenticate the `authIframeClient` via the `injectCredentialBundle` method.
-##### 1. Import the server action
+
+
+
+
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
import { initEmailRecovery } from "./actions";
```
+
-##### 2. Add an input field for the user's email
+
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
// ...
export default function Recovery() {
@@ -277,10 +244,11 @@ export default function Recovery() {
);
}
```
+
-##### 3. Create a function to initiate the recovery process
+
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
//...
export default function Recovery() {
@@ -308,10 +276,11 @@ export default function Recovery() {
);
}
```
+
-##### 4. Add an input for the credential bundle
+
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
//...
export default function Recovery() {
@@ -321,7 +290,7 @@ export default function Recovery() {
return (
- {/* If we have initiated the recovery process we'll render an input
+ {/* If we have initiated the recovery process we'll render an input
for the user to paste their credential bundle they received in their email */}
{initRecoveryResponse ? (
- recovery.tsx
-
-```tsx
+
+```ts
"use client";
import { useState } from "react";
@@ -375,7 +342,7 @@ export default function Recovery() {
return (
- {/* If we have initiated the recovery process we'll render an input
+ {/* If we have initiated the recovery process we'll render an input
for the user to paste their credential bundle they received in their email */}
{initRecoveryResponse ? (
);
}
-```
-
-
-
-
-
-
-```typescript title="src/turnkey.ts"
+ ```
+
+
+
+
+
+
+```tsx src/turnkey.ts
import { initEmailRecovery } from "./turnkey-server";
// ... rest of the code
@@ -417,25 +384,22 @@ const initRecoveryResponse = await initEmailRecovery({
// by pasting it into the UI
await authIframeClient.injectCredentialBundle(credentialBundle);
```
-
-
+
### Create User Passkey
-Next, we'll create a new passkey for the user and associate it with the email that was used in the recovery process.
-Assuming that the user has successfully received and entered their credential bundle,
-we generate a passkey to be used authenticate Turnkey requests.
+Next, we'll create a new passkey for the user and associate it with the email that was used in the recovery process. Assuming that the user has successfully received and entered their credential bundle, we generate a passkey to be used authenticate Turnkey requests.
-
-
-
-##### 1. Add a function to complete the recovery process
+
+
+
+
We'll add a new function called `completeRecovery` that will create a new passkey for the user which will be used in the final recovery step.
-```tsx title="app/recovery.tsx"
-//...
+```ts app/recovery.tsx
+//...export default function Recovery() { //... // We'll use //...
export default function Recovery() {
//...
@@ -459,10 +423,11 @@ export default function Recovery() {
return
{/* ... */}
;
}
```
+
-##### 2. Add a button to call the completeRecovery function
+
-```tsx title="app/recovery.tsx"
+```tsx app/recovery.tsx
//...
export default function Recovery() {
@@ -485,11 +450,12 @@ export default function Recovery() {
);
}
```
+
+
+
-
-
-
-```typescript title="src/turnkey.ts"
+
+```ts src/turnkey.ts
const completeRecovery = async () => {
const passkeyClient = await turnkey.passkeyClient();
@@ -503,21 +469,19 @@ const completeRecovery = async () => {
});
};
```
-
-
+
### Complete Email Recovery
-Finally, we complete the email recovery process by passing the `encodedChallenge` and `attestation` from the passkey we
-previously created to the `recoverUser` method.
-This method will complete the email recovery process and if successful,
-will return a response containing the authenticator ID of the new passkey authenticator.
+Finally, we complete the email recovery process by passing the `encodedChallenge` and `attestation` from the passkey we previously created to the `recoverUser` method. This method will complete the email recovery process and if successful, will return a response containing the authenticator ID of the new passkey authenticator.
+
+
+
-
-
-```tsx title="app/recovery.tsx"
+```ts app/recovery.tsx
+
//...
export default function Recovery() {
@@ -572,11 +536,9 @@ export default function Recovery() {
);
}
```
+
-
- Complete recovery.tsx component
-
-```tsx title="app/recovery.tsx"
+```ts app/recovery.tsx
"use client";
import { useState } from "react";
@@ -659,14 +621,13 @@ export default function Recovery() {
);
}
-```
+ ```
+
+
-
+
-
-
-
-```typescript title="/src/turnkey.ts"
+```ts src/turnkey.ts
const completeRecovery = async () => {
const passkeyClient = await turnkey.passkeyClient();
@@ -697,14 +658,11 @@ const completeRecovery = async () => {
}
};
```
-
-
+
## Conclusion
-In this guide, we've walked through the process of recovering a user using their email using the Turnkey SDKs.
-By following these steps, you can implement email recovery in your application,
-providing users with a reliable way to regain access to their accounts or to onboard new users using only their email address.
+In this guide, we've walked through the process of recovering a user using their email using the Turnkey SDKs. By following these steps, you can implement email recovery in your application, providing users with a reliable way to regain access to their accounts or to onboard new users using only their email address.
For a complete example, check out our [email recovery app](https://github.com/tkhq/sdk/tree/main/examples/email-recovery).
diff --git a/docs/solutions/embedded-wallets/code-examples/export.mdx b/embedded-wallets/code-examples/export.mdx
similarity index 70%
rename from docs/solutions/embedded-wallets/code-examples/export.mdx
rename to embedded-wallets/code-examples/export.mdx
index cd210b37..08c67c83 100644
--- a/docs/solutions/embedded-wallets/code-examples/export.mdx
+++ b/embedded-wallets/code-examples/export.mdx
@@ -1,16 +1,8 @@
---
title: "Export Wallet or Private Key"
-sidebar_position: 9
-description: Export Wallet or Private Key
-slug: /embedded-wallets/code-examples/export
+description: "This is a guide to exporting your wallet or private key from Turnkey. For more information about the security of this flow, check out [Enclave secure channels](/security/enclave-secure-channels)."
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-This is a guide to exporting your wallet or private key from Turnkey. For more information about the security of this flow, check out [Enclave secure channels](/security/enclave-secure-channels).
-
## Implementation guides
Follow along with the Turnkey CLI, Embedded iframe, NodeJS, and Local Storage guides.
@@ -21,83 +13,87 @@ Install the latest version of Turnkey CLI to access the new import functionality
#### Steps
-1. Export a wallet (Turnkey activity):
+
+
-```sh
+```bash
turnkey wallets export --name "New Wallet" -k --organization --export-bundle-output --host api.turnkey.com
```
-- The `--export-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be returned by Turnkey. This bundle contains the encrypted key material.
+* The `--export-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be returned by Turnkey. This bundle contains the encrypted key material.
+
+
-2. Decrypt the bundle:
+
-```sh
-turnkey decrypt --export-bundle-input --organization --signer-quorum-key 04bce6666ca6c12e0e00a503a52c301319687dca588165b551d369496bd1189235bd8302ae5e001fde51d1e22baa1d44249f2de9705c63797316fc8b7e3969a665
->> ""
+```bash
+turnkey decrypt --export-bundle-input --organization --signer-quorum-key 04bce6666ca6c12e0e00a503a52c301319687dca588165b551d369496bd1189235bd8302ae5e001fde51d1e22baa1d44249f2de9705c63797316fc8b7e3969a665>> ""
```
-- The `--export-bundle-input` (required) flag is the file path for the “encrypted bundle” (from the previous step) that will be decrypted.
-- The `--signer-quorum-key` is the public key of Turnkey's signer enclave. This is a static value.
+* The `--export-bundle-input` (required) flag is the file path for the “encrypted bundle” (from the previous step) that will be decrypted.
+* The `--signer-quorum-key` is the public key of Turnkey's signer enclave. This is a static value.
-Congrats! You've exported your wallet 🎉
+Congrats! You've exported your wallet
+
+
+
#### Private Key support
-1. Export a wallet (Turnkey activity):
+
+
-```sh
+```bash
turnkey private-keys export --name "New Private Key" -k --organization --export-bundle-output --host api.turnkey.com
```
-- The `--export-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be returned by Turnkey. This bundle contains the encrypted key material.
+* The `--export-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be returned by Turnkey. This bundle contains the encrypted key material.
+
+
-2. Decrypt the bundle:
+
-```sh
-turnkey decrypt --export-bundle-input --organization --signer-quorum-key 04bce6666ca6c12e0e00a503a52c301319687dca588165b551d369496bd1189235bd8302ae5e001fde51d1e22baa1d44249f2de9705c63797316fc8b7e3969a665
->> ""
+```bash
+turnkey decrypt --export-bundle-input --organization --signer-quorum-key 04bce6666ca6c12e0e00a503a52c301319687dca588165b551d369496bd1189235bd8302ae5e001fde51d1e22baa1d44249f2de9705c63797316fc8b7e3969a665>> ""
```
-- The `--export-bundle-input` (required) flag is the file path for the “encrypted bundle” (from the previous step) that will be decrypted.
-- The `--signer-quorum-key` is the public key of Turnkey's signer enclave. This is a static value.
+* The `--export-bundle-input` (required) flag is the file path for the “encrypted bundle” (from the previous step) that will be decrypted.
+* The `--signer-quorum-key` is the public key of Turnkey's signer enclave. This is a static value.
Congrats! You've exported your private key 🎉
-
+
+
### Embedded iframe
-- We have released open-source code to create target encryption keys and decrypt exported wallet mnemonics. We've deployed a static HTML page hosted on `export.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the mnemonics are encrypted to keys that the user has access to, but that your organization does not (because they live in the iframe, on a separate domain).
-- We have also built a package to help you insert this iframe and interact with it in the context of export: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+* We have released open-source code to create target encryption keys and decrypt exported wallet mnemonics. We've deployed a static HTML page hosted on `export.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the mnemonics are encrypted to keys that the user has access to, but that your organization does not (because they live in the iframe, on a separate domain).
+* We have also built a package to help you insert this iframe and interact with it in the context of export: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
In the rest of this guide we'll assume you are using these helpers.
#### Steps
-Here's a diagram summarizing the wallet export flow step-by-step ([direct link](/img/wallet_export_steps.png)):
+Here's a diagram summarizing the wallet export flow step-by-step
+([direct link](/assets/files/wallet_export_steps-5bb19c72eb9596fab8db3b1dcc52e60a.png)):
-
-
-
+
+
+
Let's review these steps in detail:
-
-1. When a user on your application clicks "export", display a new export UI. We recommend setting this export UI as a new hosted page of your application that contains language explaining the security best practices users should follow once they've successfully exported their wallet. Remember: once the wallet has been exported, Turnkey can no longer ensure its security.
+
+
+ When a user on your application clicks "export", display a new export UI. We recommend setting this export UI as a new hosted page of your application that contains language explaining the security best practices users should follow once they've successfully exported their wallet. Remember: once the wallet has been exported, Turnkey can no longer ensure its security.
While the UI is in a loading state, your application uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
- ```js
+ ```ts
const iframeStamper = new IframeStamper({
- iframeUrl: "https://export.turnkey.com",
- // Configure how the iframe element is inserted on the page
- iframeContainer: yourContainer,
- iframeElementId: "turnkey-iframe",
+ iframeUrl: "https://export.turnkey.com",
+ // Configure how the iframe element is inserted on the page
+ iframeContainer: yourContainer,
+ iframeElementId: "turnkey-iframe",
});
// Inserts the iframe in the DOM. This creates the new encryption target key
@@ -107,24 +103,32 @@ Let's review these steps in detail:
let displayIframe = "none";
return (
- // The iframe element can be hidden until the wallet is exported
-
+ // The iframe element can be hidden until the wallet is exported
+
);
```
+
+
+
+Your code receives the iframe public key. Your application prompts the user to sign a new `EXPORT_WALLET` activity with the wallet ID and the iframe public key in the parameters.
+
-2. Your code receives the iframe public key. Your application prompts the user to sign a new `EXPORT_WALLET` activity with the wallet ID and the iframe public key in the parameters.
-3. Your application polls for the activity response, which contains an export bundle. Remember: this export bundle is an encrypted mnemonic which can only be decrypted within the iframe.
+
+Your application polls for the activity response, which contains an export bundle. Remember: this export bundle is an encrypted mnemonic which can only be decrypted within the iframe.
Need help setting up async polling? Checkout our guide and helper [here](https://github.com/tkhq/sdk/tree/main/packages/http#withasyncpolling-helper).
-4. Your application injects the export bundle into the iframe for decryption and displays the iframe upon success:
+
+
+
+Your application injects the export bundle into the iframe for decryption and displays the iframe upon success:
- ```js
+ ```ts
// Inject export bundle into iframe
let success = await iframeStamper.injectWalletExportBundle(exportBundle);
if (success !== true) {
- throw new Error("unexpected error while injecting export bundle");
+ throw new Error("unexpected error while injecting export bundle");
}
// If successfully injected, update the state to display the iframe
@@ -133,15 +137,13 @@ Let's review these steps in detail:
Export is complete! The iframe now displays a sentence of words separated by spaces.
-
-
-
+
+
+
The exported wallet will remain stored within Turnkey’s infrastructure. In your Turnkey dashboard, the exported user Wallet will be flagged as “Exported”.
+
+
#### Export as Private Keys
@@ -155,25 +157,22 @@ Follow the same steps above for exporting Wallets as mnemonics, but instead use
Follow the same steps above for exporting Wallets as mnemonics, but instead use the `EXPORT_PRIVATE_KEY` activity and the `injectKeyExportBundle` method from the [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper). You can pass an optional `keyFormat` to `injectKeyExportBundle(keyFormat)` that will apply either `hexadecimal` or `solana` formatting to the private key that is exported in the iframe. The default key format is `hexadecimal`, which is used by MetaMask, MyEtherWallet, Phantom, Ledger, and Trezor for Ethereum keys. For Solana keys, you will need to pass the `solana` key format.
-
-
-
+
+
+
At the end of a successful private key export, the iframe displays a private key.
### NodeJS
-A full example Node script can be found here: https://github.com/tkhq/sdk/tree/main/examples/export-in-node
+A full example Node script can be found here: [https://github.com/tkhq/sdk/tree/main/examples/export-in-node](https://github.com/tkhq/sdk/tree/main/examples/export-in-node)
#### Steps
-1. Initialize a new Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-server";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -186,27 +185,30 @@ const turnkeyClient = new Turnkey({
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const keyPair = generateP256KeyPair();
const privateKey = keyPair.privateKey;
const publicKey = keyPair.publicKeyUncompressed;
```
+
-3. Call export (Turnkey activity):
+
-```js
+```ts
const exportResult = await turnkeyClient.apiClient().exportWallet({
walletId: walletId,
targetPublicKey: publicKey,
});
```
+
-4. Decrypt encrypted bundle:
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: privateKey,
@@ -215,15 +217,19 @@ const decryptedBundle = await decryptExportBundle({
});
```
-Congrats! You've exported your wallet 🎉
+Congrats! You've exported your wallet
The process is largely similar for both private keys and individual wallet accounts.
+
+
+
#### Private Key support
-1. Initialize a new Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-server";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -236,27 +242,30 @@ const turnkeyClient = new Turnkey({
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const keyPair = generateP256KeyPair();
const privateKey = keyPair.privateKey;
const publicKey = keyPair.publicKeyUncompressed;
```
+
-3. Call export (Turnkey activity):
+
-```js
+```ts
const exportResult = await turnkeyClient.apiClient().exportPrivateKey({
privateKeyId: privateKeyId,
targetPublicKey: publicKey,
});
```
+
-4. Decrypt encrypted bundle:
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: privateKey,
@@ -265,13 +274,16 @@ const decryptedBundle = await decryptExportBundle({
});
```
-Congrats! You've exported your private key 🎉
+Congrats! You've exported your private key
+
+
#### Wallet Account support
-1. Initialize a new Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-server";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -284,27 +296,30 @@ const turnkeyClient = new Turnkey({
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const keyPair = generateP256KeyPair();
const privateKey = keyPair.privateKey;
const publicKey = keyPair.publicKeyUncompressed;
```
+
-3. Call export (Turnkey activity):
+
-```js
+```ts
const exportResult = await turnkeyClient.apiClient().exportWalletAccount({
address: address, // your specific wallet account address
targetPublicKey: publicKey,
});
```
+
-4. Decrypt encrypted bundle:
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: privateKey,
@@ -313,7 +328,9 @@ const decryptedBundle = await decryptExportBundle({
});
```
-Congrats! You've exported your wallet account 🎉
+Congrats! You've exported your wallet account
+
+
### Local Storage
@@ -321,9 +338,10 @@ If you do not have access to an iframe (e.g. in a mobile context) or would prefe
#### Steps
-1. Initialize Turnkey client
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-browser";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -335,18 +353,20 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const embeddedKeyPair = generateP256KeyPair();
const embeddedPrivateKey = keyPair.privateKey;
const embeddedPublicKey = keyPair.publicKeyUncompressed;
```
+
-3. Save the private key in Local Storage
+
-```js
+```ts
// Storage keys
const STORAGE_KEYS = {
EMBEDDED_PRIVATE_KEY: "@turnkey/embedded_private_key",
@@ -361,19 +381,22 @@ await LocalStorage.setItem(
// Note that the public key can always be derived separately via the `getPublicKey` from `@turnkey/crypto`
await LocalStorage.setItem(STORAGE_KEYS.EMBEDDED_PUBLIC_KEY, embeddedPublicKey);
```
+
+
4. Call export (Turnkey activity), using the embedded key as the target key for the `exportWallet` activity:
-```js
+```ts
const exportResult = await passkeyClient.exportWallet({
walletId, // desired wallet ID
targetPublicKey: embeddedPublicKey,
});
```
+
-5. Decrypt encrypted bundle
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: embeddedPrivateKey,
@@ -381,10 +404,10 @@ const decryptedBundle = await decryptExportBundle({
returnMnemonic: true,
});
```
+
+
-6. Remove embedded key from Local Storage. This is recommended because (1) this key doesn't have to be persistent in the first place, and (2) reduces the risk of pattern detection.
-
-```js
+```ts
await LocalStorage.removeItem(
STORAGE_KEYS.EMBEDDED_PRIVATE_KEY,
embeddedPrivateKey,
@@ -395,15 +418,18 @@ await LocalStorage.removeItem(
);
```
-Congrats! You've exported your wallet 🎉
+Congrats! You've exported your wallet
The process is largely similar for both private keys and individual wallet accounts.
+
+
#### Private Key support
-1. Initialize Turnkey client
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-browser";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -415,18 +441,20 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const keyPair = generateP256KeyPair();
const privateKey = keyPair.privateKey;
const publicKey = keyPair.publicKeyUncompressed;
```
+
-3. Save the private key in Local Storage
+
-```js
+```ts
// Storage keys
const STORAGE_KEYS = {
EMBEDDED_PRIVATE_KEY: "@turnkey/embedded_private_key",
@@ -438,19 +466,21 @@ await LocalStorage.setItem(STORAGE_KEYS.EMBEDDED_PRIVATE_KEY, privateKey);
// Note that the public key can always be derived separately via the `getPublicKey` from `@turnkey/crypto`
await LocalStorage.setItem(STORAGE_KEYS.EMBEDDED_PUBLIC_KEY, publicKey);
```
+
-4. Call export (Turnkey activity), using the embedded key as the target key for the `exportPrivateKey` activity:
+
-```js
+```ts
const exportResult = await passkeyClient.exportPrivateKey({
privateKeyId, // desired private key ID
targetPublicKey: publicKey,
});
```
+
-5. Decrypt encrypted bundle
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: privateKey,
@@ -458,10 +488,11 @@ const decryptedBundle = await decryptExportBundle({
returnMnemonic: false, // N/A as we're working with a private key, not a wallet
});
```
+
-6. Remove embedded key from Local Storage. This is recommended because (1) this key doesn't have to be persistent in the first place, and (2) reduces the risk of pattern detection.
+
-```js
+```ts
await LocalStorage.removeItem(
STORAGE_KEYS.EMBEDDED_PRIVATE_KEY,
embeddedPrivateKey,
@@ -473,12 +504,15 @@ await LocalStorage.removeItem(
```
Congrats! You've exported your private key 🎉
+
+
#### Wallet Account support
-1. Initialize Turnkey client
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-browser";
import { generateP256KeyPair, decryptExportBundle } from "@turnkey/crypto";
@@ -490,18 +524,20 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-2. Generate a new P256 Keypair — this will serve as the target that Turnkey will encrypt key material to:
+
-```js
+```ts
const keyPair = generateP256KeyPair();
const privateKey = keyPair.privateKey;
const publicKey = keyPair.publicKeyUncompressed;
```
+
-3. Save the private key in Local Storage
+
-```js
+```ts
// Storage keys
const STORAGE_KEYS = {
EMBEDDED_PRIVATE_KEY: "@turnkey/embedded_private_key",
@@ -513,19 +549,22 @@ await LocalStorage.setItem(STORAGE_KEYS.EMBEDDED_PRIVATE_KEY, privateKey);
// Note that the public key can always be derived separately via the `getPublicKey` from `@turnkey/crypto`
await LocalStorage.setItem(STORAGE_KEYS.EMBEDDED_PUBLIC_KEY, publicKey);
```
+
+
4. Call export (Turnkey activity), using the embedded key as the target key for the `exportWalletAccount` activity:
-```js
+```ts
const exportResult = await passkeyClient.exportWalletAccount({
address, // your specific wallet account address
targetPublicKey: publicKey,
});
```
+
-5. Decrypt encrypted bundle
+
-```js
+```ts
const decryptedBundle = await decryptExportBundle({
exportBundle: exportResult.exportBundle,
embeddedKey: privateKey,
@@ -533,10 +572,11 @@ const decryptedBundle = await decryptExportBundle({
returnMnemonic: false, // N/A as we're working with a wallet account, not a wallet
});
```
+
-6. Remove embedded key from Local Storage. This is recommended because (1) this key doesn't have to be persistent in the first place, and (2) reduces the risk of pattern detection.
+
-```js
+```ts
await LocalStorage.removeItem(
STORAGE_KEYS.EMBEDDED_PRIVATE_KEY,
embeddedPrivateKey,
@@ -547,7 +587,9 @@ await LocalStorage.removeItem(
);
```
-Congrats! You've exported your wallet account 🎉
+Congrats! You've exported your wallet account
+
+
## Solana notes
@@ -559,7 +601,7 @@ When importing into a multichain wallet such as Phantom, see [this guide](https:
Everything is customizable in the import iframe except the sentence of mnemonic words, which is minimally styled: the text is left-aligned and the padding and margins are zero. Here's an example of how you can configure the styling of the iframe.
-```js
+```ts
const iframeCss = `
iframe {
box-sizing: border-box;
diff --git a/docs/solutions/embedded-wallets/code-examples/import.mdx b/embedded-wallets/code-examples/import.mdx
similarity index 68%
rename from docs/solutions/embedded-wallets/code-examples/import.mdx
rename to embedded-wallets/code-examples/import.mdx
index 1ec213d8..d5e2b94e 100644
--- a/docs/solutions/embedded-wallets/code-examples/import.mdx
+++ b/embedded-wallets/code-examples/import.mdx
@@ -1,16 +1,8 @@
---
title: "Import Wallet or Private Key"
-sidebar_position: 8
-description: Import Wallet or Private Key
-slug: /embedded-wallets/code-examples/import
+description: "This is a guide to importing your wallet or private key into Turnkey. For more information about the security of this flow, check out [Enclave secure channels](/security/enclave-secure-channels)."
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-This is a guide to importing your wallet or private key into Turnkey. For more information about the security of this flow, check out [Enclave secure channels](/security/enclave-secure-channels).
-
## Implementation guides
Follow along with the Turnkey CLI, Embedded iframe, NodeJS, and Local Storage guides.
@@ -21,33 +13,37 @@ Install the latest version of Turnkey CLI to access the new import functionality
#### Steps
-1. Generate an encryption key:
+
+
-```sh
+```bash
turnkey generate encryption-key \
--organization $ORGANIZATION_ID \
--user $USER_ID \
--encryption-key-name demo-encryption-key
```
-- The `--user` flag (required) is the id of the user importing the private key; this is required because the underlying encryption keys used for import are scoped to each user.
-- The `--encryption-key-name` flag is to specify a name for the encryption key. Note that an encryption key !== Turnkey API key; an encryption key is used exclusively for secure activities like import and export.
+* The `--user` flag (required) is the id of the user importing the private key; this is required because the underlying encryption keys used for import are scoped to each user.
+* The `--encryption-key-name` flag is to specify a name for the encryption key. Note that an encryption key !== Turnkey API key; an encryption key is used exclusively for secure activities like import and export.
+
+
-2. Initialize import:
+
-```sh
+```bash
turnkey wallets init-import \
--user $USER_ID \
--import-bundle-output "./import_bundle.txt" \
--key-name demo-api-key
```
-- The `--import-bundle-output` (required) flag is the desired output file path for the “import bundle” that will be received from Turnkey. The “import bundle” contains the ephemeral public key generated by the Turnkey signer enclave for the specified user. The private key plaintext is encrypted to this public key in Step 2.
-- Reminder: The `--key-name` flag specifies the name of API key with which to interact with the Turnkey API service. This should be the name of a previously created key. If you do not have one, visit the quickstart guide for help creating one.
+* The `--import-bundle-output` (required) flag is the desired output file path for the “import bundle” that will be received from Turnkey. The “import bundle” contains the ephemeral public key generated by the Turnkey signer enclave for the specified user. The private key plaintext is encrypted to this public key in Step 2.
+* Reminder: The `--key-name` flag specifies the name of API key with which to interact with the Turnkey API service. This should be the name of a previously created key. If you do not have one, visit the quickstart guide for help creating one.
+
-3. Encrypt without saving plaintext to filesystem. This can be done offline:
+
-```sh
+```bash
turnkey encrypt \
--user $USER_ID \
--import-bundle-input "./import_bundle.txt" \
@@ -56,13 +52,15 @@ turnkey encrypt \
--encryption-key-name demo-encryption-key
```
-- The `--import-bundle-input` flag (required) is the desired input file path for the “import bundle”.
-- The `--plaintext-input` flag is the desired input file path for the private key plaintext. You can pass a filename here or feed the plaintext string directly into the standard input as shown above.
-- The `--encrypted-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be sent to Turnkey in Step 3. The “encrypted bundle” contains the ephemeral public key generated by the CLI as part of the shared secret computation with the Turnkey signer enclave. It also contains the ciphertext, which is the plaintext input encrypted by the Turnkey signer’s ephemeral public key.
+* The `--import-bundle-input` flag (required) is the desired input file path for the “import bundle”.
+* The `--plaintext-input` flag is the desired input file path for the private key plaintext. You can pass a filename here or feed the plaintext string directly into the standard input as shown above.
+* The `--encrypted-bundle-output` (required) flag is the desired output file path for the “encrypted bundle” that will be sent to Turnkey in Step 3. The “encrypted bundle” contains the ephemeral public key generated by the CLI as part of the shared secret computation with the Turnkey signer enclave. It also contains the ciphertext, which is the plaintext input encrypted by the Turnkey signer’s ephemeral public key.
-4. Import private key:
+
-```sh
+
+
+```bash
turnkey wallets import \
--user $USER_ID \
--name "demo key" \
@@ -70,17 +68,19 @@ turnkey wallets import \
--key-name demo-api-key
```
-- The `--encrypted-bundle-input` (required) flag is the desired input file path for the “encrypted bundle” that will be sent to Turnkey.
-- Options are listed [here](/concepts/Wallets) for the `--curve` and `--address-format` flags.
+* The `--encrypted-bundle-input` (required) flag is the desired input file path for the “encrypted bundle” that will be sent to Turnkey.
+* Options are listed [here](/concepts/wallets) for the `--curve` and `--address-format` flags.
+
+
#### Private Key support
Turnkey CLI also supports importing private keys. Follow the same steps as importing a wallet via CLI but use the `turnkey private-keys` commands instead. In Step 2 (`encrypt`), pass a `--key-format` flag for key-specific formatting; the options for private keys are:
-- `hexadecimal`: Used for Ethereum. Examples: `0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e, 13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e`
-- `solana`: Used for Solana. It’s a base58-encoding of the concatenation of the private key and public key bytes. Example: `2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM`
+* `hexadecimal`: Used for Ethereum. Examples: `0x13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e, 13eff5b3f9c63eab5d53cff5149f01606b69325496e0e98b53afa938d890cd2e`
+* `solana`: Used for Solana. It’s a base58-encoding of the concatenation of the private key and public key bytes. Example: `2P3qgS5A18gGmZJmYHNxYrDYPyfm6S3dJgs8tPW6ki6i2o4yx7K8r5N8CF7JpEtQiW8mx1kSktpgyDG1xuWNzfsM`
-```sh
+```bash
turnkey encrypt \
--import-bundle-input "./import_bundle.txt" \
--plaintext-input /dev/fd/3 3<<<"$RAW_KEY_1" \
@@ -90,92 +90,98 @@ turnkey encrypt \
### Embedded iframe
-- We have released open-source code to create target encryption keys and encrypt wallet mnemonics for import. We've deployed a static HTML page hosted on `import.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the mnemonics and keys are encrypted to keys that the user has access to, but that your organization does not (because they live in the iframe, on a separate domain).
-- We have also built a package to help you insert this iframe and interact with it in the context of import: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+* We have released open-source code to create target encryption keys and encrypt wallet mnemonics for import. We've deployed a static HTML page hosted on `import.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the mnemonics and keys are encrypted to keys that the user has access to, but that your organization does not (because they live in the iframe, on a separate domain).
+* We have also built a package to help you insert this iframe and interact with it in the context of import: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
In the rest of this guide we'll assume you are using these helpers.
#### Steps
-Here's a diagram summarizing the wallet import flow step-by-step ([direct link](/img/wallet_import_steps.png)):
+Here's a diagram summarizing the wallet import flow step-by-step ([direct link](/assets/files/wallet_import_steps-6c4753c1e726e1632ce475bc838388c2.png)):
-
-
-
+
+
+
Let's review these steps in detail:
-
-1. When a user on your application clicks "import", display a new import UI. We recommend setting this import UI as a new hosted page of your application that contains the iframe element that the user will enter their mnemonic into and an import button.
+
+
+When a user on your application clicks "import", display a new import UI. We recommend setting this import UI as a new hosted page of your application that contains the iframe element that the user will enter their mnemonic into and an import button.
While the UI is in a loading state, your application uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
```js
const iframeStamper = new IframeStamper({
- iframeUrl: "https://import.turnkey.com",
- // Configure how the iframe element is inserted on the page
- iframeContainer: yourContainer,
- iframeElementId: "turnkey-iframe",
- });
-
- // Inserts the iframe in the DOM.
- await iframeStamper.init();
-
- // Set state to not display iframe
- let displayIframe = "none";
-
- return (
- // The iframe element does not need to be displayed yet
-
- );
- ```
+ iframeUrl: "https://import.turnkey.com",
+ // Configure how the iframe element is inserted on the page
+ iframeContainer: yourContainer,
+ iframeElementId: "turnkey-iframe",
+});
+
+// Inserts the iframe in the DOM.
+await iframeStamper.init();
-2. Your application prompts the user to sign a new `INIT_IMPORT_WALLET` activity with the ID of the user importing the wallet.
+// Set state to not display iframe
+let displayIframe = "none";
-3. Your application polls for the activity response, which contains an import bundle.
+return (
+ // The iframe element does not need to be displayed yet
+
+);
+ ```
+
+
+Your application prompts the user to sign a new `INIT_IMPORT_WALLET` activity with the ID of the user importing the wallet.
+
+
+Your application polls for the activity response, which contains an import bundle.
Need help setting up async polling? Checkout our guide and helper [here](https://github.com/tkhq/sdk/tree/main/packages/http#withasyncpolling-helper).
-4. Your application injects the import bundle into the iframe and displays the iframe upon success:
+
+
+Your application injects the import bundle into the iframe and displays the iframe upon success:
- ```js
- // Inject import bundle into iframe
- let success = await iframeStamper.injectImportBundle(importBundle);
+```js
+// Inject import bundle into iframe
+let success = await iframeStamper.injectImportBundle(importBundle);
- if (success !== true) {
- throw new Error("unexpected error while injecting import bundle");
- }
+if (success !== true) {
+ throw new Error("unexpected error while injecting import bundle");
+}
- // If successfully injected, update the state to display the iframe
- iframeDisplay = "block";
- ```
+// If successfully injected, update the state to display the iframe
+iframeDisplay = "block";
+```
-5. When a user clicks on the import button on your web page, your application can extract the encrypted mnemonic bundle from the iframe:
+
+
+When a user clicks on the import button on your web page, your application can extract the encrypted mnemonic bundle from the iframe:
- ```js
- // Extract the encrypted bundle from the iframe
- let encryptedBundle =
- await iframeStamper.extractWalletEncryptedBundle(importBundle);
- ```
+```js
+// Extract the encrypted bundle from the iframe
+let encryptedBundle =
+ await iframeStamper.extractWalletEncryptedBundle(importBundle);
+```
Your applications passes the encrypted bundle as a parameter in a new `IMPORT_WALLET` activity and prompts the user to sign it.
Import is complete!
In your Turnkey dashboard, the imported user Wallet will be flagged as “Imported”.
+
+
### NodeJS
-A full example Node script can be found here: https://github.com/tkhq/sdk/tree/main/examples/import-in-node
+A full example Node script can be found here: [https://github.com/tkhq/sdk/tree/main/examples/import-in-node](https://github.com/tkhq/sdk/tree/main/examples/import-in-node)
#### Steps
-1. Initialize a new Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-server";
import {
encryptPrivateKeyToBundle,
@@ -191,18 +197,18 @@ const turnkeyClient = new Turnkey({
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
```
+
+
-2. Initialize the import process (Turnkey activity):
-
-```js
+```ts
const initResult = await turnkeyClient.apiClient().initImportWallet({
userId,
});
```
+
+
-3. Encrypt wallet to bundle:
-
-```js
+```ts
const walletBundle = await encryptWalletToBundle({
mnemonic,
importBundle: initResult.importBundle,
@@ -210,10 +216,10 @@ const walletBundle = await encryptWalletToBundle({
organizationId,
});
```
+
+
-4. Import wallet (Turnkey activity):
-
-```js
+```ts
const walletImportResult = await turnkeyClient.apiClient().importWallet({
userId: userId,
walletName: "Your imported wallet!",
@@ -222,13 +228,16 @@ const walletImportResult = await turnkeyClient.apiClient().importWallet({
});
```
-Congrats! You've imported your wallet 🎉
+Congrats! You've imported your wallet
+
+
#### Private Key support
The process for importing a private key instead of wallet is largely similar, but has a key difference in that you must specify the format of your imported private key:
-1. Initialize a new Turnkey client:
+
+
```js
import { Turnkey } from "@turnkey/sdk-server";
@@ -246,16 +255,16 @@ const turnkeyClient = new Turnkey({
defaultOrganizationId: process.env.ORGANIZATION_ID!,
});
```
-
-2. Initialize the import process (Turnkey activity):
+
+
```js
const initResult = await turnkeyClient.apiClient().initImportPrivateKey({
userId,
});
```
-
-3. Encrypt private key to bundle:
+
+
```js
const privateKeyBundle = await encryptPrivateKeyToBundle({
@@ -266,8 +275,8 @@ const privateKeyBundle = await encryptPrivateKeyToBundle({
organizationId,
});
```
-
-4. Import private key (Turnkey activity):
+
+
```js
const privateKeyImportResult = await turnkeyClient
@@ -284,17 +293,20 @@ const privateKeyImportResult = await turnkeyClient
});
```
-Congrats! You've imported your private key 🎉
+Congrats! You've imported your private key
+
+
### Local Storage
-If you do not have access to an iframe (e.g. in a mobile context) or would prefer not to use an iframe, you can opt to use other environment-agnostic methods to perform import using Turnkey libraries. So while this section is called Local Storage (for consistency with the [Export](./export) guide), nothing is stored in Local Storage; instead, the relevant `encrypt{Wallet, PrivateKey}ToBundle` method uses an [ephemeral key](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/crypto/src/crypto.ts#L62-L70).
+If you do not have access to an iframe (e.g. in a mobile context) or would prefer not to use an iframe, you can opt to use other environment-agnostic methods to perform import using Turnkey libraries. So while this section is called Local Storage (for consistency with the [Export](/embedded-wallets/code-examples/export) guide), nothing is stored in Local Storage; instead, the relevant `encrypt{Wallet, PrivateKey}ToBundle` method uses an [ephemeral key](https://github.com/tkhq/sdk/blob/6b3ea14d1184c5394449ecaad2b0f445e373823f/packages/crypto/src/crypto.ts#L62-L70).
#### Steps
-1. Initialize Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-browser";
import { generateP256KeyPair, encryptWalletToBundle } from "@turnkey/crypto";
@@ -306,18 +318,18 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
+
-2. Initialize the import process (Turnkey activity):
-
-```js
+```ts
const initResult = await passkeyClient.initImportWallet({
userId, // your user ID
});
```
+
+
-3. Encrypt wallet to bundle:
-
-```js
+```ts
const walletBundle = await encryptWalletToBundle({
mnemonic,
importBundle: initResult.importBundle,
@@ -325,10 +337,10 @@ const walletBundle = await encryptWalletToBundle({
organizationId, // your organization ID
});
```
+
+
-4. Import wallet (Turnkey activity):
-
-```js
+```ts
const walletImportResult = await passkeyClient.importWallet({
userId, // your user ID
walletName: "Your imported wallet!", // your desired name for the resulting imported wallet
@@ -337,15 +349,18 @@ const walletImportResult = await passkeyClient.importWallet({
});
```
-Congrats! You've imported your wallet 🎉
+Congrats! You've imported your wallet
The process for importing a private key instead of wallet is largely similar, but has a key difference in that you must specify the format of your imported private key:
+
+
#### Private Key support
-1. Initialize Turnkey client:
+
+
-```js
+```ts
import { Turnkey } from "@turnkey/sdk-browser";
import { generateP256KeyPair, encryptPrivateKeyToBundle } from "@turnkey/crypto";
@@ -357,18 +372,18 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
+
-2. Initialize the import process (Turnkey activity):
-
-```js
+```
const initResult = await passkeyClient.initImportPrivateKey({
userId, // your user ID
});
```
+
+
-3. Encrypt private key to bundle:
-
-```js
+```ts
const privateKeyBundle = await encryptPrivateKeyToBundle({
privateKey, // your private key string
keyFormat, // your desired key format. See the Private Key notes section for more
@@ -377,23 +392,23 @@ const privateKeyBundle = await encryptPrivateKeyToBundle({
organizationId, // your organization ID
});
```
+
+
-4. Import private key (Turnkey activity):
-
-```js
+```ts
const privateKeyImportResult = await passkeyClient.importPrivateKey({
userId,
privateKeyName: "Your imported private key!", // your desired name for the resulting imported private key
encryptedBundle: privateKeyBundle,
curve: keyFormat == "SOLANA" ? "CURVE_ED25519" : "CURVE_SECP256K1",
addressFormats:
- keyFormat == "SOLANA"
- ? ["ADDRESS_FORMAT_SOLANA"]
- : ["ADDRESS_FORMAT_ETHEREUM"],
+ keyFormat == "SOLANA" ? ["ADDRESS_FORMAT_SOLANA"] : ["ADDRESS_FORMAT_ETHEREUM"],
});
```
-Congrats! You've imported your private key 🎉
+Congrats! You've imported your private key
+
+
## Private Key notes
@@ -403,7 +418,7 @@ Turnkey also supports importing Private Keys. Follow the same steps above for im
Everything is customizable in the import iframe except the sentence of mnemonic words, which is minimally styled: the text is left-aligned and the padding and margins are zero. Here's an example of how you can configure the styling of the iframe.
-```js
+```css
const iframeCss = `
iframe {
box-sizing: border-box;
diff --git a/docs/solutions/embedded-wallets/code-examples/signing-transactions.mdx b/embedded-wallets/code-examples/signing-transactions.mdx
similarity index 64%
rename from docs/solutions/embedded-wallets/code-examples/signing-transactions.mdx
rename to embedded-wallets/code-examples/signing-transactions.mdx
index ac7f9c6b..edcb4a06 100644
--- a/docs/solutions/embedded-wallets/code-examples/signing-transactions.mdx
+++ b/embedded-wallets/code-examples/signing-transactions.mdx
@@ -1,23 +1,16 @@
---
title: "Signing Transactions"
-sidebar_position: 7
-description: Signing Transactions
-slug: /embedded-wallets/code-examples/signing-transactions
+description: "This is a guide to signing transactions in the browser context. While these snippets leverage Ethers, it can be swapped out for other signers in the Viem or Solana contexts. See [here](https://github.com/tkhq/sdk/tree/main/examples/with-eth-passkeys-galore) for an example with both Ethers and Viem in the passkey + browser context, and [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana-passkeys) for a similar example with Solana."
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-This is a guide to signing transactions in the browser context. While these snippets leverage Ethers, it can be swapped out for other signers in the Viem or Solana contexts. See [here](https://github.com/tkhq/sdk/tree/main/examples/with-eth-passkeys-galore) for an example with both Ethers and Viem in the passkey + browser context, and [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana-passkeys) for a similar example with Solana.
-
## Steps using `@turnkey/sdk-react`
-This process is made the most seamless by leveraging our [React package](../../../sdks/react). Read on for a non-React implementation.
+This process is made the most seamless by leveraging our [React package](/sdks/react). Read on for a non-React implementation.
-#### 1. Initialize the React Provider
+
+
-```typescript
+```js
import { TurnkeyProvider } from "@turnkey/sdk-react";
const turnkeyConfig = {
apiBaseUrl: "https://api.turnkey.com",
@@ -34,10 +27,11 @@ const turnkeyConfig = {
```
+
-#### 2. Initialize an Ethers Provider and Turnkey Signer using the Passkey Client
+
-```typescript
+```js
import { ethers } from "ethers";
import { TurnkeySigner } from "@turnkey/ethers";
@@ -53,24 +47,27 @@ const turnkeySigner = new TurnkeySigner({
})
const connectedSigner = turnkeySigner.connect(provider);
```
+
-#### 3. Call `sendTransaction` with the Turnkey Signer
+
-```typescript
+```js
const transactionRequest = {
to: "",
value: ethers.parseEther(""),
type: 2,
};
-const sendTransaction =
- await connectedSigner.sendTransaction(transactionRequest);
+const sendTransaction = await connectedSigner.sendTransaction(transactionRequest);
```
+
+
## Alternative Steps (non-React)
-#### 1. Initialize the Passkey Client
+
+
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
@@ -79,10 +76,11 @@ const turnkey = new Turnkey({
});
const passkeyClient = turnkey.passkeyClient();
```
+
-#### 2. Initialize an Ethers Provider and Turnkey Signer
+
-```typescript
+```
import { ethers } from "ethers";
import { TurnkeySigner } from "@turnkey/ethers";
@@ -95,10 +93,11 @@ const turnkeySigner = new TurnkeySigner({
})
const connectedSigner = turnkeySigner.connect(provider);
```
+
-#### 3. Call `sendTransaction` with the Turnkey Signer
+
-```typescript
+```js
const transactionRequest = {
to: "",
value: ethers.parseEther(""),
@@ -107,3 +106,5 @@ const transactionRequest = {
const sendTransaction =
await connectedSigner.sendTransaction(transactionRequest);
```
+
+
diff --git a/docs/solutions/embedded-wallets/code-examples/wallet-auth.mdx b/embedded-wallets/code-examples/wallet-auth.mdx
similarity index 75%
rename from docs/solutions/embedded-wallets/code-examples/wallet-auth.mdx
rename to embedded-wallets/code-examples/wallet-auth.mdx
index adce2f67..04e98ea1 100644
--- a/docs/solutions/embedded-wallets/code-examples/wallet-auth.mdx
+++ b/embedded-wallets/code-examples/wallet-auth.mdx
@@ -1,27 +1,17 @@
---
title: "Wallet Authentication"
-sidebar_position: 6
-description: Authenticate a User with an Ethereum Wallet
-slug: /embedded-wallets/code-examples/wallet-auth
+description: "In this guide, we'll explore how to leverage the `WalletClient` in the Turnkey SDK to authenticate requests to Turnkey's API using either Solana or Ethereum wallets."
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
-# Wallet Authentication
-
-In this guide, we'll explore how to leverage the `WalletClient` in the Turnkey SDK to authenticate requests to Turnkey's API using either Solana or Ethereum wallets.
-
## Initialize
Begin by initializing the Turnkey SDK by passing in a config object containing:
-- `apiBaseUrl`: The base URL of the Turnkey API: `https://api.turnkey.com`.
-- `defaultOrganizationId`: Your parent organization ID, which you can find in the [Turnkey dashboard](https://app.turnkey.com/dashboard).
-- `wallet`: The wallet interface used to sign requests. In this example, we'll use the `EthereumWallet` interface.
+* `apiBaseUrl`: The base URL of the Turnkey API: `https://api.turnkey.com`.
+* `defaultOrganizationId`: Your parent organization ID, which you can find in the [Turnkey dashboard](https://app.turnkey.com/dashboard).
+* `wallet`: The wallet interface used to sign requests. In this example, we'll use the `EthereumWallet` interface.
-```ts title="config.ts"
+```ts config.ts
import { EthereumWallet } from "@turnkey/wallet-stamper";
export const turnkeyConfig = {
@@ -33,13 +23,11 @@ export const turnkeyConfig = {
wallet: new EthereumWallet(),
};
```
-
-
-
-
+
+
First, wrap your application with the `TurnkeyProvider` in your `app/layout.tsx` file:
-```tsx title="app/layout.tsx"
+```tsx app/layout.tsx
import { TurnkeyProvider } from "@turnkey/sdk-react";
import { turnkeyConfig } from "./config";
@@ -59,10 +47,9 @@ export default function RootLayout({
);
}
```
-
Then, create a new page component `app/page.tsx` where we'll implement the wallet authentication functionality:
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
import { useState } from "react";
@@ -77,12 +64,12 @@ export default function WalletAuth() {
}
```
-
-
+
+
Create a new file `src/wallet-auth.ts` where we'll implement the wallet authentication functionality:
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
import { Turnkey } from "@turnkey/sdk-browser";
import { EthereumWallet } from "@turnkey/wallet-stamper";
import { turnkeyConfig } from "./config";
@@ -95,92 +82,71 @@ const walletClient = turnkey.walletClient(new EthereumWallet());
// We'll add more functionality here in the following steps
```
-
-
+
## Sign Up
-In this section, we'll guide you through the process of implementing a sign-up flow using an Ethereum wallet for authentication.
-The sign-up process involves creating a new sub-organization within your existing organization.
-This requires authentication of the parent organization using its public/private key pair.
-Additionally, we'll cover how to verify if a user already has an associated sub-organization before proceeding.
+In this section, we'll guide you through the process of implementing a sign-up flow using an Ethereum wallet for authentication. The sign-up process involves creating a new sub-organization within your existing organization. This requires authentication of the parent organization using its public/private key pair. Additionally, we'll cover how to verify if a user already has an associated sub-organization before proceeding.
### Server-side
-Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package.
-This setup enables you to authenticate requests to Turnkey's API using the parent organization's public/private API key pair.
-This is required to create new sub-organizations on behalf of a user.
+Initialize the Turnkey SDK on the **server-side** using the `@turnkey/sdk-server` package. This setup enables you to authenticate requests to Turnkey's API using the parent organization's public/private API key pair. This is required to create new sub-organizations on behalf of a user.
-
-
+
+
+For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client. This will ensure that the function is executed on the server-side and will have access to the server-side environment variables e.g. your parent organization's public/private API key pair. For more information on Next.js server actions, see the Next.js documentation on [Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
-For Next.js, add the `"use server"` directive at the top of the file where you're initializing the Turnkey server client.
-This will ensure that the function is executed on the server-side and will have access to the
-server-side environment variables e.g. your parent organization's public/private API key pair.
-For more information on Next.js server actions, see the Next.js documentation on
-[Server Actions and Mutations](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
-
-```ts title="app/actions.ts"
+```tsx app/actions.ts
"use server";
import { Turnkey } from "@turnkey/sdk-server";
import { turnkeyConfig } from "./config";
-const { baseUrl, defaultOrganizationId } = turnkeyConfig;
+const { apiBaseUrl, defaultOrganizationId } = turnkeyConfig;
// Initialize the Turnkey Server Client on the server-side
const turnkeyServer = new Turnkey({
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
- baseUrl,
+ apiBaseUrl,
defaultOrganizationId,
}).apiClient();
```
-
-
-
-
-```typescript title="src/wallet-auth-server.ts"
+
+
+```ts src/wallet-auth-server.ts
import { Turnkey } from "@turnkey/sdk-server";
import { turnkeyConfig } from "./config";
-const { baseUrl, defaultOrganizationId } = turnkeyConfig;
+const { apiBaseUrl, defaultOrganizationId } = turnkeyConfig;
// Initialize the Turnkey Server Client on the server-side
const turnkeyServer = new Turnkey({
apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
- baseUrl,
+ apiBaseUrl,
defaultOrganizationId,
}).apiClient();
```
-
-
+
### Check for Existing User
-Before signing up a new user, we can try and retrieve the user's sub-organization ID using the public key associated
-with the Ethereum or Solana account they want to authenticate with.
-If a sub-organization is found, we can proceed with authentication; otherwise, we assume the user is signing up.
+Before signing up a new user, we can try and retrieve the user's sub-organization ID using the public key associated with the Ethereum or Solana account they want to authenticate with. If a sub-organization is found, we can proceed with authentication; otherwise, we assume the user is signing up.
We'll use the `getPublicKey` method on the `WalletClient` instance which will retrieve the public key from the user's wallet.
-:::note
-The main distinction between signing with an Ethereum Wallet and a Solana Wallet lies in how the public key is obtained.
-For Solana, the public key can be directly derived from the wallet.
-In contrast, with Ethereum, the secp256k1 public key isn't directly accessible.
-Instead, you need to first obtain a signature from the user and then recover the public key from that signature.
-This requires an additional step of signing a message with the user's Ethereum wallet before we can retrieve the public key.
-:::
-
-
-
+
+ The main distinction between signing with an Ethereum Wallet and a Solana Wallet lies in how the public key is obtained. For Solana, the public key can be directly derived from the wallet. In contrast, with Ethereum, the secp256k1 public key isn't directly accessible. Instead, you need to first obtain a signature from the user and then recover the public key from that signature. This requires an additional step of signing a message with the user's Ethereum wallet before we can retrieve the public key.
+
+
+
We'll define this function in the server-side code we initialized earlier.
-```ts title="app/actions.ts"
+```ts app/actions.ts
"use server";
// ...
@@ -197,10 +163,9 @@ export const getSubOrg = async (publicKey: string) => {
};
```
-Next, we'll add the client-side functionality to the `app/page.tsx` file we created earlier importing
-the `getSubOrg` function we defined in our server action.
+Next, we'll add the client-side functionality to the `app/page.tsx` file we created earlier importing the `getSubOrg` function we defined in our server action.
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
import { useState } from "react";
@@ -234,13 +199,11 @@ export default function WalletAuth() {
);
}
```
-
-
-
-
+
+
We'll define the `getSubOrg` function in the server-side code we initialized earlier.
-```ts title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
"use server";
// ...
@@ -259,7 +222,7 @@ export const getSubOrg = async (publicKey: string) => {
We'll use the `getSubOrg` function in the login method to check if a user already has a sub-organization.
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
import { getSubOrg } from "./wallet-auth-server";
// ...
const walletClient = turnkey.walletClient(new EthereumWallet());
@@ -280,8 +243,7 @@ export const login = async () => {
}
};
```
-
-
+
### Create Sub-Organization
@@ -290,12 +252,11 @@ Next, we'll define a method to create a sub-organization for new user sign-ups.
For more information, refer to the [Sub-Organizations](/concepts/sub-organizations) guide.
-
-
-
+
+
We'll define another server action `createSubOrg` to create a sub-organization for new user sign-ups.
-```ts title="app/actions.ts"
+```ts app/actions.ts
"use server";
// ...
@@ -342,10 +303,9 @@ export const createSubOrg = async (
};
```
-Then, we'll import and use this `createSubOrg` function within the login method. The curve type is set to `API_KEY_CURVE_SECP256K1`
-since we're using an Ethereum wallet in this example.
+Then, we'll import and use this `createSubOrg` function within the login method. The curve type is set to `API_KEY_CURVE_SECP256K1` since we're using an Ethereum wallet in this example.
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
import { getSubOrg, createSubOrg } from "./actions";
// ...
@@ -383,13 +343,11 @@ export default function WalletAuth() {
);
}
```
-
-
-
-
+
+
We'll define another server-side function `createSubOrg`, to create a sub-organization for new user sign-ups.
-```typescript title="src/wallet-auth-server.ts"
+```ts src/wallet-auth-server.ts
// ...
// Import the default Ethereum accounts helper
@@ -434,10 +392,9 @@ export const createSubOrg = async (
};
```
-Then, we'll import and use this `createSubOrg` function within the login method. The curve type is set to `API_KEY_CURVE_SECP256K1`
-since we're using an Ethereum wallet in this example.
+Then, we'll import and use this `createSubOrg` function within the login method. The curve type is set to `API_KEY_CURVE_SECP256K1` since we're using an Ethereum wallet in this example.
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
import { getSubOrg, createSubOrg } from "./wallet-auth-server";
// ...
const walletClient = turnkey.walletClient(new EthereumWallet());
@@ -464,33 +421,32 @@ export const login = async () => {
}
};
```
-
-
+
## Sign In
-At this point, we have a working sign-up flow. Next, we'll implement the signing in
-functionality by creating a read-only session and retrieving the user's wallets.
+At this point, we have a working sign-up flow. Next, we'll implement the signing in functionality by creating a read-only session and retrieving the user's wallets.
### Read-only Session
-Create a read-only session for the user by calling the `login` method on the `WalletClient` instance.
-This will save a read-only session token to the `localStorage` to authenticate future read requests.
+Create a read-only session for the user by calling the `login` method on the `WalletClient` instance. This will save a read-only session token to the `localStorage` to authenticate future read requests.
-
-
+
+
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
import { getSubOrg, createSubOrg } from "./actions";
// ...
-const walletClient = turnkey.walletClient(new EthereumWallet());
export default function WalletAuth() {
- const { walletClient } = useTurnkey();
+ const { walletClient, client } = useTurnkey();
+ // State to hold the user's wallets
+ const [wallets, setWallets] = useState([]);
const login = async () => {
+ // Get the public key of the wallet, for Ethereum wallets this will trigger a prompt for the user to sign a message
const publicKey = await walletClient?.getPublicKey();
if (!publicKey) {
@@ -504,25 +460,36 @@ export default function WalletAuth() {
});
if (loginResponse?.organizationId) {
- // User is authenticated 🎉
+ const wallets = await client.getWallets({
+ organizationId: loginResponse.organizationId,
+ });
+ setWallets(wallets);
}
} else {
// ...
}
};
+ // Render the user's wallets if defined
+ if (wallets) {
+ return (
+
+ {wallets.map((wallet) => (
+
{wallet.walletName}
+ ))}
+
+ );
+ }
+
return (
-
-
-
+ // ...
);
}
```
+
+
-
-
-
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
import { getSubOrg, createSubOrg } from "./wallet-auth-server";
// ...
@@ -535,25 +502,28 @@ export const login = async (publicKey: string) => {
});
if (loginResponse?.organizationId) {
- // User is authenticated 🎉
+ const wallets = await client.getWallets({
+ organizationId: loginResponse.organizationId,
+ });
+ // Log the user's wallets
+ console.log(wallets);
}
} else {
// ...
}
};
```
-
-
+
### Retrieve Wallets
Once the user is authenticated, we can retrieve the user's wallets by calling the `getWallets` method on the `WalletClient` instance.
-
-
+
+
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
import { getSubOrg, createSubOrg } from "./actions";
// ...
@@ -604,11 +574,10 @@ export default function WalletAuth() {
);
}
```
+
+
-
-
-
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
import { getSubOrg, createSubOrg } from "./wallet-auth-server";
// ...
@@ -632,23 +601,19 @@ export const login = async (publicKey: string) => {
}
};
```
-
-
+
### Read-write Session
-It is also possible to create a read-write session for the user by calling the `loginWithReadWriteSession` method
-on the `WalletClient` instance. This will save a read-write session token to the `localStorage` to authenticate
-future read/write requests. This session can be used with the `TurnkeyIframeClient` to make read/write requests to the Turnkey API.
+It is also possible to create a read-write session for the user by calling the `loginWithReadWriteSession` method on the `WalletClient` instance. This will save a read-write session token to the `localStorage` to authenticate future read/write requests. This session can be used with the `TurnkeyIframeClient` to make read/write requests to the Turnkey API.
-
-
+
+
-Most of the code is the same as the read-only session example. The only difference is that we replace `login` with `loginWithReadWriteSession`
-and use the `getActiveClient` method to get the `IframeClient` instance and using it to create a new wallet.
+Most of the code is the same as the read-only session example. The only difference is that we replace `login` with `loginWithReadWriteSession` and use the `getActiveClient` method to get the `IframeClient` instance and using it to create a new wallet.
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
// ...
@@ -682,10 +647,9 @@ export default function WalletAuth() {
}
```
-Define a new function `addWallet` which will create a new wallet using the read-write client.
-We'll also add a button to trigger this function.
+Define a new function `addWallet` which will create a new wallet using the read-write client. We'll also add a button to trigger this function.
-```tsx title="app/page.tsx"
+```tsx app/page.tsx
"use client";
// ...
@@ -730,14 +694,13 @@ export default function WalletAuth() {
);
}
```
+
+
-
-
+Define a helper function to initialize the the iframe client and inject the credential bundle. This will return an `IframeClient` instance that can be used to make read/write requests to the Turnkey API.
-Define a helper function to initialize the the iframe client and inject the credential bundle. This will
-return an `IframeClient` instance that can be used to make read/write requests to the Turnkey API.
+```ts src/wallet-auth.ts
-```typescript title="src/wallet-auth.ts"
// ...
const getReadWriteClient = async (credentialBundle: string) => {
@@ -763,10 +726,9 @@ export const login = async (publicKey: string) => {
/* .... */
};
```
-
Use this `getReadWriteClient` function in the `login` method:
-```typescript title="src/wallet-auth.ts"
+```ts src/wallet-auth.ts
// ...
export const login = async (publicKey: string) => {
//...
@@ -793,13 +755,14 @@ export const login = async (publicKey: string) => {
}
};
```
-
-
+
### Examples
You can find examples of how to implement the above functionality and more in the following repositories:
-- [Demo Embedded Wallet](https://github.com/tkhq/demo-embedded-wallet)
-- [Wallet stamper](https://github.com/tkhq/sdk/tree/main/examples/with-wallet-stamper)
+
+
+
+
diff --git a/embedded-wallets/overview.mdx b/embedded-wallets/overview.mdx
new file mode 100644
index 00000000..492136bf
--- /dev/null
+++ b/embedded-wallets/overview.mdx
@@ -0,0 +1,48 @@
+---
+title: "Embedded Wallets"
+sidebarTitle: "Introduction"
+---
+
+With embedded wallets on Turnkey, you can create custom wallet experiences that are seamlessly integrated into your product, without compromising on security. Whether you need custodial or non-custodial wallets, our infrastructure provides the foundation for building innovative, user-friendly crypto products.
+
+### Why embedded wallets?
+
+Embedded wallets give you the freedom to design and control the entire user experience, while offloading the complexity and risk of private key management to Turnkey. As one of our customers put it:
+
+> "The ability for us to build our own stack and control the entire user experience without worrying about security has been one of the best things about Turnkey."
+
+With Embedded wallets, you can:
+
+* Create custom wallet flows that match your product's look and feel
+* Choose between custodial or non-custodial models based on your use case
+* Leverage advanced security and UX features like multi-sig and session keys
+* Support various authentication methods, from passkeys to email-based flows
+* Focus on building great products, not managing private keys
+
+### How it works
+
+Turnkey's Embedded Wallets are built on top of our [Sub-Organizations](/concepts/sub-organizations) primitive. Each wallet is represented by a sub-organization, which can be configured with different security settings and access controls.
+
+##### Custodial vs non-custodial
+
+* For custodial wallets, your application holds the master key and can initiate transactions on behalf of users.
+* For non-custodial wallets, users hold their own private keys and must approve each transaction, whether it's via their own passkey, API key, or iframe session.
+
+##### Advanced features
+
+* Multi-sig: Require multiple approvals for sensitive transactions
+* Session Keys: Grant temporary, limited access to wallets for improved UX
+* Flexible Authentication: Use passkeys, email auth, or other methods to secure user access
+
+##### Getting started
+
+To start building with embedded wallets, check out our detailed guides and API references:
+
+
+
+
+
+
+
+
+Or, if you're looking to create smart wallets for your users, using Turnkey as a user-friendly, recoverable EOA, check out our [guide](/reference/aa-wallets) on integrating Account Abstraction (AA) wallets with Turnkey.
diff --git a/embedded-wallets/sub-organization-auth.mdx b/embedded-wallets/sub-organization-auth.mdx
new file mode 100644
index 00000000..33c5d2bb
--- /dev/null
+++ b/embedded-wallets/sub-organization-auth.mdx
@@ -0,0 +1,213 @@
+---
+title: "Email Authentication"
+description: "Email auth is a powerful feature to couple with [sub-organizations](/concepts/sub-organizations) for your users. This approach empowers your users to authenticate their Turnkey in a simple way (via email!), while minimizing your involvement: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to."
+---
+
+Our [Demo Embedded Wallet](https://wallet.tx.xyz) application serves an example of how email auth functionality might be integrated. We encourage you to try it (and check out the [code](https://github.com/tkhq/demo-embedded-wallet)) before diving into your own implementation.
+
+## Prerequisites
+
+Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth on behalf of suborgs. Check out our [Quickstart guide](/getting-started/quickstart) if you need help getting started. To allow an API user to initiate email auth, you'll need the following policy in your main organization:
+
+```json
+{
+ "effect": "EFFECT_ALLOW",
+ "consensus": "approvers.any(user, user.id == '')",
+ "condition": "activity.resource == 'AUTH' && activity.action == 'CREATE'"
+}
+```
+
+## Helper packages
+
+* We have released open-source code to create target encryption keys, decrypt auth credentials, and sign Turnkey activities. We've deployed this as a static HTML page hosted on `auth.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the auth credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
+* We have also built a package to help you insert this iframe and interact with it in the context of email auth: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+
+In the rest of this guide we'll assume you are using these helpers.
+
+## Email Auth step-by-step
+
+Here's a diagram summarizing the email auth flow step-by-step ([direct link](/assets/files/email_auth_steps-d4f7ca188fd3fb9a2de5557b4cf39c7b.png)):
+
+
+
+
+
+Let's review these steps in detail:
+
+
+ User on `yoursite.xyz` clicks "auth", and a new auth UI is shown. We recommend this auth UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to successfully authenticate. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
+
+ ```js
+ const iframeStamper = new IframeStamper({
+ iframeUrl: "https://auth.turnkey.com",
+ // Configure how the iframe element is inserted on the page
+ iframeContainerId: "your-container",
+ iframeElementId: "turnkey-iframe",
+ });
+
+ // Inserts the iframe in the DOM. This creates the new encryption target key
+ const publicKey = await iframeStamper.init();
+ ```
+
+
+ Your code receives the iframe public key and shows the auth form, and the user enters their email address.
+
+
+ Your app can now create and sign a new `EMAIL_AUTH` activity with the user email and the iframe public key in the parameters. Optional arguments include a custom name for the API key, and a specific duration (denoted in seconds) for it. Note: you'll need to retrieve the sub-organization ID based on the user email.
+
+
+
+ Email is received by the user.
+
+
+ User copies and pastes their auth code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe. In order to enable persistent sessions, save the auth code in local storage:
+
+ ```js
+ window.localStorage.setItem("BUNDLE", bundle);
+ ```
+
+ See [Email Customization](#email-customization) below to use a magic link instead of a one time code.
+
+
+ Your app injects the auth code into the iframe for decryption:
+
+ ```js
+ await iframeStamper.injectCredentialBundle(code);
+ ```
+
+
+ At this point, the user is authenticated!
+
+
+ Your app should use [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new activity, e.g. `CREATE_WALLET`:
+
+ ```js
+ // New client instantiated with our iframe stamper
+ const client = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ iframeStamper,
+ );
+
+ // Sign and submits the CREATE_WALLET activity
+ const response = await client.createWallet({
+ type: "ACTIVITY_TYPE_CREATE_WALLET",
+ timestampMs: String(Date.now()),
+ organizationId: authResponse.organizationId,
+ parameters: {
+ walletName: "Default Wallet",
+ accounts: [
+ {
+ curve: "CURVE_SECP256K1",
+ pathFormat: "PATH_FORMAT_BIP32",
+ path: "m/44'/60'/0'/0/0",
+ addressFormat: "ADDRESS_FORMAT_ETHEREUM",
+ },
+ ],
+ },
+ });
+ ```
+
+
+ User navigates to a new tab.
+
+
+ Because the code was also saved in local storage (step 6), it can be injected into the iframe across different tabs, resulting in a persistent session. See our [Demo Embedded Wallet](https://wallet.tx.xyz) for a [sample implementation](https://github.com/tkhq/demo-embedded-wallet/blob/942ccc97de7f9289892b1714b10f3a21afec71b3/src/providers/auth-provider.tsx#L150-L171), specifically dealing with sharing the iframeStamper across components.
+
+ ```js
+ const code = window.localStorage.getItem("BUNDLE");
+ await iframeStamper.injectCredentialBundle(code);
+ ```
+
+
+ Again, the user is authenticated, and able to initiate activities!
+
+
+ Just like step 8, the iframeStamper can be used to sign another activity.
+
+ ```js
+ const client = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ iframeStamper,
+ );
+
+ // Sign and submits a SIGN_TRANSACTION activity
+ const response = await client.signTransaction({
+ type: "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
+ timestampMs: String(Date.now()),
+ organizationId: authResponse.organizationId,
+ parameters: {
+ signWith: "0x...",
+ type: "TRANSACTION_TYPE_ETHEREUM",
+ unsignedTransaction: "unsigned-tx",
+ },
+ });
+ ```
+
+
+
+Congrats! You've succcessfully implemented Email Auth!
+
+## Integration notes
+
+### Email customization
+
+We offer customization for the following:
+
+* `appName`: the name of the application. This will be used in the email's subject, e.g. `Sign in to ${appName}`
+* `logoUrl`: a link to a PNG with a max width of 340px and max height of 124px
+* `magicLinkTemplate`: a template for the URL to be used in the magic link button, e.g. `https://dapp.xyz/%s`. The auth bundle will be interpolated into the `%s`
+
+```js
+// Sign and submits the EMAIL_AUTH activity
+const response = await client.emailAuth({
+ type: "ACTIVITY_TYPE_EMAIL_AUTH",
+ timestampMs: String(Date.now()),
+ organizationId: ,
+ parameters: {
+ email: ,
+ targetPublicKey: ,
+ apiKeyName: ,
+ expirationSeconds: ,
+ emailCustomization: {
+ appName: ,
+ logoUrl: ,
+ magicLinkTemplate:
+ }
+ },
+});
+```
+
+### Bespoke email templates
+
+We also support custom HTML email templates for [Enterprise](https://www.turnkey.com/pricing) clients. This allows you to inject arbitrary data from a JSON string containing key-value pairs. In this case, the `emailCustomization` variable may look like:
+
+```js
+...
+emailCustomization: {
+ templateId: ,
+ templateVariables: "{\"username\": \"alice and bob\"}"
+}
+...
+```
+
+In this specific example, the value `alice and bob` can be interpolated into the email template using the key `username`. The use of such template variables is purely optional.
+
+Here's an example of a custom HTML email containing an email auth bundle:
+
+
+
+
+
+If you are interested in implementing bespoke, fully-customized email templates, please reach out to [hello@turnkey.com](mailto:hello@turnkey.com).
+
+### Credential validity checks
+
+By default, if a Turnkey request is signed with an expired credential, the API will return a 401 error. If you'd like to validate an injected credential, you can specifically use the `whoami` endpoint:
+
+```js
+const whoamiResponse = await client.getWhoami({
+ organizationId,
+});
+```
+
+A valid response indicates the credential is still live; otherwise, an error including `unable to authenticate: api key expired` will be thrown.
diff --git a/embedded-wallets/sub-organization-recovery.mdx b/embedded-wallets/sub-organization-recovery.mdx
new file mode 100644
index 00000000..65328050
--- /dev/null
+++ b/embedded-wallets/sub-organization-recovery.mdx
@@ -0,0 +1,114 @@
+---
+title: "Email Recovery"
+description: "Email recovery shines if you are leveraging [sub-organizations](/concepts/sub-organizations) to create embedded wallets for your users. This allows your users to recover their Turnkey account if something goes wrong with their passkeys, and keeps you out of the loop: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to."
+---
+
+
+ Email Recovery is a legacy flow, now superseded by [Email Auth](/embedded-wallets/sub-organization-auth), which can used to implement recovery flows and more.
+
+
+A simple example demonstrating email recovery end-to-end can be found [here](https://github.com/tkhq/sdk/tree/main/examples/email-recovery).
+
+## Prerequisites
+
+Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email recovery on behalf of suborgs. Check out our [Quickstart guide](/getting-started/quickstart) if you need help getting started. To allow an API user to initiate email recovery, you'll need the following policy in your main organization:
+
+```json
+{
+ "effect": "EFFECT_ALLOW",
+ "consensus": "approvers.any(user, user.id == '')",
+ "condition": "activity.resource == 'RECOVERY' && activity.action == 'CREATE'"
+}
+```
+
+## Helper packages
+
+* We have released open-source code to create target encryption keys, decrypt recovery credentials, and sign Turnkey activities. We've deployed this a static HTML page hosted on `recovery.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the recovery credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
+* We have also built a package to help you insert this iframe and interact with it in the context of email recovery: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+
+In the rest of this guide we'll assume you are using these helpers.
+
+## Email Recovery step-by-step
+
+Here's a diagram summarizing the email recovery flow step-by-step ([direct link](/assets/files/email_recovery_steps-d43c6b808682671a8294b7f420efe2a5.png)):
+
+
+
+
+
+Let's review these steps in detail:
+
+
+ User on `yoursite.xyz` clicks "recovery", and a new recovery UI is shown. We recommend this recovery UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to complete recovery. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
+
+ ```js
+ const iframeStamper = new IframeStamper({
+ iframeUrl: "https://recovery.turnkey.com",
+ // Configure how the iframe element is inserted on the page
+ iframeContainerId: "your-container",
+ iframeElementId: "turnkey-iframe",
+ });
+
+ // Inserts the iframe in the DOM. This creates the new encryption target key
+ const publicKey = await iframeStamper.init();
+ ```
+
+
+ Your code receives the iframe public key and shows the recovery form, and the user enters their email address.
+
+
+ Your app can now create and sign a new `INIT_USER_EMAIL_RECOVERY` activity with the user email and the iframe public key in the parameters. Note: you'll need to retrieve the sub-organization ID based on the user email.
+
+
+
+ Email is received by the user.
+
+
+ User copies and pastes their recovery code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe.
+
+
+
+ Your app injects the recovery code into the iframe for decryption:
+
+ ```bash
+ await iframeStamper.injectCredentialBundle(code);
+ ```
+
+
+ Your app prompts the user to create a new passkey (using our SDK functionality):
+
+ ```js
+ // Creates a new passkey
+ let attestation = await getWebAuthnAttestation(...params...)
+ ```
+
+
+
+ Your app uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new `RECOVER_USER` activity:
+
+ ```js
+ // New client instantiated with our iframe stamper
+ const client = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ iframeStamper,
+ );
+
+ // Sign and submits the RECOVER_USER activity
+ const response = await client.recoverUser({
+ type: "ACTIVITY_TYPE_RECOVER_USER",
+ timestampMs: String(Date.now()),
+ organizationId: initRecoveryResponse.organizationId,
+ parameters: {
+ userId: initRecoveryResponse.userId,
+ authenticator: {
+ authenticatorName: data.authenticatorName,
+ challenge: base64UrlEncode(challenge),
+ attestation: attestation,
+ },
+ },
+ });
+ ```
+
+Once the `RECOVER_USER` activity is successfully posted, the recovery is complete! If this activity succeeds, your frontend can redirect to login/sign-in or perform crypto signing with the new passkey.
+
+
diff --git a/docs/solutions/embedded-wallets/sub-organizations-as-wallets.md b/embedded-wallets/sub-organizations-as-wallets.mdx
similarity index 92%
rename from docs/solutions/embedded-wallets/sub-organizations-as-wallets.md
rename to embedded-wallets/sub-organizations-as-wallets.mdx
index dd45a3b7..a9ffa2c2 100644
--- a/docs/solutions/embedded-wallets/sub-organizations-as-wallets.md
+++ b/embedded-wallets/sub-organizations-as-wallets.mdx
@@ -1,13 +1,8 @@
---
-sidebar_position: 2
-description: Use sub-organizations as end-user wallets
-slug: /embedded-wallets/sub-organizations-as-wallets
+title: "Integrating Embedded Wallets"
+description: "Turnkey has built a new model for private key management that utilizes secure enclaves. All transactions are signed within an enclave and private keys are never exposed to Turnkey, your software, or your team. Turnkey’s role is similar to that of a safety deposit box operator — Turnkey secures and provides access to the safety deposit boxes, but our system requires cryptographic proof of ownership to take any action with the keys held within."
---
-# Integrating Embedded Wallets
-
-Turnkey has built a new model for private key management that utilizes secure enclaves. All transactions are signed within an enclave and private keys are never exposed to Turnkey, your software, or your team. Turnkey’s role is similar to that of a safety deposit box operator — Turnkey secures and provides access to the safety deposit boxes, but our system requires cryptographic proof of ownership to take any action with the keys held within.
-
We've seen in [Sub-Organizations](/concepts/sub-organizations) that sub-organizations are independent from their parent. This guide walks through 3 ways to use sub-organizations as embedded wallets for your users. We first show that it can be used to create non-custodial wallets, or end-user controlled wallets. Then we explain how you can create custodial wallets or shared custody wallets using the same primitive.
## Sub-Organizations as end-user controlled wallets
@@ -28,7 +23,7 @@ After the end-user is logged in, your application prompts the user for passkey c
Your application then uses an API-only user to create a new sub-organization on behalf of the end-user with a `CREATE_SUB_ORGANIZATION` activity. In the example below, the new sub-organization has one root user controlled by the end user's passkey, and an associated Ethereum wallet:
-```json
+```js
{
"type": "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V4",
"timestampMs": "",
@@ -72,7 +67,7 @@ Your application then uses an API-only user to create a new sub-organization on
The response will contain the new sub-organization ID as well as details about its associated Ethereum wallet:
-```json
+```js
{
"subOrganizationId": "", // the organization_id that the end-user must use when signing requests
"wallet": {
@@ -107,7 +102,7 @@ const stamper = new WebAuthnStamper({
// New HTTP client able to sign with passkeys!
const httpClient = new TurnkeyClient(
{ baseUrl: "https://api.turnkey.com" },
- stamper
+ stamper,
);
// Signs and sends a request to Turnkey
@@ -133,7 +128,7 @@ In the snippet above we send the activity directly to Turnkey's backend. Our SDK
Next, we can derive additional accounts (addresses) given a single HD wallet. The shape of the request is as follows:
-```json
+```js
{
"type": "ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS",
"timestampMs": "",
@@ -156,7 +151,7 @@ Next, we can derive additional accounts (addresses) given a single HD wallet. Th
The end-user must provide a signature over each `SIGN_TRANSACTION` activity with their passkey. In your application, a user action (for example tapping a "Withdraw Rewards" button) might trigger the flow. The details of this transaction should be presented to the user for confirmation, followed by a passkey prompt to sign the Turnkey activity. An activity to sign a transaction looks like the following:
-```json
+```js
{
"type": "ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
"timestampMs": "",
@@ -219,9 +214,11 @@ Policies can be use to segregate permissions if needed: you could, for example,
For the sake of completeness: it is possible to create "shared custody" wallets with the sub-organization primitive. To do this, an application would setup sub-organizations with the following settings:
-- Root quorum threshold: 2
-- Root users:
- - 1 user representing the end-user (with their Passkey as an authenticator)
- - 1 user representing the business (with an API key attached)
+* Root quorum threshold: 2
+
+* Root users:
+
+ * 1 user representing the end-user (with their Passkey as an authenticator)
+ * 1 user representing the business (with an API key attached)
The signing process would then have to involve **both** the user and the business since the root quorum threshold is 2. To reduce friction for the end-user, many clients opt to start with a root quorum of 1. This way, you can take certain actions with your business's API key root user **prior** to updating the root quorum threshold to 2.
diff --git a/docs/documentation/FAQ.md b/faq.mdx
similarity index 65%
rename from docs/documentation/FAQ.md
rename to faq.mdx
index d947c9a7..4ddee231 100644
--- a/docs/documentation/FAQ.md
+++ b/faq.mdx
@@ -1,119 +1,138 @@
---
-sidebar_position: 100
-slug: /faq
+title: "FAQ"
---
-# FAQ
-
## Authentication and credentials
-### Can I sign up for Turnkey multiple times with the same email?
+
+
When you authenticate to the Turnkey dashboard, your email is used to lookup your organization and associated credentials. Currently we do not allow multiple users to be associated with the same email address.
-### Why do you require a public / private key pair to access Turnkey API?
+
+
Asymmetric cryptography offers various security benefits to you:
-- Turnkey _cannot_ leak your API private keys, even if compromised, because Turnkey only knows your API public keys.
-- Your API private key stays on the server you generated it for. This means there's a lower risk of key exfiltration compared to other methods where an API key, or API credentials in general, are generated in one place (web browser, company server), transported via a second (copy/paste, email, PDF document) and used in a third place (your server).
+* Turnkey *cannot* leak your API private keys, even if compromised, because Turnkey only knows your API public keys.
+* Your API private key stays on the server you generated it for. This means there's a lower risk of key exfiltration compared to other methods where an API key, or API credentials in general, are generated in one place (web browser, company server), transported via a second (copy/paste, email, PDF document) and used in a third place (your server).
-### Why do I need to sign the whole POST body?
+
+
Signing the whole payload is a way for Turnkey to know:
-- That you are in possession of your API key (because we can verify the signature you attach to requests).
-- That the person or program signing is approving the current request (not just any request).
+* That you are in possession of your API key (because we can verify the signature you attach to requests).
+* That the person or program signing is approving the current request (not just any request).
Concretely, Turnkey needs the following:
1. **The original request you sent**: this is achieved by simply receiving the HTTP request and its body
2. **That your API key was used to approve the request**: this is achieved by checking the signature contained in the `X-Stamp` header. For this verification we need the serialized POST body, your API public key, and the signature. This is all contained in the header value.
-3. **That the request is legitimate**: this is achieved by parsing the serialized request to make sure the intent is correct. This happens all the way down in our [Secure Enclaves](/Security/Secure-enclaves). For example, when you send a request to create a new Private Key, our policy engine parses your original request to independently derive the type of request, the payload to sign, etc. This guards against man-in-the-middle attacks.
+3. **That the request is legitimate**: this is achieved by parsing the serialized request to make sure the intent is correct. This happens all the way down in our [Secure Enclaves](/security/secure-enclaves). For example, when you send a request to create a new Private Key, our policy engine parses your original request to independently derive the type of request, the payload to sign, etc. This guards against man-in-the-middle attacks.
Turnkey would not be able to have its enclaves verify signatures and check the request intent if we didn't have your signature on the whole payload.
-### How is a Turnkey API key different from a crypto public / private key?
+
+
A Turnkey API key is simply a way to authenticate requests to Turnkey. Crypto assets are not tied to it in any way.
Think about Turnkey API keys as an access-gating mechanism to Turnkey functionality. They're flexible in what they can do (you get to decide this with [Policies](/concepts/policies/overview)!), and revocable if they are lost or compromised.
-### What happens if I lose my API key? Do I lose my crypto?
+
+
Losing your Turnkey API key doesn't mean you'll lose your crypto:
-- By default, your API key is not able to move funds
-- If you've changed policies so that your API key is allowed to unilaterally move funds, you may be at risk. Leverage the Turnkey UI to revoke your API key as soon as possible.
+* By default, your API key is not able to move funds
+* If you've changed policies so that your API key is allowed to unilaterally move funds, you may be at risk. Leverage the Turnkey UI to revoke your API key as soon as possible.
-Talk to our team () if you want to get in touch and talk more in-depth.
+Talk to our team ([hello@turnkey.com](mailto:hello@turnkey.com)) if you want to get in touch and talk more in-depth.
-### How long is a signed activity request valid for?
+
+
We require a recent timestamp in the `timestampMs` field for each new activity submission.
Our secure enclaves have their own, independent, secure source of time. We currently require request timestamps to be **less than an hour old**, and **up to 5 minutes in the future**.
-### Can I use my existing crypto private key as a Turnkey API key?
+
+
You can, but it doesn't mean you should. If you use your existing crypto private key as a turnkey API key, you are coupling Turnkey access with your crypto wallet. In essence, the risk profile of this key goes up. It's a bit like re-using passwords across many sites. Turnkey highly recommends creating a fresh public/private key pair if you need programmatic Turnkey access.
-### How can I safely rotate API key credentials?
+
+
While we don't have an off the shelf recipe, one potential approach is:
-- At sub-org creation, create your root user with 2+ API keys. One for day-to-day signing, and the other(s) securely stored.
-- If the day-to-day key is leaked, then you can use one of the secure, additional keys to remove it from all impacted sub-orgs via `ACTIVITY_TYPE_DELETE_API_KEYS`.
+* At sub-org creation, create your root user with 2+ API keys. One for day-to-day signing, and the other(s) securely stored.
+* If the day-to-day key is leaked, then you can use one of the secure, additional keys to remove it from all impacted sub-orgs via `ACTIVITY_TYPE_DELETE_API_KEYS`.
-Reach out to our team () for additional guidance.
+Reach out to our team ([hello@turnkey.com](mailto:hello@turnkey.com)) for additional guidance.
-## Limits
+
+
-### Are there limits on how many resources I can create, or activities I can execute?
+## Limits
+
+
See [resource limits](/concepts/resource-limits).
-### Do you have any rate limits in place in your public API?
+
+
Our public API currently limits users to a maximum of 60 RPS (Requests Per Second). Specific headers are returned to indicate current quota:
-- `ratelimit-limit`: indicates the total quota (60)
-- `ratelimit-remaining`: indicates the current quota
-- `x-rate-limit-request-forwarded-for` and `x-rate-limit-request-remote-addr`: echo back your remote IP and forwarded-for IP for debugging purposes
+* `ratelimit-limit`: indicates the total quota (60)
+* `ratelimit-remaining`: indicates the current quota
+* `x-rate-limit-request-forwarded-for` and `x-rate-limit-request-remote-addr`: echo back your remote IP and forwarded-for IP for debugging purposes
When rate limits are exceeded, an error with HTTP 429 is returned with the following message: `Too many requests. Please wait and try again in a few seconds`.
This limit is on a **per IP address** basis: if you have multiple servers making requests to the turnkey API under a different IP address, each server is subject to the 60 RPS limit individually.
-Please get in touch with us () if you need this limit adjusted for your use-case.
+Please get in touch with us ([help@turnkey.com](mailto:help@turnkey.com)) if you need this limit adjusted for your use-case.
-## Supported functionality
+
+
-### Does Turnkey support Ethereum (EVM)?
+## Supported functionality
+
+
Yes! See [Ethereum (EVM) support on Turnkey](/ecosystems/ethereum) for more information and concrete examples.
-### Does Turnkey support Solana (SVM)?
+
+
Yes! See [Solana (SVM) support on Turnkey](/ecosystems/solana) for more information and concrete examples.
-### Does Turnkey support Bitcoin?
+
+
Yes! See [Bitcoin support on Turnkey](/ecosystems/bitcoin) for more information and concrete examples.
-### Which cryptographic curves do you support?
+
+
Turnkey currently supports SECP256k1 and Ed25519.
-### Which ecosystems and chains do you support?
+
+
Turnkey's primitive for private keys and wallet is the **cryptographic curve** rather than specific cryptocurrencies. Our approach to supporting assets is tiered, see [this page](/ecosystems/framework) for more information.
We have deeper support for [Ethereum](/ecosystems/ethereum), [Solana](/ecosystems/solana), and [Bitcoin](/ecosystems/bitcoin).
-If there are specific ecosystems or chains you'd like to see us offer deeper support for, please let us know by contacting us at , on [X](https://x.com/turnkeyhq/), or [on Slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw).
+If there are specific ecosystems or chains you'd like to see us offer deeper support for,
+please let us know by contacting us at [hello@turnkey.com](mailto:hello@turnkey.com), on [X](https://x.com/turnkeyhq/),
+or [on Slack](https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ).
-### Do you support transaction construction and broadcast?
+
+
Turnkey does not offer native support for transaction construction and broadcast, instead we focus on transaction signing.
@@ -121,25 +140,34 @@ We suggest you use blockchain-specific libraries, like Ethers.js for Ethereum, t
You can use any blockchain node provider, like Infura or Alchemy, to broadcast your transactions.
-### What does `HASH_FUNCTION_NO_OP` mean?
+
+
+
In the ECDSA context, messages are hashed before signing. Turnkey can perform this hashing for you, as we support two hash functions: `HASH_FUNCTION_KECCAK256` and `HASH_FUNCTION_SHA256` (for Ethereum and Bitcoin ecosystems respectively). If your message had already been hashed, you should use the `HASH_FUNCTION_NO_OP` option to sign the raw hash, in which case Turnkey will sign the payload as is. `HASH_FUNCTION_NO_OP` also has privacy implications: if a raw hashed message is passed in, Turnkey has no knowledge of the underlying pre-image.
As an example, in our Viem package, the message is [hashed](https://github.com/tkhq/sdk/blob/673442f025990fde6a37436bed987b42e694a64d/packages/viem/src/index.ts#L201) before [signing](https://github.com/tkhq/sdk/blob/673442f025990fde6a37436bed987b42e694a64d/packages/viem/src/index.ts#L348).
-### What is `HASH_FUNCTION_NOT_APPLICABLE` and how does it differ from `HASH_FUNCTION_NO_OP`?
+
+
+
-Unlike ECDSA, in which a message is hashed as a separate step _before_ signing, when using Ed25519, hashing is performed _during_ signature computation, and thus cannot be skipped (for more details on the standard, see [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1): `“Ed25519 is EdDSA instantiated with: …H(x) = SHA-512"`). As a result, we have a special `HASH_FUNCTION_NOT_APPLICABLE` option for when you use ed25519/EdDSA.
+
+Unlike ECDSA, in which a message is hashed as a separate step *before* signing, when using Ed25519, hashing is performed *during* signature computation, and thus cannot be skipped (for more details on the standard, see [RFC 8032](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1): `“Ed25519 is EdDSA instantiated with: …H(x) = SHA-512"`). As a result, we have a special `HASH_FUNCTION_NOT_APPLICABLE` option for when you use ed25519/EdDSA.
An example for this case can be found in our [Solana signer](https://github.com/tkhq/sdk/blob/d9ed2aefc92d298826a40e821f959b019ea1936f/packages/solana/src/index.ts#L64).
-## Guidance
+
+
-### Do you have a status page?
+## Guidance
+
+
Yes, we report critical incidents at [turnkey-status.com](https://www.turnkey-status.com/).
-### How do you recommend testing the Turnkey API and functionality safely?
+
+
Typically we recommend that you create "test" organizations to test the API and functionality freely. When you are ready to go to production, use a "main" organization used for production only.
@@ -147,24 +175,34 @@ To do this you can use email aliases: if `firstname@domain.com` is your email, y
If you need many test organizations or if you have specific questions, our team is happy to help you get set up.
-### How do pricing and billing work?
+
+
Turnkey is priced per signature, i.e. any transaction or raw payload successfully signed by a private key created on Turnkey. Turnkey offers 25 free signatures each month. To execute more than 25 transactions in a given month, you are required to have a credit card on file or active enterprise plan on your account. To upgrade your plan, navigate to Account Settings from the menu in the top right-hand corner in the Turnkey dashboard and follow the instructions.
For more information about pricing and billing, check out the [pricing page](https://www.turnkey.com/pricing).
-### Where else can I get help with my Turnkey implementation?
+
+
-Join our slack community [here](https://join.slack.com/t/clubturnkey/shared_invite/zt-2837d2isy-gbH60kJ~XnXSSFHiqVOrqw) to get support with your integration, share product feedback, and connect with other crypto builders. Or, reach out directly to help@turnkey.com. Teams that are looking for more in-depth integration support can upgrade to an Enterprise plan via hello@turnkey.com.
-### What is your data deletion policy?
+Join our slack community [here](https://join.slack.com/t/clubturnkey/shared_invite/zt-31v4yhgw6-PwBzyNsWCCBTk2xft3EoHQ) to get support with your integration, share product feedback, and connect with other crypto builders. Or, reach out directly to [help@turnkey.com.](mailto:help@turnkey.com.) Teams that are looking for more in-depth integration support can upgrade to an Enterprise plan via [hello@turnkey.com](mailto:hello@turnkey.com).
-To request the deletion of your parent organization from our records, please email us at privacy@turnkey.com.
+
+
-### Is my country supported?
+To request the deletion of your parent organization from our records, please email us at [privacy@turnkey.com](mailto:privacy@turnkey.com).
+
+
+
Turnkey is not currently available to users in any countries currently subject to US OFAC sanctions.
-### Where can I learn more about Turnkey's internal architecture?
+
+
Check out our whitepaper, available at [whitepaper.turnkey.com](https://whitepaper.turnkey.com)! It covers our foundations and architecture in great detail.
+
+
+
+
diff --git a/favicon-light.png b/favicon-light.png
new file mode 100644
index 00000000..fb7b686c
Binary files /dev/null and b/favicon-light.png differ
diff --git a/favicon.svg b/favicon.svg
new file mode 100644
index 00000000..67d17f56
--- /dev/null
+++ b/favicon.svg
@@ -0,0 +1 @@
+
diff --git a/getting-started.mdx b/getting-started.mdx
new file mode 100644
index 00000000..2e9d8832
--- /dev/null
+++ b/getting-started.mdx
@@ -0,0 +1,20 @@
+---
+title: Getting Started
+description: Get started with Turnkey
+mode: wide
+sidebarTitle: Overview
+---
+
+
+
+ 2 items
+
+
+
+ Check out some of our example apps and use cases
+
+
+
+ A checklist to help you get your app to production!
+
+
diff --git a/getting-started/embedded-wallet-quickstart.mdx b/getting-started/embedded-wallet-quickstart.mdx
new file mode 100644
index 00000000..8d033cb4
--- /dev/null
+++ b/getting-started/embedded-wallet-quickstart.mdx
@@ -0,0 +1,506 @@
+---
+title: "Embedded Wallets Quickstart"
+description: "Turnkey's Embedded Wallets enable you to integrate secure, custom wallet experiences directly into your product. With features like advanced security, seamless authentication, and flexible UX options, you can focus on building great products while we handle the complexities of private key management."
+sidebarTitle: "Embedded Wallets"
+---
+
+## Prerequisites
+
+This guide assumes you've completed the steps to create an account, organization, and API keypair as described in the [Quickstart](/getting-started/quickstart) section.
+
+## Installation
+
+Create a new Next.js app via `npx create-next-app@latest`. Or install into an existing project.
+
+
+
+```bash npm
+npm install @turnkey/sdk-react
+```
+
+```bash pnpm
+pnpm add @turnkey/sdk-react
+```
+
+```bash yarn
+yarn add @turnkey/sdk-react
+```
+
+
+ **React 19 Users**
+
+ If you're using Next.js 15 with React 19 you may encounter an installation error with `@turnkey/sdk-react`. Consider:
+
+ * Downgrading React to `18.x.x`
+ * Using `npm install --force` or `--legacy-peer-deps`
+
+ You may learn more about this [here](https://ui.shadcn.com/docs/react-19).
+
+
+## Setup
+
+
+
+ The following environment variables are necessary to use the Turnkey SDK.
+
+ ```bash .env
+ NEXT_PUBLIC_ORGANIZATION_ID=
+ TURNKEY_API_PUBLIC_KEY=
+ TURNKEY_API_PRIVATE_KEY=
+ NEXT_PUBLIC_BASE_URL=https://api.turnkey.com
+ ```
+
+
+
+ Fill in with your Organization ID and API Base URL.
+
+
+
+ ```tsx src/app/layout.tsx
+ const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID,
+ };
+ ```
+
+
+
+ Wrap your layout with the `TurnkeyProvider` component, and import styles from sdk-react.
+
+ ```tsx src/app/layout.tsx
+ import { TurnkeyProvider } from "@turnkey/sdk-react";
+ import "@turnkey/sdk-react/styles"; // required to render auth component styles properly
+
+ export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ ```
+
+ **React 19 Users**
+
+ `@turnkey/sdk-react` is built with React 18. If you're using React 19 you'll find a type mismatch on the children type.
+
+ To fix this, you can use the `@ts-ignore` directive to suppress the error.
+
+ src/app/layout.tsx
+
+ ```
+ {/* @ts-ignore */} {children}
+ ```
+
+ We're actively working towards React 19 compatibility.
+
+
+
+
+
+## Authenticate
+
+
+
+The auth component contains the UI and logic to handle the authentication flow.
+
+
+ For simplicity, this app will only support email authentication. We have other guides on additional authentication methods. Additionally, you can customize the order in which the auth methods are displayed.
+
+ ```tsx src/app/page.tsx
+ "use client";
+
+ export default function Home() {
+ // The auth methods to display in the UI
+ const config = {
+ authConfig: {
+ emailEnabled: true,
+ // Set the rest to false to disable them
+ passkeyEnabled: false,
+ phoneEnabled: false,
+ appleEnabled: false,
+ facebookEnabled: false,
+ googleEnabled: false,
+ },
+ // The order of the auth methods to display in the UI
+ configOrder: ["email" /* "passkey", "phone", "socials" */],
+ };
+
+ return ;
+ }
+ ```
+
+ ```
+ type AuthConfig = {
+ emailEnabled: boolean;
+ passkeyEnabled: boolean;
+ phoneEnabled: boolean;
+ appleEnabled: boolean;
+ googleEnabled: boolean;
+ facebookEnabled: boolean;
+ };
+ ```
+
+
+
+
+ Import the auth component into your app and pass in the config object.
+
+ ```tsx src/app/page.tsx
+ "use client";
+
+ import { Auth } from "@turnkey/sdk-react";
+
+ export default function Home() {
+ const config = {
+ authConfig: {
+ emailEnabled: true,
+ passkeyEnabled: false,
+ phoneEnabled: false,
+ appleEnabled: false,
+ facebookEnabled: false,
+ googleEnabled: false,
+ },
+ configOrder: ["email"],
+ };
+
+ return (
+
+
+
+ );
+ }
+ ```
+
+
+
+ Define two functions to handle the "success" and "error" states. Initially, the `onError` function will set an `errorMessage` state variable which will be used to display an error message to the user. The `onAuthSuccess` function will route the user to the dashboard after successful authentication.
+
+ A new [sub-organization](/concepts/sub-organizations) and [wallet](/concepts/wallets) is created for each new user during the authentication flow.
+
+ ```tsx src/app/page.tsx
+ "use client";
+
+ import { useState } from "react";
+ import { Auth } from "@turnkey/sdk-react";
+
+ export default function Home() {
+ const [errorMessage, setErrorMessage] = useState("");
+ const router = useRouter();
+
+ const onAuthSuccess = async () => {
+ // We'll add the dashboard route in the next step
+ router.push("/dashboard");
+ };
+
+ const onError = (errorMessage: string) => {
+ setErrorMessage(errorMessage);
+ };
+
+ // Add the handlers to the config object
+ const config = {
+ // ...
+ onAuthSuccess: onAuthSuccess,
+ onError: onError,
+ };
+
+ return (
+
+
+
+ );
+ }
+ ```
+
+
+ Add a dashboard route to the app where the user will be able to view their account and sign messages.
+
+ ```tsx src/app/dashboard/page.tsx
+ export default function Dashboard() {
+ return
Dashboard
;
+ }
+ ```
+
+Since the app is wrapped with the `TurnkeyProvider` component, the `useTurnkey` hook is available to all child components. Calling `turnkey.getCurrentUser()` will return the current user's session information from local storage.
+
+Add a state variable to store the user:
+
+```tsx src/app/dashboard/page.tsx
+import { useState, useEffect } from "react";
+import { useTurnkey } from "@turnkey/sdk-react";
+
+export default function Dashboard() {
+ const { turnkey } = useTurnkey();
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ if (turnkey) {
+ const user = turnkey.getCurrentUser();
+ setUser(user);
+ }
+ }, [turnkey]);
+
+ return
Dashboard
;
+}
+```
+
+
+ ```
+ export interface User {
+ // Unique identifier for the user.
+ userId: string;
+ // Username of the user.
+ username: string;
+ organization: {
+ // Unique identifier for the organization.
+ organizationId: string;
+ // Name of the organization.
+ organizationName: string;
+ };
+ session:
+ | {
+ // read-only session .
+ read?: ReadOnlySession;
+ // read-write session details.
+ write?: ReadWriteSession;
+ // Authenticated client associated with the session.
+ authClient: AuthClient;
+ }
+ | undefined;
+ }
+
+ export interface ReadOnlySession {
+ // Read-only session token for `X-Session` header
+ token: string;
+ // Expiry time in seconds since Unix epoch.
+ expiry: number;
+ }
+
+ export interface ReadWriteSession {
+ // Credential bundle for iFrame client, generated by `createReadWriteSession` or `createApiKeys`.
+ credentialBundle: string;
+ // Expiry time in seconds since Unix epoch.
+ expiry: number;
+ }
+ ```
+
+
+
+
+## Sign Message
+
+Turnkey supports signing arbitrary messages with the [`signRawPayload`](/api-reference/signing/sign-raw-payload) method.
+
+The `signRawPayload` method requires these parameters:
+
+* `payload`: The raw unsigned payload to sign
+* `signWith`: The signing address (wallet account, private key address, or private key ID)
+* `encoding`: The message encoding format
+* `hashFunction`: The selected hash algorithm
+
+
+ For simplicity, a human readable string, `message`, will be the payload to sign. Add a state variable to store the message and an input field to allow the user to enter the message:
+
+```tsx src/app/dashboard/page.tsx
+import { useState, useEffect } from "react";
+
+export default function Dashboard() {
+ //...
+
+ const [message, setMessage] = useState("");
+
+ //...
+
+ return (
+
+ setMessage(e.target.value)}
+ placeholder="Enter message to sign"
+ />
+
+ );
+}
+```
+
+
+ Signing messages requires a signer e.g. a Turnkey wallet address to sign with and a payload or message to sign. A new wallet is created for each user during the authentication flow.
+
+ Create a function called `getSignWith`, to get the user's wallet account address which will be used to sign the message.
+
+Use the `getActiveClient` method from the `useTurnkey` hook to get the client authenticated with the user's read-write session:
+
+```tsx src/app/dashboard/page.tsx
+import { useState, useEffect } from "react";
+import { useTurnkey } from "@turnkey/sdk-react";
+
+export default function Dashboard() {
+ const { turnkey, getActiveClient } = useTurnkey();
+ const [user, setUser] = useState(null);
+
+ const getSignWith = async () => {
+ // This will return the authIframeClient with the credential bundle injected
+ const client = await getActiveClient();
+
+ // The user's sub-organization id
+ const organizationId = user?.organization.organizationId;
+
+ // Get the user's wallets
+ const wallets = await client?.getWallets({
+ organizationId,
+ });
+
+ // Get the first wallet of the user
+ const walletId = wallets?.wallets[0].walletId ?? "";
+
+ // Use the `walletId` to get the accounts associated with the wallet
+ const accounts = await client?.getWalletAccounts({
+ organizationId,
+ walletId,
+ });
+
+ const signWith = accounts?.accounts[0].address ?? "";
+
+ return signWith;
+ };
+
+ useEffect(/* ... */*/);
+
+ return (/*
...
*/*/);
+}
+```
+
+
+
+Create a function called `signMessage`. This function will:
+
+* Get the user's wallet account for signing the message
+* Compute the keccak256 hash of the message
+* Call the `signRawPayload` method
+
+Note: To compute the `keccak256` hash of the message, this example uses the `hashMessage` function from `viem`. However, any other hashing library can be used.
+
+```tsx
+const signMessage = async () => {
+ const payload = await hashMessage(message);
+ const signWith = await getSignWith();
+
+ const signature = await client?.signRawPayload({
+ payload,
+ signWith,
+ // The message encoding format
+ encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
+ // The hash function used to hash the message
+ hashFunction: "HASH_FUNCTION_KECCAK256",
+ });
+};
+```
+
+
+
+Add a button to the UI to trigger the `signMessage` function.
+
+```tsx src/app/dashboard/page.tsx
+import { useState, useEffect } from "react";
+import { useTurnkey } from "@turnkey/sdk-react";
+import { hashMessage } from "viem";
+
+export default function Dashboard() {
+ //...
+
+ const [message, setMessage] = useState("");
+
+ const signMessage = async () => {
+ const payload = await hashMessage(message);
+ const signWith = await getSignWith();
+
+ const signature = await client?.signRawPayload({
+ payload,
+ signWith,
+ // The message encoding format
+ encoding: "PAYLOAD_ENCODING_TEXT_UTF8",
+ // The hash function used to hash the message
+ hashFunction: "HASH_FUNCTION_KECCAK256",
+ });
+ };
+
+ return (
+
+ );
+}
+ ```
+
+
+## Next Steps
+
+
+
+ Check out our examples.
+
+
diff --git a/docs/documentation/getting-started/examples.md b/getting-started/examples.mdx
similarity index 75%
rename from docs/documentation/getting-started/examples.md
rename to getting-started/examples.mdx
index ec89eaff..8d4c707a 100644
--- a/docs/documentation/getting-started/examples.md
+++ b/getting-started/examples.mdx
@@ -1,13 +1,8 @@
---
-sidebar_position: 4
-description: Check out some of our example apps and use cases
-slug: /getting-started/examples
+title: "Examples"
+description: "Turnkey infrastructure is flexible by default. We intentionally prioritize low-level primitives in our product to avoid creating blockers for developers building new kinds of applications on Turnkey."
---
-# Examples
-
-Turnkey infrastructure is flexible by default. We intentionally prioritize low-level primitives in our product to avoid creating blockers for developers building new kinds of applications on Turnkey.
-
That said, we have built out several example services and applications to help illustrate the types of functionality that Turnkey can enable.
## Code Examples
@@ -43,107 +38,85 @@ That said, we have built out several example services and applications to help i
A comprehensive demo showcasing how to build an embedded wallet using Turnkey. This demo uses the [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser), [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) and [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server) packages and includes features such as:
-- User authentication with passkeys, email auth, and OAuth
-- Creating new wallets and wallet accounts
-- Sending and receiving funds
-- Importing/Exporting a wallet
-- Adding a credential to the wallet
-
-
-
-
-
-
-See https://github.com/tkhq/demo-embedded-wallet for the code.
+* User authentication with passkeys, email auth, and OAuth
+* Creating new wallets and wallet accounts
+* Sending and receiving funds
+* Importing/Exporting a wallet
+* Adding a credential to the wallet
+
+
+
+
+
+
+
+
+
+
+
+See [https://github.com/tkhq/demo-embedded-wallet](https://github.com/tkhq/demo-embedded-wallet) for the code.
### Demo Consumer Wallet ([code](https://github.com/tkhq/demo-consumer-wallet))
A minimal consumer wallet app powered by Turnkey. Behind the scenes, it uses [`@turnkey/ethers`](https://www.npmjs.com/package/@turnkey/ethers) for signing and WalletConnect (v1) for accessing dapps.
-
-
-
+
+
+
+
-See https://github.com/tkhq/demo-consumer-wallet for the code.
+See [https://github.com/tkhq/demo-consumer-wallet](https://github.com/tkhq/demo-consumer-wallet) for the code.
### Demo Embedded Wallet ([code](https://github.com/tkhq/demo-embedded-wallet), [live link](https://wallet.tx.xyz))
-A wallet application showing how users can register and authenticate using passkeys.
-This demo uses the Turnkey API to create a new [Turnkey Sub-Organization](/concepts/sub-organizations) for each user, create a testnet Ethereum address and send a transaction on Sepolia (ETH testnet).
+A wallet application showing how users can register and authenticate using passkeys. This demo uses the Turnkey API to create a new [Turnkey Sub-Organization](/concepts/sub-organizations) for each user, create a testnet Ethereum address and send a transaction on Sepolia (ETH testnet).
-
-
-
+
+
+
-See https://wallet.tx.xyz (and https://github.com/tkhq/demo-embedded-wallet for the code).
+See [https://wallet.tx.xyz](https://wallet.tx.xyz) (and [https://github.com/tkhq/demo-embedded-wallet](https://github.com/tkhq/demo-embedded-wallet) for the code).
### Demo Ethers Passkeys ([code](https://github.com/tkhq/demo-ethers-passkeys))
A simple application demonstrating how to create sub-organizations, create private keys, and sign with the [`@turnkey/ethers`](https://github.com/tkhq/sdk/tree/main/packages/ethers) signer, using passkeys.
-
-
-
+
+
+
-See https://github.com/tkhq/demo-ethers-passkeys for the code.
+See [https://github.com/tkhq/demo-ethers-passkeys](https://github.com/tkhq/demo-ethers-passkeys) for the code.
### Demo Viem Passkeys ([code](https://github.com/tkhq/demo-viem-passkeys))
A similar, simple application demonstrating how to create sub-organizations, create private keys, and sign with the [`@turnkey/viem`](https://github.com/tkhq/sdk/tree/main/packages/viem) signer, using passkeys.
-
-
-
+
+
+
-See https://github.com/tkhq/demo-viem-passkeys for the code.
+See [https://github.com/tkhq/demo-viem-passkeys](https://github.com/tkhq/demo-viem-passkeys) for the code.
### Demo Viem Passkeys with Gelato Relay ([code](https://github.com/gelatodigital/gelato-turnkey-passkeys-relay))
This example demonstrates how to leverage Turnkey’s secure key management and Gelato's battle-tested relay infrastructure to enable seamless, sponsored interactions with meta-transactions using the [`@turnkey/viem`](https://github.com/tkhq/sdk/tree/main/packages/viem) signer and [`@gelatonetwork/relay-sdk-viem`](https://github.com/gelatodigital/relay-sdk-viem).
-
-
-
+
+
+
#### How Infinex Leverages Turnkey and Gelato
Infinex, a platform designed to unify the decentralized ecosystem and applications under a single UX layer, eliminates the complexities of navigating fragmented crypto protocols. By integrating **Turnkey** and **Gelato**, Infinex delivers a seamless, secure, and cost-efficient experience for decentralized finance users.
-- **Secure Key Management with Turnkey**: Infinex ensures private keys are securely managed within Turnkey’s infrastructure, removing the need for traditional wallet pop-ups. This approach streamlines authentication through passkeys, offering a frictionless and secure user experience.
+* **Secure Key Management with Turnkey**: Infinex ensures private keys are securely managed within Turnkey’s infrastructure, removing the need for traditional wallet pop-ups. This approach streamlines authentication through passkeys, offering a frictionless and secure user experience.
-- **Gasless Transactions with Gelato**: Leveraging Gelato’s Relay (ERC-2771), Infinex enables fully **sponsored transactions**, allowing users to interact with decentralized applications without ever paying gas fees. This enhances accessibility and usability, ensuring that users can participate without holding or managing native blockchain tokens for fees.
+* **Gasless Transactions with Gelato**: Leveraging Gelato’s Relay (ERC-2771), Infinex enables fully **sponsored transactions**, allowing users to interact with decentralized applications without ever paying gas fees. This enhances accessibility and usability, ensuring that users can participate without holding or managing native blockchain tokens for fees.
The synergy between Turnkey and Gelato allows Infinex to offer an intuitive, cost-free user experience while maintaining the highest standards of security and scalability.
@@ -151,22 +124,26 @@ The synergy between Turnkey and Gelato allows Infinex to offer an intuitive, cos
A React Native app that demonstrates how to use the Turnkey's JavaScript packages in a mobile environment to authenticate users, create wallets, export wallets, sign messages, and more
-
-
-
-
-See https://github.com/tkhq/react-native-demo-wallet for the code.
+
+
+
+See [https://github.com/tkhq/react-native-demo-wallet](https://github.com/tkhq/react-native-demo-wallet) for the code.
### Flutter Demo App ([code](https://github.com/tkhq/dart-sdk/tree/main/examples/flutter-demo-app))
A Flutter app that demonstrates how to use the Turnkey's Flutter packages to authenticate users, create wallets, export wallets, sign messages, and more
-
+ >
+
#### Complete the installation and setup by following the instructions in the [README](https://github.com/tkhq/dart-sdk/blob/main/examples/flutter-demo-app/README.md) file.
diff --git a/sdks/golang.mdx b/sdks/golang.mdx
new file mode 100644
index 00000000..64792fe8
--- /dev/null
+++ b/sdks/golang.mdx
@@ -0,0 +1,6 @@
+---
+title: "Golang"
+description: "Turnkey offers native tooling for interacting with the API using Golang. See [https://github.com/tkhq/go-sdk](https://github.com/tkhq/go-sdk) for more details."
+mode: wide
+---
+
diff --git a/sdks/introduction.mdx b/sdks/introduction.mdx
new file mode 100644
index 00000000..e6ec3966
--- /dev/null
+++ b/sdks/introduction.mdx
@@ -0,0 +1,40 @@
+---
+title: "SDK Reference"
+description: "Turnkey provides a variety of client and server SDKs which simplify interacting with Turnkey's API. The SDKs offer methods, utilities, and helper functions to quickly implement features and perform common workflows."
+sidebarTitle: "Introduction"
+---
+
+The following SDK reference tables separate our SDKs by Client and Server. The column headers indicate the specific languages or frameworks for which we have an SDK. The rows indicate a specific feature or capability that Turnkey provides.
+
+A green checkmark in the table indicates that the SDK provides either a complete implementation for a feature or helper methods exist and can be composed in a few lines of code to implement a workflow. If no checkmark is present it means the SDK does not offer support for that feature.
+
+Turnkey also has several [wrappers for popular web3 libraries](/category/web3-libraries) to streamline integration into existing dApps.
+
+## Client Side SDKs
+| | TypeScript | React | React Native | Flutter | iOS |
+|-------------------------|------------|--------|--------------|---------|------|
+| **Authentication** | | | | | |
+| Email | | | | | |
+| SMS | | | | | |
+| Passkey | | | | | |
+| Google | | | | | |
+| Facebook | | | | | |
+| Apple | | | | | |
+| Web3 Wallets | | | | |
+| **Embedded Wallets** | | | | | |
+| Wallet Creation | | | | | |
+| Signing | | | | | |
+| Import | | | | | |
+| Export | | | | | |
+| **Arbitrary Request Signing** | | | | | |
+| Stamping | | | | | |
+
+
+## Server Side SDKs
+
+| | TypeScript | Go | Ruby | Rust | Python |
+|---------------------|------------|-----|------|------|--------|
+| **Authentication** | | | | | |
+| **Wallet Management** | | | | | |
+| **Policy Management** | | | | | |
+
diff --git a/docs/sdks/javascript-browser.mdx b/sdks/javascript-browser.mdx
similarity index 68%
rename from docs/sdks/javascript-browser.mdx
rename to sdks/javascript-browser.mdx
index 55f2a7b1..bbad6a09 100644
--- a/docs/sdks/javascript-browser.mdx
+++ b/sdks/javascript-browser.mdx
@@ -1,45 +1,32 @@
---
title: "JavaScript Browser"
-sidebar_position: 2
-description: JavaScript Browser SDK
-slug: /sdks/javascript-browser
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
## Overview
The [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser) package exposes functionality that lets developers build browser based applications that interact with the Turnkey API with different types of authentication.
It consists of the `passkeyClient`, `iframeClient` and `walletClient` that enable requests to the API to be authenticated via different auth methods. It also contains methods to manage information and state related to authentication like auth bundles and sessions, retrieving user information and server signing API requests.
-If you are working with React – check out our [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) package.
+If you are working with React - check out our [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) package.
## Installation
-
-
+
-```bash
+```bash NPM
npm install @turnkey/sdk-browser
```
-
-
-
-```bash
+```bash Yarn
yarn add @turnkey/sdk-browser
```
-
-
-
+
## Initializing
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const turnkey = new Turnkey({
apiBaseUrl: "https://api.turnkey.com",
@@ -49,80 +36,35 @@ const turnkey = new Turnkey({
#### Parameters
-
+
An object containing configuration settings for the Browser Client.
-
+
-
+
The root organization that requests will be made from unless otherwise specified
-
-
-
-
-The base URL that API requests will be sent to (use https://api.turnkey.com when making requests to Turnkey's API)
-
-
-
-
+
+
+
+
+The base URL that API requests will be sent to (use [https://api.turnkey.com](https://api.turnkey.com) when making requests to Turnkey's API)
+
+
+
+
The Relying Party ID used for WebAuthn flows (will default to the value returned from `window.location.hostname` unless otherwise specified)
-
+
-
+
The URL to send requests that need to be signed from a backend codebase by the root organization's API key if using the `serverSign` flow.
-
-
+
Calls to Turnkey's API must be signed with a valid credential from the appropriate user and, from a browser client, can either be sent directly to Turnkey or proxied through a server. Turnkey's Browser SDK contains the following different clients that manage the process of validating these requests depending on the kind of authentication credential that is being used.
## TurnkeyBrowserClient
@@ -135,7 +77,7 @@ Below are all of the methods exposed by `TurnkeyBrowserClient`
Creates a read-only session for the current user, storing session details like userId, organizationId, sessionExpiry and which authentication client was used in local storage. This session allows for read-only actions within the Turnkey API. If you would like to instantiate a read only `TurnkeyBrowserClient` after logging in, you can use the [`currentUserSession()`](#currentusersession) method.
-```typescript
+```js
import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
const config = {
@@ -150,11 +92,215 @@ const browserClient = new TurnkeyBrowserClient(config);
const readOnlySession = await browserClient.login({ organizationId: "org-id" });
```
+### `loginWithBundle()`
+
+Authenticate a user via the credential bundle emailed to them and creates a read-write session.
+
+```js
+import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
+
+const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+};
+
+// Create a client instance
+const browserClient = new TurnkeyBrowserClient(config);
+
+// Authenticate with a credential bundle from email
+const session = await browserClient.loginWithBundle({
+ bundle: "credential-bundle-from-email",
+ expirationSeconds: "900", // 15 minutes
+});
+```
+
+#### Parameters
+
+
+ An object containing the parameters to authenticate via a credential bundle.
+
+
+
+ The credential bundle string emailed to the user.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+
+ The public key of the target user. This will be inferred from the
+ `TurnkeyIframeClient` if `targetPublicKey` is not provided.
+
+
+### `loginWithPasskey()`
+
+Authenticate a user via Passkey and create a read-only or read-write session.
+
+```js
+import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
+
+const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+};
+
+// Create a client instance
+const browserClient = new TurnkeyBrowserClient(config);
+const iframeClient = await turnkeySDK.iframeClient();
+
+// Authenticate with a passkey to create a read-write session
+const session = await browserClient.loginWithPasskey({
+ sessionType: "READ_WRITE",
+ iframeClient: iframeClient,
+ expirationSeconds: "900", // 15 minutes
+});
+```
+
+#### Parameters
+
+
+ An object containing the parameters to authenticate via a Passkey.
+
+
+
+ The type of session to be created. Either read-only or read-write.
+
+
+
+ An instance of a `TurnkeyIframeClient`.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+
+ The public key of the target user. This will be inferred from the
+ `TurnkeyIframeClient` if `targetPublicKey` is not provided.
+
+
+### `loginWithSession()`
+
+Log in with a session object created via a server action. The session can be either read-only or read-write.
+
+```js
+import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
+
+const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+};
+
+// Create a client instance
+const browserClient = new TurnkeyBrowserClient(config);
+
+// Login with a session created by a server
+const loggedIn = await browserClient.loginWithSession(serverCreatedSession);
+```
+
+#### Parameters
+
+
+ An existing session to authenticate the user with.
+
+
+### `loginWithWallet()`
+
+Login with an existing wallet e.g. Metamask.
+
+```js
+import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
+
+const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+};
+
+// Create a client instance
+const browserClient = new TurnkeyBrowserClient(config);
+const iframeClient = await turnkeySDK.iframeClient();
+
+// Login with a wallet to create a read-write session
+const session = await browserClient.loginWithWallet({
+ sessionType: "READ_WRITE",
+ iframeClient: iframeClient,
+ expirationSeconds: "900", // 15 minutes
+});
+```
+
+#### Parameters
+
+
+ An object containing the parameters to authenticate via a browser wallet.
+
+
+
+ The type of session to be created. Either read-only or read-write.
+
+
+
+ An instance of a `TurnkeyIframeClient`.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+
+ The public key of the target user. This will be inferred from the
+ `TurnkeyIframeClient` if `targetPublicKey` is not provided.
+
+
+### `refreshSession()`
+
+Attempts to refresh an existing, active session and will extend the session expiry using the `expirationSeconds` parameter.
+
+```js
+import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
+
+const config = {
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+};
+
+// Create a client instance
+const browserClient = new TurnkeyBrowserClient(config);
+
+// Refresh the current session
+const refreshedSession = await browserClient.refreshSession({
+ sessionType: "READ_WRITE",
+ expirationSeconds: "900", // 15 minutes
+});
+```
+
+#### Parameters
+
+
+ An object containing the `RefreshSessionParams`.
+
+
+
+ The type of `Session` that is being refreshed.
+
+
+
+ Specify how long to extend the session. Defaults to 900 seconds or 15 minutes.
+
+
+
+ The public key of the target user. This will be inferred from the
+ `TurnkeyIframeClient` if `targetPublicKey` is not provided.
+
+
### `loginWithReadWriteSession()`
Creates a read-write session. This method infers the current user's organization ID and target userId. To be used in conjunction with an `iframeStamper`: the resulting session's credential bundle can be injected into an iframeStamper to create a session that enables both read and write requests.
-```typescript
+```js
import { TurnkeyBrowserClient } from "@turnkey/sdk-browser";
const config = {
@@ -175,7 +321,7 @@ const readWriteSession = await browserClient.loginWithReadWriteSession(
## TurnkeyPasskeyClient
-The `TurnkeyPasskeyClient` class extends `TurnkeyBrowserClient` and specializes it for user authentication through passkeys, which leverage the WebAuthn standard for passwordless authentication. This class enables the implementation of strong, user-friendly authentication experiences in a web-based application without relying on passwords. TurnkeyPasskeyClient handles passkey creation, session management with passkeys and integrates with WebAuthn and Embedded API Keys.
+The `TurnkeyPasskeyClient` class extends `TurnkeyBrowserClient` and specializes it for user authentication through Passkeys, which leverage the WebAuthn standard for passwordless authentication. This class enables the implementation of strong, user-friendly authentication experiences in a web-based application without relying on passwords. TurnkeyPasskeyClient handles Passkey creation, session management with Passkeys and integrates with WebAuthn and Embedded API Keys.
To see how to instantiate the `TurnkeyPasskeyClient`, [look here](#passkeyclient)
@@ -185,7 +331,7 @@ Below are the methods exposed by the `TurnkeyPasskeyClient`
Creates a passkey for an end-user, handling lower-level configurations for the WebAuthn protocol, including challenges and user details. For more detailed examples using this method [look here](/embedded-wallets/code-examples/add-credential).
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -210,9 +356,9 @@ const passkey = await passkeyClient.createUserPasskey({
### `createPasskeySession()`
-Uses passkey authentication to create a read-write session, via an embedded API key, and stores + returns the resulting auth bundle that contains the encrypted API key. This auth bundle (also referred to as a credential bundle) can be injected into an iframeStamper, resulting in a touch-free authenticator. Unlike `loginWithReadWriteSession`, this method assumes the end-user's organization ID (i.e. the sub-organization ID) is already known.
+Uses Passkey authentication to create a read-write session, via an embedded API key, and stores + returns the resulting auth bundle that contains the encrypted API key. This auth bundle (also referred to as a credential bundle) can be injected into an iframeStamper, resulting in a touch-free authenticator. Unlike `loginWithReadWriteSession`, this method assumes the end-user's organization ID (i.e. the sub-organization ID) is already known.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -237,7 +383,7 @@ const session = await passkeyClient.createPasskeySession(
## TurnkeyIframeClient
-The `TurnkeyIframeClient` class extends `TurnkeyBrowserClient` such that it is specialized for use with an iframe-based session. Our iFrame stamping implementation leverages the postMessage communication mechanism to send and receive messages within the iframe, ensuring the credential does not leave its secure environment. This approach is particularly crucial in sensitive flows such as Email Auth, and Key or Wallet Export, where heightened security is required. For further information on our iframe stamping process, checkout our [iframeStamper package documentation](./advanced/iframe-stamper).
+The `TurnkeyIframeClient` class extends `TurnkeyBrowserClient` such that it is specialized for use with an iframe-based session. Our iFrame stamping implementation leverages the postMessage communication mechanism to send and receive messages within the iframe, ensuring the credential does not leave its secure environment. This approach is particularly crucial in sensitive flows such as Email Auth, and Key or Wallet Export, where heightened security is required. For further information on our iframe stamping process, checkout our [iframeStamper package documentation](/sdks/advanced/iframe-stamper).
To see how to instantiate the `TurnkeyIframeClient`, [look here](#iframeclient).
@@ -247,7 +393,7 @@ Here are all of the methods exposed by `TurnkeyIframeClient`
Injects an encrypted credential bundle (API key or session token) directly into the iframe for session-based authentication and authorization.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -271,7 +417,7 @@ const success = await iframeClient.injectCredentialBundle(
Injects a wallet export bundle into the iframe, associating it with a specified organization. This allows secure transfer of wallet credentials.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -296,7 +442,7 @@ const success = await iframeClient.injectWalletExportBundle(
Injects a key export bundle into the iframe, supporting optional key formats. This is useful for transferring specific key credentials securely.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -322,7 +468,7 @@ const success = await iframeClient.injectKeyExportBundle(
Injects an import bundle into the iframe, associating it with a specific organization and user, enabling secure import of user credentials.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -348,7 +494,7 @@ const success = await iframeClient.injectImportBundle(
Extracts an encrypted wallet bundle from the iframe. Useful for securely retrieving wallet credentials from the iframe to the main application.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -370,7 +516,7 @@ const walletBundle = await iframeClient.extractWalletEncryptedBundle();
Extracts an encrypted key bundle from the iframe, providing secure retrieval of key credentials.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -396,7 +542,7 @@ The `TurnkeyWalletClient` extends `TurnkeyBrowserClient` such that it is special
This method enables easy access to the wallet public key from the `TurnkeyWalletClient` to be used in authentication flows.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
import { EthereumWallet } from "@turnkey/wallet-stamper";
@@ -416,7 +562,7 @@ const publicKey = await walletsClient.getPublicKey();
This method provides easy access to the full object that represents the wallet being used to stamp requests for this client.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
import { EthereumWallet } from "@turnkey/wallet-stamper";
@@ -438,11 +584,9 @@ The `TurnkeyBrowserSDK` serves as the main entry point for interacting with Turn
### `passkeyClient()`
-Creates an instance of TurnkeyPasskeyClient with a specified or default rpId (relying party ID). This client can prompt users to sign with a passkey credential for authentication. If you'd like to use your passkey client to proxy requests to your server, to be signed with parent organization credentials, include the server URL in the `serverSignUrl` parameter.
+Creates an instance of TurnkeyPasskeyClient with a specified or default rpId (relying party ID). This client can prompt users to sign with a Passkey credential for authentication. If you'd like to use your Passkey client to proxy requests to your server, to be signed with parent organization credentials, include the server URL in the `serverSignUrl` parameter.
-{/* */}
-
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -460,16 +604,9 @@ const walletsResponse = await passkeyClient.getWallets();
### `iframeClient()`
-Creates an instance of `TurnkeyIframeClient` by initializing an iframe stamper with the specified iframeUrl
-and optional element ID. The iframe client is used to interact with a series of iframes hosted by Turnkey,
-designed for sensitive operations such as storing an expiring credential within the [Email Recovery](/authentication/email#recovery-flow)
-and [Email Auth](/authentication/email) flows, and facilitating Wallet [Import](/wallets/import-wallets) and [Export](/wallets/export-wallets).
-The code powering these iframes can be found at https://github.com/tkhq/frames. If you'd like to use your iframe client to proxy
-requests to your server, to be signed with parent organization credentials, include the server URL in the `serverSignUrl` parameter.
-
-{/* */}
+Creates an instance of `TurnkeyIframeClient` by initializing an iframe stamper with the specified iframeUrl and optional element ID. The iframe client is used to interact with a series of iframes hosted by Turnkey, designed for sensitive operations such as storing an expiring credential within the [Email Recovery](/authentication/email#recovery-flow) and [Email Auth](/authentication/email) flows, and facilitating Wallet [Import](/wallets/import-wallets) and [Export](/wallets/export-wallets). The code powering these iframes can be found at [https://github.com/tkhq/frames](https://github.com/tkhq/frames). If you'd like to use your iframe client to proxy requests to your server, to be signed with parent organization credentials, include the server URL in the `serverSignUrl` parameter.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -499,9 +636,7 @@ if (response) {
Creates an instance of `TurnkeyWalletClient`, taking in an wallet, wrapped by an object that matches the `WalletInterface` class. The wallet client is used to interact with the Turnkey API, authenticating requests by using the wallet to stamp the requests. If you'd like to use your wallet client to proxy requests to your server, to be signed with parent organization credentials, include the server URL in the `serverSignUrl` parameter.
-{/* */}
-
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
import { EthereumWallet } from "@turnkey/wallet-stamper";
@@ -521,7 +656,7 @@ const walletClient = turnkeySDK.walletClient(new EthereumWallet());
The serverSign function is used to proxy requests from a root parent organization to a child organization. The API key cannot be stored client-side, which is why the serverSign flow exists: to forward authenticated client-side requests to Turnkey via proxy backend.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -553,11 +688,15 @@ if (subOrgIdsResponse.organizationIds?.length > 0) {
}
```
+### `getSession()`
+
+Attempts to refresh an existing, active session and will extend the session expiry using the `expirationSeconds` parameter
+
### `currentUserSession()`
Generally speaking, in order to ensure a seamless UX, you might not want a passkey user have to manually authenticate every read request from Turnkey's API with a credential (e.g. via FaceID or TouchID). In order to reduce friction, you can have a user login() to Turnkey with a credential once. This method facilitates this process and creates an instance of The TurnkeyBrowserClient that allows multiple read-only requests to Turnkey's API.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -583,7 +722,7 @@ const walletsResponse = await userSessionClient.getWallets();
If there is a valid, current read-session, this will return an auth bundle and its expiration. This auth bundle can be used in conjunction with an iframeStamper to create a read + write session.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -602,7 +741,7 @@ const readWriteSession = await turnkeySDK.getReadWriteSession();
Retrieves information about the user's current sub-organization from the user data stored in local storage. Useful for obtaining the user's organization context.
-```typescript
+```js
import { Turnkey } from "@turnkey/sdk-browser";
const config = {
@@ -619,8 +758,26 @@ const subOrganization = await turnkeySDK.getCurrentSubOrganization();
## Examples
-#### [1. Implementing an embedded wallet authentication flow with passkeys](/embedded-wallets/code-examples/create-sub-org-passkey)
-
-#### [2. Implementing an embedded wallet authentication flow with email](/embedded-wallets/code-examples/authenticate-user-email)
-
-#### [3. Signing Transactions](/embedded-wallets/code-examples/signing-transactions)
+
+
+
+
+
diff --git a/sdks/javascript-server.mdx b/sdks/javascript-server.mdx
new file mode 100644
index 00000000..62d71fa0
--- /dev/null
+++ b/sdks/javascript-server.mdx
@@ -0,0 +1,353 @@
+---
+title: "JavaScript Server"
+description: "The [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server) package exposes functionality that lets developers build server-side functionality for applications that interact with the Turnkey API."
+---
+
+## Overview
+
+It exposes a ready-made API client class which manages the process of constructing requests to the Turnkey API and authenticating them with a valid API key. Furthermore, it exposes API proxies that forward requests from your application's client that need to be signed by parent organizations API key.
+
+Use the [`@turnkey/sdk-server`](https://www.npmjs.com/package/@turnkey/sdk-server) package to handle server-side interactions for applications that interact with the Turnkey API.
+
+## Installation
+
+
+```bash NPM
+npm install @turnkey/sdk-server
+```
+
+```bash Yarn
+yarn add @turnkey/sdk-server
+```
+
+
+
+## Initializing
+
+```js
+import { Turnkey } from "@turnkey/sdk-server";
+
+const turnkey = new Turnkey({
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+ apiBaseUrl: "https://api.turnkey.com",
+ apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY,
+ apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY,
+});
+```
+
+#### Parameters
+
+
+
+An object containing configuration settings for the Server Client.
+
+
+
+
+
+The root organization that requests will be made from unless otherwise specified
+
+
+
+
+The base URL that API requests will be sent to (use [https://api.turnkey.com](https://api.turnkey.com) when making requests to Turnkey's API)
+
+
+
+
+The API Private Key to sign requests with (this will normally be the API Private Key to your root organization)
+
+
+
+
+The API Public Key associated with the configured API Private Key above
+
+
+## Creating Clients
+
+Calls to Turnkey's API must be signed with a valid credential (often referred to in the docs as [stamping](https://docs-git-omkar-sdk-docs-turnkey.vercel.app/api-overview/stamps)) from the user initiating the API call. When using the Server SDK, the user initiating the API call is normally your root organization, and the API call is authenticated with the API keypair you create on the Turnkey dashboard.
+
+#### `apiClient()`
+
+The `apiClient` method returns an instance of the `TurnkeyApiClient` which will sign requests with the injected `apiPrivateKey`, and `apiPublicKey` credentials.
+
+```js
+const apiClient = turnkey.apiClient();
+const walletsResponse = await apiClient.getWallets();
+
+// this will sign the request with the configured api credentials
+```
+
+## Creating API Proxies
+
+There are certain actions that are initiated by users, but require the activity to be signed by the root organization itself. Examples of this include the initial creation of the user `subOrganization` or sending an email to a user with a login credential as part of an `emailAuth` flow.
+
+These can be implemented in your backend by creating an `apiClient` and handling requests from your browser application at different routes, but we have also provided a convenience method for doing this by having allowing a single `apiProxy` to handle requests at a single route and automatically sign specific user actions with the root organization's credentials.
+
+#### expressProxyHandler()
+
+The `expressProxyHandler()` method creates a proxy handler designed as a middleware for Express applications. It provides an API endpoint that forwards requests to the Turnkey API server.
+
+```js
+const turnkeyProxyHandler = turnkey.expressProxyHandler({
+ allowedMethods: ["createSubOrganization", "emailAuth", "getSubOrgIds"],
+});
+
+app.post("/apiProxy", turnkeyProxyHandler);
+
+// this will sign requests made with the client-side `serverSign` function with the root organization's API key for the allowedMethods in the config
+```
+
+#### 2. nextProxyHandler()
+
+The `nextProxyHandler()` method creates a proxy handler designed as a middleware for Next.js applications. It provides an API endpoint that forwards requests to the Turnkey API server.
+
+```js
+// Configure the Next.js handler with allowed methods
+const turnkeyProxyHandler = turnkey.nextProxyHandler({
+ allowedMethods: ["createSubOrganization", "emailAuth", "getSubOrgIds"],
+});
+
+export default turnkeyProxyHandler;
+
+// this will sign requests made with the client-side `serverSign` function with the root organization's API key for the allowedMethods in the config
+```
+
+## TurnkeyServerClient
+
+The `@turnkey/sdk-server` exposes NextJS Server Actions. These server actions can be used to facilitate implementing common authentication flows.
+
+### `sendOtp()`
+
+Initiate an OTP authentication flow for either an `EMAIL` or `SMS`.
+
+```typescript
+import { server } from "@turnkey/sdk-server";
+
+const initAuthResponse = await server.sendOtp({
+ suborgID: suborgId!,
+ otpType,
+ contact: value,
+ ...(emailCustomization && { emailCustomization }),
+ ...(sendFromEmailAddress && { sendFromEmailAddress }),
+ ...(customSmsMessage && { customSmsMessage }),
+ userIdentifier: authIframeClient?.iframePublicKey!,
+});
+
+if (initAuthResponse && initAuthResponse.otpId) {
+ // proceed to verifyOtp
+} else {
+ // error handling
+}
+```
+
+#### Parameters
+
+
+ An object containing the parameters to initiate an `EMAIL` or `SMS` OTP
+ authentication flow.
+
+
+
+ The ID of the sub organization for the given request.
+
+
+
+ The type of OTP request, either `EMAIL` or `SMS`.
+
+
+
+ The contact information (email or phone number) where the OTP will be sent.
+
+
+
+ Use to customize the SMS message.
+
+
+
+ IP Address, iframePublicKey, or other unique identifier used for rate
+ limiting.
+
+
+### `verifyOtp()`
+
+Verify the OTP Code sent to the user via `EMAIL` or `SMS`. If verification is successful, a Session is returned which is used to log in with.
+
+```typescript
+import { server } from "@turnkey/sdk-server";
+
+const authSession = await server.verifyOtp({
+ suborgID: suborgId,
+ otpId,
+ otpCode: otp,
+ targetPublicKey: authIframeClient!.iframePublicKey!,
+ sessionLengthSeconds,
+});
+
+if (authSession?.token) {
+ // log in with Session
+ await authIframeClient!.loginWithSession(authSession);
+ // call onValidateSuccess callback
+ await onValidateSuccess();
+} else {
+ // error handling
+}
+```
+
+#### Parameters
+
+
+ An object containing the parameters to verify an OTP authentication attempt.
+
+
+
+ The ID of the sub organization for the given request.
+
+
+
+ The ID for the given OTP request. This ID is returned in the `SendOtpResponse`
+ from `sendOtp()`.
+
+
+
+ The OTP Code sent to the user.
+
+
+
+ The public key of the target user.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+### `oauth()`
+
+Complete an OAuth authentication flow once the OIDC Token has been obtained from the OAuth provider.
+
+```typescript
+import { server } from "@turnkey/sdk-server";
+
+const oauthSession = await server.oauth({
+ suborgID: suborgId!,
+ oidcToken: credential,
+ targetPublicKey: authIframeClient?.iframePublicKey!,
+ sessionLengthSeconds: authConfig.sessionLengthSeconds,
+});
+
+if (oauthSession && oauthSession.token) {
+ // log in with Session
+ await authIframeClient!.loginWithSession(oauthSession);
+ // call onAuthSuccess callback
+ await onAuthSuccess();
+} else {
+ // error handling
+}
+```
+
+#### Parameters
+
+
+ An object containing the parameters to complete an OAuth authentication flow.
+
+
+
+ The ID of the sub organization for the given request.
+
+
+
+ The OIDC (OpenID Connect) Token issued by the OAuth provider which contains
+ basic profile information about the user.
+
+
+
+ The public key of the target user.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+### `sendCredential()`
+
+Send a login credential to a user's email address.
+
+```typescript
+import { server } from "@turnkey/sdk-server";
+
+const sendCredentialResponse = await server.sendCredential({
+ email,
+ targetPublicKey: authIframeClient?.iframePublicKey!,
+ organizationId: suborgId!,
+ ...(apiKeyName && { apiKeyName }),
+ ...(sendFromEmailAddress && { sendFromEmailAddress }),
+ ...(sessionLengthSeconds && { sessionLengthSeconds }),
+ ...(invalidateExisting && { invalidateExisting }),
+ ...(emailCustomization && { emailCustomization }),
+ ...(sendFromEmailAddress && { sendFromEmailAddress }),
+});
+```
+
+#### Parameters
+
+
+ An object containing the parameters to send a login credential via email.
+
+
+
+ The email address provided by the user.
+
+
+
+ The public key of the target user.
+
+
+
+ The ID of the sub organization for the given request.
+
+
+
+ The name of the API Key.
+
+
+
+ IP Address, iframePublicKey, or other unique identifier used for rate
+ limiting.
+
+
+
+ Specify the length of the session in seconds. Defaults to 900 seconds or 15
+ minutes.
+
+
+
+ Invalidate all pre-existing sessions. Defaults to `false`.
+
+
+
+ An option to customize the email.
+
+
+
+ Provide a custom email address which will be used as the sender of the email.
+
diff --git a/docs/sdks/migration.mdx b/sdks/migration-path.mdx
similarity index 91%
rename from docs/sdks/migration.mdx
rename to sdks/migration-path.mdx
index ba162998..e5dae3a3 100644
--- a/docs/sdks/migration.mdx
+++ b/sdks/migration-path.mdx
@@ -1,12 +1,8 @@
---
title: "Migration Path"
-sidebar_position: 99
-description: Migration Path
-slug: /sdks/migration-path
+description: "This guide aims to cover the process for migrating from lower-level Turnkey SDK (i.e. `@turnkey/{ http, api-key-stamper, webauthn-stamper, iframe-stamper }`) libraries, to our more recent abstractions: `@turnkey/{ sdk-browser, sdk-server, sdk-react }`"
---
-This guide aims to cover the process for migrating from lower-level Turnkey SDK (i.e. `@turnkey/{ http, api-key-stamper, webauthn-stamper, iframe-stamper }`) libraries, to our more recent abstractions: `@turnkey/{ sdk-browser, sdk-server, sdk-react }`
-
### Why migrate?
Turnkey’s low-level libraries allow developers to get as close to the bare Turnkey metals as possible, allowing you to specify all request parameters. While some may desire this configurability, it does have a slight cost of convenience. Enter our higher-order libraries — `@turnkey/{ sdk-browser, sdk-server, sdk-react }` abstract away the details that most developers don’t need to configure, enabling you to focus more on business logic and less on configuration.
@@ -15,11 +11,11 @@ Turnkey’s low-level libraries allow developers to get as close to the bare Tur
In short, this depends on your use case. Here are some example paths:
-#### Turnkey in the server
+**Turnkey in the server**
If you’re using Turnkey in a backend setting, you’re most likely using a combination of `@turnkey/http` and `@turnkey/api-key-stamper`. The transition to using `@turnkey/sdk-server` is fairly straightforward: just bring your API key details, and you’ll be able to reduce the amount of code you need to include.
-#### Turnkey on the client
+**Turnkey on the client**
In a browser setting, you’re most likely using `@turnkey/http` and `@turnkey/api-key-stamper` and/or `@turnkey/iframe-stamper`. If you’re using NextJS or React in general, you’ll benefit from using `@turnkey/sdk-react`.
@@ -27,11 +23,11 @@ We’ve included some details on making these transitions below:
### Examples
-#### Turnkey in the server
+**Turnkey in the server**
To illustrate the difference, here’s an example creating a new Ethereum wallet via a combination of `@turnkey/http` and `@turnkey/api-key-stamper`:
-```typescript
+```js
import { TurnkeyClient } from "@turnkey/http";
import { ApiKeyStamper } from "@turnkey/api-key-stamper";
@@ -66,7 +62,7 @@ const newAddress =
And here’s how you might do the same with just `@turnkey/sdk-server`:
-```typescript
+```js
import { Turnkey, DEFAULT_ETHEREUM_ACCOUNTS } from "@turnkey/sdk-server";
const turnkeyClient = new Turnkey({
@@ -86,11 +82,11 @@ const newAddress = addresses[0];
As you can see, much less boilerplate and dealing with low-level details such as activity types and nested results.
-#### Turnkey on the client
+**Turnkey on the client**
Here’s a similar illustration, starting with an initial combination of `@turnkey/http` and `@turnkey/webauthn-stamper`
-```typescript
+```js
import { TurnkeyClient } from "@turnkey/http";
import { WebauthnStamper } from "@turnkey/webauthn-stamper";
@@ -124,7 +120,7 @@ const newAddress =
And here’s how you might do the same with just `@turnkey/sdk-react`:
-```typescript
+```js
import { TurnkeyProvider } from "@turnkey/sdk-react";
// Configure once in the root of your app (e.g. _app.tsx)
diff --git a/docs/sdks/react-native.mdx b/sdks/react-native.mdx
similarity index 71%
rename from docs/sdks/react-native.mdx
rename to sdks/react-native.mdx
index c50399fe..53409ae2 100644
--- a/docs/sdks/react-native.mdx
+++ b/sdks/react-native.mdx
@@ -1,12 +1,8 @@
---
title: "React Native"
-sidebar_position: 6
-description: React Native SDK
-slug: /sdks/react-native
+description: "This documentation contains guides for using Turnkey's React Native compatible [JavaScript packages](https://github.com/tkhq/sdk/tree/main/packages)."
---
-This documentation contains guides for using Turnkey's React Native compatible [JavaScript packages](https://github.com/tkhq/sdk/tree/main/packages).
-
Using these packages combined will help you create a fully-featured React Native app, powered by Turnkey.
## React Native Compatible Packages
@@ -18,7 +14,7 @@ Using these packages combined will help you create a fully-featured React Native
| @turnkey/api-key-stamper | A JavaScript package for API stamping functionalities. It is meant to be used with Turnkey's HTTP package. | [@turnkey/api-key-stamper](https://www.npmjs.com/package/@turnkey/api-key-stamper) |
| @turnkey/encoding | This package contains decoding and encoding functions used by other Turnkey packages. | [@turnkey/encoding](https://www.npmjs.com/package/@turnkey/encoding) |
| @turnkey/react-native-passkey-stamper | A React Native package for stamping payloads with passkeys. It is meant to be used with Turnkey's HTTP package. | [@turnkey/react-native-passkey-stamper](https://www.npmjs.com/package/@turnkey/react-native-passkey-stamper) |
-| @turnkey/sdk-react-native | This package simplifies the integration of the Turnkey API into React Native applications. It provides secure session management, authentication, and cryptographic operations. | [@turnkey/react-native-sessions](https://www.npmjs.com/package/@turnkey/react-native-sessions) |
+| @turnkey/react-native-sessions | This package provides developers with an easy way to manage sessions and securely store public-private key pairs on iOS and Android devices. | [@turnkey/react-native-sessions](https://www.npmjs.com/package/@turnkey/react-native-sessions) |
| @turnkey/ethers | A package for integrating Turnkey signers with the Ethers.js library. | [@turnkey/ethers](https://www.npmjs.com/package/@turnkey/ethers) |
| @turnkey/solana | A package for integrating Turnkey signers with the Solana blockchain. | [@turnkey/solana](https://www.npmjs.com/package/@turnkey/solana) |
| @turnkey/cosmjs | A package for integrating Turnkey Signers with the CosmJS library. | [@turnkey/cosmjs](https://www.npmjs.com/package/@turnkey/cosmjs) |
@@ -28,25 +24,20 @@ You can see all of Turnkey's JavaScript packages on [npmjs](https://www.npmjs.co
## Getting Started
-The easiest way to build a React Native app with Turnkey is to use our [React Native demo wallet](https://github.com/tkhq/react-native-demo-wallet) as a starter.
-This app is a fully-featured example that demonstrates how to use the Turnkey's JavaScript packages to authenticate users, create wallets, export wallets, sign messages, and more.
+The easiest way to build a React Native app with Turnkey is to use our [React Native demo wallet](https://github.com/tkhq/react-native-demo-wallet) as a starter. This app is a fully-featured example that demonstrates how to use the Turnkey's JavaScript packages to authenticate users, create wallets, export wallets, sign messages, and more.
-The app includes a backend [JavaScript server](https://github.com/tkhq/react-native-demo-wallet/blob/main/app/turnkey%2Bapi.ts) which uses [@turnkey/sdk-server](https://www.npmjs.com/package/@turnkey/sdk-server) to make requests to Turnkey that must be signed by the parent organization.
-An example of a request that must be signed by the parent organization is creating a [sub-organization](../documentation/concepts/sub-organizations.md). [(code reference)](https://github.com/tkhq/react-native-demo-wallet/blob/6200e34563ed5076a1f7305c9aaa8f65d9242b53/app/turnkey%2Bapi.ts#L132-L192)
+The app includes a backend [JavaScript server](https://github.com/tkhq/react-native-demo-wallet/blob/main/app/turnkey%2Bapi.ts) which uses [@turnkey/sdk-server](https://www.npmjs.com/package/@turnkey/sdk-server) to make requests to Turnkey that must be signed by the parent organization. An example of a request that must be signed by the parent organization is creating a [sub-organization](/concepts/sub-organizations). [(code reference)](https://github.com/tkhq/react-native-demo-wallet/blob/6200e34563ed5076a1f7305c9aaa8f65d9242b53/app/turnkey%2Bapi.ts#L132-L192)
-Some requests made to Turnkey must be signed by the sub-organization. These are ran by the app directly. You can find these requests in the app's [TurnkeyProvider](https://github.com/tkhq/react-native-demo-wallet/blob/main/providers/turnkey.tsx).
-An example of a request that must be signed by the sub-organization is creating a wallet. [(code reference)](https://github.com/tkhq/react-native-demo-wallet/blob/6200e34563ed5076a1f7305c9aaa8f65d9242b53/providers/turnkey.tsx#L609-L642)
+Some requests made to Turnkey must be signed by the sub-organization. These are ran by the app directly. You can find these requests in the app's [TurnkeyProvider](https://github.com/tkhq/react-native-demo-wallet/blob/main/providers/turnkey.tsx). An example of a request that must be signed by the sub-organization is creating a wallet. [(code reference)](https://github.com/tkhq/react-native-demo-wallet/blob/6200e34563ed5076a1f7305c9aaa8f65d9242b53/providers/turnkey.tsx#L609-L642)
## Video
-
-
+
-
-
-#### Complete the installation and setup by following the instructions in the [README](https://github.com/tkhq/react-native-demo-wallet/blob/main/README.md) file.
+ src="https://github.com/tkhq/react-native-demo-wallet/raw/refs/heads/main/assets/videos/demo_video.mov"
+ width="100%"
+ height="420"
+ controls
+>
+
+
+**Complete the installation and setup by following the instructions in the [README](https://github.com/tkhq/react-native-demo-wallet/blob/main/README.md) file.**
diff --git a/docs/sdks/react.mdx b/sdks/react.mdx
similarity index 73%
rename from docs/sdks/react.mdx
rename to sdks/react.mdx
index 6d7847dc..1b21111a 100644
--- a/docs/sdks/react.mdx
+++ b/sdks/react.mdx
@@ -1,14 +1,7 @@
---
title: "React"
-sidebar_position: 4
-description: React SDK
-slug: /sdks/react
---
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Parameter from "@site/src/components/parameter";
-
## Overview
The [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) package wraps the functionality from the [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser) package to allow developers to build React-based applications that interact with the Turnkey API with different types of authentication.
@@ -18,29 +11,21 @@ It allows developers to use the same clients exposed in [`@turnkey/sdk-browser`]
Use the [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) package when you’re building React-based frontend applications that interact with the Turnkey API.
## Installation
-
-
-
-
-```bash
+
+```bash NPM
npm install @turnkey/sdk-react
```
-
-
-
-```bash
+```bash Yarn
yarn add @turnkey/sdk-react
```
-
-
-
+
## Initializing
In `App.tsx` (or equivalent file)
-```typescript
+```ts
import { TurnkeyProvider } from "@turnkey/sdk-react";
const turnkeyConfig = {
@@ -66,87 +51,36 @@ For further context on RPID's for passkeys, used in the above example, [look her
#### Parameters
-
+
An object containing configuration settings for the Browser Client.
+
-
-
-
+
The root organization that requests will be made from unless otherwise specified. For example, if you are using methods that require signing with an auth credential from a sub-organization, you will need to specify the sub-organization's ID in your client's config.
+
+
-
-
-
-
-The base URL that API requests will be sent to (use https://api.turnkey.com when making requests to Turnkey's API)
-
-
-
-
-
-The [Relying Party](https://developer.mozilla.org/en-US/docs/Glossary/Relying_party) ID used for WebAuthn flows (will default to the value returned from `window.location.hostname` unless otherwise specified)
+The base URL that API requests will be sent to (use [https://api.turnkey.com](https://api.turnkey.com) when making requests to Turnkey's API)
+
-
+
-
+The [Relying Party](https://developer.mozilla.org/en-US/docs/Glossary/Relying_party) ID used for WebAuthn flows (will default to the value returned from `window.location.hostname` unless otherwise specified)
+
+
The URL to send requests that need to be signed from a backend codebase by the root organization's API key if using the `serverSign` flow
-
-
+
## Using the React SDK to interact with Turnkey
The [`@turnkey/sdk-react`](https://www.npmjs.com/package/@turnkey/sdk-react) is a package that provides abstractions on top of the [`@turnkey/sdk-browser`](https://www.npmjs.com/package/@turnkey/sdk-browser) package, for usage in React-based applications.
-In any React component nested under the `TurnkeyProvider`, you'll be able to call `useTurnkey()` as in the following example. You can also instantiate clients like `passkeyClient` and `authIframeClient` by pulling them out of the provider directly as [such](https://docs.turnkey.com/sdks/migration-path#turnkey-on-the-client-1).
+In any React component nested under the `TurnkeyProvider`, you'll be able to call `useTurnkey()` as in the following example. You can also instantiate clients like `passkeyClient` and `authIframeClient` by pulling them out of the provider directly as [such](/sdks/migration-path#turnkey-on-the-client-1).
-```typescript
+```ts
import { useTurnkey } from "@turnkey/sdk-react";
const { turnkey, passkeyClient, authIframeClient } = useTurnkey();
diff --git a/sdks/rust.mdx b/sdks/rust.mdx
new file mode 100644
index 00000000..e5a2fa62
--- /dev/null
+++ b/sdks/rust.mdx
@@ -0,0 +1,7 @@
+---
+title: "Rust"
+description: "Turnkey offers native tooling for interacting with the API using Rust. See [https://github.com/tkhq/rust-sdk](https://github.com/tkhq/rust-sdk) for more details."
+mode: wide
+---
+
+
diff --git a/sdks/swift.mdx b/sdks/swift.mdx
new file mode 100644
index 00000000..a195e465
--- /dev/null
+++ b/sdks/swift.mdx
@@ -0,0 +1,14 @@
+---
+title: Turnkey Swift SDK
+sidebarTitle: "Overview"
+description: "This documentation contains guides for using the [Turnkey Swift SDK](https://github.com/tkhq/swift-sdk)."
+---
+
+
+
+ Using the Proxy Middleware from the Turnkey Swift SDK
+
+
+ Register Passkey
+
+
diff --git a/docs/sdks/swift/proxy-middleware.mdx b/sdks/swift/proxy-middleware.mdx
similarity index 77%
rename from docs/sdks/swift/proxy-middleware.mdx
rename to sdks/swift/proxy-middleware.mdx
index d097ecd7..bffc3cad 100644
--- a/docs/sdks/swift/proxy-middleware.mdx
+++ b/sdks/swift/proxy-middleware.mdx
@@ -1,14 +1,11 @@
---
title: "Proxy Middleware"
-description: Using the Proxy Middleware from the Turnkey Swift SDK
-slug: /sdks/swift/proxy-middleware
+description: "The [`ProxyMiddleware`](https://github.com/tkhq/swift-sdk/blob/bd8993b4b6b35c44d4a917b06dd44490961c4f28/Sources/Middleware/ProxyMiddleware.swift) is integrated into the `TurnkeyClient` through its initializer that accepts a proxy server URL."
---
-# Proxy Middleware
-
## Overview
-The [`ProxyMiddleware`](https://github.com/tkhq/swift-sdk/blob/bd8993b4b6b35c44d4a917b06dd44490961c4f28/Sources/Middleware/ProxyMiddleware.swift) is integrated into the `TurnkeyClient` through its initializer that accepts a proxy server URL. This setup is particularly useful for handling scenarios where direct authenticated requests are not feasible, such as during onboarding flows or when additional server-side processing is required before reaching Turnkey's backend.
+This setup is particularly useful for handling scenarios where direct authenticated requests are not feasible, such as during onboarding flows or when additional server-side processing is required before reaching Turnkey's backend.
## Initialize
@@ -25,9 +22,9 @@ This initializer configures the `TurnkeyClient` to route all requests through th
This setup is especially useful for operations like:
-- Email [authentication](/authentication/email)
-- Wallet [import](/wallets/import-wallets) & [export](/wallets/export-wallets)
-- [Sub-organization creation](/concepts/sub-organizations#creating-sub-organizations)
+* Email [authentication](/authentication/email)
+* Wallet [import](/wallets/import-wallets) & [export](/wallets/export-wallets)
+* [Sub-organization creation](/concepts/sub-organizations#creating-sub-organizations)
## Request Header
@@ -35,7 +32,7 @@ The middleware adds an `X-Turnkey-Request-Url` header to each request, which con
Example implementation of a Node.js proxy server:
-```javascript
+```js
const express = require("express");
const app = express();
diff --git a/docs/sdks/swift/register-passkey.mdx b/sdks/swift/register-passkey.mdx
similarity index 73%
rename from docs/sdks/swift/register-passkey.mdx
rename to sdks/swift/register-passkey.mdx
index 5d349be5..03fcf515 100644
--- a/docs/sdks/swift/register-passkey.mdx
+++ b/sdks/swift/register-passkey.mdx
@@ -1,29 +1,22 @@
---
-title: "Register Passkey"
-description: Register Passkey
-slug: /sdks/swift/register-passkey
+title: "Introduction"
+description: "This guide explains how to use the `PasskeyManager` class to register a new passkey within your iOS application. We'll cover the necessary configurations and provide code examples with detailed explanations."
+sidebarTitle: "Register Passkey"
---
-# Introduction
-
-This guide explains how to use the `PasskeyManager` class to register a new passkey within your iOS application.
-We'll cover the necessary configurations and provide code examples with detailed explanations.
-
## Prerequisites
-Before integrating passkey registration, ensure the following prerequisites are met.
-You may proceed to the [Passkey Registration](#passkey-registration) section if you have already configured the associated domains and the app site association file.
+Before integrating passkey registration, ensure the following prerequisites are met. You may proceed to the [Passkey Registration](#passkey-registration) section if you have already configured the associated domains and the app site association file.
### Associated Domains Entitlement
-Your app must have the Associated Domains capability enabled. This allows your app to access passkeys stored in the user's iCloud Keychain.
-Ensure that your domain supports HTTPS and is properly configured.
+Your app must have the Associated Domains capability enabled. This allows your app to access passkeys stored in the user's iCloud Keychain. Ensure that your domain supports HTTPS and is properly configured.
1. In Xcode, select your project and navigate to the Signing & Capabilities tab.
2. Click the + Capability button and add Associated Domains.
3. Add your domain to the Associated Domains section, prefixed with webcredentials:. For example:
-```text
+```bash
webcredentials:your.domain.com
```
@@ -31,16 +24,15 @@ Reference: [Apple Developer Documentation - Supporting Associated Domains](https
### Apple App Site Association File
-Your domain must host an `apple-app-site-association` file that specifies the app identifiers allowed to access credentials.
-The file should be available at:
+Your domain must host an `apple-app-site-association` file that specifies the app identifiers allowed to access credentials. The file should be available at:
-```text
+```bash
https://your.domain.com/.well-known/apple-app-site-association
```
The content of the file should include the webcredentials service, as shown:
-```json
+```bash
{
"webcredentials": {
"apps": ["."]
@@ -54,50 +46,46 @@ Replace `` and `` with your actual App ID p
Once the prerequisites are in place, you can proceed to implement passkey registration using `PasskeyManager`.
-import { Step, Steps } from "@site/src/components/step";
-
+
+ At the top of your `ViewController` or relevant class, import the necessary modules:
-Import Required Modules
-
-At the top of your `ViewController` or relevant class, import the necessary modules:
-
-```swift title="ViewController.swift"
+```swift ViewController.swift
import UIKit
import AuthenticationServices
import Shared // Import Shared to use PasskeyManager
import TurnkeySDK // Import TurnkeySDK to use TurnkeyClient
```
+
-Initialize PasskeyManager
+
-Create an instance of `PasskeyManager`, providing the [Relying Party Identifier](/authentication/passkeys/options#rp) and the [presentation anchor]().
+Create an instance of `PasskeyManager`, providing the [Relying Party Identifier](/authentication/passkeys/options#rp) and the [presentation anchor](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerpresentationcontextproviding/presentationanchor\(for:\)).
-```swift title="ViewController.swift"
+```swift ViewController.swift
class ViewController: UIViewController {
-
var passkeyManager: PasskeyManager?
-
override func viewDidLoad() {
super.viewDidLoad()
// Additional setup if needed
}
-
// ... rest of the class
}
```
+
-Set Up User Interface
+
Implement a method to initiate passkey registration, typically triggered by a user action such as tapping a button.
The `PasskeyManager` requires two parameters:
-- `rpId`: The relying party identifier, typically your domain.
- This must match the domain configured in the Associated Domains entitlement and the `apple-app-site-association` file.
-- `presentationAnchor`: The window in which the authentication services will present UI, usually obtained from `view.window`.
+* `rpId`: The relying party identifier, typically your domain. This must match the domain configured in the Associated Domains entitlement and the `apple-app-site-association` file.
+* `presentationAnchor`: The window in which the authentication services will present UI, usually obtained from `view.window`.
-```swift title="ViewController.swift"
+ViewController.swift
+
+```swift ViewController.swift
@IBAction func registerPasskeyTapped(_ sender: Any) {
guard let window = view.window else {
print("No window available")
@@ -118,12 +106,14 @@ The `PasskeyManager` requires two parameters:
passkeyManager?.registerPasskey(email: email)
}
```
+
-Register for Notifications
+
To handle the results of the passkey registration process, register for the relevant notifications provided by PasskeyManager.
-```swift title="ViewController.swift"
+
+```swift ViewController.swift
func registerForPasskeyNotifications() {
NotificationCenter.default.addObserver(
self,
@@ -145,22 +135,24 @@ func registerForPasskeyNotifications() {
)
}
```
+
-##### Cleanup
+
Remove the observers when they are no longer needed to avoid memory leaks.
-```swift title="ViewController.swift"
+```swift ViewController.swift
deinit {
NotificationCenter.default.removeObserver(self)
}
```
+
-Implement Notification Handlers
+
Define the methods that handle the passkey registration outcomes.
-```swift title="ViewController.swift"
+```swift ViewController.swift
@objc func passkeyRegistrationCompleted(_ notification: Notification) {
if let result = notification.userInfo?["result"] as? PasskeyRegistrationResult {
// Handle successful registration
@@ -185,24 +177,19 @@ Define the methods that handle the passkey registration outcomes.
// Update UI or take appropriate action
}
```
-
+
## Sign Up New User
-After successful passkey registration, use the `PasskeyRegistrationResult` to sign up a new user by creating a
-sub-organization using the `TurnkeyClient` from the TurnkeySDK.
+After successful passkey registration, use the `PasskeyRegistrationResult` to sign up a new user by creating a sub-organization using the `TurnkeyClient` from the TurnkeySDK.
+
-Initialize TurnkeyClient with Proxy
-
-When handling the completion of passkey registration, set up the `TurnkeyClient` with a proxy server URL using the [`ProxyMiddleware`](/sdks/swift/proxy-middleware).
-This configuration is essential for situations where the parent organization's API keys are required to authenticate requests
-for creating a sub-organization. Your backend should relay the request to the Turnkey API, ensuring it is authenticated with
-the parent organization's API keys.
+When handling the completion of passkey registration, set up the `TurnkeyClient` with a proxy server URL using the [`ProxyMiddleware`](/sdks/swift/proxy-middleware). This configuration is essential for situations where the parent organization's API keys are required to authenticate requests for creating a sub-organization. Your backend should relay the request to the Turnkey API, ensuring it is authenticated with the parent organization's API keys.
-```swift title="ViewController.swift"
+```swift ViewController.swift
func signUpWithPasskey(with passkeyRegistrationResult: PasskeyRegistrationResult) {
Task {
do {
@@ -219,17 +206,15 @@ func signUpWithPasskey(with passkeyRegistrationResult: PasskeyRegistrationResult
}
```
-:::note
-
-The middleware adds an `X-Turnkey-Request-Url` header to each request, which contains the original request URL. For more details, see the [Proxy Middleware](/sdks/swift/proxy-middleware) guide.
-
-:::
-
-Attestation Object
+
+ The middleware adds an `X-Turnkey-Request-Url` header to each request, which contains the original request URL. For more details, see the [Proxy Middleware](/sdks/swift/proxy-middleware) guide.
+
+
+
Construct the attestation object using the `PasskeyRegistrationResult`.
-```swift title="ViewController.swift"
+```swift ViewController.swift
let attestation = Components.Schemas.Attestation(
credentialId: passkeyRegistrationResult.attestation.credentialId,
clientDataJson: passkeyRegistrationResult.attestation.clientDataJson,
@@ -237,13 +222,13 @@ let attestation = Components.Schemas.Attestation(
transports: [.AUTHENTICATOR_TRANSPORT_BLE] // Adjust transports as needed
)
```
+
-Define Parameters
+
-Set up the necessary parameters for the sub-organization and root user.
-We'll use the `passkeyRegistrationResult` we received in the previous step to create a passkey authenticator for this new sub-organization.
+Set up the necessary parameters for the sub-organization and root user. We'll use the `passkeyRegistrationResult` we received in the previous step to create a passkey authenticator for this new sub-organization.
-```swift title="ViewController.swift"
+```swift ViewController.swift
let parentOrganizationId = "your-parent-organization-id"
let subOrganizationName = "New Sub Organization"
@@ -288,18 +273,16 @@ let disableEmailAuth = false
let disableSmsAuth = false
let disableOtpEmailAuth = false
```
+
+
+ You can find more information about the optional parameters in the [Organization Features](/concepts/organizations#features) section of the documentation.
+
-:::note
-
-You can find more information about the optional parameters in the [Organization Features](/concepts/organizations#features) section of the documentation.
-
-:::
-
-Create Sub-Organization
+
Use the `TurnkeyClient` to create the sub-organization with the provided parameters.
-```swift title="ViewController.swift"
+```swift ViewController.swift
let output = try await turnkeyClient.createSubOrganization(
organizationId: parentOrganizationId, // Organization ID can be empty when using proxy
subOrganizationName: subOrganizationName,
@@ -312,12 +295,13 @@ let output = try await turnkeyClient.createSubOrganization(
disableOtpEmailAuth: disableOtpEmailAuth
)
```
+
-Handle the Response
+
Process the response from the `createSubOrganization` call to retrieve information about the new sub-organization and root users.
-```swift title="ViewController.swift"
+```swift ViewController.swift
// Handle the response
switch output {
case let .ok(response):
@@ -339,11 +323,11 @@ case let .undocumented(statusCode, undocumentedPayload):
print("Undocumented response: \(statusCode)")
}
```
-
+
### References
-- [Apple Developer Documentation - Supporting Passkeys](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys)
-- [Apple Developer Documentation - ASAuthorizationController](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller)
-- [Apple Developer Documentation - ASAuthorizationPlatformPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
+* [Apple Developer Documentation - Supporting Passkeys](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys)
+* [Apple Developer Documentation - ASAuthorizationController](https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller)
+* [Apple Developer Documentation - ASAuthorizationPlatformPublicKeyCredentialProvider](https://developer.apple.com/documentation/authenticationservices/asauthorizationplatformpublickeycredentialprovider)
diff --git a/sdks/web3/cosmjs.mdx b/sdks/web3/cosmjs.mdx
new file mode 100644
index 00000000..1bb60ae6
--- /dev/null
+++ b/sdks/web3/cosmjs.mdx
@@ -0,0 +1,19 @@
+---
+title: "CosmJS"
+description: "[`@turnkey/cosmjs`](https://www.npmjs.com/package/@turnkey/cosmjs) exports a `TurnkeyDirectWallet` that serves as a drop-in replacement for a CosmJS direct wallet. It includes support for `signDirect`. See full implementation [here](https://github.com/tkhq/sdk/tree/main/packages/cosmjs) for more details and examples."
+mode: wide
+---
+
+```js
+// Initialize a Turnkey Signer
+const turnkeySigner = await TurnkeyDirectWallet.init({
+ config: {
+ ...
+ },
+ prefix: "celestia", // can be replaced with other Cosmos chains
+});
+
+const account = refineNonNull((await turnkeySigner.getAccounts())[0]);
+const compressedPublicKey = toHex(account.pubkey);
+const selfAddress = account.address;
+```
diff --git a/sdks/web3/eip-1193.mdx b/sdks/web3/eip-1193.mdx
new file mode 100644
index 00000000..ebd74c33
--- /dev/null
+++ b/sdks/web3/eip-1193.mdx
@@ -0,0 +1,7 @@
+---
+title: "EIP 1193"
+description: "[`@turnkey/eip-1193-provider`](https://www.npmjs.com/package/@turnkey/eip-1193-provider) is a Turnkey-compatible Ethereum provider that conforms to the [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) standard. It's built to seamlessly integrate with a broad spectrum of EVM-compatible chains, offering capabilities like account management, transaction signing, and blockchain interaction."
+mode: wide
+---
+
+See [`with-eip-1193-provider`](https://github.com/tkhq/sdk/tree/main/examples/with-eip-1193-provider) for an example.
diff --git a/sdks/web3/ethers.mdx b/sdks/web3/ethers.mdx
new file mode 100644
index 00000000..a3b4c25d
--- /dev/null
+++ b/sdks/web3/ethers.mdx
@@ -0,0 +1,19 @@
+---
+title: "Ethers"
+description: "[`@turnkey/ethers`](https://www.npmjs.com/package/@turnkey/ethers) exports a `TurnkeySigner` that serves as a drop-in replacement for an Ethers signer."
+mode: wide
+---
+
+Out of the box, it supports `{ signTransaction | signMessage | signTypedData }`. See full implementation [here](https://github.com/tkhq/sdk/tree/main/packages/ethers) for more details and examples. Note that you must **bring your own provider and connect it** to the TurnkeySigner.
+
+```js
+// Initialize a Turnkey Signer
+const turnkeySigner = new TurnkeySigner({
+ ...
+});
+
+// Bring your own provider (such as Alchemy or Infura: https://docs.ethers.org/v6/api/providers/)
+const network = "goerli";
+const provider = new ethers.providers.InfuraProvider(network);
+const connectedSigner = turnkeySigner.connect(provider);
+```
diff --git a/sdks/web3/solana.mdx b/sdks/web3/solana.mdx
new file mode 100644
index 00000000..a0125854
--- /dev/null
+++ b/sdks/web3/solana.mdx
@@ -0,0 +1,5 @@
+---
+title: "Solana"
+description: "We have released a package that you can use to sign transactions and messages: [`@turnkey/solana`](https://www.npmjs.com/package/@turnkey/solana). See [here](https://github.com/tkhq/sdk/tree/main/examples/with-solana) for an example."
+mode: wide
+---
diff --git a/sdks/web3/viem.mdx b/sdks/web3/viem.mdx
new file mode 100644
index 00000000..281d68e7
--- /dev/null
+++ b/sdks/web3/viem.mdx
@@ -0,0 +1,8 @@
+---
+title: "Viem"
+description: "[`@turnkey/viem`](https://www.npmjs.com/package/@turnkey/viem) provides a Turnkey [Custom Account](https://viem.sh/docs/accounts/custom.html#custom-account) (signer) which implements the signing APIs expected by Viem clients."
+mode: wide
+---
+
+See [`with-viem`](https://github.com/tkhq/sdk/tree/main/examples/with-viem) and [`with-viem-and-passkeys`](https://github.com/tkhq/sdk/tree/main/examples/with-viem-and-passkeys) for examples.
+See [`with-viem`](https://github.com/tkhq/sdk/tree/main/examples/with-viem) and [`with-viem-and-passkeys`](https://github.com/tkhq/sdk/tree/main/examples/with-viem-and-passkeys) for examples.
diff --git a/security/disaster-recovery.mdx b/security/disaster-recovery.mdx
new file mode 100644
index 00000000..1005bb97
--- /dev/null
+++ b/security/disaster-recovery.mdx
@@ -0,0 +1,13 @@
+---
+title: "Disaster Recovery"
+description: "We have a comprehensive disaster recovery process in place for all critical Turnkey data."
+---
+
+In particular, there are two main categories of data that we consider to be critical:
+
+* **Organization data:** Core data within your organization, including details for users, encrypted private key material, policies, tags, activity history, etc.
+* **Quorum Keys**: Keys used by members of the Quorum Set to boot secure applications, and perform sensitive actions within an enclave like decrypting private keys or making policy decisions.
+
+For organization data, because all enclave applications are stateless, our persistence strategy is very similar to a traditional web application. Data is encrypted, stored redundantly across geographies, and consistently backed up and exported to our disaster recovery accounts.
+
+For Quorum Keys, as described in [Quorum deployments](/security/quorum-deployments), we split the key between members of the Quorum set and have a level of redundancy in those shards. In the unlikely event that all members of the Quorum Set were to lose their active shares, we have a set of offline backup shares securely stored across geographically distributed locations.
diff --git a/docs/documentation/security/enclave-secure-channels.md b/security/enclave-secure-channels.mdx
similarity index 76%
rename from docs/documentation/security/enclave-secure-channels.md
rename to security/enclave-secure-channels.mdx
index 56aedd34..b4b5ab82 100644
--- a/docs/documentation/security/enclave-secure-channels.md
+++ b/security/enclave-secure-channels.mdx
@@ -1,13 +1,10 @@
---
-sidebar_position: 7
-title: Enclave secure channels
-description: Learn about Turnkey's enclave to end-user secure channels
-slug: /security/enclave-secure-channels
+title: "Enclave to end-user secure channels"
+description: "Turnkey does not trust anything running outside of secure enclaves. See [our approach](/security/our-approach) for more details. When enclaves and end-users need to exchange private information, we've rely on a protocol based on [HPKE](https://datatracker.ietf.org/doc/html/rfc9180) to establish a **secure channel**."
+sidebarTitle: "Enclave Secure Channels"
---
+This channel is a short-lived, one-way communication channel used in the following case:
-# Enclave to end-user secure channels
-
-Turnkey does not trust anything running outside of secure enclaves. See [our approach](./our-approach.md) for more details. When enclaves and end-users need to exchange private information, we've rely on a protocol based on [HPKE](ttps://datatracker.ietf.org/doc/html/rfc9180) to establish a **secure channel**. This channel is a short-lived, one-way communication channel used in the following case:
* **Key import**: the end-user encrypts key material to a Turnkey enclave
* **Key export**: a Turnkey enclave encrypts key material to an end-user
* **Authentication**: a Turnkey enclave encrypts an API key credential to an end-user after authentication
@@ -18,11 +15,11 @@ Neither the client or the server should reused keys to send or receive more than
### Terms
-- **Encapsulated Key** ("Encapped Key"): the public key of the sender used for ECDH.
-- **Target Key Pair**: the key pair of the receiver that the sender encrypts to the public key of. Only one message should ever be encrypted to the public key.
-- **Server**: a server inside of the enclave; normally an enclave application.
-- **Client**: a client outside of the enclave; normally a turnkey end user.
-- **Enclave HPKE Key Pair**: a key pair derived from the Quorum master seed specifically for the purpose of establishing secure channels with clients.
+* **Encapsulated Key** ("Encapped Key"): the public key of the sender used for ECDH.
+* **Target Key Pair**: the key pair of the receiver that the sender encrypts to the public key of. Only one message should ever be encrypted to the public key.
+* **Server**: a server inside of the enclave; normally an enclave application.
+* **Client**: a client outside of the enclave; normally a turnkey end user.
+* **Enclave HPKE Key Pair**: a key pair derived from the Quorum master seed specifically for the purpose of establishing secure channels with clients.
### Overview
@@ -41,21 +38,21 @@ This protocol builds on top of the HPKE standard ([RFC 9180](https://datatracker
#### Server to Client
1. Client generates target pair and sends `clientTargetPub` key to server.
-1. Server computes `ciphertext, serverEncappedPub = ENCRYPT(plaintext, clientTargetPub)` and clears `clientTargetPub` from memory.
-1. Server computes `serverEncappedPub_sig_enclaveAuthPriv = SIGN(serverEncappedPub, enclaveAuthPriv)`.
-1. Server sends `(ciphertext, serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)` to client.
-1. Client runs `VERIFY(serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)`.
-1. Client recovers plaintext by computing `DECRYPT(ciphertext, serverEncappedPub, clientTargetPriv)` and the client target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt. There is no hard mechanism to prevent a faulty client from resubmitting the same target public key.
+2. Server computes `ciphertext, serverEncappedPub = ENCRYPT(plaintext, clientTargetPub)` and clears `clientTargetPub` from memory.
+3. Server computes `serverEncappedPub_sig_enclaveAuthPriv = SIGN(serverEncappedPub, enclaveAuthPriv)`.
+4. Server sends `(ciphertext, serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)` to client.
+5. Client runs `VERIFY(serverEncappedPub, serverEncappedPub_sig_enclaveAuthPriv)`.
+6. Client recovers plaintext by computing `DECRYPT(ciphertext, serverEncappedPub, clientTargetPriv)` and the client target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt. There is no hard mechanism to prevent a faulty client from resubmitting the same target public key.
#### Client to Server
1. Client sends request to server for target key.
-1. Server generates server target pair and computes `serverTargetPub_sig_enclaveAuthPriv = SIGN(serverTargetPub, enclaveAuthPriv)`.
-1. Server sends `(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)` to client.
-1. Client runs `VERIFY(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)`.
-1. Client computes `ciphertext, clientEncappedPub = ENCRYPT(plaintext, serverTargetPub)` and clears serverTargetPub from memory.
-1. Client sends `(ciphertext, clientEncappedPub)` to server and the client is cleared from memory.
-1. Server recovers plaintext by computing `DECRYPT(ciphertext, clientEncappedPub, clientTargetPriv)` and server target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt.
+2. Server generates server target pair and computes `serverTargetPub_sig_enclaveAuthPriv = SIGN(serverTargetPub, enclaveAuthPriv)`.
+3. Server sends `(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)` to client.
+4. Client runs `VERIFY(serverTargetPub, serverTargetPub_sig_enclaveAuthPriv)`.
+5. Client computes `ciphertext, clientEncappedPub = ENCRYPT(plaintext, serverTargetPub)` and clears serverTargetPub from memory.
+6. Client sends `(ciphertext, clientEncappedPub)` to server and the client is cleared from memory.
+7. Server recovers plaintext by computing `DECRYPT(ciphertext, clientEncappedPub, clientTargetPriv)` and server target pair is cleared from memory. If the target pair is used multiple times we increase the count of messages that an attacker with the compromised target private key can decrypt.
## Import flow
@@ -63,13 +60,9 @@ Turnkey's import functionality works by anchoring import in a **target encryptio
The following diagram summarizes the flow:
-
-
-
+
+
+
The client-side iframe plays the role of the client and encrypts the wallet's mnemonic or private key to the secure enclave using the protocol described above. The encrypted key material is then passed as a parameter inside of a signed `IMPORT_WALLET` or `IMPORT_PRIVATE_KEY` activity. During this activity, the Turnkey enclave uses its key pair to decrypt and import the encrypted key material.
@@ -79,13 +72,9 @@ Turnkey's export functionality works by anchoring export in a **target encryptio
The following diagram summarizes the flow:
-
-
-
+
+
+
The client-side iframe plays the role of the server: the public portion of this key pair is passed as a parameter inside of a signed `EXPORT_WALLET`, `EXPORT_PRIVATE_KEY`, or `EXPORT_WALLET_ACCOUNT` activity.
@@ -97,9 +86,9 @@ Once the activity succeeds, the encrypted mnemonic or private key can be decrypt
Unlike typical auth and recovery flows in the industry, Turnkey doesn't send unencrypted tokens. We use the protocol above to send credentials to end-users with no man-in-the-middle risk. This ensures that even if the content of an auth email is leaked, an attacker cannot decrypt and use the underlying credential. The following diagram summarizes the email auth flow:
-
-
-
+
+
+
Our email auth flow works by anchoring on a **target encryption key** (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
@@ -111,9 +100,9 @@ Once the encrypted credential is received via email, it's decrypted where the ta
Our OTP flows work similarly, except the bundle is not emailed to the user directly. Instead, it is returned as part of the `OTP_AUTH` activity results.
-
-
-
+
+
+
## Security details
@@ -121,8 +110,8 @@ Our OTP flows work similarly, except the bundle is not emailed to the user direc
We achieve recipient authentication for both the server and client:
-- **Client to Server**: client verifies that the server's target key is signed by the enclave. This check is critical for import/export flows. If the client accepts key material (e.g. a wallet seed) from a malicious party, they might not realize they have the wrong wallet (compromised seed because known or with bad entropy). If the client encrypts their seed to a malicious party, they lose funds directly. This is NOT required for authentication flows: the client can afford to decrypt and use a bad API key. A bad API key will simply produce an invalid signature when used.
-- **Server to Client**: server relies on upstream checks by our policy engine, as well as the overall activity signing scheme to enforce rules that guarantee authenticity of the client's target key. Specifically, when the client sends their target public key, it is sent as part of a signed payload (the activity request), and that payload must be signed with an existing credential persisted in org data.
+* **Client to Server**: client verifies that the server's target key is signed by the enclave. This check is critical for import/export flows. If the client accepts key material (e.g. a wallet seed) from a malicious party, they might not realize they have the wrong wallet (compromised seed because known or with bad entropy). If the client encrypts their seed to a malicious party, they lose funds directly. This is NOT required for authentication flows: the client can afford to decrypt and use a bad API key. A bad API key will simply produce an invalid signature when used.
+* **Server to Client**: server relies on upstream checks by our policy engine, as well as the overall activity signing scheme to enforce rules that guarantee authenticity of the client's target key. Specifically, when the client sends their target public key, it is sent as part of a signed payload (the activity request), and that payload must be signed with an existing credential persisted in org data.
### Forward secrecy
diff --git a/docs/documentation/security/non-custodial-key-management.md b/security/non-custodial-key-mgmt.mdx
similarity index 86%
rename from docs/documentation/security/non-custodial-key-management.md
rename to security/non-custodial-key-mgmt.mdx
index 8f04ebca..bd67d9f8 100644
--- a/docs/documentation/security/non-custodial-key-management.md
+++ b/security/non-custodial-key-mgmt.mdx
@@ -1,22 +1,18 @@
---
-sidebar_position: 2
-description: Learn how Turnkey handles private keys
-slug: /security/non-custodial-key-mgmt
+title: "Non-Custodial Key Management"
---
-# Non-custodial key management
-
## Turnkey's non-custodial infrastructure
Turnkey has built a new model for private key management that utilizes secure enclaves — highly constrained compute environments that can cryptographically attest to the code running inside. All private key material is only decrypted within an enclave, and transaction signing happens according to customer-defined policies.
-This novel security architecture means raw private keys themselves are never exposed to Turnkey, your software, or your team. Specifically, Turnkey stores encrypted private keys that are only decrypted when you authenticate to an auditable, tamper-proof secure enclave with your secret (e.g., API key or Passkey credentials). You (and/or your end users, depending on your implementation) remain the owner of your private keys and the funds controlled by those private keys at all times. See [quorum deployments](./Quorum-deployment.md) for more details on how we provision secure enclaves to ensure you’re always in control of your private keys.
+This novel security architecture means raw private keys themselves are never exposed to Turnkey, your software, or your team. Specifically, Turnkey stores encrypted private keys that are only decrypted when you authenticate to an auditable, tamper-proof secure enclave with your secret (e.g., API key or Passkey credentials). You (and/or your end users, depending on your implementation) remain the owner of your private keys and the funds controlled by those private keys at all times. See [quorum deployments](/security/quorum-deployments) for more details on how we provision secure enclaves to ensure you’re always in control of your private keys.
Although we’re not a bank, by analogy to physical security, Turnkey’s role is similar to that of a safety deposit box operator. Turnkey secures, and facilitates access to, the safety deposit boxes (wallets), but only the customer can unlock the safety deposit box and access the contents (digital assets) inside.
## Private key storage
-Turnkey does not store unencrypted private keys, but rather persists encrypted private key ciphertext inside of our primary and disaster recovery databases. This ciphertext is only to be decrypted from within the bounds of a secure enclave running verified Turnkey applications. Our [security overview](./our-approach.md) goes over the process in depth.
+Turnkey does not store unencrypted private keys, but rather persists encrypted private key ciphertext inside of our primary and disaster recovery databases. This ciphertext is only to be decrypted from within the bounds of a secure enclave running verified Turnkey applications. Our [security overview](/security/our-approach) goes over the process in depth.
## Non-custodial wallets for end users
diff --git a/docs/documentation/security/our-approach.md b/security/our-approach.mdx
similarity index 77%
rename from docs/documentation/security/our-approach.md
rename to security/our-approach.mdx
index b00483fb..dd0a0d57 100644
--- a/docs/documentation/security/our-approach.md
+++ b/security/our-approach.mdx
@@ -1,13 +1,9 @@
---
-sidebar_position: 1
-description: Learn about Turnkey's unique security framework
-slug: /security/our-approach
+title: "Our Approach"
+description: "At Turnkey we’ve developed a security framework that allows us, and eventually our users, to prove that all systems with security critical workloads are running exactly the software we expect at any given time, no single engineer can access any enclave or reconstruct a secret, and the system is always safe as long as enclaves are not compromised."
+mode: wide
---
-# Our approach
-
-At Turnkey we’ve developed a security framework that allows us, and eventually our users, to prove that all systems with security critical workloads are running exactly the software we expect at any given time, no single engineer can access any enclave or reconstruct a secret, and the system is always safe as long as enclaves are not compromised.
-
At the highest level, Turnkey runs all critical workloads in **“Secure Enclaves,”** a type of Trusted Execution Environment. To run our secure enclaves we have built [QuorumOS](https://github.com/tkhq/qos) to give us the ability to remotely attest to the integrity of the machines and the code running. Every time we deploy, we attest to the code running in the enclaves prior to posting shares of core secrets into the enclave. This helps to **ensure that we can trust the secure applications running in the enclaves,** which perform actions such as private key generation, transaction signing, and policy evaluation.
QuorumOS gives us end-to-end transparency into the code we’re running and ensures **no single developer at Turnkey can alter or deploy enclaves, or reconstruct core secrets.**
diff --git a/docs/documentation/security/Quorum-deployment.md b/security/quorum-deployments.mdx
similarity index 80%
rename from docs/documentation/security/Quorum-deployment.md
rename to security/quorum-deployments.mdx
index e92e1ab1..d08c7b35 100644
--- a/docs/documentation/security/Quorum-deployment.md
+++ b/security/quorum-deployments.mdx
@@ -1,24 +1,16 @@
---
-description: Learn how we deploy our secure applications
-sidebar_position: 4
-slug: /security/quorum-deployments
+title: "Quorum Deployments"
+description: "To run our applications in secure enclaves, we built QuorumOS: a minimal, immutable, and deterministic Linux unikernel build system for use cases that require high security and accountability. QuorumOS also contains an initialization and attestation framework for running applications within this environment."
+mode: wide
---
-# Quorum deployments
-
-To run our applications in secure enclaves, we built QuorumOS: a minimal, immutable, and deterministic Linux unikernel build system for use cases that require high security and accountability. QuorumOS also contains an initialization and attestation framework for running applications within this environment.
-
Each instance of QOS is configured with a Quorum Set – A group of individuals who hold shares of a service’s master secret. This master secret is also called the Quorum Key.
After the service is launched, QOS responds to attestation requests and waits to receive key shares from QOS Operators or members of the Quorum Set. Once a threshold number of shares have been sent into the enclave it will reconstruct its core secret and launch the application it was provisioned with. This process is outlined in the images below:
-
-
-
+
+
+
Remote attestation is the process by which core attributes of a machine can be retrieved and verified from a remote location. The enclave’s secure co-processor, in this case AWS’s NSM, observes the enclave as it is being launched and records certain important values such as the hash of the enclave image, the hash of the kernel, and the hash of the boot filesystem. This means that each attestation request verifies that the code actually running in the enclave is only the code we expect to be running.
diff --git a/docs/documentation/security/vulnerability-reporting.md b/security/reporting-a-vulnerability.mdx
similarity index 54%
rename from docs/documentation/security/vulnerability-reporting.md
rename to security/reporting-a-vulnerability.mdx
index 79bea372..12e4fb95 100644
--- a/docs/documentation/security/vulnerability-reporting.md
+++ b/security/reporting-a-vulnerability.mdx
@@ -1,17 +1,10 @@
---
-description: Overview of Turnkey's responsible disclosure program
-slug: /security/reporting-a-vulnerability
-sidebar_position: 100
+title: "Reporting a Vulnerability"
+description: "Turnkey highly values the security of our software, services, and systems and we actively encourage the ethical reporting of any security vulnerabilities discovered. We invite researchers and users to report potential security vulnerabilities to our Bug Bounty Program via the form below, or to us via email at [security@turnkey.com](mailto:security@turnkey.com). When submitting a report via email, please provide a thorough description of the vulnerability, including steps to reproduce it and its potential impact."
+mode: wide
---
-# Reporting a vulnerability
-
-Turnkey highly values the security of our software, services, and systems and we actively encourage the ethical reporting of any security vulnerabilities discovered. We invite researchers and users to report potential security vulnerabilities to our Bug Bounty Program via the form below, or to us via email at [security@turnkey.com](mailto:security@turnkey.com). When submitting a report via email, please provide a thorough description of the vulnerability, including steps to reproduce it and its potential impact.
-
-If you believe you have found very serious vulnerability, we ask that you
-encrypt the message to the `security.turnkey.com` PGP key (FP: `AD6C 3E61 17A5
-886E 550E F8BB 3ACD E5EA 8DC7 9275`). This can also be found on Turnkey's
-website at `https://www.turnkey.com/.well-known/security.asc`
+If you believe you have found very serious vulnerability, we ask that you encrypt the message to the `security.turnkey.com` PGP key (FP: `AD6C 3E61 17A5 886E 550E F8BB 3ACD E5EA 8DC7 9275`). This can also be found on Turnkey's website at `https://www.turnkey.com/.well-known/security.asc`
Upon receiving a report, our team promptly assesses and prioritizes the vulnerability based on its severity and potential impact. We then take reasonable and appropriate steps to mitigate and remediate the identified risks in accordance with our internal policies and timelines. Where feasible, we will endeavor to keep the reporter informed throughout the process. Our approach is designed to ensure confidentiality and offer safe harbor to researchers, promising that those who report vulnerabilities ethically and in good faith will not face legal action.
@@ -19,10 +12,8 @@ We expect reporters to treat vulnerability reports submitted to Turnkey, along w
For further inquiries or more information about our program, please contact our security team at [security@turnkey.com](mailto:security@turnkey.com).
-import BugcrowdForm from '@site/src/components/BugcrowdForm';
-
-# Bug bounty submissions
+## Bug bounty submissions
Use the form below to directly submit vulnerabilities for triage and evaluation as part of our bug bounty program.
-
+
diff --git a/docs/documentation/security/Secure-enclaves.md b/security/secure-enclaves.mdx
similarity index 56%
rename from docs/documentation/security/Secure-enclaves.md
rename to security/secure-enclaves.mdx
index 7b4c3ecf..c7fa14b8 100644
--- a/docs/documentation/security/Secure-enclaves.md
+++ b/security/secure-enclaves.mdx
@@ -1,25 +1,17 @@
---
-description: Overview of secure enclaves and how we use them
-slug: /security/secure-enclaves
-sidebar_position: 3
+title: "Secure Enclaves"
+description: "A core security anchor at Turnkey is the ability to prove to ourselves and our users that all systems within secure enclaves are running exactly the software we expect at any given time. To accomplish this, all security-critical Turnkey services, which perform actions including key generation, signing, and our policy engine, are deployed in secure enclaves."
+mode: wide
---
-# Secure enclaves
-
-A core security anchor at Turnkey is the ability to prove to ourselves and our users that all systems within secure enclaves are running exactly the software we expect at any given time. To accomplish this, all security-critical Turnkey services, which perform actions including key generation, signing, and our policy engine, are deployed in secure enclaves.
-
Secure enclaves, also called Trusted Execution Environments, are highly constrained compute environments that support cryptographic attestation to verify the enclave’s identity and ensure that only authorized code is running. These enclaves operate in hardware-enforced isolation –– they have no persistent storage, no interactive access, and no external networking.
The following outlines the structure of a single enclave application:
-
-
-
+
+
+
-In this diagram _Host_ represents a standard AWS virtual machine. We run a basic application that receives traffic from the network and calls into the enclave. This creates a layer of insulation from our most secure environment and offers a convenient place to gather metrics and other operational information about the enclaves.
+In this diagram *Host* represents a standard AWS virtual machine. We run a basic application that receives traffic from the network and calls into the enclave. This creates a layer of insulation from our most secure environment and offers a convenient place to gather metrics and other operational information about the enclaves.
-_Enclave_ represents a machine with no external connectivity. The only connection it can have is a virtual serial connection to the host and its own secure co-processor. In AWS this is called the Nitro Security Module (NSM). This runs an instance of Turnkey’s enclave operating system, QuorumOS (QOS), and a secure application running on top of QOS.
+*Enclave* represents a machine with no external connectivity. The only connection it can have is a virtual serial connection to the host and its own secure co-processor. In AWS this is called the Nitro Security Module (NSM). This runs an instance of Turnkey’s enclave operating system, QuorumOS (QOS), and a secure application running on top of QOS.
diff --git a/security/verifiable-data.mdx b/security/verifiable-data.mdx
new file mode 100644
index 00000000..bfe6daa2
--- /dev/null
+++ b/security/verifiable-data.mdx
@@ -0,0 +1,13 @@
+---
+title: "Verifiable Data"
+description: "Enclave applications in Turnkey’s infrastructure are stateless meaning, there is no persistent data held behind the enclave boundary. Instead, data is held in a PostgreSQL instance in our primary AWS account. Before any enclave applications operate on the data in a Turnkey account, it first verifies that that data has been recently notarized by Turnkey’s notarizer. A recent stamp could be the result of an update or initiated by the heartbeat service."
+mode: wide
+---
+
+By verifying the authenticity of data using cryptographic signatures (no passwords!) and timestamping, we enable zero-risk data sharing between these apps and block man-in-the-middle or downgrade attacks. The combination of these features results in a system and an audit trail that is verifiable end-to-end.
+
+The entire Turnkey architecture including this verifiable data flow is described below:
+
+
+
+
diff --git a/security/whitepaper.mdx b/security/whitepaper.mdx
new file mode 100644
index 00000000..f791135f
--- /dev/null
+++ b/security/whitepaper.mdx
@@ -0,0 +1,7 @@
+---
+title: "The Turnkey Whitepaper"
+description: "We have published an in-depth whitepaper describing the principles with which we've built Turnkey and explaining in great detail the infrastructure foundations as well as the system architecture underpinning our product."
+mode: wide
+---
+
+Our whitepaper is available online at **[whitepaper.turnkey.com](https://whitepaper.turnkey.com)**.
diff --git a/sidebars.js b/sidebars.js
deleted file mode 100644
index 1688dac4..00000000
--- a/sidebars.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Creating a sidebar enables you to:
- - create an ordered group of docs
- - render a sidebar for each doc of that group
- - provide next/previous navigation
-
- The sidebars can be generated from the filesystem, or explicitly defined here.
-
- Create as many sidebars as you want.
- */
-
-// @ts-check
-
-/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
-const sidebars = {
- // Autogenerate sidebars for each documentation section
- sdksSidebar: [
- {
- type: "autogenerated",
- dirName: "sdks",
- },
- ],
- solutionsSidebar: [
- {
- type: "autogenerated",
- dirName: "solutions",
- },
- ],
- documentationSidebar: [
- {
- type: "doc",
- id: "welcome",
- label: "Welcome",
- },
- {
- type: "autogenerated",
- dirName: "documentation",
- },
- ],
-};
-
-module.exports = sidebars;
diff --git a/signing-automation/code-examples/signing-transactions.mdx b/signing-automation/code-examples/signing-transactions.mdx
new file mode 100644
index 00000000..0fbca2a7
--- /dev/null
+++ b/signing-automation/code-examples/signing-transactions.mdx
@@ -0,0 +1,49 @@
+---
+title: "Signing Transactions"
+description: "This is a guide to signing transactions in a server context. While these snippets leverage Ethers, it can be swapped out for other signers in the Viem or Solana contexts. An example for Ethers can be found , and for Viem in the server context. A similar example with Solana can be found ."
+mode: wide
+---
+
+
+
+
+```ts
+import { Turnkey } from "@turnkey/sdk-browser";
+
+const turnkeyClient = new Turnkey({
+ apiBaseUrl: "https://api.turnkey.com",
+ defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
+ apiPrivateKey: process.env.API_PRIVATE_KEY,
+ apiPublicKey: process.env.API_PUBLIC_KEY,
+});
+```
+
+
+
+
+```ts
+import { ethers } from "ethers";
+import { TurnkeySigner } from "@turnkey/ethers";
+
+const provider = new ethers.JsonRpcProvider();
+const turnkeySigner = new TurnkeySigner({
+ client: turnkeyClient.apiClient(),
+ organizationId: process.env.ORGANIZATION_ID!,
+ signWith: process.env.SIGN_WITH!,
+ });
+```
+
+
+
+
+```ts
+const transactionRequest = {
+ to: "",
+ value: ethers.parseEther(""),
+ type: 2,
+};
+const sendTransaction =
+ await connectedSigner.sendTransaction(transactionRequest);
+```
+
+
diff --git a/docs/solutions/signing-automation/overview.md b/signing-automation/overview.mdx
similarity index 79%
rename from docs/solutions/signing-automation/overview.md
rename to signing-automation/overview.mdx
index 1a12c419..d38fc2f0 100644
--- a/docs/solutions/signing-automation/overview.md
+++ b/signing-automation/overview.mdx
@@ -1,12 +1,8 @@
---
-sidebar_position: 1
-description: Intro to signing automation on Turnkey
-slug: /signing-automation/overview
-sidebar_label: Introduction
+title: "Signing Automation"
+sidebarTitle: "Introduction"
---
-# Signing Automation
-
## Securely automate any signing workflow
Turnkey empowers teams to automate complex signing workflows at scale without compromising on security or flexibility. Our API and policy engine provide the building blocks for creating custom automation solutions that fit your specific needs, from smart contract interactions to staking management.
@@ -15,11 +11,11 @@ Turnkey empowers teams to automate complex signing workflows at scale without co
Turnkey's secure, flexible, and scalable private key infrastructure is built on a custom secure-enclave-based architecture, ensuring the highest level of protection for your automated signing workflows. With Turnkey, you can:
-- Automate millions of signatures with customizable security controls
-- Implement role-based access controls and multi-party approvals
-- Support any blockchain or asset type with our chain-agnostic, arbitrary signing capabilities
-- Minimize attack surface area with our paranoid security model
-- Easily migrate existing wallets and keys in and out of Turnkey
+* Automate millions of signatures with customizable security controls
+* Implement role-based access controls and multi-party approvals
+* Support any blockchain or asset type with our chain-agnostic, arbitrary signing capabilities
+* Minimize attack surface area with our paranoid security model
+* Easily migrate existing wallets and keys in and out of Turnkey
### Code examples
@@ -38,6 +34,6 @@ Explore the following code examples to see Turnkey's server-side signing automat
Staking teams can use Turnkey to streamline their operations and secure their keys. Automate critical workflows like:
-- Mnemonic and key generation
-- Rewards claiming and distribution
-- Network voting and governance actions
+* Mnemonic and key generation
+* Rewards claiming and distribution
+* Network voting and governance actions
diff --git a/src/components/BugcrowdForm.js b/src/components/BugcrowdForm.js
deleted file mode 100644
index 5bcc8998..00000000
--- a/src/components/BugcrowdForm.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React, { useEffect } from "react";
-
-function BugcrowdForm() {
- useEffect(() => {
- const script = document.createElement("script");
- script.src =
- "https://bugcrowd.com/a9e1eca7-990e-4602-bf6d-a9f70df1c2fa/external/script";
- script.async = true;
- script.setAttribute(
- "data-bugcrowd-program",
- "https://bugcrowd.com/a9e1eca7-990e-4602-bf6d-a9f70df1c2fa/external/report",
- );
-
- // Find the container and append the script there
- const container = document.getElementById("bugcrowd-form-container");
- if (container) {
- container.appendChild(script);
- }
-
- return () => {
- if (container) {
- container.removeChild(script);
- }
- };
- }, []);
-
- return ;
-}
-
-export default BugcrowdForm;
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx
deleted file mode 100644
index 009b8cb4..00000000
--- a/src/components/Dropdown.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React, { useState } from "react";
-import "./dropdown.css";
-
-interface DropdownProps {
- title: string;
- children: React.ReactNode;
- className?: string;
-}
-
-export const Dropdown = ({
- title,
- children,
- className = "",
-}: DropdownProps) => {
- const [isOpen, setIsOpen] = useState(false);
-
- return (
-
-
- {isOpen &&
{children}
}
-
- );
-};
diff --git a/src/components/HomepageFeatures/.index.tsx.swp b/src/components/HomepageFeatures/.index.tsx.swp
deleted file mode 100644
index b3700986..00000000
Binary files a/src/components/HomepageFeatures/.index.tsx.swp and /dev/null differ
diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx
deleted file mode 100644
index ee86e919..00000000
--- a/src/components/HomepageFeatures/index.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from "react";
-import clsx from "clsx";
-import styles from "./styles.module.css";
-
-type FeatureItem = {
- title: string;
- Svg: React.ComponentType>;
- description: JSX.Element;
-};
-
-const FeatureList: FeatureItem[] = [
- {
- title: "Easy to Use",
- Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default,
- description: (
- <>
- Docusaurus was designed from the ground up to be easily installed and
- used to get your website up and running quickly.
- >
- ),
- },
- {
- title: "Focus on What Matters",
- Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default,
- description: (
- <>
- Docusaurus lets you focus on your docs, and we'll do the chores. Go
- ahead and move your docs into the docs directory.
- >
- ),
- },
- {
- title: "Powered by React",
- Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
- description: (
- <>
- Extend or customize your website layout by reusing React. Docusaurus can
- be extended while reusing the same header and footer.
- >
- ),
- },
-];
-
-function Feature({ title, Svg, description }: FeatureItem) {
- return (
-