Skip to main content
The SDK supports two authentication modes: API key and JWT. API keys are the default and easiest way to get started. JWTs are an alternative for integrators that need finer-grained control over token lifetime, key rotation, or per-request sponsorship policies.
JWT authentication is experimental. The config option is prefixed with experimental_ and the API may change in future versions.

Why JWTs

JWTs are short-lived, asymmetrically-signed tokens (RS256 or ES256) issued by your backend. Compared to a long-lived API key, they let you:
  • Cut latency and backend code. Clients hit Rhinestone directly with a bounded access token instead of round-tripping through your server for every SDK call. For sponsored intents, a separate extension token binds your approval to the exact payload — so you stay in control per intent without proxying the submission.
  • Bound the blast radius of a leaked credential. Access tokens expire on a TTL you choose. An API key stays valid until you notice and rotate it.
  • Rotate signing keys without downtime. Register a new kid, start minting tokens with it, and tokens signed under the old kid keep verifying until they expire. No coordinated client deploy, no revocation race.
For unsponsored intents, the difference in hops looks like this:

Dashboard setup

Before you can issue JWTs, you need to register a signing key with Rhinestone. See JWT keys for how to generate or upload a key in the Dashboard. The Integrator ID you set there becomes the iss claim, and the Key ID becomes the kid header — both referenced in Configuration below.

Configuration

Signing a JWT requires five values. Three come from the Dashboard; the other two are free-form labels you pick yourself. integratorId, keyId, and projectId are verified server-side. The access token’s (iss, kid) must resolve to a registered key, and the token’s sub must equal the project that key was registered against. Mismatches produce a 401 or 403 at verification time — see Troubleshooting. appId is a free-form environment/app label — typically prod, staging, etc. It isn’t registered anywhere. The only server-side check is that the access token’s app_id matches the accompanying intent-extension token’s app_id, so a sponsorship approval issued for one deployment can’t be spent by another. Use it to correlate logs or split rate limits per deployment; if you don’t need that, use the same value everywhere.
keyId and appId are independent axes. keyId rotates on key rotation (same deployment, new signing key). appId rotates on environment changes (same key, new deployment). You can reuse one signing key across multiple environments, or rotate keys within a single environment.

SDK usage

There are two integration patterns depending on where the signing key lives.
When the SDK runs on the client (browser, mobile) and a separate backend holds the private key, fetch tokens from your backend over HTTP:
Your backend is responsible for issuing the two token types. See Sponsorship signing server for a drop-in implementation.

Sponsorship signing server

For the client-server pattern, your backend needs two endpoints: one that issues short-lived access tokens, and one that signs an intent extension token for each sponsored intent. The SDK ships ready-made handlers for both.

Web standard handlers

The Web Standard handlers accept a Request and return a Response. They work with Next.js App Router, Hono, SvelteKit, Remix, Deno, Bun, and Cloudflare Workers:

Express

For Express, use createExpressRouter. It mounts GET /access-token and POST /extension-token:

Custom sponsorship policy

By default the signer sponsors any intent your users submit. To restrict this, pass a shouldSponsor filter. Each predicate can be sync or async, and omitted predicates default to true. Filters are AND-composed — the intent must pass all of them to be signed:
The same shouldSponsor config can be passed to the handler factories (createAccessTokenHandler, createExtensionTokenHandler, createExpressRouter). Denied requests throw a SponsorshipDeniedError, which the handlers surface as a 403. When calling the signer directly, the error is instanceof-checkable:

Troubleshooting

Auth failures surface as HTTP 4xx responses with a message field. The common ones: If you’re hitting a 401 seconds after registering or rotating a key, the verification cache may briefly hold the old state — wait a minute and retry, or register under a fresh kid.