> ## Documentation Index
> Fetch the complete documentation index at: https://namespace.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Revokable Tokens

> Create long-lived tenant credentials for systems outside your platform, and revoke them when access should end.

A revokable token is a long-lived credential for one tenant.
Use one when a credential has to leave your service, for example when a customer's CI pipeline needs to run builds in their tenant.
Unlike [tenant tokens](/docs/platform/authentication/tenant-tokens), revokable tokens can live for up to a year, and you can revoke them at any time.

Each revokable token has two parts:

* A **bearer token**, starting with `nsrt_`, that authenticates requests. Namespace returns it only once, when the token is created.
* A **token ID**, starting with `tok_`, that identifies the token when you list or revoke it. It is not a secret.

Revokable tokens are managed from inside a tenant, so every call on this page uses a tenant client.
The examples start from `tenantClient`, created from a [tenant token](/docs/platform/authentication/tenant-tokens#create-a-tenant-client).

## Create a revokable token

The request needs a name that is unique within the tenant, an expiry of up to one year, and at least one grant listing what the token can do.
This token can create and manage instances, which is all a CI pipeline needs:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { timestampFromDate } from "@bufbuild/protobuf/wkt";

  const created = await tenantClient.tokens.createRevokableToken({
    name: "ci-pipeline",
    description: "CI pipeline for Customer 1",
    expiresAt: timestampFromDate(new Date(Date.now() + 90 * 24 * 60 * 60 * 1000)),
    access: {
      grants: [
        {
          resourceType: "instance",
          resourceId: "*",
          actions: ["create", "list", "get", "destroy"],
        },
      ],
    },
  });

  const revokableBearerToken = created.bearerToken; // Store this securely now.
  const tokenId = created.token!.tokenId;
  ```

  ```go Go theme={null}
  import "google.golang.org/protobuf/types/known/timestamppb"

  created, err := tenantClient.Tokens.CreateRevokableToken(ctx, &iamv1beta.CreateRevokableTokenRequest{
  	Name:        "ci-pipeline",
  	Description: "CI pipeline for Customer 1",
  	ExpiresAt:   timestamppb.New(time.Now().Add(90 * 24 * time.Hour)),
  	Access: &iamv1beta.AccessPolicy{
  		Grants: []*iamv1beta.Permission{{
  			ResourceType: "instance",
  			ResourceId:   "*",
  			Actions:      []string{"create", "list", "get", "destroy"},
  		}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  revokableBearerToken := created.BearerToken // Store this securely now.
  tokenId := created.Token.TokenId
  ```
</CodeGroup>

Hand the bearer token to the system that needs it, for example as a secret in the customer's CI settings.
Namespace does not show it again. If it is lost, revoke the token and create a new one.

The [permissions reference](/docs/platform/workspaces/permissions) lists the resource types and actions you can grant.

## Use a revokable token

A revokable token works like any other bearer token.
Pass it to a client, and every request acts inside the tenant with the token's grants:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createComputeClient } from "@namespacelabs/sdk/api/compute";
  import { fromBearerToken } from "@namespacelabs/sdk/auth";

  const compute = createComputeClient({
    tokenSource: fromBearerToken(revokableBearerToken),
  });
  ```

  ```go Go theme={null}
  import "namespacelabs.dev/integrations/api/compute"

  computeClient, err := compute.NewClient(ctx, bearerTokenSource(revokableBearerToken))
  if err != nil {
  	log.Fatal(err)
  }
  defer computeClient.Close()
  ```
</CodeGroup>

<Info>
  In practice, the system that received the token, such as a CI pipeline, usually reads it from a secret or environment variable rather than keeping it in a variable.
</Info>

## List revokable tokens

Listing returns each token's ID, name, expiry, grants, and state, but never the bearer token.
By default, only active tokens are returned. Set `includeRevoked` to see revoked tokens as well.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const { tokens } = await tenantClient.tokens.listRevokableTokens({
    includeRevoked: true,
  });

  for (const token of tokens) {
    console.log(token.tokenId, token.name, token.revokedAt ? "revoked" : "active");
  }
  ```

  ```go Go theme={null}
  resp, err := tenantClient.Tokens.ListRevokableTokens(ctx, &iamv1beta.ListRevokableTokensRequest{
  	IncludeRevoked: true,
  })
  if err != nil {
  	log.Fatal(err)
  }

  for _, token := range resp.Tokens {
  	fmt.Println(token.TokenId, token.Name, token.State)
  }
  ```
</CodeGroup>

```text Output theme={null}
tok_oprvnfuhiu5rkjer28grfkitq6 ci-pipeline active
```

Results come back in pages. Pass each response's `paginationCursor` into the next request until a page comes back empty.

## Revoke a revokable token

Revoke a token by its ID.
The next request made with its bearer token fails as unauthenticated.

<CodeGroup>
  ```typescript TypeScript theme={null}
  await tenantClient.tokens.revokeRevokableToken({ tokenId });
  ```

  ```go Go theme={null}
  _, err = tenantClient.Tokens.RevokeRevokableToken(ctx, &iamv1beta.RevokeRevokableTokenRequest{
  	TokenId: tokenId,
  })
  ```
</CodeGroup>

A revoked token stays in the list when you set `includeRevoked`, with `revokedAt` recording when it was revoked and `revokedByActorId` identifying who revoked it.

## Tokens tied to a member

Every token above is tenant-scoped, which is the default.
A token can instead be tied to the member who creates it by setting `scope` to `TENANT_MEMBERSHIP_SCOPE`. Such a token stops working when that member leaves the tenant, and it is the only kind that can be created without an expiry.

This token has no `expiresAt`, so it stays valid until it is revoked or its creator leaves the tenant:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { RevokableToken_Scope } from "@namespacelabs/sdk/proto/namespace/cloud/iam/v1beta/tokens_pb";

  const created = await tenantClient.tokens.createRevokableToken({
    name: "local-development",
    description: "Local development for user 4821",
    scope: RevokableToken_Scope.TENANT_MEMBERSHIP_SCOPE,
    access: {
      grants: [{ resourceType: "instance", resourceId: "*", actions: ["list", "get"] }],
    },
  });
  ```

  ```go Go theme={null}
  created, err := tenantClient.Tokens.CreateRevokableToken(ctx, &iamv1beta.CreateRevokableTokenRequest{
  	Name:        "local-development",
  	Description: "Local development for user 4821",
  	Scope:       iamv1beta.RevokableToken_TENANT_MEMBERSHIP_SCOPE,
  	Access: &iamv1beta.AccessPolicy{
  		Grants: []*iamv1beta.Permission{{
  			ResourceType: "instance",
  			ResourceId:   "*",
  			Actions:      []string{"list", "get"},
  		}},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  ```
</CodeGroup>

You can still set `expiresAt` on a member-scoped token, with the same one-year limit as tenant-scoped tokens.

## Next steps

<Columns cols={2}>
  <Card title="Tenant tokens" icon="ticket" href="/docs/platform/authentication/tenant-tokens">
    Short-lived tokens for your own service.
  </Card>

  <Card title="Multi-tenancy" icon="building-2" href="/docs/platform/tenants">
    How tenants isolate your customers.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.