← back

How it runs

The hub is not a form over a database. This is the machinery: what runs on a schedule, how a deployment joins, and what the two of them say to each other.

01 — SCHEDULE

Seven jobs on the clock

Straight from the deployment's cron table.

every 2 min
*/2 * * * *
/api/cron/status-probe

One probe per tracked component. The public page reads a 5-minute window, so two ticks per window leaves room for a dropped one.

every 5 min
*/5 * * * *
/api/cron/heartbeat-watch

Flips every heartbeat whose last ping is older than its own interval + grace to missed, records the transition, and pages its recipients.

every 15 min
*/15 * * * *
/api/cron/email-verify-pending

Re-polls the mail provider for domains stuck in pending DNS, so nobody has to come back and click Verify after fixing their records.

daily 03:00 UTC
0 3 * * *
/api/cron/fx-rates

One FX pull for the whole platform. Rates ride the sync envelope down to every tenant instead of each one holding its own API key.

daily 04:00 UTC
0 4 * * *
/api/cron/revenue-snapshot

Per-tenant MRR snapshot joined against licenses, instances and orgs — revenue recorded next to the load that produced it.

daily 06:30 UTC
30 6 * * *
/api/cron/epay-hard-delete

Hard-deletes merchants past their revocation grace window. Soft-deleted tenants inside the window are left alone to wind down.

02 — ONBOARDING

From cold boot to first sync

A claim code binds a fresh instance to a tenant, a license and a subscription — no env-file archaeology.

01

Boot the instance

On first start with no license, the instance asks the hub for a claim code and prints it. The code is good for 15 minutes; the instance polls for the result every 3 seconds.

Setup required — link your license

    Code:   KPXY-4829
    Go to:  https://kuploy.app/claim
02

Claim it

Sign in at kuploy.app/claim, enter the code, and pick the tenant it should land under — an existing one, or a new one so a PaaS deployment doesn't get mixed into a SaaS billing entity. The hub mints the license and opens the subscription for that product.

POST /api/claim/generate
{ "instanceUrl": "https://dc1.acme.net", "instanceName": "dc1-montreal", "productType": "kuploy-cloud" }

→ { "code": "KPXY-4829", "expiresAt": "…+15m" }
03

It starts syncing

The next poll returns the license key and instance id, and the hourly sync takes over from there. No env file to hand-edit — though the license page will hand you one if you prefer that route.

GET /api/claim/KPXY-4829
→ { "status": "claimed", "licenseKey": "lic_isp_acme_…", "instanceId": "inst_3f9a2c71" }

# or, by hand:
LICENSE_KEY=lic_isp_acme_2f81c4d9e6b04a7c
LICENSE_HUB_URL=https://kuploy.app
03 — THE_WIRE

The API a deployment speaks

One hourly round trip carries usage up, and entitlements, credentials and reference data down.

POSTkuploy.app/api/license/sync

Hourly, from every instance. One round trip carries usage up and entitlements, credentials and reference data down.

request
{ "licenseKey": "lic_isp_acme_2f81c4d9e6b04a7c", "instanceId": "inst_3f9a2c71", "localUsage": { "projects": 5, "apps": 12, "databases": 3, "buildMinutes": 210, "storageBytes": 48318382080 }, "serviceStats": { "totalContainers": 84, "runningContainers": 81, "erroredContainers": 0, "avgCpuPercent": 37.2 }, "clusterLocation": { "city": "Montréal", "country": "CA", "latitude": 45.50, "longitude": -73.57 }, "visitorSummary": { "totalSessions30d": 8420, "clusterCount": 3, "topCountries": [{ "country": "CA", "count": 5120 }] }, "expectedChecks": ["cnpg-control", "ingress"], "vaultRequested": true, "epayRequested": true
}
response
{ "valid": true, "tenantId": "tnt_acme", "plan": "business", "limits": { "projects": 50, "apps": 150, "databases": 75, "customDomains": 50, "buildMinutes": 5000 }, "features": { "customDomains": true, "aiAssistant": true, "multiServer": true, "supportTier": "priority", "enablePaymentGateway": true, "managedBackupVault": true, "vaultRetentionDays": 30, "vaultQuotaGB": 100 }, "vault": { "bucket": "kuploy-vault-acme-3f9a2c71", "endpoint": "https://<account>.r2.cloudflarestorage.com", "accessKeyId": "...", "secretAccessKey": "<once, at vend>", "secretName": "cnpg-backup-s3", "retentionDays": 30 }, "epay": { "gatewayUrl": "https://epay.kuploy.app", "merchantCode": "mch_acme_7f21", "merchantKey": "..." }, "orgDomain": [{ "orgId": "org_42", "domain": "shop.acme.net", "emailStatus": "verified", "storefrontStatus": "verified" }], "fxRates": [{ "baseCurrency": "USD", "quoteCurrency": "XAF", "rate": 604.21, "fetchedAt": "2026-08-25T03:00:04.117Z" }], "effectiveRemaining": { "projects": 38, "apps": 119, "buildMinutes": 3160 }, "warnings": ["buildMinutes at 92% - upgrade soon"], "gracePeriodHours": 72, "cacheExpiresAt": "2026-08-25T15:04:00.000Z"
}