Skip to main content

Tenant API

Manage tenant registration, authentication, and profile updates.

Endpoints

Register Tenant

Create a new tenant account (admin-only).

POST /tenants/register

Authentication: Admin API Key (X-Admin-Key header)

Request Body:

{
"name": "My Product App",
"appId": "my-app", // Optional, auto-generated if not provided
"appSecret": "strong-secret-password",
"webhookUrl": "https://myapp.com/webhooks/billing" // Optional
}

Parameters:

FieldTypeRequiredDescription
namestringYesTenant display name (1-255 chars)
appIdstringNoApplication identifier (alphanumeric + hyphens, unique)
appSecretstringYesStrong password (min 12 chars)
webhookUrlstringNoURL for billing webhooks (valid URL)

Response (201 Created):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"appId": "my-app",
"name": "My Product App",
"webhookUrl": "https://myapp.com/webhooks/billing",
"status": "ACTIVE",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}

Errors:

CodeMessageCause
400Validation failedInvalid request body
401UnauthorizedMissing or invalid X-Admin-Key
409ConflictappId already exists

Example:

curl -X POST http://localhost:3000/tenants/register \
-H "Content-Type: application/json" \
-H "X-Admin-Key: your-admin-key" \
-d '{
"name": "Test App",
"appId": "test-app",
"appSecret": "super-secret-password-123",
"webhookUrl": "https://example.com/webhook"
}'

Login

Authenticate and receive JWT access token.

POST /tenants/login

Authentication: None (public endpoint)

Request Body:

{
"appId": "my-app",
"appSecret": "strong-secret-password"
}

Parameters:

FieldTypeRequiredDescription
appIdstringYesApplication identifier
appSecretstringYesApplication secret

Response (200 OK):

{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJhcHBJZCI6Im15LWFwcCIsImlhdCI6MTcwNDEyMzYwMCwiZXhwIjoxNzA0MjEwMDAwfQ.signature",
"expiresIn": "24h",
"tokenType": "Bearer"
}

Token Payload:

{
"sub": "550e8400-e29b-41d4-a716-446655440000", // Tenant ID
"appId": "my-app",
"iat": 1704123600, // Issued at (Unix timestamp)
"exp": 1704210000 // Expires at (Unix timestamp)
}

Errors:

CodeMessageCause
400Validation failedMissing appId or appSecret
401Invalid credentialsWrong appId or appSecret
401Tenant suspendedTenant status is SUSPENDED
401Tenant deletedTenant status is DELETED

Example:

curl -X POST http://localhost:3000/tenants/login \
-H "Content-Type: application/json" \
-d '{
"appId": "test-app",
"appSecret": "super-secret-password-123"
}'

Response:

{
"accessToken": "eyJhbGci...",
"expiresIn": "24h",
"tokenType": "Bearer"
}

Using the token:

curl -X GET http://localhost:3000/tenants/me \
-H "Authorization: Bearer eyJhbGci..."

Get Current Tenant

Retrieve authenticated tenant information.

GET /tenants/me

Authentication: JWT required (Authorization: Bearer <token>)

Response (200 OK):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"appId": "my-app",
"name": "My Product App",
"webhookUrl": "https://myapp.com/webhooks/billing",
"status": "ACTIVE",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}

Fields:

FieldTypeDescription
idstring (UUID)Unique tenant identifier
appIdstringApplication identifier
namestringTenant display name
webhookUrlstring | nullWebhook URL for billing events
statusstringTenant status (ACTIVE, SUSPENDED, DELETED)
createdAtstring (ISO 8601)Creation timestamp
updatedAtstring (ISO 8601)Last update timestamp

Errors:

CodeMessageCause
401UnauthorizedMissing or invalid JWT token
401Tenant not foundTenant ID from JWT doesn't exist
401Tenant suspendedTenant status is not ACTIVE

Example:

