Skip to content

Partner Business Registration API

Register a new business on HeldSway from an authorized external partner platform. The request payload is encrypted using a shared secret for secure server-to-server communication.


Prerequisites

This endpoint is available exclusively to authorized partners with a pre-shared PARTNER_TOKEN.

RequirementDetails
AuthenticationAuthorization: Bearer <PARTNER_TOKEN> header
EncryptionRequest body must be AES-256-CBC encrypted using the same PARTNER_TOKEN
Rate Limit10 requests per minute per IP

Encryption Specification

All request payloads must be encrypted before transmission. This provides a dual-layer security model: the Bearer token authenticates the caller, and the encrypted payload ensures data integrity and confidentiality.

Algorithm

ParameterValue
CipherAES-256-CBC
Key DerivationSHA-256(PARTNER_TOKEN) — produces a 32-byte encryption key
IV16 bytes, randomly generated per request
IntegrityHMAC-SHA256 over iv_bytes + ciphertext_bytes using the derived key

Encryption Steps

  1. Derive the key: key = SHA-256(PARTNER_TOKEN) (binary, 32 bytes)
  2. Generate IV: 16 random bytes
  3. Encrypt: ciphertext = AES-256-CBC(JSON_payload, key, iv) using OPENSSL_RAW_DATA flag
  4. Compute MAC: mac = HMAC-SHA256(iv_bytes + ciphertext_bytes, key) — hex-encoded, 64 characters
  5. Encode: Base64-encode both ciphertext and iv

Reference Implementation (PHP)

php
$token = 'your-partner-token';
$key   = hash('sha256', $token, binary: true);
$iv    = random_bytes(16);

$plaintext  = json_encode([
    'business_name' => 'Acme Rentals',
    'owner_name'    => 'John Doe',
    'email'         => 'john@acme.com',
    'phone'         => '+1234567890',
    'address'       => '123 Main St, City, ST 12345',
    'website_url'   => 'https://acme.com',
]);

$ciphertext = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$mac        = hash_hmac('sha256', $iv . $ciphertext, $key);

$requestBody = [
    'payload' => base64_encode($ciphertext),
    'iv'      => base64_encode($iv),
    'mac'     => $mac,
];

Reference Implementation (Node.js)

javascript
const crypto = require('crypto');

const token = 'your-partner-token';
const key   = crypto.createHash('sha256').update(token).digest(); // 32-byte Buffer

const data = JSON.stringify({
  business_name: 'Acme Rentals',
  owner_name:    'John Doe',
  email:         'john@acme.com',
  phone:         '+1234567890',
  address:       '123 Main St, City, ST 12345',
  website_url:   'https://acme.com',
});

const iv         = crypto.randomBytes(16);
const cipher     = crypto.createCipheriv('aes-256-cbc', key, iv);
const ciphertext = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
const mac        = crypto.createHmac('sha256', key).update(Buffer.concat([iv, ciphertext])).digest('hex');

const requestBody = {
  payload: ciphertext.toString('base64'),
  iv:      iv.toString('base64'),
  mac:     mac,
};

Reference Implementation (Python)

python
import hashlib, hmac, json, os, base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

token = "your-partner-token"
key   = hashlib.sha256(token.encode()).digest()  # 32 bytes

data = json.dumps({
    "business_name": "Acme Rentals",
    "owner_name":    "John Doe",
    "email":         "john@acme.com",
    "phone":         "+1234567890",
    "address":       "123 Main St, City, ST 12345",
    "website_url":   "https://acme.com",
}).encode()

# PKCS7 padding (AES-CBC requires block-aligned input)
padder  = padding.PKCS7(128).padder()
padded  = padder.update(data) + padder.finalize()

iv         = os.urandom(16)
cipher     = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor  = cipher.encryptor()
ciphertext = encryptor.update(padded) + encryptor.finalize()

mac = hmac.new(key, iv + ciphertext, hashlib.sha256).hexdigest()

request_body = {
    "payload": base64.b64encode(ciphertext).decode(),
    "iv":      base64.b64encode(iv).decode(),
    "mac":     mac,
}

Register Business

POST /api/v1/partner/register-business
Authorization: Bearer <PARTNER_TOKEN>
Content-Type: application/json

Request Body (Encrypted Envelope)

FieldTypeRequiredDescription
payloadstringYesBase64-encoded AES-256-CBC ciphertext
ivstringYesBase64-encoded 16-byte initialization vector
macstringYes64-character hex HMAC-SHA256 digest

Decrypted Payload Fields

These fields must be JSON-encoded, then encrypted as described above.

FieldTypeRequiredConstraintsDescription
business_namestringYesmax 255 charsName of the business
owner_namestringYesmax 255 charsFull name of the business owner
emailstringYesvalid email, max 255 charsOwner's email address
phonestringNomax 50 charsOwner's phone number
addressstringNomax 500 charsBusiness address
website_urlstringNovalid URL, max 255 charsBusiness website

Example

