Tokens for scripts and machines
A bypass token is a credential for one app, made for something that can’t sign in: a CI job, a cron script, another service. It still goes through the edge, which checks it on every request and tells your app the caller is that token.
Create a token
Section titled “Create a token”jiayang token create acme/hello ciCreated bt_vtc7lz30r6658d5i (viewer). The secret is shown once; store it now.Call the app with it: curl -H 'Authorization: Bearer jyb_mgedwvsggwuxd53b5gjdv2qosz2gk3i6cw4unnw7nqnzbjylriuqdeo37o7' https://jiayang-apps.cloud/acme/hello/The name (ci here) is for you: it’s how the token shows up in lists and in the audit log. Make one token per script or service, so revoking one stops only that one.
The secret starts with jyb_ and is shown once. The platform keeps only a hash of it, so nobody can show it to you again. Put it in your CI’s secret settings or a secret manager, never in code.
Options
Section titled “Options”| Flag | What it does |
|---|---|
--role viewer |
The default. The app is told the caller is a viewer. |
--role editor |
The app is told the caller is an editor. A token can’t be an owner. |
--days <n> |
How many days it works for: 1 to 30, 30 by default. |
A token made from the CLI can’t outlive the CLI session that made it: its end date is capped at the session’s. A CLI session lasts 30 days from jiayang login, so a token made with it lasts 30 days at most, and --days asking for more is refused. It ends sooner when the session does: the command says when it stops working, and --json shows that as expires_at:
jiayang --json token create acme/hello nightly-sync --role editor --days 30{ "token": { "id": "bt_r2xhl2u419nd5zi9", "name": "nightly-sync", "role": "editor", "hint": "jyb_…r5dz", "created_by": "ana@example.com", "created_at": "2026-09-23T20:54:17.737234Z", "expires_at": "2026-10-23T20:54:16.881591Z", "last_used_at": null, "revoked_at": null }, "secret": "jyb_gg3c6y77qrjette4h3foas3nzutq4h5zkzgawywp7o6ur4zdjrjaa3zr5dz", "url": "https://jiayang-apps.cloud/acme/hello"}For a token that outlives your CLI session, make it in the dashboard. Open the app, go to Bypass tokens, and select New token. Pick a name, Viewer or Editor, and how long it lasts: 7, 30, 90 or 365 days. The dashboard asks you to sign in again if your last sign-in was more than 15 minutes ago.
Who can make one
Section titled “Who can make one”App editors and owners, and the workspace’s owners and admins, can create, list and revoke an app’s tokens. Creating one also needs you to have accepted the current terms of service in the dashboard.
Call the app
Section titled “Call the app”Send the secret as a bearer token to the apps domain, with the workspace and the app as the first two parts of the path:
curl -H "Authorization: Bearer $JIAYANG_TOKEN" https://jiayang-apps.cloud/acme/hello/The app gets the request without /acme/hello, so https://jiayang-apps.cloud/acme/hello/api/items reaches your app as /api/items.
jiayang curl does the same. Set JIAYANG_TOKEN from your secret store, then:
jiayang curl acme/helloHello, there.That’s the app from your first deploy, which greets whoever X-Jiayang-Email names, and a token has no email. An app that verifies the caller with the SDK gets the token’s id instead, as in What your app sees.
It prints the status on stderr: 200 OK. --token works too, but a flag lands in your shell history. Add --fail to exit with code 22 on a 4xx or 5xx, as curl does. A machine that has never run jiayang login sends the request to https://jiayang-apps.cloud; --apps-url, or JIAYANG_APPS_URL, points it somewhere else.
What the edge changes on the way
Section titled “What the edge changes on the way”- It removes your
Authorizationheader, so the token never reaches your app. - It removes cookies from the request, and
Set-Cookiefrom the response. - It adds
Content-Security-Policy: sandboxto the response. - It rewrites a relative redirect from your app, like
Location: /login, to stay under/acme/hello.
What your app sees
Section titled “What your app sees”Each request arrives with a fresh identity token in X-Jiayang-Identity, as it would for a person. For a bypass token, its kind is service, its subject is service: and the token’s id, and it has no email. The role is the token’s role, or viewer while the person who made it is only a viewer of the app: see When its maker loses access.
The hello example greets whoever called, which is how the output above reads service:bt_vtc7lz30r6658d5i.
To treat machines differently from people, check the kind:
import { requireUser } from "@jiayang-cloud/sdk";
const user = await requireUser(request, env);if (user.kind === "service") { // user.sub is "service:bt_…" and user.email is null}from jiayang import require_user
user = require_user(request.headers)if user.kind == "service": ... # user.sub is "service:bt_…" and user.email is Noneuser, err := jiayang.RequireUser(r)if err == nil && user.Kind == "service" { // user.Sub is "service:bt_…" and user.Email is ""}let user = verifier.require_user(&headers).await?;if user.kind == jiayang::Kind::Service { // user.sub is "service:bt_…" and user.email is None}The SDK pages cover setup for each language.
List tokens
Section titled “List tokens”jiayang token list acme/hellobt_r2xhl2u419nd5zi9 editor active nightly-syncbt_vtc7lz30r6658d5i viewer active ciA token past its expiry is listed as expired, and a revoked one as revoked. Neither works any more.
The list never shows a secret. jiayang --json token list acme/hello adds created_by, expires_at, revoked_at and last_used_at, and a hint with the secret’s last four characters, like jyb_…r5dz. last_used_at is updated at most once a minute.
Revoke a token
Section titled “Revoke a token”jiayang token revoke acme/hello bt_vtc7lz30r6658d5iRevoked bt_vtc7lz30r6658d5i. It stops working on the next request.The edge checks every token request with the control plane and keeps no answer, so the next call is refused. A WebSocket, stream or download already open with the token is checked every 10 seconds, and closed within 15 seconds of the revoke.
The next call:
jiayang curl acme/hellounauthorizedOn stderr: 401 Unauthorized.
A revoked token stays in jiayang token list, marked revoked. Revoking it again gets:
error: not found: acme/hello has no token bt_vtc7lz30r6658d5i still in use, or it isn't an app you can seeDeleting the app deletes its tokens too.
When its maker loses access
Section titled “When its maker loses access”A token can never do more than the person who made it can do now. The edge asks on every request, with no cache, so a change takes effect on the token’s next request. An open WebSocket, stream or download is closed within 15 seconds, as for a revoke.
- Editor or owner of the app, or an owner or admin of the workspace: the token works with the role it was made with.
- Viewer of the app, by a share or because the app is open to the whole workspace: the token works, but your app is told
viewer, even for an editor token. Make them an editor again and it’s an editor token again. - No access at all: the token is refused with
401 unauthorized, as if it were revoked.
The token is also revoked, and shows as revoked in jiayang token list and the dashboard, when its maker:
- is removed from the workspace, or leaves it
- is taken off the app, and the app isn’t open to the whole workspace
- is changed from owner or admin to member, and has no share of the app
- deletes their account
It’s revoked too when the app is closed to the workspace, or the workspace’s allowed email domains change, and that leaves its maker with no access. Any other token the edge refuses this way is revoked within the hour, logged as the platform’s with the reason creator_lost_access. A token revoked this way stays revoked. Giving its maker access again doesn’t bring it back: make a new one.
jiayang token list and the dashboard say when a token does less than it was made to, and why: a viewer's for now: ada@example.com can only view the app. In --json and the API, that’s effective_role (null for nothing) and capped_because: viewer_grant, workspace_member, no_grant, not_member, outside_domains or no_account.
You can only make a token you could use yourself. An owner or admin whose own address is outside the workspace’s allowed email domains is refused with 409 outside_domains, since the edge would refuse their token from the start.
So a token for a job that should outlive one person’s access is best made by someone who will keep theirs, like a workspace owner.
When a call is refused
Section titled “When a call is refused”| Status | Body | Why |
|---|---|---|
| 401 | unauthorized |
No token, one that isn’t well formed, one that was revoked or has expired, one whose app was deleted, one whose maker no longer has access to the app, or two credentials in one Authorization header. |
| 401 | unauthorized |
The token went to the app’s own address instead of the apps domain. |
| 403 | forbidden |
A token that still works was sent to another app, or to an address where no app exists or nothing is deployed. |
| 402 or 403 | A page | The workspace can’t serve requests right now: see Limits. |
Every refusal is in the audit log, with the reason.
Limits
Section titled “Limits”Each app has a limit on live tokens: 5 on Free, 50 on Team and 100 on Business. Revoked and expired tokens don’t count. At the limit:
error: This app has 5 live tokens, which is all the Free plan allows per app. Revoke one, or upgrade: Team allows 50 tokens per app. Billing: https://app.jiayang.cloud/w/acme/billingRequests made with a token count towards the workspace’s monthly requests, like a person’s. See Plans and billing.
In the audit log
Section titled “In the audit log”Creating and revoking a token are logged as token.create and token.revoke, with the token’s id. A token revoked because its maker lost access has a token.revoke of its own, by whoever made the change (the maker, if they left or deleted their account), with created_by and a reason: member_removed, member_left, grant_removed, role_changed, visibility_changed, domains_changed or account_deleted. Each request made with it shows the token’s id as the caller, including one refused because the token was revoked, has expired or its maker lost access, so you can see a leaked token being tried. Tried at another workspace’s app, it’s refused there without its id: that workspace’s log doesn’t learn it. In jiayang audit:
2026-09-23T20:54:17Z access allow (allowed) bt_vtc7lz30r6658d5i GET /acme/hello/jiayang watch shows the same request live:
20:54:17 ALLOW token:bt_vtc7lz30r6658d5i GET /acme/hello/ -Related
Section titled “Related”- Share an app: access for people.
- Public paths and webhooks: for a caller that can’t hold any token.
- Secrets and outbound calls: credentials your app uses to call other services.