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

# Authentication

> Learn how to authenticate your API requests with API keys and HMAC signatures

## Overview

Bayse Markets API uses API key authentication with HMAC-SHA256 signatures for secure request verification. Authentication requirements vary by endpoint:

* **Public endpoints**: No authentication required.
* **Read endpoints**: API key only (`X-Public-Key` header).
* **Write endpoints**: API key + timestamp + HMAC signature.

## API key structure

API keys come in pairs:

* **Public key** (`pk_*`): Identifies your API key (safe to expose in headers).
* **Secret key** (`sk_*`): Used to sign requests (keep secure, never expose).

```
Public key:  pk_live_abcdef123456
Secret key:  sk_live_secret789xyz
```

<Warning>
  Your secret key is only shown once during creation. Store it securely in environment variables or a secrets manager.
</Warning>

## Get your API keys

You can create and manage API keys in the Bayse web app at [app.bayse.markets/settings/api-keys](https://app.bayse.markets/settings/api-keys).

Or in the web app, via **More** > **Account Settings** > **API Keys** in the **Developer Tool** section.

If you prefer, you can also manage API keys programmatically through the API. Log in with your email and password to get a session token and device ID, then create, list, revoke, or rotate keys.

## Authentication levels

### Public endpoints

Some endpoints require no authentication:

```bash theme={null}
curl https://relay.bayse.markets/health
```

### Read authentication

For read operations, include your public key in the `X-Public-Key` header:

```bash theme={null}
curl -X GET "https://relay.bayse.markets/v1/pm/events" \
  -H "X-Public-Key: pk_live_abcdef123456"
```

**Endpoints requiring read authentication:**

* `GET /v1/pm/portfolio`.
* `GET /v1/pm/orders`.
* `GET /v1/pm/activities`.

### Write authentication

Write operations require three headers:

1. **X-Public-Key**: Your public API key.
2. **X-Timestamp**: Current Unix timestamp (seconds).
3. **X-Signature**: HMAC-SHA256 signature of the request payload (base64-encoded).

The signing payload format is: `{timestamp}.{METHOD}.{path}.{bodyHash}`

* **timestamp**: The same Unix timestamp sent in `X-Timestamp`.
* **METHOD**: The HTTP method in uppercase (e.g., `POST`, `DELETE`).
* **path**: The request path (e.g., `/v1/pm/orders/abc123`).
* **bodyHash**: SHA-256 hex digest of the request body. Empty string if there is no body.

```bash theme={null}
PUBLIC_KEY="pk_live_abcdef123456"
SECRET_KEY="sk_live_secret789xyz"
TIMESTAMP=$(date +%s)
METHOD="POST"
URL_PATH="/v1/pm/events/evt_123/markets/mkt_456/orders"
BODY='{"side":"BUY","outcome":"YES","amount":100,"currency":"USD"}'

# Compute body hash (SHA-256 hex digest; empty string if no body)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/.*= //')

# Build signing payload
PAYLOAD="${TIMESTAMP}.${METHOD}.${URL_PATH}.${BODY_HASH}"

# Create HMAC-SHA256 signature (base64-encoded)
SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | base64)

curl -X POST "https://relay.bayse.markets${URL_PATH}" \
  -H "X-Public-Key: ${PUBLIC_KEY}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Signature: ${SIGNATURE}" \
  -H "Content-Type: application/json" \
  -d "$BODY"
```

**Endpoints requiring write authentication:**

* `POST /v1/pm/events/{eventId}/markets/{marketId}/orders`.
* `DELETE /v1/pm/orders/{orderId}`.

## Implementing HMAC signatures

### How it works

1. Get the current Unix timestamp (seconds since epoch).
2. Build the signing payload: `{timestamp}.{METHOD}.{path}.{bodyHash}`.
   * If the request has a JSON body, `bodyHash` is the SHA-256 hex digest of the raw body bytes.
   * If there is no body, `bodyHash` is an empty string (the payload ends with a trailing `.`).
3. Compute the HMAC-SHA256 of the payload using your secret key.
4. Base64-encode the result.
5. Send in the `X-Signature` header along with `X-Timestamp`.

The server verifies the signature matches and the timestamp is within a 5-minute window (prevents replay attacks).

### Code examples

<CodeGroup>
  ```bash cURL theme={null}
  PUBLIC_KEY="pk_live_abcdef123456"
  SECRET_KEY="sk_live_secret789xyz"
  TIMESTAMP=$(date +%s)
  METHOD="POST"
  URL_PATH="/v1/pm/events/evt_123/markets/mkt_456/orders"
  BODY='{"side":"BUY","outcome":"YES","amount":100,"currency":"USD"}'

  # Compute body hash (SHA-256 hex digest)
  BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/.*= //')

  # Build signing payload and create HMAC-SHA256 signature
  PAYLOAD="${TIMESTAMP}.${METHOD}.${URL_PATH}.${BODY_HASH}"
  SIGNATURE=$(printf '%s' "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | base64)

  # Make authenticated request
  curl -X POST "https://relay.bayse.markets${URL_PATH}" \
    -H "X-Public-Key: ${PUBLIC_KEY}" \
    -H "X-Timestamp: ${TIMESTAMP}" \
    -H "X-Signature: ${SIGNATURE}" \
    -H "Content-Type: application/json" \
    -d "$BODY"
  ```

  ```javascript Node.js theme={null}
  import crypto from 'crypto';

  class BayseClient {
    constructor(publicKey, secretKey) {
      this.publicKey = publicKey;
      this.secretKey = secretKey;
      this.baseUrl = 'https://relay.bayse.markets';
    }

    createSignature(timestamp, method, path, body = null) {
      let bodyHash = '';
      if (body) {
        bodyHash = crypto.createHash('sha256').update(body).digest('hex');
      }
      const payload = `${timestamp}.${method}.${path}.${bodyHash}`;
      return crypto
        .createHmac('sha256', this.secretKey)
        .update(payload)
        .digest('base64');
    }

    async makeAuthenticatedRequest(method, path, body = null) {
      const timestamp = Math.floor(Date.now() / 1000);
      const bodyStr = body ? JSON.stringify(body) : null;
      const signature = this.createSignature(timestamp, method, path, bodyStr);

      const options = {
        method,
        headers: {
          'X-Public-Key': this.publicKey,
          'X-Timestamp': timestamp.toString(),
          'X-Signature': signature,
          'Content-Type': 'application/json',
        },
      };

      if (bodyStr) {
        options.body = bodyStr;
      }

      const response = await fetch(`${this.baseUrl}${path}`, options);
      return response.json();
    }

    async placeOrder(eventId, marketId, order) {
      return this.makeAuthenticatedRequest(
        'POST',
        `/v1/pm/events/${eventId}/markets/${marketId}/orders`,
        order
      );
    }
  }

  // Usage
  const client = new BayseClient(
    'pk_live_abcdef123456',
    'sk_live_secret789xyz'
  );

  await client.placeOrder('evt_123', 'mkt_456', {
    side: 'BUY',
    outcome: 'YES',
    amount: 100,
    currency: 'USD',
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import base64
  import json
  import time
  import requests

  class BayseClient:
      def __init__(self, public_key, secret_key):
          self.public_key = public_key
          self.secret_key = secret_key
          self.base_url = 'https://relay.bayse.markets'

      def create_signature(self, timestamp, method, path, body=None):
          """Create HMAC-SHA256 signature of the request payload"""
          body_hash = ''
          if body:
              body_hash = hashlib.sha256(body.encode()).hexdigest()
          payload = f'{timestamp}.{method}.{path}.{body_hash}'
          return base64.b64encode(
              hmac.new(
                  self.secret_key.encode(),
                  payload.encode(),
                  hashlib.sha256
              ).digest()
          ).decode()

      def make_authenticated_request(self, method, path, body=None):
          """Make authenticated API request"""
          timestamp = int(time.time())
          body_str = json.dumps(body) if body else None
          signature = self.create_signature(timestamp, method, path, body_str)

          headers = {
              'X-Public-Key': self.public_key,
              'X-Timestamp': str(timestamp),
              'X-Signature': signature,
              'Content-Type': 'application/json',
          }

          response = requests.request(
              method,
              f'{self.base_url}{path}',
              headers=headers,
              data=body_str,
          )
          return response.json()

      def place_order(self, event_id, market_id, order):
          """Place a prediction market order"""
          return self.make_authenticated_request(
              'POST',
              f'/v1/pm/events/{event_id}/markets/{market_id}/orders',
              body=order,
          )

  # Usage
  client = BayseClient(
      'pk_live_abcdef123456',
      'sk_live_secret789xyz'
  )

  result = client.place_order('evt_123', 'mkt_456', {
      'side': 'BUY',
      'outcome': 'YES',
      'amount': 100,
      'currency': 'USD',
  })
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/base64"
      "encoding/hex"
      "fmt"
      "strconv"
      "time"
  )

  type BayseClient struct {
      PublicKey string
      SecretKey string
      BaseURL   string
  }

  func NewBayseClient(publicKey, secretKey string) *BayseClient {
      return &BayseClient{
          PublicKey: publicKey,
          SecretKey: secretKey,
          BaseURL:   "https://relay.bayse.markets",
      }
  }

  func (c *BayseClient) CreateSignature(timestamp int64, method, path string, body []byte) string {
      bodyHash := ""
      if len(body) > 0 {
          h := sha256.Sum256(body)
          bodyHash = hex.EncodeToString(h[:])
      }
      payload := fmt.Sprintf("%d.%s.%s.%s", timestamp, method, path, bodyHash)
      mac := hmac.New(sha256.New, []byte(c.SecretKey))
      mac.Write([]byte(payload))
      return base64.StdEncoding.EncodeToString(mac.Sum(nil))
  }

  func (c *BayseClient) GetAuthHeaders(method, path string, body []byte) map[string]string {
      timestamp := time.Now().Unix()
      signature := c.CreateSignature(timestamp, method, path, body)

      return map[string]string{
          "X-Public-Key": c.PublicKey,
          "X-Timestamp":  strconv.FormatInt(timestamp, 10),
          "X-Signature":  signature,
          "Content-Type": "application/json",
      }
  }

  // Usage
  func main() {
      client := NewBayseClient(
          "pk_live_abcdef123456",
          "sk_live_secret789xyz",
      )

      body := []byte(`{"side":"BUY","outcome":"YES","amount":100,"currency":"USD"}`)
      headers := client.GetAuthHeaders("POST", "/v1/pm/events/evt_123/markets/mkt_456/orders", body)
      fmt.Printf("Headers: %+v\n", headers)
  }
  ```
</CodeGroup>

## Social sign-in users

If you signed up for Bayse using **Apple** or **Google**, your account doesn't have a password yet. The API requires email and password authentication to create and manage API keys.

To set up a password:

1. Open the Bayse app and go to **Forgot Password** (or use the password reset flow).
2. Enter the email associated with your Apple/Google account.
3. Follow the instructions to create a password.

Once you've set a password, you can use it with the [login endpoint](#getting-a-session-token) to get a session token and start managing API keys.

<Note>
  Setting a password does **not** change or remove your existing sign-in method. You can continue using Apple or Google to sign in to the Bayse app as usual. The password is only needed for API access.
</Note>

<Note>
  You can manage API keys in the Bayse web app at [app.bayse.markets/settings/api-keys](https://app.bayse.markets/settings/api-keys), or in the web app, via **More** > **Account Settings** > **API Keys** in the **Developer Tool** section.
</Note>

## Managing API keys

You can manage API keys from the Bayse web app or programmatically through the endpoints below.

### Getting a session token

Before you can create or manage API keys, you need to log in with your Bayse account credentials to get a session token and device ID:

```bash theme={null}
curl -X POST https://relay.bayse.markets/v1/user/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@example.com",
    "password": "your-password"
  }'
```

<Accordion title="Response">
  ```json theme={null}
  {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "deviceId": "d_abc123",
    "userId": "usr_456def"
  }
  ```
</Accordion>

Use the `token` and `deviceId` from the response as the `x-auth-token` and `x-device-id` headers for all API key management requests below.

<Note>
  The login endpoint is rate-limited to 1 request per 2 minutes per email address. Cache your session token and reuse it. See [Rate limits](/rate-limits) for details.
</Note>

### Creating API keys

With your session token and device ID, create an API key:

```bash theme={null}
curl -X POST https://relay.bayse.markets/v1/user/me/api-keys \
  -H "x-auth-token: YOUR_TOKEN" \
  -H "x-device-id: YOUR_DEVICE_ID" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production API Key"}'
```

<Accordion title="Response">
  ```json theme={null}
  {
    "id": "key_abc123",
    "publicKey": "pk_live_abcdef123456",
    "secretKey": "sk_live_secret789xyz",
    "name": "Production API Key",
    "createdAt": "2026-02-16T10:30:00Z"
  }
  ```
</Accordion>

### Listing API keys

```bash theme={null}
curl -X GET https://relay.bayse.markets/v1/user/me/api-keys \
  -H "x-auth-token: YOUR_TOKEN" \
  -H "x-device-id: YOUR_DEVICE_ID"
```

### Revoking API keys

```bash theme={null}
curl -X DELETE https://relay.bayse.markets/v1/user/me/api-keys/key_abc123 \
  -H "x-auth-token: YOUR_TOKEN" \
  -H "x-device-id: YOUR_DEVICE_ID"
```

### Rotating API keys

Generate a new secret key while keeping the same public key:

```bash theme={null}
curl -X POST https://relay.bayse.markets/v1/user/me/api-keys/key_abc123/rotate \
  -H "x-auth-token: YOUR_TOKEN" \
  -H "x-device-id: YOUR_DEVICE_ID"
```

<Accordion title="Response">
  ```json theme={null}
  {
    "id": "key_abc123",
    "publicKey": "pk_live_abcdef123456",
    "secretKey": "sk_live_newsecret456def",
    "name": "Production API Key",
    "rotatedAt": "2026-02-16T11:00:00Z"
  }
  ```
</Accordion>

## Security best practices

<AccordionGroup>
  <Accordion title="Store credentials securely" icon="lock">
    * Never commit API keys to version control
    * Use environment variables or secrets managers
    * Rotate keys regularly
    * Use separate keys for development and production
  </Accordion>

  <Accordion title="Protect your secret key" icon="shield-halved">
    * Never expose secret keys in client-side code
    * Don't log secret keys
    * Revoke compromised keys immediately
    * The secret key is only shown once - save it during creation
  </Accordion>

  <Accordion title="Implement proper error handling" icon="triangle-exclamation">
    * Don't expose secret keys in error messages
    * Handle authentication errors gracefully
    * Implement retry logic with exponential backoff
    * Monitor for suspicious authentication patterns
  </Accordion>

  <Accordion title="Use HTTPS" icon="globe">
    * Always use HTTPS in production
    * Verify SSL certificates
    * Never send credentials over HTTP
  </Accordion>
</AccordionGroup>

## Common errors

### Invalid signature

```json theme={null}
{
  "error": "invalid_signature",
  "message": "The provided signature does not match the expected signature"
}
```

**Causes:**

* Incorrect secret key.
* Timestamp mismatch (signed different timestamp than sent in `X-Timestamp`).
* Wrong payload format — must be `{timestamp}.{METHOD}.{path}.{bodyHash}`.
* Body hash mismatch — the exact bytes sent in the request body must match what was hashed when signing. Avoid trimming or reformatting the body after signing.
* Incorrect HMAC algorithm (must be SHA-256).
* Incorrect encoding (signature must be base64, body hash must be hex).

### Timestamp too old

```json theme={null}
{
  "error": "timestamp_expired",
  "message": "Request timestamp is too old"
}
```

**Cause:** The timestamp in `X-Timestamp` is too far in the past. Ensure your system clock is synchronized.

### Missing API key

```json theme={null}
{
  "error": "unauthorized",
  "message": "API key is required for this endpoint"
}
```

**Cause:** The `X-Public-Key` header is missing or invalid.

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="book" href="/api-reference/introduction">
    Explore all available endpoints
  </Card>

  <Card title="Prediction markets" icon="chart-line" href="/concepts/prediction-markets">
    Learn about prediction market operations
  </Card>

  <Card title="User endpoints" icon="user" href="/api-reference/user/create-api-key">
    Manage your API keys programmatically
  </Card>
</CardGroup>
