> ## 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.

# Partner Credentials

> Sign short-lived partner tokens with your private key and create a client that manages your tenants.

Partner credentials identify your platform to Namespace.
Instead of a shared secret, your platform signs a short-lived JSON Web Token (JWT) with its private key, and Namespace verifies the signature with the matching public key it holds for your platform.
The private key never leaves your infrastructure.

This page shows how to sign those tokens and create a partner client, which the [tenant management pages](/docs/platform/tenants) use for every operation.

## Before you start

You need four values from your partner setup:

| Value | Example | Used as |
| - | - | - |
| Partner ID | `user_01abcdefghjkmnpqrstvwxyz00` | The token's `sub` claim |
| Issuer | `https://auth.example.com` | The token's `iss` claim |
| Key ID | `0123456789abcdef0123456789abcdef01234567` | The token's `kid` header |
| Private key | A P-256 (ES256) key in PEM format | Signs the token |

<Warning>
  Keep the private key in a secret store. Never send it to Namespace, log it, pass it on a command line, or commit it. Namespace only needs the public key.
</Warning>

### Become a partner

To become a Namespace partner, contact [support@namespace.so](mailto:support@namespace.so).
The Namespace team sets up your partner account, registers your public key, and gives you your partner ID, issuer, and key ID.

While you wait, you can start building the parts of your platform that run inside a tenant with your own workspace. See [Build on your own workspace](/docs/platform/authentication/local-development).

## Create a partner client

<Steps titleSize="h3">
  <Step title="Add a client library">
    <CodeGroup>
      ```bash TypeScript theme={null}
      npm install @namespacelabs/sdk @bufbuild/protobuf
      ```

      ```bash Go theme={null}
      go get namespacelabs.dev/integrations github.com/golang-jwt/jwt/v4
      ```
    </CodeGroup>
  </Step>

  <Step title="Sign partner tokens">
    A partner token is an ES256-signed JWT with these fields, prefixed with `oidc_`:

    | Field | Value |
    | - | - |
    | `kid` (header) | Your key ID |
    | `iss` | Your issuer |
    | `sub` | Your partner ID |
    | `aud` | `namespace.so` |
    | `iat` | The current time |
    | `exp` | A short expiry, such as 20 minutes |

    Wrap the signing in a token source.
    The client asks the token source for a token when it needs one, and asks again when the current token is about to expire, so a long-running service always sends a valid token.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      import { sign } from "node:crypto";
      import { readFileSync } from "node:fs";
      import type { TokenSource } from "@namespacelabs/sdk/auth";

      const partnerId = "user_01abcdefghjkmnpqrstvwxyz00";
      const issuer = "https://auth.example.com";
      const keyId = "0123456789abcdef0123456789abcdef01234567";
      const privateKey = readFileSync("partner-key.pem");

      function base64url(value: object): string {
        return Buffer.from(JSON.stringify(value)).toString("base64url");
      }

      const partnerTokenSource: TokenSource = {
        async issueToken() {
          const now = Math.floor(Date.now() / 1000);
          const header = base64url({ alg: "ES256", typ: "JWT", kid: keyId });
          const claims = base64url({
            iss: issuer,
            sub: partnerId,
            aud: "namespace.so",
            iat: now,
            exp: now + 20 * 60,
          });
          const signature = sign("sha256", Buffer.from(`${header}.${claims}`), {
            key: privateKey,
            dsaEncoding: "ieee-p1363",
          });

          return `oidc_${header}.${claims}.${signature.toString("base64url")}`;
        },
      };
      ```

      ```go Go theme={null}
      import (
      	"context"
      	"crypto/ecdsa"
      	"time"

      	"github.com/golang-jwt/jwt/v4"
      )

      const (
      	partnerID = "user_01abcdefghjkmnpqrstvwxyz00"
      	issuer    = "https://auth.example.com"
      	keyID     = "0123456789abcdef0123456789abcdef01234567"
      )

      type partnerTokenSource struct {
      	privateKey *ecdsa.PrivateKey
      }

      func (p partnerTokenSource) IssueToken(ctx context.Context, minDuration time.Duration, force bool) (string, error) {
      	now := time.Now()
      	token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.RegisteredClaims{
      		Issuer:    issuer,
      		Subject:   partnerID,
      		Audience:  jwt.ClaimStrings{"namespace.so"},
      		IssuedAt:  jwt.NewNumericDate(now),
      		ExpiresAt: jwt.NewNumericDate(now.Add(20 * time.Minute)),
      	})
      	token.Header["kid"] = keyID

      	signed, err := token.SignedString(p.privateKey)
      	if err != nil {
      		return "", err
      	}

      	return "oidc_" + signed, nil
      }
      ```
    </CodeGroup>

    The TypeScript SDK caches the token and calls `issueToken` again when fewer than five minutes of validity remain.
    The Go SDK calls `IssueToken` for every request, which signs a new token each time. Signing is local and cheap.
  </Step>

  <Step title="Test Partner Token Source">
    Create a partner client with the token source, then list your tenants to confirm it authenticates.
    Listing tenants is a read-only call, which makes it a safe first request.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      import { createIAMClient } from "@namespacelabs/sdk/api/iam";

      const partnerClient = createIAMClient({ tokenSource: partnerTokenSource });

      const { tenants } = await partnerClient.tenants.listTenants({ limit: 10 });
      console.log(`Authenticated. Found ${tenants.length} tenants.`);
      ```

      ```go Go theme={null}
      import (
      	"fmt"
      	"log"
      	"os"

      	"github.com/golang-jwt/jwt/v4"
      	"namespacelabs.dev/integrations/api/iam"
      	iamv1beta "namespacelabs.dev/integrations/proto/namespace/cloud/iam/v1beta"
      )

      pemBytes, err := os.ReadFile("partner-key.pem")
      if err != nil {
      	log.Fatal(err)
      }

      privateKey, err := jwt.ParseECPrivateKeyFromPEM(pemBytes)
      if err != nil {
      	log.Fatal(err)
      }

      partnerClient, err := iam.NewClient(ctx, partnerTokenSource{privateKey})
      if err != nil {
      	log.Fatal(err)
      }
      defer partnerClient.Close()

      resp, err := partnerClient.Tenants.ListTenants(ctx, &iamv1beta.ListTenantsRequest{Limit: 10})
      if err != nil {
      	log.Fatal(err)
      }

      fmt.Printf("Authenticated. Found %d tenants.\n", len(resp.Tenants))
      ```
    </CodeGroup>

    A new partner account has no tenants yet, so `Found 0 tenants` is a successful result.
    The partner client exposes the tenant and token services, and it is the `partnerClient` that the rest of the multi-tenancy docs start from.
  </Step>
</Steps>

## Next steps

<Columns cols={2}>
  <Card title="Create tenants" icon="building-2" href="/docs/platform/tenants/create">
    Give each of your customers their own tenant.
  </Card>

  <Card title="Tenant tokens" icon="ticket" href="/docs/platform/authentication/tenant-tokens">
    Act inside a tenant on behalf of a customer.
  </Card>
</Columns>


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