bash
curl -X POST https://<API_DOMAIN>/api/v1/partner/register-business \
  -H "Authorization: Bearer <PARTNER_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "payload": "dGhpcyBpcyBhIGJhc2U2NC1lbmNvZGVkIGNpcGhlcnRleHQ=",
    "iv": "c29tZS1iYXNlNjQtaXY=",
    "mac": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
  }'

Success Response

HTTP 201 Created

json
{
  "success": true,
  "message": "Business registered successfully.",
  "data": {
    "business": {
      "id": 42,
      "name": "Acme Rentals",
      "slug": "acme-rentals",
      "url": "http://acme-rentals.example.com"
    },
    "credentials": {
      "api_key": "as_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTu",
      "api_secret": "aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkL",
      "access_token": "xYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGh",
      "token_type": "Bearer",
      "expires_in": 14400,
      "scopes": [
        "affiliates:read",
        "affiliates:write",
        "conversions:read",
        "conversions:write",
        "clicks:read",
        "clicks:write",
        "commissions:read",
        "reports:read",
        "sdk:init",
        "pageviews:write"
      ]
    },
    "owner": {
      "email": "john@acme.com",
      "name": "John Doe"
    }
  }
}

Response Fields

FieldTypeDescription
business.idintegerUnique business identifier
business.namestringBusiness display name
business.slugstringURL-safe subdomain slug
business.urlstringFull URL for the business panel
credentials.api_keystringPublic API key (prefix: as_). Safe to store.
credentials.api_secretstringSensitive. Shown only once. Store securely.
credentials.access_tokenstringShort-lived Bearer token for immediate API use
credentials.token_typestringAlways "Bearer"
credentials.expires_inintegerAccess token lifetime in seconds (4 hours)
credentials.scopesstring[]Scopes granted to this API key
owner.emailstringOwner's email address
owner.namestringOwner's display name

Important Notes

  • The api_secret is returned only once in this response. It cannot be retrieved again. Store it securely.
  • The access_token expires after 4 hours. Use the api_key and api_secret to issue new tokens via [POST /v1/auth/token](authentication.md).
  • If the owner email already belongs to an existing HeldSway user, the business is created under that existing account.

Error Reference

HTTP StatusError CodeCause
401UNAUTHORIZEDMissing or invalid Authorization: Bearer header
400DECRYPTION_FAILEDPayload could not be decrypted (bad ciphertext, IV, or MAC)
409BUSINESS_EXISTSA business with the same name already exists for this owner
422VALIDATION_ERRORMissing or invalid fields in the decrypted payload
429Rate limit exceeded (10 requests/minute)
503PARTNER_DISABLEDPartner integration is disabled or not configured
503PARTNER_NOT_CONFIGUREDPartner token is not set on the server

Error Response Examples

Invalid credentials (401)

json
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid partner credentials."
  }
}

Decryption failed (400)

json
{
  "success": false,
  "error": {
    "code": "DECRYPTION_FAILED",
    "message": "Failed to decrypt the request payload. Verify the encryption parameters."
  }
}

Duplicate business (409)

json
{
  "success": false,
  "error": {
    "code": "BUSINESS_EXISTS",
    "message": "Business 'Acme Rentals' already exists for this owner."
  }
}

Validation error (422)

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid business data in encrypted payload.",
    "details": {
      "business_name": ["The business name field is required."],
      "email": ["The email field must be a valid email address."]
    }
  }
}

Partner disabled (503)

json
{
  "success": false,
  "error": {
    "code": "PARTNER_DISABLED",
    "message": "Partner integration is currently disabled."
  }
}

Subdomain & Business URL

When a business is registered, a subdomain slug is automatically generated from the business name:

Business NameGenerated SlugBusiness URL
Acme Rentalsacme-rentalshttp://acme-rentals.<APP_DOMAIN>
My Storemy-storehttp://my-store.<APP_DOMAIN>
Acme Rentals (duplicate slug)acme-rentals-2http://acme-rentals-2.<APP_DOMAIN>

Slug rules:

  • Lowercase alphanumeric characters and hyphens only
  • Auto-generated from business_name via URL-safe transliteration
  • If the slug is already taken, a numeric suffix is appended (-2, -3, etc.)

Using the Returned Credentials

After registration, use the credentials to interact with the HeldSway API:

1. Immediate use with access_token

The access_token returned in the registration response is valid for 4 hours:

bash
curl https://<API_DOMAIN>/api/v1/affiliates \
  -H "Authorization: Bearer <access_token>"

2. Long-term use with api_key + api_secret

After the access token expires, issue new tokens using the API key and secret:

bash
# Step 1: Get a new access token
curl -X POST https://<API_DOMAIN>/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "<api_key>",
    "api_secret": "<api_secret>"
  }'

# Step 2: Use the new token
curl https://<API_DOMAIN>/api/v1/affiliates \
  -H "Authorization: Bearer <new_access_token>"

See Authentication for full token issuance details.


Rate Limiting

This endpoint is throttled at 10 requests per minute per IP address. Exceeding the limit returns HTTP 429 Too Many Requests with a Retry-After header indicating when you can retry.


Business Connection API

Check your business's connection status and programmatically disconnect from the platform. These endpoints operate on the business associated with the authenticated API token — no business ID parameter is needed.


Authentication

