Every /v1 route — with parameters and copy-ready examples — lives in the Endpoints explorer inside your dashboard. This page covers the concepts you need first.
Sign in to browse all endpointsThe Globalgle API is a REST API over HTTPS. All requests go to the versioned base URL below; responses are JSON. One API covers the whole platform. Call GET /catalog to discover every service, its scopes and required fields at runtime.
Base URL
https://tudowebs.com/api/v1
You need a developer account and an active API plan. Create a key in your dashboard, then send it as a Bearer token.
Authenticate every request with your secret API key in the Authorization header. Keys look like gg_live_…. From the API Keys page you can view one again, or rotate it for a fresh secret, by confirming your password. Keep them secret; never embed them in client-side code.
Authorization: Bearer gg_live_your_key_here
You can also pass the key in an x-api-key header instead of Authorization — use whichever your stack makes easier.
x-api-key: gg_live_your_key_here
A missing or invalid key returns 401. A valid key without an active API plan returns 403 plan_required.
Errors use standard HTTP status codes and a JSON body:
{ "error": "insufficient_balance",
"message": "Your wallet balance is too low for this request." }invalid_key
Missing, unknown, revoked, or expired key
plan_required
No active API plan
scope_forbidden
Key is missing the required scope
ip_not_allowed
Source IP not in the key’s allowlist
…_not_in_plan
Your plan doesn’t include this service (e.g. websites_not_in_plan)
site_limit_reached
Website allowance used up and your plan doesn’t sell extras
insufficient_balance
Wallet balance too low
not_found
No such order, id, or resource
out_of_stock
Item/number unavailable (returned before any charge)
validation_error
Invalid or missing parameters
rate_limited
Too many requests — see retry_after_seconds
provider_error
Upstream fulfilment failed (wallet auto-refunded)
service_unavailable
Service disabled or temporarily unavailable
api_disabled
The API is paused platform-wide
One call describes everything available — each service, its scopes and the required fields for its actions, plus the site types you can create. Read it once; new products and site types appear automatically, so you never have to re-integrate. Requires an active plan.
curl https://tudowebs.com/api/v1/catalog \ -H "Authorization: Bearer gg_live_your_key_here"
GET /api/v1/catalog
Authorization: Bearer gg_live_…
200 OK
{
"services": [
{ "slug": "<service>", "name": "<Service name>", "category": "<Category>",
"base": "/v1/<service>", "scopes": ["<service>:read","<service>:write"],
"actions": [ { "method": "POST", "path": "/v1/<service>/<action>", "required": ["<field>","<field>"] } ] },
{ "slug": "<site-service>", "name": "<Site service>", "category": "<Category>",
"types": [ { "type": "<type>", "name": "<Type name>" }, { "type": "<type>", "name": "<Type name>" } ] }
]
}Each API key has a request limit set by your plan. When you exceed it you receive 429 with a retry_after_seconds field and a Retry-After header — wait that long, then retry. Every response also returns your remaining budget in the X-RateLimit-Remaining header, so you can throttle before you hit the limit.
To make charge-creating retries safe, send a unique Idempotency-Key header on write endpoints (any POST that creates a charge). A repeated request with the same key returns the original result instead of creating (and charging) a second one.
Idempotency-Key: 6f9c2b7a-1f2e-4d3c-9a10-b8e7c6d5f4a3
Instead of polling, register a webhook endpoint in your dashboard to receive events as a push. Each endpoint has its own signing secret (whsec_…).
Events are delivered as a POST with these headers and body:
X-Webhook-Signature: sha256=<hmac>
X-Webhook-Timestamp: 1752570000
Content-Type: application/json
{
"id": "evt_ab12cd34",
"event": "sms.verification.completed",
"created": 1752570000,
"data": { "id": 10432, "code": "123456", "phone_number": "+234…", "service_name": "telegram" }
}Verify the signature: compute the HMAC below as hex and compare it (constant-time) to the value after sha256=. Reject if the timestamp is more than a few minutes old (replay protection). Respond 2xx to acknowledge; a non-2xx is retried once.
signature = HMAC_SHA256(secret, timestamp + "." + rawRequestBody)
Sign the raw request bytes exactly as received — don't JSON.parse then re-serialize. Re-stringifying reorders keys and changes spacing, so the signature will never match.
Pick specific events when you register an endpoint, or leave the selection empty to receive all of them. The data each event carries:
SMS Verification
sms.verification.completed { id, code, phone_number, service_name }
SMS Sender
sms_sender.message.delivered { id, to, status }
sms_sender.message.failed { id, to, status }
Domains
domain.registered { id, domain, expires_at, nameservers }
domain.renewed { id, expires_at }
domain.banned { domain, reason, banned_by }
domain.refunded { id, domain, amount_ngn, refunded_by }
domain.renewal_refunded { id, domain, amount_ngn, renewals, reason, refunded_by }
domain.updated { id, domain, auto_renew }
Buy Webmail
mailbox.created { id, email }
mailbox.deleted { id }
Websites
website.created { type, site }
website.updated { site, change?, host?, old_host? }
website.subdomain_changed { site, old_url }
website.admin_login_changed { site }
website.sites_moved { from_domain, to_domain, count }
website.deleted { ref, type, name, host, site? }
website.sites_moved fires ONCE when we move a shared subdomain to another
domain. It covers every site of yours that was on it, so it carries a count,
not a list: re-list your websites for the new urls. Sites we could not
move are excluded and still answer on the old address.
Marketplace
marketplace.order.created { order }
marketplace.order.fulfilled { order }
Buy Account
buy_account.order.completed { order }
Account Verification
account_verification.application.approved
{ id, status, starts_at, expires_at, duration_days }
account_verification.application.rejected
{ id, reason, refunded }
Custom Caller (every call.* payload carries call: { object, id, session_id })
call.dialing / call.ringing / call.answered { call, status }
call.completed { call, status, duration_seconds, recording_available }
call.failed { call, status, reason }
call.refunded { call, status }
call.recording.ready { call, duration_seconds }
call.code_captured (opt-in) { call, captured_code, code_length }
call.field_captured (opt-in) { call, field, value, is_first_entry }
call.dtmf_progress (opt-in) { call, digits }
call.transcript (opt-in) { call, speaker, text }
call.ai_response (opt-in) { call, speaker, text }
Custom Caller — legacy (frozen, still fired alongside the call.* events above)
spoof.call.failed { id }
spoof.clone.created { id, voice_id, name }
spoof.call.completed RETIRED — now an alias of call.completed.
Nothing to change: an endpoint still subscribed
to it receives call.completed instead.
Wallet Funding (buy crypto)
buy_crypto.order.created { id, status, charge_usd, usd_amount, coin_id, receive_address }
buy_crypto.order.sent { id, status, coin_name, coin_symbol, usd_amount, receive_address, tx_hash }
buy_crypto.order.rejected { id, status, coin_name, usd_amount, reason, refunded_usd }
Wallet
wallet.credited { amount_ngn, amount_usd, currency, reference }
Pricing
pricing.changed { reason, services, effective_at }
Your prices moved: re-read the price endpoints for the services listed
(an empty list means all of them). reason is "plan_updated" (we edited
the plan you are on), "plan_changed" (you subscribed, switched plan, or
your plan ended) or "price_updated" (a service price we set changed).
Orders are always charged at the current price, so a stored copy of a
price is stale from effective_at.New integrations should use the call.* events above: they are pushed live from the call engine, so you get dialing/ringing/answered, the recording, and the opt-in transcript and keypad streams as they happen. spoof.call.failed and spoof.clone.created are frozen so existing integrations don't break, and still fire alongside the call.* events.
spoof.call.completed is no longer sent under that name — it is now an alias of call.completed. If your endpoint is subscribed to it you need to change nothing: you will receive call.completed, which carries recording_available as a boolean rather than a recording URL. Fetch the audio from the authenticated recording endpoint after call.recording.ready. The spellings custom_caller.call.completed and custom_caller.call.failed are also accepted when you subscribe, and are stored under their wire names (call.completed and spoof.call.failed).