curl -X GET http://localhost:3000/tenants/me \
-H "Authorization: Bearer <your-jwt-token>"

Update Current Tenant

Update authenticated tenant information.

PATCH /tenants/me

Authentication: JWT required

Request Body:

{
"name": "Updated Product App",
"webhookUrl": "https://newurl.com/webhooks"
}

Parameters (all optional):

FieldTypeDescription
namestringUpdated tenant name (1-255 chars)
webhookUrlstring | nullUpdated webhook URL (valid URL or null to remove)

Response (200 OK):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"appId": "my-app",
"name": "Updated Product App",
"webhookUrl": "https://newurl.com/webhooks",
"status": "ACTIVE",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T12:00:00.000Z"
}

Notes:

  • Cannot update appId (immutable)
  • Cannot update appSecret via this endpoint (security)
  • Cannot update status (admin action only)

Errors:

CodeMessageCause
400Validation failedInvalid name or webhookUrl format
401UnauthorizedMissing or invalid JWT token

Example:

curl -X PATCH http://localhost:3000/tenants/me \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "New App Name",
"webhookUrl": "https://new-webhook.com"
}'

Remove webhook URL:

curl -X PATCH http://localhost:3000/tenants/me \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": null
}'

Tenant Status

Tenants can have one of three statuses:

ACTIVE

  • Can authenticate and receive JWT
  • Can make API calls
  • Can create payments
  • Receives webhook events

SUSPENDED

  • Cannot authenticate (401 Unauthorized)
  • Existing JWTs are rejected
  • Cannot make API calls
  • Data is preserved
  • Can be reactivated by admin

Use cases:

  • Non-payment of fees
  • Terms of service violation
  • Temporary suspension for investigation

DELETED

  • Cannot authenticate (401 Unauthorized)
  • Existing JWTs are rejected
  • Cannot make API calls
  • Soft delete (data preserved for audit)
  • Cannot be reactivated (create new tenant instead)

Use cases:

  • Tenant requests account deletion
  • Long-term suspension

Authentication Flow


Security Best Practices

For Administrators

  1. Admin API Key:

    • Use strong, random keys (minimum 32 characters)
    • Store in environment variables (never commit to git)
    • Rotate periodically
    • Use different keys for dev/staging/production
  2. Tenant Registration:

    • Verify tenant legitimacy before registration
    • Enforce strong appSecret requirements
    • Monitor for suspicious registration patterns

For Tenants

  1. Credentials:

    • Keep appId and appSecret secure
    • Never commit credentials to version control
    • Use environment variables or secret management
    • Rotate appSecret periodically (admin action)
  2. JWT Tokens:

    • Store tokens securely (never in localStorage if possible)
    • Use httpOnly cookies when possible
    • Refresh before expiration (24h)
    • Implement token refresh logic
  3. Webhook URL:

    • Use HTTPS only
    • Validate webhook signatures
    • Implement idempotency for webhook handlers

Example Integration

Node.js Client

import axios from 'axios';

class BillingClient {
private baseUrl = 'http://localhost:3000';
private accessToken: string | null = null;

async login(appId: string, appSecret: string): Promise<void> {
const response = await axios.post(`${this.baseUrl}/tenants/login`, {
appId,
appSecret,
});

this.accessToken = response.data.accessToken;
}

async getCurrentTenant(): Promise<any> {
if (!this.accessToken) {
throw new Error('Not authenticated');
}

const response = await axios.get(`${this.baseUrl}/tenants/me`, {
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
});

return response.data;
}

async updateTenant(updates: { name?: string; webhookUrl?: string }): Promise<any> {
if (!this.accessToken) {
throw new Error('Not authenticated');
}

const response = await axios.patch(`${this.baseUrl}/tenants/me`, updates, {
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
});

return response.data;
}
}

// Usage
const client = new BillingClient();
await client.login('my-app', 'my-secret');
const tenant = await client.getCurrentTenant();
console.log(tenant);

See Also