Starship Rewards API

Authentication

Learn how to authenticate with the Starship Rewards API

Authentication Overview

The Starship Rewards API uses API Key authentication -- stateless, secure, and simple. Every request is authenticated with two headers. Optionally, you can add HMAC request signing for tamper-proof requests and verified webhook deliveries.

API Key Authentication

Send your API Key and Secret with every request:

X-API-Key: sk_live_your_api_key_here
X-API-Secret: your_api_secret_here

That's it. No login step, no token management, no refresh logic.

Setup

  1. Log in to the Client PortalSettingsAPI Keys
  2. Click Generate API Key & Secret on any credential
  3. Copy the API Secret immediately -- it is shown only once
  4. Store both values securely in your backend configuration (environment variables, secrets manager)

Example

# List products
curl -X GET {{host}}/api/v1/products \
  -H "X-API-Key: sk_live_abc123def456" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json"

# Create an order
curl -X POST {{host}}/api/v1/orders \
  -H "X-API-Key: sk_live_abc123def456" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-order-id-123" \
  -d '{
    "product_id": 42,
    "denomination": 100,
    "quantity": 1,
    "email": "recipient@example.com"
  }'
<?php
class StarshipAPI {
    private string $apiKey;
    private string $apiSecret;
    private string $baseUrl;

    public function __construct(string $apiKey, string $apiSecret, string $baseUrl = '{{host}}') {
        $this->apiKey = $apiKey;
        $this->apiSecret = $apiSecret;
        $this->baseUrl = $baseUrl;
    }

    public function request(string $method, string $endpoint, ?array $data = null): array {
        $headers = [
            'X-API-Key: ' . $this->apiKey,
            'X-API-Secret: ' . $this->apiSecret,
            'Content-Type: application/json',
        ];

        $context = stream_context_create([
            'http' => [
                'method' => $method,
                'header' => implode("\r\n", $headers),
                'content' => $data ? json_encode($data) : null,
            ]
        ]);

        $response = file_get_contents($this->baseUrl . $endpoint, false, $context);
        return json_decode($response, true);
    }

    public function getProducts(): array {
        return $this->request('GET', '/api/v1/products');
    }

    public function createOrder(array $orderData, string $idempotencyKey): array {
        return $this->request('POST', '/api/v1/orders', $orderData);
    }
}

// Usage
$api = new StarshipAPI('sk_live_abc123def456', 'your-secret-here');
$products = $api->getProducts();
import requests

class StarshipAPI:
    def __init__(self, api_key: str, api_secret: str, base_url: str = "{{host}}"):
        self.base_url = base_url
        self.headers = {
            "X-API-Key": api_key,
            "X-API-Secret": api_secret,
            "Content-Type": "application/json",
        }

    def get_products(self):
        response = requests.get(f"{self.base_url}/api/v1/products", headers=self.headers)
        response.raise_for_status()
        return response.json()

    def create_order(self, order_data: dict, idempotency_key: str):
        headers = {**self.headers, "Idempotency-Key": idempotency_key}
        response = requests.post(
            f"{self.base_url}/api/v1/orders",
            json=order_data,
            headers=headers,
        )
        response.raise_for_status()
        return response.json()

# Usage
api = StarshipAPI("sk_live_abc123def456", "your-secret-here")
products = api.get_products()
const axios = require('axios');

class StarshipAPI {
  constructor(apiKey, apiSecret, baseUrl = '{{host}}') {
    this.client = axios.create({
      baseURL: baseUrl,
      headers: {
        'X-API-Key': apiKey,
        'X-API-Secret': apiSecret,
        'Content-Type': 'application/json',
      },
    });
  }

  async getProducts() {
    const { data } = await this.client.get('/api/v1/products');
    return data;
  }

  async createOrder(orderData, idempotencyKey) {
    const { data } = await this.client.post('/api/v1/orders', orderData, {
      headers: { 'Idempotency-Key': idempotencyKey },
    });
    return data;
  }
}

// Usage
const api = new StarshipAPI('sk_live_abc123def456', 'your-secret-here');
const products = await api.getProducts();

Error Responses

StatusErrorMeaning
401X-API-Secret header requiredAPI key sent without secret
401Invalid API keyAPI key not found or inactive
401Invalid API secretSecret doesn't match
403ForbiddenValid auth but insufficient permissions

The Signing Secret -- One Key, Two Purposes

When you generate an API credential, you receive three values:

ValueHeaderPurpose
API KeyX-API-KeyIdentifies your credential
API SecretX-API-SecretAuthenticates your requests
Signing SecretUsed in HMAC computationSigns your API requests AND verifies webhook deliveries

