Domain Transfers API
API reference for transferring a verified domain to another Taifa Mail account or workspace.
Base URL: https://govconnect.ke/v1
All endpoints require authentication via API Key or JWT cookie. API keys are passed as Authorization: Bearer tfm_k_....
A transfer is a two-sided handshake: the source owner initiates, the recipient accepts, then a 24-hour safety window runs before ownership moves. See the Transfer a Domain guide for the full flow.
Domain transfer may be gated during rollout. When the feature is disabled, these endpoints return 404.
The transfer object
Transfer statuses
| Status | Meaning |
|---|---|
pending | Initiated, waiting for the recipient to accept. Expires 7 days after initiated_at. |
accepted | Recipient accepted. The 24-hour safety window is running (cooloff_until). |
completed | Safety window ended. Ownership has moved. |
declined | Recipient declined the request. |
cancelled | Source cancelled the request while it was pending. |
reversed | Source stopped the transfer during the safety window. Ownership never moved. |
expired | The 7-day request window passed without acceptance. |
Initiate a transfer
Starts a transfer of a domain you own. Runs the preconditions and emails the recipient an accept link. Rate limited to 5 requests per minute.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
target_email | string | Yes | Email of the recipient. Must match an existing Taifa Mail account. |
note | string | No | Optional message shown to the recipient. Max 1000 characters. |
Response (200): the transfer object with status: "pending".
Preconditions. The request is rejected with 409 Conflict and a code when:
code | Meaning |
|---|---|
transfer_active | A pending or accepted transfer already exists for this domain. |
broadcasts_in_flight | A broadcast on this domain's senders is sending or scheduled. The offending items are listed under blockers. |
scheduled_emails | This domain has scheduled transactional emails. |
self_transfer | The target email is your own account. |
target_not_found returns 404 when no Taifa Mail account uses the target email.
List incoming transfers
Returns active (pending or accepted) transfers addressed to the authenticated user's email.
Response: an array of transfer objects.
List outgoing transfers
Returns transfers the authenticated user has initiated (most recent first).
Response: an array of transfer objects.
Check accept eligibility
Reports whether the authenticated user can accept an incoming transfer right now. The dashboard calls this before showing the Accept button so the recipient sees a plan domain-limit block up front, instead of hitting it on the accept call.
Response (200):
| Field | Type | Description |
|---|---|---|
can_accept | boolean | true if the user can accept an incoming transfer now. |
code | string | null | Reason the user is blocked (domain_limit), or null when can_accept is true. |
message | string | null | Human-readable explanation to show the user, or null when unblocked. |
current_domains | integer | Domains the user currently owns. |
domain_limit | integer | The user's plan domain limit (-1 means unlimited). |
This check is advisory for the UI. The same limit is still enforced server-side on accept, so a transfer can never push an account past its plan.
Accept a transfer by token
Accepts using the one-shot token from the invitation email. Moves the transfer to accepted and starts the 24-hour safety window. The recipient's plan domain limit is enforced here.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
target_org_id | string | No | Workspace to receive the domain. Defaults to your oldest workspace. |
rotate_dkim | boolean | No | Mint a fresh DKIM key on transfer. Defaults to false (keep the current key). |
Response (200): the transfer object with status: "accepted" and a cooloff_until timestamp.
Errors:
| Status | code | Meaning |
|---|---|---|
| 404 | not_found | The token is invalid or already used. |
| 409 | not_pending | The transfer can no longer be accepted. |
| 410 | expired | The 7-day request window has passed. |
| 403 | wrong_recipient | The transfer was sent to a different account. |
| 403 | org_access_lost | You no longer belong to the chosen workspace. Pick another and retry. |
Accept a transfer by id
Accepts from the authenticated dashboard without the email token. The caller is matched to the transfer by their account email. Body and responses match accept by token.
Decline a transfer
Declines a pending transfer addressed to you. The sender is notified.
Response (200): the transfer object with status: "declined".
Cancel a transfer
Withdraws a transfer you initiated while it is still pending. To reverse after acceptance, use stop instead.
Response (200): the transfer object with status: "cancelled".
Once a transfer is accepted, DELETE returns 409 with code: "not_pending". Use the stop link during the safety window.
Stop a transfer during the safety window
Reverses an accepted transfer during the 24-hour safety window using the stop token from the "stop this transfer" email. Ownership never moves.
Response (200): the transfer object with status: "reversed".
Errors:
| Status | code | Meaning |
|---|---|---|
| 404 | not_found | The stop link is invalid or already used. |
| 409 | not_stoppable | The transfer is no longer in the safety window. |
Admin force transfer
Platform-admin override for stuck cases. Skips the email handshake and the safety window: ownership moves immediately. Requires admin re-authentication.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
target_email | string | Yes | Email of the recipient account. |
rotate_dkim | boolean | No | Mint a fresh DKIM key on transfer. Defaults to true. |
Response (200): the transfer object with status: "completed".
Admin force transfer is immediate and irreversible. It revokes the previous owner's SMTP credentials for the domain and writes an audit entry on both sides.