> For the complete documentation index, see [llms.txt](https://docs.universal.xyz/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.universal.xyz/docs/developers/integrate-the-issuance-and-redemption-universal-api/authentication.md).

# Authentication

### Overview

The Issuance and Redemption API uses a two-step authentication process:

1. **API Key Authentication**: Your partner API key identifies your organization
2. **JWT Token Authentication**: JWT tokens authorize operations for specific wallet addresses

### API Key Authentication

#### API Key Format

```
<keyId>.<secret>
```

Components:

* **keyId**: Public identifier (e.g., `partner_abc123`)
* **secret**: Private secret (e.g., `sk_live_1234567890abcdef`)

#### Using API Keys

API keys are used **only** for JWT token issuance and refresh operations.

**Header Format**:

```
X-API-Key: <keyId>.<secret>
```

**Endpoints that accept API keys**:

* `POST /token` - Issue new JWT token (modern)
* `POST /token/refresh` - Refresh JWT token (modern)

#### Obtaining an API Key

To request a partner API key, contact <dev@universal.xyz> with:

* Your organization name
* Use case description
* Expected transaction volume
* Technical contact information

#### API Key Security

* Store API keys in environment variables or secret management systems
* Never commit API keys to version control
* Rotate API keys regularly

### JWT Token Authentication

#### Token Structure

JWT tokens are RS256-signed JSON Web Tokens containing:

```json
{
  "iss": "https://api.universal.xyz/auth",
  "aud": "https://api.universal.xyz",
  "sub": "0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b1",
  "iat": 1704067200,
  "exp": 1735689600,
  "verified_credentials": [
    {
      "address": "0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b1"
    }
  ],
  "azp": "550e8400-e29b-41d4-a716-446655440000",
  "https://universal.xyz/partner_id": "550e8400-e29b-41d4-a716-446655440000",
  "https://universal.xyz/type": "B2B"
}
```

**Key Claims**:

* `sub`: Wallet address (lowercase)
* `exp`: Expiration timestamp (1 year from issuance)
* `verified_credentials`: Array of verified wallet addresses
* `azp`: Partner ID
* `https://universal.xyz/partner_id`: Your partner identifier
* `https://universal.xyz/type`: User type (`B2B` for partners)

#### Obtaining JWT Tokens

**Request**:

```http
POST /token HTTP/1.1
Host: api.universal.xyz/auth
X-API-Key: partner_abc123.sk_live_1234567890abcdef
Content-Type: application/json

{
  "walletAddress": "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b1"
}
```

**Response**:

```json
{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcGkudW5pdmVyc2FsLnh5ei9hdXRoIiwiYXVkIjoiYnJpZGdlLWFwaSIsInN1YiI6IjB4NzQyZDM1Y2M2NjM0YzA1MzI5MjVhM2I4ZDRjOWRiOTZjNGI0ZDhiMSIsImlhdCI6MTcwNDA2NzIwMCwiZXhwIjoxNzM1Njg5NjAwLCJ2ZXJpZmllZF9jcmVkZW50aWFscyI6W3siYWRkcmVzcyI6IjB4NzQyZDM1Y2M2NjM0YzA1MzI5MjVhM2I4ZDRjOWRiOTZjNGI0ZDhiMSJ9XSwidHlwZSI6IkIyQiIsImF6cCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsImh0dHBzOi8vdW5pdmVyc2FsLnh5ei9jaGFubmVsIjoicGFydG5lci1hcGkiLCJodHRwczovL3VuaXZlcnNhbC54eXovcGFydG5lcl9pZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCJ9.signature",
  "expiresAt": 1735689600
}
```

#### Using JWT Tokens

Include the JWT token in the `Authorization` header for all API requests:

```http
Authorization: Bearer <token>
```

**Example**:

```http
GET /mint/wallets HTTP/1.1
Host: api.universal.xyz/issuance-redemption
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```

#### Token Validation

The API validates:

* ✅ Token signature using RS256 algorithm
* ✅ Token expiration (`exp` claim)
* ✅ Issuer (`iss` claim)
* ✅ Audience (`aud` claim)
* ✅ Wallet address matches verified credentials
* ✅ User wallet status is active

#### Wallet Address Binding

**Important**: Each JWT token is bound to a specific wallet address. All operations performed with that token will be associated with the wallet address specified in the `sub` claim.

**To use multiple wallet addresses**:

```javascript
// Different tokens for different wallets
const token1 = await issueToken(apiKey, '0xWallet1');
const token2 = await issueToken(apiKey, '0xWallet2');

// Use token1 for wallet1 operations
await createMintWallet(token1, { chain: 'sol', destinationChain: 'base' });

// Use token2 for wallet2 operations
await createMintWallet(token2, { chain: 'xrp', destinationChain: 'arbitrum' });
```

#### Token Refresh

Refresh tokens before expiration to maintain uninterrupted access:

**Request**:

```http
POST /token/refresh HTTP/1.1
Host: api.universal.xyz/auth
X-API-Key: partner_abc123.sk_live_1234567890abcdef
Content-Type: application/json

{
  "walletAddress": "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b1"
}
```

**Response**:

```json
{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": 1767225600
}
```

### Error Responses

#### Invalid API Key

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "success": false,
  "message": "Invalid API key"
}
```

#### Malformed API Key

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "success": false,
  "message": "Missing or malformed X-API-Key"
}
```

#### Expired JWT Token

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "success": false,
  "message": "Token expired"
}
```

#### Invalid JWT Token

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "success": false,
  "message": "Invalid token signature"
}
```

#### Inactive Wallet

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "success": false,
  "message": "Wallet is not active"
}
```

### Best Practices

1. **Token Caching**: Cache JWT tokens and reuse them until near expiration
2. **Proactive Refresh**: Refresh tokens before they expire (e.g., when 90% of lifetime has elapsed)
3. **Error Handling**: Implement retry logic for 401/403 errors after token refresh
4. **Secure Storage**: Store tokens securely, never in client-side JavaScript
5. **HTTPS Only**: Always use HTTPS for API requests
6. **Wallet Management**: Track which token corresponds to which wallet address

### Next Steps

* [Mint API](/docs/developers/integrate-the-issuance-and-redemption-universal-api/mint-api.md) - Create deposit addresses and issue universal tokens
* [Burn API](/docs/developers/integrate-the-issuance-and-redemption-universal-api/burn-api.md) - Redeem universal tokens to native chains
* [Examples](/docs/developers/integrate-the-issuance-and-redemption-universal-api/examples.md) - Complete authentication examples


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.universal.xyz/docs/developers/integrate-the-issuance-and-redemption-universal-api/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
