POST /v1/keys/rotate
Self-service rotation of API and webhook secrets.
/api/v1/keys/rotateAuth requiredSelf-service rotation of apiSecret and webhookSecret. The request is signed with the current apiSecret; that signature is the proof of possession. No operator interaction is required.
The new secret(s) appear in the response exactly once. The previous value is invalidated immediately on success. Any in-flight request signed with the old secret returns code 3 AUTH_INVALID.
Request
rotatearray<"apiSecret" | "webhookSecret">requiredResponse
{
"code": 0,
"msg": "",
"data": {
"apiSecret": "newApiSecretShownOnceXXXXXXXXXXXX...",
"webhookSecret": "newWebhookSecretShownOnceXXXXX..."
}
}data.apiSecretstring (optional)data.webhookSecretstring (optional)Errors
| code | msg | HTTP | Description |
|---|---|---|---|
| 1 | INVALID_REQUEST | 400 | Body fails validation: unsupported asset, disabled pair, unparseable amount, invalid address, FLOAT without refundAddress, malformed Idempotency-Key. The same code + HTTP 409 fires on Idempotency-Key reuse with a different payload. |
| 2 | AUTH_REQUIRED | 401 | One of X-API-KEY / X-API-SIGN is missing on an endpoint that requires authentication. |
| 3 | AUTH_INVALID | 401 | Unknown key, malformed signature, signature mismatch, expired or replayed nonce, decrypt failure. Generic body — never an oracle for "which" of those it was. |
| 4 | AUTH_DISABLED | 401 | Partner record enabled = false. Reachable only with a valid signature, so the operator can distinguish a kill-switched partner from a stolen-and-revoked key. |
| 5 | RATE_LIMIT | 429 | Per-partner weight budget exhausted within the 60-second sliding window. Response includes Retry-After: <seconds>. |
| 14 | ROTATION_CONFLICT | 409 | Concurrent POST /v1/keys/rotate lost the conditional-claim race. The other call already rotated the secrets; treat the rotation as done and use whichever new secret was returned to that caller. If the new secret was not captured, request a fresh rotation through the operator. |
| 99 | INTERNAL | 500 | Unexpected server-side condition. Already logged on our side; safe to retry. |
Code examples
# Rotate apiSecret and/or webhookSecret. The current secret signs
# the request; the response carries the new secret(s) exactly once.
APIKEY="rWqZ...Rg"
APISECRET="G1JV...n4"
NONCE=$(openssl rand -hex 16)
BODY='{"rotate":["apiSecret","webhookSecret"]}'
SIGN=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$APISECRET" | sed 's/^.* //')
curl -sS -X POST "https://0trace.io/api/v1/keys/rotate" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $APIKEY" \
-H "X-API-SIGN: $SIGN" \
-H "X-API-NONCE: $NONCE" \
--data "$BODY"Rate limit
Weight 1 per call. Counts against the default 2500 weight-unit / minute sliding-window budget per partner.
Notes
Concurrency. Rotation is serialised. Two concurrent calls cannot both succeed. The loser receives { code: 14, msg: "ROTATION_CONFLICT" } with HTTP 409. Retry once the contention has cleared.
Audit. Every successful rotation is recorded for support and incident review. Source IPs are not stored in cleartext.
No staged rotation. The old secret is dead the moment this call returns 200. There is no overlap window during which both secrets are valid. Plan your deploy: rotate during a maintenance window or from a single process so the new value lands atomically across your fleet.