API Reference
The Selgeo API lets you track clicks, report conversions, manage partners, and automate your affiliate program. All endpoints use API version v1.
Base URL
https://api.selgeo.com/api/v1/
All API requests must use HTTPS. HTTP requests are rejected.
The tracking endpoint (POST /v1/clicks) does not use the /api/v1/ prefix — its full URL is https://api.selgeo.com/v1/clicks. All other endpoints listed in this reference use the base URL above.
Versioning
The API is versioned via the URL path (/api/v1/). Webhook payloads include an api_version field set to "v1". Breaking changes will be introduced under a new version path (e.g., /api/v2/), and the previous version will remain available for a deprecation period.
Authentication
Selgeo uses Stripe-style API keys for server-to-server authentication and a separate public key for client-side tracking.
Key types
| Key prefix | Name | Use case | Scope |
|---|---|---|---|
pk_test_* | Test public key | JS snippet (test mode) | Client-side click tracking only |
pk_live_* | Live public key | JS snippet (live mode) | Client-side click tracking only |
sk_test_* | Test secret key | Server-side API calls (test mode) | Conversion API and attribution audit endpoints |
sk_live_* | Live secret key | Server-side API calls (live mode) | Conversion API and attribution audit endpoints |
Public keys (pk_*)
Public keys are embedded in the JavaScript tracking snippet on your website. They can only be used to register clicks via the tracking endpoint. Public keys do not grant access to any other API resources.
<script
src="https://cdn.selgeo.com/v1/selgeo.js"
data-merchant="pk_test_YOUR_KEY"
async
></script>
Secret keys (sk_*)
Secret keys authenticate server-side API calls. Include the key in the Authorization header:
Authorization: Bearer sk_test_YOUR_KEY
Example request:
curl https://api.selgeo.com/api/v1/conversions \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"click_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "purchase",
"external_transaction_id": "order_456",
"amount_cents": 4900,
"currency": "EUR"
}'
Never expose secret keys in client-side code, public repositories, or browser requests. Secret keys grant access to the Conversion API and attribution audit endpoints in the corresponding mode. Dashboard management endpoints (partners, commissions, analytics) require JWT authentication via the merchant login flow.
Test vs. live keys
The key prefix determines which mode the request operates in. Test keys (*_test_*) access test-mode data; live keys (*_live_*) access live-mode data. See Test Mode for details on data isolation.
Rate limits
Rate limits protect the API from abuse and ensure fair usage for all merchants. Limits are applied per key or per IP address, depending on the endpoint.
| Zone | Endpoints | Limit | Scope |
|---|---|---|---|
| Tracking | POST /v1/clicks | 1,000 req/min | Per pk_* key |
| Conversion API | POST /api/v1/conversions | 120 req/min | Per sk_* key |
| Auth | /api/v1/auth/** (login, register, refresh, forgot/reset password) | 10 req/min | Per IP address |
| General API | All other /api/v1/* endpoints | 300 req/min | Per IP address |
When you exceed a rate limit, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.
{
"statusCode": 429,
"message": "Rate limit exceeded",
"error": "Too Many Requests",
"code": "RATE_LIMIT_EXCEEDED"
}
If you need higher limits for high-volume integrations, contact support.
Error format
All API errors return a consistent JSON structure:
{
"statusCode": 409,
"message": "Referral code already taken",
"error": "Conflict",
"code": "REFERRAL_CODE_TAKEN"
}
| Field | Type | Description |
|---|---|---|
statusCode | number | HTTP status code |
message | string | Human-readable error description (not stable — do not match on this) |
error | string | HTTP status text |
code | string | Machine-readable error code (stable — use this for programmatic handling) |
Validation errors (400) include per-field details:
{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request",
"code": "VALIDATION_ERROR",
"details": [
{
"field": "amount_cents",
"message": "Expected number, received string"
}
]
}
Error codes
General errors
| Code | HTTP Status | Description | Suggested fix |
|---|---|---|---|
VALIDATION_ERROR | 400 | Request body or query parameters failed validation | Check the details array for specific field errors |
INVALID_API_KEY | 401 | Missing or invalid secret API key (Conversion API) | Verify your sk_* key is correct, active, and matches the mode |
INVALID_PUBLIC_KEY | 401 | Missing or invalid public key (tracking endpoints) | Verify your pk_* key is correct, active, and matches the mode |
INVALID_SECRET_KEY | 401 | Missing or invalid secret key (audit endpoints) | Verify your sk_* key is correct and active |
FORBIDDEN | 403 | Valid credentials but insufficient permissions | Check that your key type matches the endpoint (e.g., sk_* for server-side) |
NOT_FOUND | 404 | Resource does not exist | Verify the resource ID and that it belongs to your account |
RATE_LIMIT_EXCEEDED | 429 | Too many requests | Wait for the duration in the Retry-After header, then retry |
INTERNAL_ERROR | 500 | Unexpected server error | Retry after a brief delay. If persistent, contact support |
Conversion errors
| Code | HTTP Status | Description | Suggested fix |
|---|---|---|---|
CONVERSION_DUPLICATE | 409 | A conversion with this external_transaction_id already exists | This is expected for idempotent retries. No action needed |
NO_ATTRIBUTION_SIGNAL | 422 | Neither click_id nor promo_code was provided | Include at least one attribution signal in the request |
MODE_MISMATCH | 422 | Test-mode key used with a live-mode signal (or vice versa) | Use matching key and signal modes (both test or both live) |
When a click_id or promo_code is provided but cannot be resolved (e.g., expired attribution, unknown click), the API returns 201 with "attributed": false instead of an error. Check the attribution.explanation field in the response for details.
Partner errors
| Code | HTTP Status | Description | Suggested fix |
|---|---|---|---|
PARTICIPANT_ERASED | 409 | The partner's data has been erased (GDPR) | This partner can no longer participate. No action possible |
REFERRAL_CODE_TAKEN | 409 | The requested referral code is already in use | Choose a different referral code |
APPLICATION_DUPLICATE | 409 | The partner has already applied to this program | No action needed — check the existing application status |
Webhook errors
| Code | HTTP Status | Description | Suggested fix |
|---|---|---|---|
WEBHOOK_NOT_FOUND | 404 | Webhook endpoint does not exist | Verify the endpoint ID |
WEBHOOK_ENDPOINT_LIMIT_EXCEEDED | 422 | Maximum of 10 endpoints per mode reached | Delete unused endpoints before creating new ones |
WEBHOOK_URL_NOT_HTTPS | 422 | Live-mode webhooks require HTTPS | Use an HTTPS URL for live-mode endpoints |
WEBHOOK_URL_PRIVATE_IP | 422 | URL resolves to a private/reserved IP address | Use a publicly accessible URL |
WEBHOOK_URL_UNRESOLVABLE | 422 | Could not resolve the URL hostname | Check for typos in the URL or DNS configuration |
Pagination
List endpoints return paginated results:
{
"data": [...],
"total": 142,
"page": 1,
"limit": 20
}
Use the page and limit query parameters to navigate. The default page size is 20, with a maximum of 100.
Request IDs
Every request is assigned a unique request ID for log correlation. If you include an X-Request-Id header in your request, Selgeo uses that value; otherwise, one is generated automatically. Include your X-Request-Id value (or the timestamp and endpoint of your request) when contacting support — it helps us locate the relevant logs quickly.
OpenAPI Specification
The full OpenAPI (Swagger) specification is available for download. You can import this file into tools like Postman, Insomnia, or any OpenAPI-compatible client to explore available endpoints.