Python & REST API setup instructions

This commit is contained in:
Konstantin Wohlwend 2026-06-03 17:12:18 -07:00
parent 841a1dd85b
commit 1e9e29d0bf
13 changed files with 1010 additions and 150 deletions

View File

@ -49,8 +49,8 @@ curl https://api.hexclave.com/api/v1/ \
| `X-Stack-Access-Token` | string | Client only | Optional. The current user's access token. Used to act on behalf of a specific user. |
<Info>
To see how to use these headers in various programming languages, see the
[examples](/guides/going-further/backend-integration).
To set up a backend in JavaScript, Python, or another language using the REST
API, see [Setup](/guides/getting-started/setup).
</Info>
## Getting Started

View File

@ -75,7 +75,6 @@
{
"group": "Going Further",
"pages": [
"guides/going-further/backend-integration",
"guides/going-further/cli",
"guides/going-further/local-vs-cloud-dashboard",
"guides/going-further/hexclave-config"
@ -298,6 +297,10 @@
{
"source": "/guides/going-further/stack-app",
"destination": "/sdk/objects/stack-app"
},
{
"source": "/guides/going-further/backend-integration",
"destination": "/guides/getting-started/setup"
}
]
}

View File

@ -278,6 +278,6 @@ Use a JWT viewer such as [jwt.io](https://jwt.io/) to inspect tokens and verify
## Related Concepts
* [API Keys](/guides/apps/api-keys/overview) - Alternative authentication method for server-to-server communication
* [Backend Integration](/guides/going-further/backend-integration) - How to verify JWTs in your backend
* [Setup](/guides/getting-started/setup) - How to verify user sessions in your backend
* [Permissions](/guides/apps/rbac/overview) - Understanding user permissions (not included in JWTs)
* [Teams](/guides/apps/teams/overview) - Understanding team context in JWTs

File diff suppressed because one or more lines are too long

View File

@ -1,127 +0,0 @@
---
title: "Integrating with Backends"
description: "Integrate Hexclave with your own server with the REST APIs"
sidebarTitle: "Integrating with Backends"
---
To authenticate your endpoints, you need to send the user's access token in the headers of the request to your server, and then make a request to Stack's server API to verify the user's identity.
## Sending requests to your server endpoints
To authenticate your own server endpoints using Stack's server API, you need to protect your endpoints by sending the user's access token in the headers of the request.
On the client side, you can retrieve the access token from the `user` object by calling `user.getAuthJson()`. This will return an object containing `accessToken`.
Then, you can call your server endpoint with these two tokens in the headers, like this:
```typescript
const { accessToken } = await user.getAuthJson();
const response = await fetch('/api/users/me', {
headers: {
'x-stack-access-token': accessToken,
},
// your other options and parameters
});
```
## Authenticating the user on the server endpoints
Hexclave provides two methods for authenticating users on your server endpoints:
1. **JWT Verification**: A fast, lightweight approach that validates the user's token locally without making external requests. While efficient, it provides only essential user information encoded in the JWT.
2. **REST API Verification**: Makes a request to Hexclave's servers to validate the token and retrieve comprehensive user information. This method provides access to the complete, up-to-date user profile.
### Using JWT
<Tabs>
<Tab title="Node.js">
```javascript
// you need to install the jose library if it's not already installed
import * as jose from 'jose';
// you can cache this and refresh it with a low frequency
const jwks = jose.createRemoteJWKSet(new URL("https://api.hexclave.com/api/v1/projects/<your-project-id>/.well-known/jwks.json"));
const accessToken = 'access token from the headers';
try {
const { payload } = await jose.jwtVerify(accessToken, jwks);
console.log('Authenticated user with ID:', payload.sub);
} catch (error) {
console.error(error);
console.log('Invalid user');
}
```
</Tab>
<Tab title="Python">
```python
# you need to install PyJWT and cryptography libraries if they're not already installed
# pip install PyJWT[crypto] requests
import jwt
import requests
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
# you can cache this and refresh it with a low frequency
jwks_client = PyJWKClient("https://api.hexclave.com/api/v1/projects/<your-project-id>/.well-known/jwks.json")
access_token = 'access token from the headers'
try:
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
payload = jwt.decode(
access_token,
signing_key.key,
algorithms=["ES256"],
audience="<your-project-id>"
)
print('Authenticated user with ID:', payload['sub'])
except Exception as error:
print(error)
print('Invalid user')
```
</Tab>
</Tabs>
### Using the REST API
<Tabs>
<Tab title="Node.js">
```javascript
const url = 'https://api.hexclave.com/api/v1/users/me';
const headers = {
'x-stack-access-type': 'server',
'x-stack-project-id': 'generated on the Hexclave dashboard',
'x-stack-secret-server-key': 'generated on the Hexclave dashboard',
'x-stack-access-token': 'access token from the headers',
};
const response = await fetch(url, { headers });
if (response.status === 200) {
console.log('User is authenticated', await response.json());
} else {
console.log('User is not authenticated', response.status, await response.text());
}
```
</Tab>
<Tab title="Python">
```python
import requests
url = 'https://api.hexclave.com/api/v1/users/me'
headers = {
'x-stack-access-type': 'server',
'x-stack-project-id': 'generated on the Hexclave dashboard',
'x-stack-secret-server-key': 'generated on the Hexclave dashboard',
'x-stack-access-token': 'access token from the headers',
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
print('User is authenticated', response.json())
else:
print('User is not authenticated', response.status_code, response.text)
```
</Tab>
</Tabs>

View File

@ -320,7 +320,7 @@ See [Payments](/guides/apps/payments/overview) for checkout and entitlement usag
`dbSync.externalDatabases` defines external databases that Hexclave can sync to.
For more information on connecting Hexclave with your own backend and database workflows, see [Integrating with Backends](/guides/going-further/backend-integration) and the [REST API overview](/api/overview).
For more information on connecting Hexclave with your own backend and database workflows, see [Setup](/guides/getting-started/setup) and the [REST API overview](/api/overview).
| Field | Type | Description |
| --- | --- | --- |

View File

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Python</title><path fill="#3776AB" d="M11.92 0c-.99 0-1.94.09-2.78.25-2.48.44-2.93 1.36-2.93 3.06v2.24h5.86v.75H4.02c-1.7 0-3.18 1.02-3.65 2.95-.54 2.21-.56 3.59 0 5.9.42 1.72 1.42 2.95 3.12 2.95h2.02v-2.68c0-1.93 1.67-3.63 3.65-3.63h5.85c1.63 0 2.93-1.35 2.93-3V3.31c0-1.6-1.35-2.8-2.93-3.06C14.01.08 12.91 0 11.92 0ZM8.75 1.8c.6 0 1.09.5 1.09 1.11 0 .6-.49 1.09-1.09 1.09-.61 0-1.1-.49-1.1-1.09 0-.61.49-1.11 1.1-1.11Z"/><path fill="#FFD43B" d="M12.15 24c.99 0 1.94-.09 2.78-.25 2.48-.44 2.93-1.36 2.93-3.06v-2.24H12v-.75h8.05c1.7 0 3.18-1.02 3.65-2.95.54-2.21.56-3.59 0-5.9-.42-1.72-1.42-2.95-3.12-2.95h-2.02v2.68c0 1.93-1.67 3.63-3.65 3.63H9.06c-1.63 0-2.93 1.35-2.93 3v5.48c0 1.6 1.35 2.8 2.93 3.06 1 .17 2.1.25 3.09.25Zm3.17-1.8c-.6 0-1.09-.5-1.09-1.11 0-.6.49-1.09 1.09-1.09.61 0 1.1.49 1.1 1.09 0 .61-.49 1.11-1.1 1.11Z"/></svg>

After

Width:  |  Height:  |  Size: 915 B

View File

@ -148,14 +148,14 @@ export const copyGeneratedSetupPrompt = async (event) => {
{/* Going Further */}
<div className="relative mb-10">
<div className="absolute -left-[2.55rem] top-0.5 h-4 w-4 rounded-full border-2 border-[#6b5df7] bg-[#6b5df7]" />
<SectionLink href="/guides/going-further/backend-integration">Going Further</SectionLink>
<SectionLink href="/guides/going-further/local-vs-cloud-dashboard">Going Further</SectionLink>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
Compare development flows, integrate your backend, and use lower-level interfaces where needed.
Compare development flows, configure your project, and use lower-level interfaces where needed.
</p>
<div className="mt-3 flex flex-wrap gap-2">
<ChipLink href="/guides/going-further/backend-integration">Backend Integration</ChipLink>
<ChipLink href="/guides/going-further/local-vs-cloud-dashboard">Local vs. Cloud</ChipLink>
<ChipLink href="/guides/going-further/hexclave-config">Config File</ChipLink>
<ChipLink href="/guides/going-further/cli">CLI</ChipLink>
</div>
</div>
@ -183,7 +183,7 @@ export const copyGeneratedSetupPrompt = async (event) => {
<div className="mt-3 flex flex-wrap gap-2">
<ChipLink href="/api/overview">Overview</ChipLink>
<ChipLink href="/guides/apps/webhooks/overview">Webhooks</ChipLink>
<ChipLink href="/guides/going-further/backend-integration">Backend Integration</ChipLink>
<ChipLink href="/guides/getting-started/setup">Backend Setup</ChipLink>
</div>
</div>
</div>

View File

@ -35,7 +35,6 @@ The full docs sidebar — generated from the live navigation. Fetch any of these
- [User Fundamentals](https://docs.hexclave.com/guides/getting-started/user-fundamentals)
- [AI Integration](https://docs.hexclave.com/guides/getting-started/ai-integration)
- **Going Further**
- [Backend Integration](https://docs.hexclave.com/guides/going-further/backend-integration)
- [CLI](https://docs.hexclave.com/guides/going-further/cli)
- [Local Vs Cloud Dashboard](https://docs.hexclave.com/guides/going-further/local-vs-cloud-dashboard)
- [Hexclave Config](https://docs.hexclave.com/guides/going-further/hexclave-config)
@ -132,6 +131,8 @@ To use it, you can use the sections below to set up Hexclave in the project. For
Follow these instructions in order to set up and get started with the Hexclave SDK in various languages.
Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli).
Not all steps are applicable to every type of application; for example, React apps have some extra steps that are not needed with other frameworks.
The frameworks and languages with explicit SDK support are:
@ -756,6 +757,262 @@ Follow these instructions to integrate Hexclave with Convex.
<Step title="Done!" />
</Steps>
## Python Backend Setup
Follow these instructions to authenticate requests to a Python backend with Hexclave.
This setup is for Python backends that do not use the JavaScript SDK. The backend flow is: your frontend sends the user's access token to your backend, and your backend verifies it before serving protected data.
<Steps titleSize="h3">
<Step title="Choose a project setup">
You can use either a development environment with the local dashboard or a Hexclave Cloud project.
<AccordionGroup>
<Accordion title="Option 1: Local dashboard (recommended)" defaultOpen>
If this project already has a `hexclave.config.ts` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new `hexclave.config.ts` file in your workspace:
```ts hexclave.config.ts
import type { HexclaveConfig } from "@hexclave/js";
export const config: HexclaveConfig = "show-onboarding";
```
Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables:
```json package.json
{
"scripts": {
"dev": "hexclave dev --config-file ./hexclave.config.ts -- <your-backend-dev-command>"
}
}
```
Your backend should read `HEXCLAVE_PROJECT_ID` and `HEXCLAVE_SECRET_SERVER_KEY` from the environment.
</Accordion>
<Accordion title="Option 2: Hexclave Cloud project">
Create or select a project on [app.hexclave.com](https://app.hexclave.com). Then copy the project ID and a secret server key into your backend environment:
```.env .env
HEXCLAVE_PROJECT_ID=<your-project-id>
HEXCLAVE_SECRET_SERVER_KEY=<your-secret-server-key>
```
The secret server key must only be available to your backend. Never expose it to browser code, mobile clients, logs, or public repositories.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Install backend dependencies">
Install `requests` for REST API verification. If you want to use JWT verification, also install `PyJWT[crypto]`.
```sh
pip install requests PyJWT[crypto]
```
</Step>
<Step title="Send the user's access token to your backend">
From your frontend, get the current user's access token and pass it to your backend endpoint.
```ts
// this is your frontend's code!
const { accessToken } = await user.getAuthJson();
const response = await fetch("<your-backend-endpoint>", {
headers: {
"x-stack-access-token": accessToken,
},
});
```
</Step>
<Step title="Verify the token">
Hexclave supports two backend verification approaches. JWT verification is faster and local to your backend. REST endpoint verification asks Hexclave to validate the token and return the current user object.
<AccordionGroup>
<Accordion title="Verify with JWT" defaultOpen>
JWT verification validates the token locally in your backend. It does not require a request to Hexclave on every call, but it only gives you the information contained in the token, such as the user ID.
```python
import os
import jwt
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
jwks_client = PyJWKClient(
f"https://api.hexclave.com/api/v1/projects/{os.environ['HEXCLAVE_PROJECT_ID']}/.well-known/jwks.json"
)
def get_current_user_id_from_jwt(request):
access_token = request.headers.get("x-stack-access-token")
if not access_token:
return None
try:
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
payload = jwt.decode(
access_token,
signing_key.key,
algorithms=["ES256"],
audience=os.environ["HEXCLAVE_PROJECT_ID"],
)
return payload["sub"]
except InvalidTokenError:
return None
```
</Accordion>
<Accordion title="Verify with the Hexclave REST endpoint">
REST endpoint verification asks Hexclave to validate the token and returns the current user object. Use this when you want the complete, up-to-date user profile or do not want to implement JWT verification yourself.
```python
import os
import requests
def get_current_hexclave_user(request):
access_token = request.headers.get("x-stack-access-token")
if not access_token:
return None
response = requests.get(
"https://api.hexclave.com/api/v1/users/me",
headers={
"x-stack-access-type": "server",
"x-stack-project-id": os.environ["HEXCLAVE_PROJECT_ID"],
"x-stack-secret-server-key": os.environ["HEXCLAVE_SECRET_SERVER_KEY"],
"x-stack-access-token": access_token,
},
timeout=10,
)
if response.status_code == 200:
return response.json()
return None
```
If the response is `200 OK`, the user is authenticated. If the response is not `200 OK`, treat the request as unauthenticated.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Protect authenticated endpoints">
Wrap your protected endpoints with a helper that extracts `x-stack-access-token`, verifies it with either JWT verification or REST API verification, and returns `401 Unauthorized` when verification fails.
<Note>
Disable HTTP caching for authenticated responses with a header like `Cache-Control: private, no-store`.
</Note>
</Step>
<Step title="Done!" />
</Steps>
## Other Backend Setup (REST API)
Follow these instructions to authenticate requests from any backend language using Hexclave's REST API.
Use this option when your backend is not JavaScript/TypeScript or Python, or when you want to call Hexclave over plain HTTP. The backend flow is: your frontend sends the user's access token to your backend, and your backend verifies it before serving protected data.
<Steps titleSize="h3">
<Step title="Choose a project setup">
You can use either a development environment with the local dashboard or a Hexclave Cloud project.
<AccordionGroup>
<Accordion title="Option 1: Local dashboard (recommended)" defaultOpen>
If this project already has a `hexclave.config.ts` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new `hexclave.config.ts` file in your workspace:
```ts hexclave.config.ts
import type { HexclaveConfig } from "@hexclave/js";
export const config: HexclaveConfig = "show-onboarding";
```
Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables:
```json package.json
{
"scripts": {
"dev": "hexclave dev --config-file ./hexclave.config.ts -- <your-backend-dev-command>"
}
}
```
Your backend should read `HEXCLAVE_PROJECT_ID` and `HEXCLAVE_SECRET_SERVER_KEY` from the environment.
</Accordion>
<Accordion title="Option 2: Hexclave Cloud project">
Create or select a project on [app.hexclave.com](https://app.hexclave.com). Then copy the project ID and a secret server key into your backend environment:
```.env .env
HEXCLAVE_PROJECT_ID=<your-project-id>
HEXCLAVE_SECRET_SERVER_KEY=<your-secret-server-key>
```
The secret server key must only be available to your backend. Never expose it to browser code, mobile clients, logs, or public repositories.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Send the user's access token to your backend">
From your frontend, get the current user's access token and pass it to your backend endpoint.
```ts
// this is your frontend's code!
const { accessToken } = await user.getAuthJson();
const response = await fetch("<your-backend-endpoint>", {
headers: {
"x-stack-access-token": accessToken,
},
});
```
</Step>
<Step title="Verify the token">
Hexclave supports two backend verification approaches. JWT verification is faster and local to your backend. REST endpoint verification asks Hexclave to validate the token and return the current user object.
<AccordionGroup>
<Accordion title="Verify with JWT" defaultOpen>
JWT verification validates the token locally in your backend. It does not require a request to Hexclave on every call, but it only gives you the information contained in the token, such as the user ID.
```text
1. Read the access token from the `x-stack-access-token` header.
2. Fetch the JWKS from:
https://api.hexclave.com/api/v1/projects/<your-project-id>/.well-known/jwks.json
3. Verify the JWT signature with an ES256-capable JWT library.
4. Verify the token audience is your Hexclave project ID.
5. Use the `sub` claim as the authenticated user ID.
6. Reject the request if any verification step fails.
```
</Accordion>
<Accordion title="Verify with the Hexclave REST endpoint">
REST endpoint verification asks Hexclave to validate the token and returns the current user object. Use this when you want the complete, up-to-date user profile or do not want to implement JWT verification yourself.
```sh
curl https://api.hexclave.com/api/v1/users/me \
-H "x-stack-access-type: server" \
-H "x-stack-project-id: $HEXCLAVE_PROJECT_ID" \
-H "x-stack-secret-server-key: $HEXCLAVE_SECRET_SERVER_KEY" \
-H "x-stack-access-token: <access-token-from-request>"
```
If the response is `200 OK`, the user is authenticated. If the response is not `200 OK`, treat the request as unauthenticated.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Protect authenticated endpoints">
Wrap your protected endpoints with a helper that extracts `x-stack-access-token`, verifies it with either JWT verification or REST API verification, and returns `401 Unauthorized` when verification fails.
<Note>
Disable HTTP caching for authenticated responses with a header like `Cache-Control: private, no-store`.
</Note>
</Step>
<Step title="Done!" />
</Steps>
## CLI Setup
Follow these instructions to authenticate users in a command line application with Hexclave.

File diff suppressed because one or more lines are too long

View File

@ -333,6 +333,198 @@ export const cliSetupPrompt = deindent`
</Steps>
`;
function getRestBackendSetupPrompt(kind: "python" | "rest-api") {
const isPython = kind === "python";
const title = isPython ? "Python Backend Setup" : "Other Backend Setup (REST API)";
const intro = isPython
? "Follow these instructions to authenticate requests to a Python backend with Hexclave."
: "Follow these instructions to authenticate requests from any backend language using Hexclave's REST API.";
const useCase = isPython
? "This setup is for Python backends that do not use the JavaScript SDK."
: "Use this option when your backend is not JavaScript/TypeScript or Python, or when you want to call Hexclave over plain HTTP.";
const dependencyStep = isPython ? deindent`
<Step title="Install backend dependencies">
Install \`requests\` for REST API verification. If you want to use JWT verification, also install \`PyJWT[crypto]\`.
\`\`\`sh
pip install requests PyJWT[crypto]
\`\`\`
</Step>
` : "";
const jwtVerification = isPython ? deindent`
\`\`\`python
import os
import jwt
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
jwks_client = PyJWKClient(
f"https://api.hexclave.com/api/v1/projects/{os.environ['HEXCLAVE_PROJECT_ID']}/.well-known/jwks.json"
)
def get_current_user_id_from_jwt(request):
access_token = request.headers.get("x-stack-access-token")
if not access_token:
return None
try:
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
payload = jwt.decode(
access_token,
signing_key.key,
algorithms=["ES256"],
audience=os.environ["HEXCLAVE_PROJECT_ID"],
)
return payload["sub"]
except InvalidTokenError:
return None
\`\`\`
` : deindent`
\`\`\`text
1. Read the access token from the \`x-stack-access-token\` header.
2. Fetch the JWKS from:
https://api.hexclave.com/api/v1/projects/<your-project-id>/.well-known/jwks.json
3. Verify the JWT signature with an ES256-capable JWT library.
4. Verify the token audience is your Hexclave project ID.
5. Use the \`sub\` claim as the authenticated user ID.
6. Reject the request if any verification step fails.
\`\`\`
`;
const restVerification = isPython ? deindent`
\`\`\`python
import os
import requests
def get_current_hexclave_user(request):
access_token = request.headers.get("x-stack-access-token")
if not access_token:
return None
response = requests.get(
"https://api.hexclave.com/api/v1/users/me",
headers={
"x-stack-access-type": "server",
"x-stack-project-id": os.environ["HEXCLAVE_PROJECT_ID"],
"x-stack-secret-server-key": os.environ["HEXCLAVE_SECRET_SERVER_KEY"],
"x-stack-access-token": access_token,
},
timeout=10,
)
if response.status_code == 200:
return response.json()
return None
\`\`\`
` : deindent`
\`\`\`sh
curl https://api.hexclave.com/api/v1/users/me \\
-H "x-stack-access-type: server" \\
-H "x-stack-project-id: $HEXCLAVE_PROJECT_ID" \\
-H "x-stack-secret-server-key: $HEXCLAVE_SECRET_SERVER_KEY" \\
-H "x-stack-access-token: <access-token-from-request>"
\`\`\`
`;
return deindent`
## ${title}
${intro}
${useCase} The backend flow is: your frontend sends the user's access token to your backend, and your backend verifies it before serving protected data.
<Steps titleSize="h3">
<Step title="Choose a project setup">
You can use either a development environment with the local dashboard or a Hexclave Cloud project.
<AccordionGroup>
<Accordion title="Option 1: Local dashboard (recommended)" defaultOpen>
If this project already has a \`hexclave.config.ts\` file for another frontend or backend, reuse that same file so the whole project shares one Hexclave config. Otherwise, create a new \`hexclave.config.ts\` file in your workspace:
\`\`\`ts hexclave.config.ts
import type { HexclaveConfig } from "@hexclave/js";
export const config: HexclaveConfig = "show-onboarding";
\`\`\`
Run your backend through the Hexclave CLI so it starts the local dashboard and injects the Hexclave environment variables:
\`\`\`json package.json
{
"scripts": {
"dev": "hexclave dev --config-file ./hexclave.config.ts -- <your-backend-dev-command>"
}
}
\`\`\`
Your backend should read \`HEXCLAVE_PROJECT_ID\` and \`HEXCLAVE_SECRET_SERVER_KEY\` from the environment.
</Accordion>
<Accordion title="Option 2: Hexclave Cloud project">
Create or select a project on [app.hexclave.com](https://app.hexclave.com). Then copy the project ID and a secret server key into your backend environment:
\`\`\`.env .env
HEXCLAVE_PROJECT_ID=<your-project-id>
HEXCLAVE_SECRET_SERVER_KEY=<your-secret-server-key>
\`\`\`
The secret server key must only be available to your backend. Never expose it to browser code, mobile clients, logs, or public repositories.
</Accordion>
</AccordionGroup>
</Step>
${dependencyStep}
<Step title="Send the user's access token to your backend">
From your frontend, get the current user's access token and pass it to your backend endpoint.
\`\`\`ts
// this is your frontend's code!
const { accessToken } = await user.getAuthJson();
const response = await fetch("<your-backend-endpoint>", {
headers: {
"x-stack-access-token": accessToken,
},
});
\`\`\`
</Step>
<Step title="Verify the token">
Hexclave supports two backend verification approaches. JWT verification is faster and local to your backend. REST endpoint verification asks Hexclave to validate the token and return the current user object.
<AccordionGroup>
<Accordion title="Verify with JWT" defaultOpen>
JWT verification validates the token locally in your backend. It does not require a request to Hexclave on every call, but it only gives you the information contained in the token, such as the user ID.
${jwtVerification}
</Accordion>
<Accordion title="Verify with the Hexclave REST endpoint">
REST endpoint verification asks Hexclave to validate the token and returns the current user object. Use this when you want the complete, up-to-date user profile or do not want to implement JWT verification yourself.
${restVerification}
If the response is \`200 OK\`, the user is authenticated. If the response is not \`200 OK\`, treat the request as unauthenticated.
</Accordion>
</AccordionGroup>
</Step>
<Step title="Protect authenticated endpoints">
Wrap your protected endpoints with a helper that extracts \`x-stack-access-token\`, verifies it with either JWT verification or REST API verification, and returns \`401 Unauthorized\` when verification fails.
<Note>
Disable HTTP caching for authenticated responses with a header like \`Cache-Control: private, no-store\`.
</Note>
</Step>
<Step title="Done!" />
</Steps>
`;
}
export const pythonBackendSetupPrompt = getRestBackendSetupPrompt("python");
export const restApiBackendSetupPrompt = getRestBackendSetupPrompt("rest-api");
export const aiAgentConfigPreparationPrompt = deindent`
## AI Agent Configuration
@ -412,6 +604,8 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
Follow these instructions in order to set up and get started with the Hexclave SDK ${typeLabel ? `for ${typeLabel} ` : "in various languages"}.
Note: These instructions are for setting up the Hexclave SDK to build your own CLIs. If you're looking to use the Hexclave CLI instead, see the [CLI documentation](https://docs.hexclave.com/guides/going-further/cli).
${isAiPrompt ? "Not all steps are applicable to every type of application; for example, React apps have some extra steps that are not needed with other frameworks." : ""}
${isAiPrompt ? deindent`
@ -841,6 +1035,10 @@ export const aiSetupPrompt = deindent`
${supabaseSetupPrompt}
${pythonBackendSetupPrompt}
${restApiBackendSetupPrompt}
${cliSetupPrompt}
${aiAgentConfigPreparationPrompt}

View File

@ -77,7 +77,6 @@ const docsJson = {
{
"group": "Going Further",
"pages": [
"guides/going-further/backend-integration",
"guides/going-further/cli",
"guides/going-further/local-vs-cloud-dashboard",
"guides/going-further/hexclave-config"
@ -308,6 +307,10 @@ const docsJson = {
{
"source": "/guides/going-further/stack-app",
"destination": "/sdk/objects/stack-app"
},
{
"source": "/guides/going-further/backend-integration",
"destination": "/guides/getting-started/setup"
}
]
} as const;

View File

@ -1,6 +1,6 @@
import path from "path";
import { readFileSync } from "fs";
import { aiSetupPrompt, cliSetupPrompt, convexSetupPrompt, getSdkSetupPrompt, supabaseSetupPrompt } from "../packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt";
import { aiSetupPrompt, cliSetupPrompt, convexSetupPrompt, getSdkSetupPrompt, pythonBackendSetupPrompt, restApiBackendSetupPrompt, supabaseSetupPrompt } from "../packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt";
import { deindent } from "../packages/stack-shared/src/utils/strings";
import { writeFileSyncIfChanged } from "./utils";
import { remindersPrompt } from "../packages/stack-shared/src/ai/unified-prompts/reminders";
@ -11,6 +11,7 @@ type SdkSetupToolCategory = "frontend" | "backend" | "database" | "other";
type SdkSetupTool = {
label: string,
where: SdkSetupToolCategory[],
filterGroups: ("js" | "python" | "other")[],
imageUrl: string,
monochromeLogo: boolean,
tabs: { label: string, mdContent: string }[],
@ -25,6 +26,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
nextjs: {
label: "Next.js",
where: ["frontend", "backend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/nextjs.svg",
monochromeLogo: true,
tabs: [{
@ -36,6 +38,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
react: {
label: "React",
where: ["frontend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/react.svg",
monochromeLogo: false,
tabs: [{
@ -47,6 +50,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
js: {
label: "Other JS/TS",
where: ["frontend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/javascript.svg",
monochromeLogo: false,
tabs: [{
@ -58,6 +62,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
"tanstack-start": {
label: "Tanstack Start",
where: ["frontend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/tanstack.svg",
monochromeLogo: true,
tabs: [{
@ -69,6 +74,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
"tanstack-query": {
label: "Tanstack Query",
where: ["frontend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/tanstack.svg",
monochromeLogo: true,
tabs: [],
@ -77,6 +83,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
nodejs: {
label: "Node.js",
where: ["backend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/nodejs.svg",
monochromeLogo: false,
tabs: [{
@ -88,6 +95,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
bun: {
label: "Bun",
where: ["backend"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/bun.svg",
monochromeLogo: false,
tabs: [{
@ -96,9 +104,34 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
}],
extraFeatures: [],
},
python: {
label: "Python",
where: ["backend"],
filterGroups: ["python"],
imageUrl: "/images/setup-tools/python.svg",
monochromeLogo: false,
tabs: [{
label: "Python",
mdContent: pythonBackendSetupPrompt,
}],
extraFeatures: [],
},
"rest-api": {
label: "Other (REST API)",
where: ["backend"],
filterGroups: ["other"],
imageUrl: "/images/setup-tools/cli.svg",
monochromeLogo: true,
tabs: [{
label: "Other (REST API)",
mdContent: restApiBackendSetupPrompt,
}],
extraFeatures: [],
},
convex: {
label: "Convex",
where: ["backend", "database"],
filterGroups: ["js"],
imageUrl: "/images/setup-tools/convex.svg",
monochromeLogo: false,
tabs: [{
@ -110,6 +143,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
supabase: {
label: "Supabase",
where: ["database"],
filterGroups: ["other"],
imageUrl: "/images/setup-tools/supabase.svg",
monochromeLogo: false,
tabs: [{
@ -121,6 +155,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
cli: {
label: "CLI",
where: ["other"],
filterGroups: ["other"],
imageUrl: "/images/setup-tools/cli.svg",
monochromeLogo: true,
tabs: [{
@ -132,6 +167,7 @@ const sdkSetupTools: Record<string, SdkSetupTool> = {
/*mcp: {
label: "MCP",
where: ["other"],
filterGroups: ["other"],
imageUrl: "/images/setup-tools/mcp.svg",
monochromeLogo: true,
tabs: [{
@ -201,6 +237,7 @@ function renderToolCards(category: SdkSetupToolCategory) {
data-tool-label="${tool.label}"
data-tool-has-tabs="${hasTabs ? "true" : "false"}"
data-tool-extra-features="${tool.extraFeatures.join(",")}"
data-tool-filter-groups="${tool.filterGroups.join(",")}"
onClick={onSetupToolClick}
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
title="${tool.label}"
@ -222,7 +259,7 @@ function renderToolCards(category: SdkSetupToolCategory) {
function renderToolCategory(category: SdkSetupToolCategory) {
return deindent`
<section className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<section data-setup-tool-category="true" className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">${categoryLabels.get(category)}</h3>
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
${renderToolCards(category)}
@ -231,6 +268,21 @@ function renderToolCategory(category: SdkSetupToolCategory) {
`;
}
function renderSetupFilterButton(filterId: string, label: string) {
return deindent`
<button
type="button"
aria-pressed="true"
data-setup-filter-button="true"
data-filter-id="${filterId}"
onClick={onSetupFilterClick}
className="inline-flex items-center justify-center rounded-full px-2.5 py-1 text-xs font-medium text-[#526994] transition-colors duration-150 hover:transition-none hover:bg-white/45 hover:text-[#2a4272] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/70 aria-pressed:text-[#243d70] aria-pressed:shadow-sm dark:text-[#9eb3d8] dark:hover:bg-white/10 dark:hover:text-white dark:aria-pressed:bg-white/14 dark:aria-pressed:text-white"
>
${label}
</button>
`;
}
function renderMarkdownInTabPanel(mdContent: string) {
return mdContent.replace(/<Note>\n([\s\S]*?)\n\s*<\/Note>/g, (_match, noteContent: string) => {
const blockquoteContent = deindent(noteContent)
@ -366,8 +418,28 @@ writeFileSyncIfChanged(
};
export const updateSetupBuilder = (root, syncUrl = true) => {
const activeFilterIds = new Set(
Array.from(root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']"))
.map((button) => button.getAttribute("data-filter-id"))
.filter((filterId) => filterId != null)
);
for (const toolCard of root.querySelectorAll("[data-setup-tool-card='true']")) {
const filterGroups = (toolCard.getAttribute("data-tool-filter-groups") ?? "")
.split(",")
.filter((filterGroup) => filterGroup.length > 0);
const shouldShow = filterGroups.some((filterGroup) => activeFilterIds.has(filterGroup));
toolCard.hidden = !shouldShow;
toolCard.style.display = shouldShow ? "" : "none";
}
for (const toolCategory of root.querySelectorAll("[data-setup-tool-category='true']")) {
const hasVisibleCard = Array.from(toolCategory.querySelectorAll("[data-setup-tool-card='true']"))
.some((toolCard) => !toolCard.hidden);
toolCategory.hidden = !hasVisibleCard;
toolCategory.style.display = hasVisibleCard ? "" : "none";
}
const selectedToolIds = new Set(
Array.from(root.querySelectorAll("[data-setup-tool-card='true'][aria-pressed='true']"))
.filter((card) => !card.hidden)
.map((card) => card.getAttribute("data-tool-id"))
.filter((toolId) => toolId != null)
);
@ -436,6 +508,21 @@ writeFileSyncIfChanged(
updateSetupBuilder(root);
};
export const onSetupFilterClick = (event) => {
const button = event.currentTarget;
const root = button.closest("[data-setup-builder='true']");
if (root == null) {
return;
}
const nextPressed = button.getAttribute("aria-pressed") !== "true";
const currentlyPressedFilters = root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']").length;
if (!nextPressed && currentlyPressedFilters <= 1) {
return;
}
button.setAttribute("aria-pressed", nextPressed ? "true" : "false");
updateSetupBuilder(root);
};
<Visibility for="agents">
{hexclaveAgentRemindersText}
</Visibility>
@ -461,6 +548,14 @@ writeFileSyncIfChanged(
</div>
<div className="not-prose mt-5 space-y-4 rounded-2xl border border-[#d6e4ff] bg-gradient-to-b from-[#f7faff] to-[#eaf2ff] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_30px_-24px_rgba(47,79,140,0.35)] dark:border-[#1f2d45] dark:from-[#11203a] dark:to-[#070f1f] dark:shadow-[inset_0_1px_0_rgba(112,152,224,0.18),0_16px_34px_-24px_rgba(2,8,20,0.85)] sm:p-4">
<div className="flex flex-col gap-2 border-b border-[#d6e4ff]/80 pb-3 dark:border-[#263a5f] sm:flex-row sm:items-center">
<span className="text-sm font-medium text-[#526994] dark:text-[#9eb3d8]">Filter:</span>
<div className="flex flex-wrap gap-2">
${renderSetupFilterButton("js", "JS")}
${renderSetupFilterButton("python", "Python")}
${renderSetupFilterButton("other", "Other")}
</div>
</div>
${renderToolCategory("frontend")}
${renderToolCategory("backend")}
${renderToolCategory("database")}