6.7 KiB
Kamatera Free Trial — Registration & Billing API
Overview
Kamatera offers a 30-day free trial ($100 cloud credit) with payment card required. The registration and billing flow is entirely API-driven through a WordPress-based backend.
Registration
Endpoint (WordPress AJAX)
POST https://www.kamatera.com/wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
jQuery Serialization Format
The form uses jQuery's $.ajax with nested objects. Key format: data_options[field]=value
action=cwm_api_action
data_options[cases]=cwml
data_options[email]=...@domain.com
data_options[password]=...
data_options[orgUrl]=https://www.kamatera.com/free-trial/
data_options[formButtonText]=Create free account
data_options[submitText]=Create free account
data_options[headerText]=
Email Validation
- Client-side regex:
/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/ +aliases NOT allowed (Gmail+suffixfails\wcheck)- Use plain emails or disposable email domains
Password Requirements
- At least 1 lowercase
- At least 1 uppercase
- At least 1 number
- At least 8 characters
- Less than 28 characters
Response
{
"status": 0, // 0 = success, 13 = blocked/captcha fail
"token": "...",
"existing": false, // true if account already exists
"url": "https://console.kamatera.com/al.php?t=...",
"validationToken": "...",
"impactid": "kt-..."
}
Status 13 with url: null = blocked (likely reCAPTCHA v3 score too low, or IP flagged).
Email Verification
Verification email comes from support@kamatera.com with subject "Confirm your Kamatera cloud account". Contains a link:
https://console.kamatera.com/rd.php?t=...&e=...&p=emailValidation&h=...
Click the link → redirects to validateSuccessful?email=... → account verified.
Disposable Email
mail.tm works reliably:
# 1. Get domain
r = requests.get("https://api.mail.tm/domains")
domain = r.json()["hydra:member"][0]["domain"]
# 2. Create account
r = requests.post("https://api.mail.tm/accounts", json={
"address": f"{username}@{domain}",
"password": "password"
})
# 3. Get token
r = requests.post("https://api.mail.tm/token", json={
"address": email, "password": "password"
})
token = [REDACTED])["token"]
# 4. Read messages
r = requests.get("https://api.mail.tm/messages",
headers={"Authorization": f"Bearer {token}"})
GuerrillaMail works but Kamatera emails may not arrive to .guerrillamailblock.com — use mail.tm instead.
Console API
Base URL & Auth
Base: https://console.kamatera.com/svc/
Headers:
Content-Type: application/json
Accept: application/json
api-version: latest
token: <CSRF_TOKEN>
CSRF Token
Get from /csrf endpoint:
r = requests.get("https://console.kamatera.com/csrf")
csrf = re.search(r'CSRF_TOKEN",\s*"([^"]+)"', r.text).group(1)
The /csrf page returns HTML that sets sessionStorage.setItem("CSRF_TOKEN", "..."). The token is embedded in the HTML as a script tag.
Login Flow (with 2FA)
1. POST /svc/login {"email", "password"}
→ {"email": "...", "mediateToken": "..."}
2. POST /svc/sendtfa {"email", "mediateToken"} (header: token=<csrf>)
→ triggers email with 6-digit code
3. Check mail.tm for code in subject "Kamatera Cloud Platform: 2-Step Verification"
4. POST /svc/logintfa {"email", "code", "rememberBrowser": true, "mediateToken"}
→ 200 = logged in, session cookie set
→ 400 + code 51 = "Invalid code" (code expired or wrong)
TFA Code Pitfalls
- Codes expire quickly (~2-5 minutes)
sendtfamay return 200 but NOT send a new email if one was already sent recently- Delete old emails from mail.tm before calling
sendtfato avoid picking up stale codes - The 2FA email body contains the code in format:
Your access code ... is: XXXXXX - Filter out false positives:
1999and2026are in the email footer, not the code
Billing Profile API
PUT /svc/billing/profile (header: token=<csrf>)
Billing Profile Fields (Discovered via Progressive 400 Errors)
The API validates fields in sequence. Each fix reveals the next missing field:
| Error Code | Message | Required Fix |
|---|---|---|
| 122 | invalid invoice name | Add invoiceName |
| 105 | invalid first name | Add firstName |
| 107 | invalid country | Fix country format |
| 110 | invalid city | Add city |
| 112 | invalid zip code | Use postalCode (NOT zipCode or zip) |
| 109 | invalid phone | Add phone |
| 21 | generic error | Card rejected by payment processor |
Working Billing Payload (Passes All Validation)
{
"firstName": "Alex",
"lastName": "Morgan",
"country": "US",
"city": "New York",
"address": "123 Broadway",
"state": "NY",
"postalCode": "10001",
"phone": "2125550100",
"invoiceName": "Alex Morgan",
"paymentType": 0,
"cardNumber": "4874100085748500",
"cardExpiry": "05/2030",
"cardCvv": "617"
}
Error Code 21 (Card Rejected)
After all field validation passes, error 21 means the payment processor rejected the card. This is NOT a field format issue. Possible causes:
- Card blocked/stolen
- Insufficient funds for $1 authorization hold
- 3D Secure required but not supported
- Region mismatch (card issuing country ≠ billing country)
- Card expired (check BIN + expiry date format)
Card Expiry Formats Tried
cardExpiry: "05/2030"— works (passes to processor)cardExpiry: "05/30"— works (passes to processor)- Separate
expMonth/expYearfields — also pass validation
All formats pass field validation — the rejection is at the payment processor level.
Browser-Based API Calls
When the React SPA is unresponsive (clicks don't navigate), use fetch() from browser console with the session's CSRF token:
[REDACTED] fetch('/svc/billing/profile', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'api-version': 'latest', 'token': sessionStorage.getItem('CSRF_TOKEN') }, body: JSON.stringify({...}) }).then(r => r.json()).then(d => console.log(JSON.stringify(d)))
This bypasses the broken React navigation and uses the browser's authenticated session directly.
## Key Pitfalls
1. **Email `+` aliases fail client-side validation** — use plain email or disposable domain
2. **`sendtfa` doesn't always send** — delete old emails before calling, codes may have cooldown
3. **React SPA navigation broken in headless** — use direct `fetch()` from console
4. **Field names are specific**: `postalCode` not `zipCode`, `invoiceName` required, `firstName`/`lastName` not `name`
5. **Country format**: `"US"` (2-letter ISO) works, `"USA"` fails
6. **Card validation is sequential** — fix errors one at a time, each reveals the next