# Campaigns endpoint

List campaigns with live status and progress, or launch a package against a pair. Launching is irreversible.

## GET /campaigns

```
GET https://www.dextrending.net/api/v1/campaigns
```

Your campaigns, newest first, up to 100. Status and progress are recalculated on every request, so
the values are current rather than cached.

```
{
  "campaigns": [
    {
      "order_no": "CMP-TK4LPJDC84",
      "package": "12 Hour Window",
      "chain": "solana",
      "token": {
        "address": "Ai66LHZG9MCzg1WKdawwqduVAXpNDUuV8M3uyq5ppump",
        "symbol": "CATE",
        "name": "Catecoin",
        "logo_url": "https://cdn.dexscreener.com/cms/images/...",
        "pair_url": "https://dexscreener.com/solana/hmzvseemtzhhvznw9uwbag85hctmfnkbhzux16cy7ca3"
      },
      "hours": 12,
      "status": "running",
      "progress": 23,
      "seconds_remaining": 33167,
      "starts_at": "2026-08-21T15:29:43+00:00",
      "ends_at": "2026-08-22T03:29:43+00:00"
    }
  ],
  "count": 1
}
```

| Field | Type | Notes |
| ------------------- | -------------- | ------------------------------------------------------------ |
| `order_no` | string | Public reference, use it in support tickets |
| `status` | string | `scheduled`, `running`, `completed` or `cancelled` |
| `progress` | integer | 0 to 100. Share of the paid window elapsed, not performance. |
| `seconds_remaining` | integer | Until the window closes. 0 once completed. |
| `token.logo_url` | string or null | Null when the token publishes no logo |
| `token.pair_url` | string or null | The pair on Dexscreener |

## POST /campaigns

```
POST https://www.dextrending.net/api/v1/campaigns
```

Spends one available package against a pair.

This is irreversible. The package is consumed at the moment the campaign is created, including
for a scheduled start. Validate the address with GET /token first.

### Request body

```
{
  "credit_id": 20,
  "chain": "solana",
  "address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
  "start_at": "asap",
  "notes": "listing at 14:00 UTC"
}
```

| Field | Required | Notes |
| ----------- | -------- | ----------------------------------------------------------- |
| `credit_id` | Yes | From `/packages`, must be `available` |
| `chain` | Yes | Must match the package `chain_group` |
| `address` | Yes | Token contract address on that chain |
| `start_at` | No | `asap` or ISO 8601 UTC. Max 30 days ahead. Defaults to now. |
| `notes` | No | Free text, up to 1000 characters, visible to support |

### 201 Created

```
{
  "campaign": {
    "order_no": "CMP-TK48JX43",
    "package": "24 Hour Window",
    "chain": "solana",
    "token": {
      "address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
      "symbol": "Bonk",
      "name": "Bonk"
    },
    "hours": 24,
    "status": "scheduled",
    "starts_at": "2026-08-21T10:45:33+00:00",
    "ends_at": "2026-08-22T10:45:33+00:00"
  }
}
```

## A complete launch

Validating with GET /token and picking from
GET /packages before spending anything:

```
const BASE = "https://www.dextrending.net/api/v1";
const KEY  = process.env.DT_KEY;

async function dt(path, options = {}) {
  const res = await fetch(BASE + path, {
    ...options,
    headers: { "X-API-Key": KEY, "Content-Type": "application/json", ...(options.headers || {}) }
  });
  const body = await res.json();
  if (!res.ok) throw new Error(body.error || res.status);
  return body;
}

const chain   = "solana";
const address = "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263";

// 1. confirm the pair reads before spending anything
await dt(`/token?chain=${chain}&address=${address}`);

// 2. find a package that matches
const { packages } = await dt("/packages");
const ready = packages.find(p => p.status === "available" && p.chain_group === chain);
if (!ready) throw new Error("No package available for " + chain);

// 3. launch
const { campaign } = await dt("/campaigns", {
  method: "POST",
  body: JSON.stringify({ credit_id: ready.credit_id, chain, address, start_at: "asap" })
});

console.log(campaign.order_no, campaign.status);
```

## Polling while it runs

Campaign state changes on the scale of hours, so poll `GET /campaigns` every few minutes at
most. `seconds_remaining` tells you exactly how long is left, so you can sleep for that long
rather than polling until it closes.

## Questions

**Can I cancel a campaign through the API?**

No. Launching is irreversible and the package is consumed at that moment. Contact support immediately if you launched against the wrong pair.

**What does progress mean?**

The share of the paid window that has elapsed, from 0 to 100. It is time, not performance.

**How often should I poll?**

Every few minutes at most. Use seconds_remaining to sleep until the window closes rather than polling continuously.

**Can I schedule a launch for later?**

Yes. Pass start_at as an ISO 8601 UTC timestamp up to 30 days ahead. The package is still consumed immediately.

