# API quick-start guide

Log in to your dashboard, grab your keys, and prepare your project

This guide gets you ready to call Nexus APIs from your app or scripts. Create or find your keys, keep secrets off the client, and verify everything with runnable examples.

<Aside type="note">
The code samples use Node and cURL. Any HTTP client works.
</Aside>

## What you will do

1. Sign in to the Nexus dashboard
2. Create a **program-scoped** Public key and Private key in **Sandbox**
3. Save keys in environment variables
4. Validate a creator code with your Public key
5. Post a test attribution with your Private key

## Keys at a glance

Use **program-scoped** keys by default. They implicitly target a single creator program group, which means you do not need to pass a `groupId` on each request and you avoid cross-program mistakes.

* **Public key**: client-side only for safe reads like creator code validation. Treat it as sensitive.
* **Private key**: server-side only for writes like posting attributions after a purchase. Never ship this to a client.

> Global keys exist but are rarely needed. If you choose a global key you must pass the `groupId` on each request. See [if you must use a global key](#if-you-must-use-a-global-key).

## Find or create your keys

1. Sign in to [https://www.nexus.gg/publisher/dashboard](https://www.nexus.gg/publisher/dashboard)
2. Open **Settings**, then **Developer**
3. In the scope selector at the top of the table, choose your program. Leave it on **Global** only if you need a key that works across every program
4. Select **Create API key**, or copy an existing key with the copy action on its row
5. Repeat in **Sandbox** and **Production**

Record the following:

* A **Public key** for client-side validation
* A **Private key** for server-side attribution posts

<ThemedImage light={apiKeysLight} dark={apiKeysDark} alt="The Developer settings page listing a public and a private API key for one program, with each key masked." />

## Environments

* **Sandbox** for test data: `https://api.nexus-dev.gg`
* **Production** for live data: `https://api.nexus.gg`

Keys are separate per environment. Rotate keys on a schedule or when people leave the team.

## Store secrets with environment variables

Keep keys out of source control. For Node, create a `.env` file for local use and a secret store in your deployment platform.

```bash
NEXUS_BASE_URL=https://api.nexus.gg/v1
NEXUS_PUBLIC_KEY=nexus_pk_your_key_here
NEXUS_PRIVATE_KEY=nexus_sk_your_key_here
```

## Step 1: validate a creator code

![A game dialog prompting the user to enter a creator code, which expires 14 days after entry.](../../assets/docs/api-quick-start-guide/game-creator-code-input-dialog.webp)

![The creator code input dialog with an example code entered in the text field.](../../assets/docs/api-quick-start-guide/game-creator-code-entry-example.webp)

Validate a creator code with your **program-scoped Public key**. No `groupId` needed.

<ApiMethod method="GET" path="/manage/members" />

<LinkCard title="View full reference" description="Parameters, responses, and status codes." href="/api/operations/getmembers/" />

<Tabs>
<TabItem label="cURL">
```bash
curl -G "https://api.nexus.gg/v1/manage/members" \
  -H "X-SHARED-SECRET: nexus_pk_your_key_here" \
  --data-urlencode "code=walt"
```

</TabItem>

<TabItem label="JavaScript">
```javascript
async function validateCreatorCode(code) {
  const url = new URL(`${process.env.NEXUS_BASE_URL}/manage/members`);
  url.searchParams.set('code', code);

  const res = await fetch(url, {
    headers: { 'X-SHARED-SECRET': process.env.NEXUS_PUBLIC_KEY }
  });

  if (!res.ok) throw new Error(`Validation failed with ${res.status}`);
  return res.json();
}

validateCreatorCode('walt')
  .then(data => console.log('Valid:', data))
  .catch(err => console.error('Invalid:', err.message));
```
</TabItem>
</Tabs>

If the code belongs to your program, the status should be `200 OK` with a single member record. The JSON response body looks something like this:

<Tabs>
<TabItem label="200">
```json
{
  "groupId": "2ixlNNFdJh-9scoXek80w",
  "groupName": "Creator Loop",
  "groupDefaultRevShare": 10,
  "id": "Hv98bvRCSm1jDDPhls1qC",
  "name": "turndownforwalt",
  "logoImage": "https://cdn.nexus.gg/FzVN5qcpr8s8irlokp/iQJWVLokKREqYXPxkO/images/logo-image.jpg",
  "tier": {
    "id": "M9cgAXgP3DqyuZcoszkeE",
    "name": "Tier 2",
    "revShare": 15
  },
  "codes": [
    {
      "code": "walt",
      "isPrimary": true,
      "isGenerated": false,
      "isManaged": false
    }
  ]
}
```
</TabItem>

<TabItem label="400">
```json
{
  "code": "CodeNotInGroup",
  "message": "Member not found for groupId 2ixlNNFdJh-9scoXek80w:  notacreator."
}
```
</TabItem>
</Tabs>

![A success notification confirming that a percentage of purchases will now go to the creator.](../../assets/docs/api-quick-start-guide/game-creator-code-success-message.webp)

## Step 2: attribute the purchase to the creator

![A game notification showing that 500 Aether Bucks were acquired as a new item reward.](../../assets/docs/api-quick-start-guide/game-item-reward-notification.webp)

Post an attribution after purchase with your **program-scoped Private key** from your server.

<ApiMethod method="POST" path="/attributions/transactions" />

<LinkCard title="View full reference" description="Parameters, responses, and status codes." href="/api/operations/creatorattribution/" />

<Aside type="note">
`playerId` only needs to be unique and stable for each player. It does not need to be the id you use internally. If you treat that id as PII, send a hash or a GUID instead, as long as the same player always gets the same value.

Nexus uses it to tell one player from another, which is what makes [unique-player and repeat-purchase analysis](/creator-code-api/attributions/performance-metrics/) possible for your program.
</Aside>

<Tabs>
<TabItem label="cURL">
```bash
curl -X POST "$NEXUS_BASE_URL/attributions/transactions" \
  -H "Content-Type: application/json" \
  -H "X-SHARED-SECRET: $NEXUS_PRIVATE_KEY" \
  -d '{
    "code": "walt",
    "subtotal": 499,
    "currency": "USD",
    "description": "500 Aether Bucks",
    "skuId": "500_Aether_Bucks",
    "transactionId": "71e09e4a-ea48-4cdc-acd4-092f37861731",
    "transactionDate": "2025-03-13T17:32:28Z",
    "playerId": "6f10c7f4-psd-123",
    "metrics": {
      "joinDate": "2017-07-22T17:32:28Z",
      "conversion": {
        "lastPurchase": {
          "date": "2025-07-22T17:32:28Z",
          "platform": "Android"
        },
        "totalSpendToDate": {
          "total": 12999,
          "currency": "USD"
        }
      }
    }
  }'
```
</TabItem>

<TabItem label="JavaScript">
```javascript
async function postAttribution(payload) {
  const res = await fetch(`${process.env.NEXUS_BASE_URL}/attributions/transactions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-SHARED-SECRET': process.env.NEXUS_PRIVATE_KEY
    },
    body: JSON.stringify(payload)
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Attribution failed: ${res.status} ${text}`);
  }
  return res.json();
}

postAttribution({
  code: "walt",
  subtotal: 499,
  currency: "USD",
  description: "500 Aether Bucks",
  skuId: "500_Aether_Bucks",
  transactionId: "71e09e4a-ea48-4cdc-acd4-092f37861731",
  transactionDate: "2025-03-13T17:32:28Z",
  playerId: "6f10c7f4-psd-123",
  metrics: {
    joinDate: "2017-07-22T17:32:28Z",
    conversion: {
      lastPurchaseDate: {
        date: "2025-01-22T17:32:28Z",
        platform: "Android"
      },
      totalSpendToDate: {
        total: 12999,
        currency: "USD"
      }
    }
  }
})
  .then(data => console.log("Attributed:", data))
  .catch(err => console.error(err));

```
</TabItem>
</Tabs>

<Aside type="caution">
The `POST /v1/attributions/transactions` response will not include `memberSharePercent`, because that value is only calculated asynchronously after the attribution is processed. To retrieve it, poll `GET /v1/attributions/transactions/{transactionId}` with backoff until it becomes available, or fetch it later via the list endpoint once processing has completed.
</Aside>

The status should be `200 OK` with a JSON response body that looks something like this:

<Tabs>
<TabItem label="200">
```json
{
  "transaction": {
    "creatorPaid": false,
    "currency": "USD",
    "description": "500 Aether Bucks",
    "skuId": "500_Aether_Bucks",
    "id": "PwezzrD9LbqPEF6pOm0mC",
    "memberId": "oleyE4Z6zwN07YnytKuw2",
    "playerId": "",
    "playerName": "",
    "platform": null,
    "subtotal": 499,
    "total": 499,
    "totalCurrency": "USD",
    "transactionDate": "2025-03-13T17:32:28Z",
    "transactionId": "71e09e4a-ea48-4cdc-acd4-092f37861731",
    "transactionStatus": "Normal",
    "metrics": null,
    "code": "walt"
  }
}
```
</TabItem>

<TabItem label="400">
```json
{
  "status": 400,
  "errors": [
    {
      "instancePath": "",
      "schemaPath": "#/oneOf/0/required",
      "keyword": "required",
      "params": {
        "missingProperty": "transactionId"
      },
      "message": "must have required property 'transactionId'"
    }
    ...
  ]
}
```
</TabItem>
</Tabs>

![A game notification indicating that the player received 25 Aether Bucks for supporting a creator.](../../assets/docs/api-quick-start-guide/game-creator-support-reward-notification.webp)

## Client versus server

* **Client**: validate a creator code before purchase using the Public key.
* **Server**: create attributions after purchase using the Private key. Include your internal order id in `transactionId` for reconciliation.

## Team and local tips

* Separate `.env` files per environment
* Basic retries for server calls
* Log request ids and `transactionId` for support
* Keep one spare key per environment to make rotation easy

## Common gotchas

* Posting attributions from a client: always post from your backend.
* Using a list route to find a single member: pass the creator code to get a single record.

## If you must use a global key

Global keys require a `groupId` on every request; omitting it is a common mistake.

### curl with a global Public key

```bash
curl -G "$NEXUS_BASE_URL/manage/members" \
  -H "X-SHARED-SECRET: $NEXUS_PUBLIC_KEY_GLOBAL" \
  --data-urlencode "code=NEXUSCREATORCODE" \
  --data-urlencode "groupId=ZhyoQskfRpO7J5c1g3"

```

### curl with a global Private key

```bash
curl -X POST "$NEXUS_BASE_URL/attributions/transactions?groupId=ZhyoQskfRpO7J5c1g3" \
  -H "Content-Type: application/json" \
  -H "X-SHARED-SECRET: $NEXUS_PRIVATE_KEY_GLOBAL" \
  -d '{
    "code": "NEXUSCREATORCODE",
    "subtotal": 199,
    "currency": "USD",
    "description": "bundle_100_gems",
    "transactionId": "order-123",
    "transactionDate": "2025-10-16T13:01:44.000Z",
    "playerId": "6f10c7f4-psd-123",
    "metrics": {
      "joinDate": "2017-07-22T17:32:28Z",
      "conversion": {
        "lastPurchaseDate": "2025-01-22T17:32:28Z",
        "totalSpendToDate": {
          "total": 12999,
          "currency": "USD"
        }
      }
    }
  }'
```
