POST Reopen Member
Fund a previously revoked member again. Reopening starts a new seat interval, increments the membership version and returns a new Connect link. The member must allow your app again; old credentials are not restored.
Endpoint
POST https://app.crosstrade.io/v1/api/partner/users/{external_id}/reopen
Headers
| Name | Required | Value |
|---|---|---|
| Authorization | Yes | Bearer <partner access token> with partner:provision |
| Content-Type | Yes | application/json (UTF-8) |
| Idempotency-Key | Yes | Unique per logical request; 1–200 printable ASCII characters without spaces |
| If-Match | Yes | Current membership ETag, such as "v2", from GET Member or the revoke response |
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Member id used at creation; 1–64 ASCII letters, digits, periods, underscores or hyphens |
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
authorization | object | Yes | Fresh consent request with a new PKCE challenge. See authorization fields |
No query parameters. Unknown body fields are rejected; the maximum body size is 64 KiB. The member must be revoked, your service must allow new seats, and the account must remain eligible for partner funding. An independently purchased paid CrossTrade plan can make it ineligible.
Code examples
Set PARTNER_TOKEN, MEMBER_ETAG, XT_REDIRECT_URI, CODE_CHALLENGE, OAUTH_STATE and IDEMPOTENCY_KEY. Use the member's current ETag, not a hard-coded version. Generate and retain a fresh PKCE pair with the body, ETag and key before sending. Retries of the same operation must retain all four.
- Python
- JavaScript
- cURL
import os
import requests
response = requests.post(
"https://app.crosstrade.io/v1/api/partner/users/th_8831/reopen",
headers={
"Authorization": f"Bearer {os.environ['PARTNER_TOKEN']}",
"Idempotency-Key": os.environ["IDEMPOTENCY_KEY"],
"If-Match": os.environ["MEMBER_ETAG"],
},
json={
"authorization": {
"scope": "tv:read manage:read",
"redirect_uri": os.environ["XT_REDIRECT_URI"],
"code_challenge": os.environ["CODE_CHALLENGE"],
"code_challenge_method": "S256",
"state": os.environ["OAUTH_STATE"],
"delivery": "pickup",
},
},
timeout=30,
)
response.raise_for_status()
result = response.json()
print("New ETag:", response.headers.get("ETag"))
connect = result["connect"]
# Store flow_id with the verifier and deliver the URL privately.
# An expired idempotent replay can return connect=None.
const response = await fetch(
"https://app.crosstrade.io/v1/api/partner/users/th_8831/reopen",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PARTNER_TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": process.env.IDEMPOTENCY_KEY,
"If-Match": process.env.MEMBER_ETAG,
},
body: JSON.stringify({
authorization: {
scope: "tv:read manage:read",
redirect_uri: process.env.XT_REDIRECT_URI,
code_challenge: process.env.CODE_CHALLENGE,
code_challenge_method: "S256",
state: process.env.OAUTH_STATE,
delivery: "pickup",
},
}),
},
);
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
console.log("New ETag:", response.headers.get("etag"));
const { connect } = await response.json();
// Store flow_id with the verifier and deliver the URL privately.
// An expired idempotent replay can return connect=null.
curl -sS -i -X POST "https://app.crosstrade.io/v1/api/partner/users/th_8831/reopen" \
-H "Authorization: Bearer $PARTNER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "If-Match: $MEMBER_ETAG" \
-d "{
\"authorization\": {
\"scope\": \"tv:read manage:read\",
\"redirect_uri\": \"$XT_REDIRECT_URI\",
\"code_challenge\": \"$CODE_CHALLENGE\",
\"code_challenge_method\": \"S256\",
\"state\": \"$OAUTH_STATE\",
\"delivery\": \"pickup\"
}
}"
Response
200 OK. A revoked member at version 2 becomes version 3, with ETag: "v3". The new URL lasts 10 minutes.
{
"success": true,
"member": {
"external_id": "th_8831",
"uid": "k3Qz9pLa",
"email": "member@example.com",
"name": "Jamie Rivera",
"status": "provisioned",
"ownership_verified": true,
"version": 3,
"environments": [
"demo"
],
"metadata": {},
"seat": {
"started": "2026-11-20T08:00:00Z",
"ended": null
},
"created": "2026-10-15T14:02:11Z",
"authorization": {
"status": "pending",
"flow_id": "pcf_99887766ccdd",
"purpose": "authorize",
"generation": 3
},
"grant": null
},
"connect": {
"flow_id": "pcf_99887766ccdd",
"purpose": "authorize",
"url": "https://app.crosstrade.io/v1/connect/xt_tk_...",
"expires": "2026-11-20T08:10:00Z"
},
"meta": {
"requestId": "preq_c0ffee0123456789",
"etag": "\"v3\""
}
}
Recorded results replay for 24 hours with Idempotent-Replayed: true. The original ETag is part of the request fingerprint, so retain it on retries even after success. Once the URL expires, replay returns connect: null; use POST Create Connect Link with a new key to obtain another URL for the now-open seat.
Errors
| Status | Error | Meaning |
|---|---|---|
| 400 | validation_failed | Invalid body, authorization, path or idempotency key |
| 401 | unauthorized | Invalid partner token |
| 403 | insufficient_scope, partner_not_enabled, partner_past_due, partner_suspended, partner_disabled, partner_not_active | Scope or service policy blocks new seats |
| 404 | member_not_found | Unknown member |
| 409 | conflict | Member is not revoked, or the legacy enrollment was never attached to an account |
| 409 | existing_account_ineligible | Account was deleted, disabled or acquired an incompatible paid plan |
| 409 | partner_tag_required, seat_cap_reached | Business name or seat capacity needs attention |
| 409 | idempotency_conflict, idempotency_in_progress, idempotency_key_expired | See replay rules |
| 412 | precondition_failed | Stale or malformed ETag; read the member and reassess before starting a new request |
| 428 | precondition_failed | Missing If-Match header |
| 413 / 415 | validation_failed | Body too large or unsupported content type/encoding |
| 429 | rate_limited, too_many_concurrent_requests | Honor Retry-After |
| 503 | outcome_unknown | Retain and retry the exact body, original ETag and key |
See Partner errors for common availability errors and response shapes.