Requires a Bearer access token with the appropriate scope:

ScopeGrants Access To
connections:readRetrieve connection status and business details
connections:writeDisconnect the business from the platform
Authorization: Bearer <access_token>

To obtain a token, see authentication.md.


Rate Limits

ScopeLimit
connections:read120 requests/minute
connections:write60 requests/minute

Table of Contents


Data Models

Connection Status Object

The object returned by GET /v1/connection/status:

json
{
  "connected": true,
  "business_details": {
    "name": "Acme Corp",
    "slug": "acme",
    "website": "https://acme.com",
    "subdomain_url": "http://acme.heldsway.com",
    "logo_url": "https://cdn.example.com/logo.png",
    "commission_rate": 15
  }
}

Connection Status Field Reference

FieldTypeDescription
connectedbooleantrue when the business is active on the platform. Always true for a valid token (inactive businesses are rejected at the authentication layer).

Business Details Field Reference

FieldTypeDescription
namestringThe registered business name
slugstringUnique URL-safe identifier used in the subdomain ({slug}.heldsway.com)
websitestring|nullThe business's website URL. null if not configured.
subdomain_urlstringFull URL of the business's panel. Uses the verified custom domain if available, otherwise falls back to the subdomain pattern.
logo_urlstring|nullURL for the business logo (from branding settings). null if no logo is configured.
commission_ratenumber|nullThe business's default commission rate (%). null if not configured (uses platform default). Per-affiliate overrides take priority.

Get Connection Status

Retrieve the connection status and profile details for the business associated with the current API token.

GET /v1/connection/status

Scope: connections:read

Example request:

bash
curl https://<API_DOMAIN>/api/v1/connection/status \
  -H "Authorization: Bearer <token>"

Response — 200 OK

json
{
  "success": true,
  "message": "OK",
  "data": {
    "connected": true,
    "business_details": {
      "name": "Acme Corp",
      "slug": "acme",
      "website": "https://acme.com",
      "subdomain_url": "http://acme.heldsway.com",
      "logo_url": "https://cdn.example.com/logo.png",
      "commission_rate": 15
    }
  }
}

Response — 200 OK (no optional fields configured)

json
{
  "success": true,
  "message": "OK",
  "data": {
    "connected": true,
    "business_details": {
      "name": "Acme Corp",
      "slug": "acme",
      "website": null,
      "subdomain_url": "http://acme.heldsway.com",
      "logo_url": null,
      "commission_rate": null
    }
  }
}

Note: This endpoint will always return connected: true for a valid token. If the business has been disconnected (inactive), the authentication middleware rejects the request before it reaches this endpoint — you will receive a 403 Forbidden response instead.


Disconnect Business

Deactivate the business on the platform. This is a destructive, one-way operation from the API perspective.

POST /v1/connection/disconnect
Content-Type: application/json

Scope: connections:write

Body Parameters — All fields are optional.

FieldTypeRequiredConstraintsDescription
reasonstring|nullNoMax 500 charactersOptional reason for disconnecting. Recorded in the audit log for administrative reference.

Example request:

bash
curl -X POST https://<API_DOMAIN>/api/v1/connection/disconnect \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Migrating to a different platform" }'

Response — 200 OK

json
{
  "success": true,
  "message": "Business disconnected successfully."
}

Example — Disconnect without a reason:

bash
curl -X POST https://<API_DOMAIN>/api/v1/connection/disconnect \
  -H "Authorization: Bearer <token>"

Response — 200 OK

json
{
  "success": true,
  "message": "Business disconnected successfully."
}

Error — 422 Unprocessable Entity (reason too long)

json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "reason": ["The reason field must not be greater than 500 characters."]
    }
  }
}

Side Effects

After a successful disconnect:

EffectDetail
Business deactivatedbusinesses.is_active is set to false
All API tokens invalidatedThe authentication middleware rejects tokens for inactive businesses. All existing tokens for this business immediately stop working.
Audit log entry createdA status_changed entry is written to activity_logs with the disconnect reason (if provided), business ID, and timestamp.
Subdomain access blockedThe business's subdomain panel ({slug}.heldsway.com) becomes inaccessible.

Reconnection

Reconnection is not available via the API. Only a platform super admin can reactivate a disconnected business from the main admin panel. Contact your platform administrator to restore access.

Idempotency

Once disconnected, subsequent calls to this endpoint will fail with 403 Forbidden because the token is no longer valid for an inactive business. The endpoint does not need explicit idempotency handling — the authentication layer enforces it naturally.


Error Reference

All endpoints follow the standard response envelope.

HTTP StatusError CodeCause
401UNAUTHORIZEDMissing or invalid Bearer token
403FORBIDDENToken does not have the required scope (connections:read or connections:write), or the business is already disconnected (inactive)
422VALIDATION_ERRORInvalid request body (see details for field-level errors)
429Rate limit exceeded
500SERVER_ERRORUnexpected server error

Quick Reference

MethodPathScopeDescription
GET/v1/connection/statusconnections:readGet connection status and business details
POST/v1/connection/disconnectconnections:writeDisconnect business from the platform

Released under the MIT License.