The signing secret (whsec_...) serves both purposes:

  1. Inbound (your requests to us): When HMAC signing is enabled, you use the signing secret to compute an X-Signature header on every API request. This proves the request hasn't been tampered with in transit. See HMAC Request Signing.

  2. Outbound (our webhooks to you): When you create a webhook linked to a credential, we use the same signing secret to sign webhook delivery payloads. You verify the signature to confirm the webhook came from Starship and hasn't been modified. See Webhooks.

                     Signing Secret (whsec_...)
                            |
              +-------------+-------------+
              |                           |
     YOUR API REQUESTS            WEBHOOK DELIVERIES
     (you sign outbound)         (we sign, you verify)
              |                           |
      X-Signature header          X-Signature header
      on requests to us           on deliveries to you

The signing secret is separate from the API Secret. The API Secret authenticates WHO you are. The signing secret proves WHAT was sent hasn't been tampered with.


Migration Path -- Adopting HMAC Step by Step

HMAC request signing is optional today and controlled per credential by your admin. We recommend a gradual rollout:

Step 1: API Key Authentication (Start Here)

Get your integration working with X-API-Key + X-API-Secret. This is sufficient for most use cases and is already secure over HTTPS.

X-API-Key: sk_live_abc123def456
X-API-Secret: your-secret-here

Step 2: Enable Webhooks with Signature Verification

Register a webhook endpoint. The signing secret is returned at creation time. Verify inbound webhook signatures to confirm authenticity.

curl -X POST "{{host}}/api/v1/webhooks" \
  -H "X-API-Key: sk_live_abc123def456" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order Updates",
    "url": "https://your-endpoint.example.com/webhooks",
    "events": ["order.delivered", "order.cancelled"],
    "credential_id": 1
  }'

When credential_id is provided, the webhook uses that credential's signing secret for delivery signatures. This means you verify webhooks with the same key you'll later use for HMAC signing.

Step 3: Enable HMAC Request Signing

Ask your admin to enable HMAC on your credential (or it may be enabled by default for new credentials). From that point, every API request must include an X-Signature header.

X-API-Key: sk_live_abc123def456
X-API-Secret: your-secret-here
X-Signature: t=1740000000,v1=a1b2c3d4e5f6...

See the full signing spec: HMAC Request Signing

Step 4: Rotate Signing Secrets

Periodically rotate your signing secret without downtime using the rotation endpoint:

curl -X POST "{{host}}/client/api/api-keys/1/rotate-signing-secret" \
  -H "Cookie: session=your-portal-session"

This generates a new signing secret. Update your integration, then the old secret stops working. Webhook deliveries automatically use the new secret.


API Endpoints Reference

EndpointMethodAuthDescription
/api/v1/*AnyX-API-Key + X-API-SecretAll API operations
/api/v1/*Any+ X-Signature (when HMAC enabled)Tamper-proof requests

Key Management (Client Portal)

These endpoints use cookie-based session auth from the Client Portal:

EndpointMethodDescription
/client/api/api-keysGETList all API credentials
/client/api/api-keysPOSTCreate a new credential
/client/api/api-keys/:idGETGet credential details
/client/api/api-keys/:idPUTUpdate credential name
/client/api/api-keys/:idDELETEDelete credential
/client/api/api-keys/:id/generate-api-keyPOSTGenerate API key + secret + signing secret
/client/api/api-keys/:id/revoke-api-keyPOSTRevoke API key and secret
/client/api/api-keys/:id/rotate-signing-secretPOSTRotate signing secret only

Legacy: JWT Token Authentication

JWT authentication is supported for backward compatibility only. All new integrations must use API Key authentication. JWT support may be deprecated in a future release.

JWT auth requires a login step to exchange credentials for short-lived tokens:

# Step 1: Login
POST {{host}}/auth/login
Content-Type: application/json

{"username": "your_username", "password": "your_password"}
# Returns: { "access_token": "...", "refresh_token": "..." }

# Step 2: Use access token
GET {{host}}/api/v1/products
Authorization: Bearer {access_token}

# Step 3: Refresh when expired
POST {{host}}/auth/refresh
Content-Type: application/json

{"refresh_token": "{refresh_token}"}

# Step 4: Logout
POST {{host}}/auth/logout
Authorization: Bearer {access_token}

JWT tokens expire and require refresh logic. API Key auth avoids this complexity entirely. HMAC request signing is not available with JWT authentication.


Further Reading