Welcome to The Crimson Market Developers Portal

Build powerful integrations with The Crimson Market API. Create OAuth applications, manage your credentials, and access our comprehensive documentation to bring trading functionality to your platform.

Developers Documentation

TCM OAuth Integration Docs

SDK-backed guidance for @crimsoncorp/oauth-react, plus portal-specific setup details for apps, scopes, and redirect registration.

Embedded Catalog Integration

Host a TCM catalog inside your own app, bridge the linked user into a TCM session, and authenticate the iframe with postMessage.

Local portal docs

Use this guide when you want to host a TCM catalog inside your own site or app with an iframe.

If you already have a TCM auth token and only need to validate it from your backend before trusting an embedded user, start with Embedded Widget Auth.

This is the recommended path for partner services that:

  • keep their own local user/session model
  • already use external_club provisioning
  • want users to open TCM listings, messaging, and trading inside the partner UI

Summary

Cross-domain embeds should not rely on third-party cookies.

Instead:

  1. Provision or link your users through the External Club Integration.
  2. Keep your OAuth client secret on your server.
  3. Request a server-backed bridge token for the linked TCM user.
  4. Deliver that token pair into the iframe with a postMessage handshake.
  5. Optionally brand the embed with theme query params.

The embedded catalog then uses the delivered TCM token as its authenticated session.

If your integration cannot use postMessage and instead passes the TCM session token to another backend-controlled surface, validate it server-to-server with POST /oauth/external-club/session/resolve before creating any local session.

What you need before starting

  • A registered OAuth client in the Developers portal.
  • The external_club scope enabled on that client.
  • A server-backed app. Do not expose clientSecret in the browser.
  • A provisioning flow that calls POST /:uiName/external-club/user.
  • A stable partner user id that you store as clubUserId.
  • If you want custom branding, a set of agreed brand colors and an optional helper icon URL.

If you have not completed those steps yet, start with:

Architecture

The recommended flow has three moving parts:

1. Partner app session

Your app authenticates the user with your own local session or JWT.

2. Server bridge

Your backend calls the TCM bridge endpoint using your OAuth client credentials and the linked external-club user id.

3. Iframe handshake

Your frontend waits for the embedded catalog to announce readiness, then posts the TCM token pair into the iframe.

Bridge endpoint

Endpoint

POST /oauth/external-club/session

Tenant-prefixed deployments may also expose:

POST /:uiName/oauth/external-club/session

For your current environments, the tenant-prefixed endpoint is:

Production: https://www.thecrimsonmarket.com/mana/oauth/external-club/session
Development: https://dev.portal.raum.au/mana/oauth/external-club/session

Authentication

Use HTTP Basic auth with your OAuth client credentials:

  • clientId
  • clientSecret

The client must be active and allowed to use external_club.

Request body

{
  "externalUserId": "partner-user-123"
}

externalUserId must match the clubUserId that was previously linked through POST /:uiName/external-club/user.

Example server request

const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");

