Skip to main content

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​

NameRequiredValue
AuthorizationYesBearer <partner access token> with partner:provision
Content-TypeYesapplication/json (UTF-8)
Idempotency-KeyYesUnique per logical request; 1–200 printable ASCII characters without spaces
If-MatchYesCurrent membership ETag, such as "v2", from GET Member or the revoke response

Path parameters​

NameTypeRequiredDescription
external_idstringYesMember id used at creation; 1–64 ASCII letters, digits, periods, underscores or hyphens

Body parameters​

NameTypeRequiredDescription
authorizationobjectYesFresh 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.

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.

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​

StatusErrorMeaning
400validation_failedInvalid body, authorization, path or idempotency key
401unauthorizedInvalid partner token
403insufficient_scope, partner_not_enabled, partner_past_due, partner_suspended, partner_disabled, partner_not_activeScope or service policy blocks new seats
404member_not_foundUnknown member
409conflictMember is not revoked, or the legacy enrollment was never attached to an account
409existing_account_ineligibleAccount was deleted, disabled or acquired an incompatible paid plan
409partner_tag_required, seat_cap_reachedBusiness name or seat capacity needs attention
409idempotency_conflict, idempotency_in_progress, idempotency_key_expiredSee replay rules
412precondition_failedStale or malformed ETag; read the member and reassess before starting a new request
428precondition_failedMissing If-Match header
413 / 415validation_failedBody too large or unsupported content type/encoding
429rate_limited, too_many_concurrent_requestsHonor Retry-After
503outcome_unknownRetain and retry the exact body, original ETag and key

See Partner errors for common availability errors and response shapes.