const response = await fetch(`${TCM_OAUTH_API_URL}/oauth/external-club/session`, {
  method: "POST",
  headers: {
    Authorization: `Basic ${credentials}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    externalUserId: partnerUserId,
  }),
});

Success response

{
  "status": 201,
  "message": "Created",
  "data": {
    "token": "tcm_user_token",
    "refreshToken": "tcm_refresh_token",
    "userId": "portal-user-id"
  }
}

Your backend should usually unwrap response.data.data.

Iframe handshake

Child iframe behavior

The catalog sends:

{
  "type": "IFRAME_READY"
}

The catalog also sends resize events:

{
  "type": "IFRAME_HEIGHT",
  "height": 1840
}

Hosts should use that message to resize the iframe and avoid nested scrollbars.

Parent host response

Your app should respond with:

{
  "type": "TOKEN_DELIVERY",
  "token": "tcm_user_token",
  "refreshToken": "tcm_refresh_token"
}

Example host page

<iframe
  id="tcm-catalog"
  src="https://www.thecrimsonmarket.com/catalog/listings/pokemonlistings?parentOrigin=https%3A%2F%2Fpartner.example.com"
  width="100%"
  height="900"
  style="border:0"
  scrolling="no"
></iframe>

<script>
  const iframe = document.getElementById("tcm-catalog");

  window.addEventListener("message", async (event) => {
    if (event.origin !== "https://www.thecrimsonmarket.com") return;
    if (event.data?.type === "IFRAME_HEIGHT" && Number.isFinite(event.data?.height)) {
      iframe.style.height = `${Math.ceil(event.data.height)}px`;
      return;
    }

    if (event.data?.type !== "IFRAME_READY") return;

    const response = await fetch("/api/trade/catalog-session", { method: "POST" }).then((res) => res.json());
    const session = response.data;

    iframe?.contentWindow?.postMessage(
      {
        type: "TOKEN_DELIVERY",
        token: session.token,
        refreshToken: session.refreshToken,
      },
      "https://www.thecrimsonmarket.com",
    );
  });
</script>

Recommended iframe route

Use the internal catalog embed route:

https://www.thecrimsonmarket.com/catalog/listings/:subject

Examples:

  • https://www.thecrimsonmarket.com/catalog/listings/pokemonlistings
  • https://www.thecrimsonmarket.com/catalog/listings/morelistings

Do not use the public /:uiName/listings/... route for embedded partners.

Optional branding

The catalog supports a standardized set of iframe query params so partner brands can adjust the embedded experience without forking the catalog UI.

Embedded controls

Use embed_ params when the iframe should show partner-controlled controls inside the catalog page itself. These controls scroll with normal page content and are persisted during catalog navigation.

Subject selector params

  • embed_subject_selector: set to true to show a full-width subject dropdown at the top of catalog listing pages.

Currency params

  • embed_currency: set to true to show a currency action section.
  • embed_currency_subjects: comma-separated catalog subject slugs where the currency section should appear. Omit this param to show it on every subject.
  • embed_currency_relic_type: the service.relics currency or relic type key, for example charades-coin.
  • embed_currency_label: display name for the currency.
  • embed_currency_rate: optional exchange-rate text, for example 100 Charades Coin = $1 AUD.
  • embed_currency_wallet_label: wallet button text.
  • embed_currency_buy_label: buy button text.
  • embed_currency_info_label: optional info-link text. If omitted, the info action is hidden.

Host navigation params used by embedded controls

  • host_vault_url: where the wallet button sends the parent app.
  • host_buy_currency_url: where the buy button sends the parent app. This is treated as an exact host-owned destination and can include {relicType} if the route needs the selected service.relics currency key.
  • host_currency_info_url: where the optional info button sends the parent app.

Relative host URLs are resolved by the parent application. Absolute URLs can be used for standalone partner sites.

Stock Checker example

https://www.thecrimsonmarket.com/catalog/listings/pokemonlistings?parentOrigin=https%3A%2F%2Fwww.thecrimsonmarket.com&embed_subject_selector=true&embed_currency=true&embed_currency_subjects=pokemonlistings&embed_currency_relic_type=charades-coin&embed_currency_label=Charades%20Coin&embed_currency_rate=100%20Charades%20Coin%20%3D%20%241%20AUD&embed_currency_wallet_label=Wallet&embed_currency_buy_label=Buy%20Charades%20Coin&host_vault_url=%2Fvault&host_buy_currency_url=%2Fvault%3Ftab%3Dbuy%26relicType%3Dcharades-coin&host_currency_info_url=%2Fcharades-coin

Partner currency example

Use the currency key issued by service.relics for embed_currency_relic_type. Do not expose service secrets in iframe URLs.

https://www.thecrimsonmarket.com/catalog/listings/morelistings?parentOrigin=https%3A%2F%2Fpartner.example.com&embed_subject_selector=true&embed_currency=true&embed_currency_subjects=morelistings&embed_currency_relic_type=partner-credit&embed_currency_label=Partner%20Credit&embed_currency_rate=100%20Partner%20Credit%20%3D%20%241%20USD&embed_currency_wallet_label=Wallet&embed_currency_buy_label=Buy%20Partner%20Credit&host_vault_url=https%3A%2F%2Fpartner.example.com%2Fwallet&host_buy_currency_url=https%3A%2F%2Fpartner.example.com%2Fwallet%2Fbuy

Color and surface tokens

  • theme_brand
  • theme_brand_dark
  • theme_brand_deep
  • theme_primary_button
  • theme_primary_gradient_start
  • theme_primary_gradient_end
  • theme_secondary_button
  • theme_secondary_button_hover
  • theme_secondary_button_inactive
  • theme_header
  • theme_back_container
  • theme_front_container
  • theme_middle_container_top
  • theme_front_container_top
  • theme_container_background
  • theme_container_background_hover
  • theme_border
  • theme_input
  • theme_input_border
  • theme_input_focus_border
  • theme_link
  • theme_club_fire_light

Helper icon token

  • theme_ai_helper_icon

This changes the icon used beside the "Help me Search" AI helper input.

Example branded iframe URL

https://www.thecrimsonmarket.com/catalog/listings/pokemonlistings?parentOrigin=https%3A%2F%2Fpartner.example.com&theme_brand=%23e24a2a&theme_primary_button=%23e24a2a&theme_primary_gradient_start=%23ff6b4a&theme_primary_gradient_end=%23a61f08&theme_back_container=%230b0b0f&theme_border=rgba(255,255,255,0.18)&theme_ai_helper_icon=https%3A%2F%2Fpartner.example.com%2Fmascot.webp

Branding guidance

  • Prefer changing only the standardized theme params, not internal catalog classes.
  • Keep contrast high enough for white catalog text.
  • Provide a stable public image URL for theme_ai_helper_icon.
  • Pass only exact values you want to override; all other tokens fall back to TCM defaults.

Recommended implementation pattern

For production partner apps:

  • authenticate the user in your own system first
  • keep your OAuth clientSecret on the server only
  • create a partner-only backend route such as /api/trade/catalog-session
  • have that route call TCM with the current partner user id
  • cache only as needed; most apps can request a fresh bridge session on page load
  • listen for IFRAME_HEIGHT and resize the iframe instead of showing an inner scrollbar
  • treat the iframe URL as configuration, including optional branding query params

Security requirements

  • Never expose your OAuth clientSecret in browser code.
  • Validate the iframe event.origin before answering IFRAME_READY.
  • Post tokens back only to the exact TCM origin you embedded.
  • Pass parentOrigin in the iframe URL when you want stricter child-side origin validation.
  • Treat the returned token and refreshToken as user session material.
  • Sanitize or strictly control any branding query params you append to the iframe URL.

Minimum rollout checklist

  1. Create an OAuth client with external_club.
  2. Provision partner users through POST /:uiName/external-club/user.
  3. Add a backend route that exchanges externalUserId for a TCM session.
  4. Embed https://www.thecrimsonmarket.com/catalog/listings/:subject.
  5. Add an iframe page that waits for IFRAME_READY.
  6. Reply with TOKEN_DELIVERY using the bridged TCM tokens.
  7. Listen for IFRAME_HEIGHT and resize the iframe.
  8. Add optional theme params if you want partner-specific branding.
  9. Test login, refresh, signed-out behavior, and iframe resizing.

Related guides