APRESLY API V1

Connect your own backend

A Node.js example with authentication, lead registration, lookup and bounded retries that respect Retry-After.

Verified against the production API: October 9, 2026

On this page

Prepare your backend

This example requires Node.js 22 or newer and runs only on your server. Store the key as APRESLY_API_KEY in your environment configuration. Publish a campaign and choose its ID using the quick start.

Download apresly-client.mjs next to your application code. The module does not send email. It retries 429, 503 and unavailable responses for up to three attempts in total; for 429 it respects Retry-After.

API client

// Node.js 22+. Server-side only. Store the key in APRESLY_API_KEY.
const base = 'https://app.apresly.com/api/public/v1';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

export class ApreslyApiError extends Error {
  constructor(status, code, requestId) {
    super(`Apresly API: ${code} (HTTP ${status})`);
    this.status = status;
    this.code = code;
    this.requestId = requestId;
  }
}

export async function apresly(path, body) {
  const key = process.env.APRESLY_API_KEY;
  if (!key) throw new Error('Set APRESLY_API_KEY on your server');
  // Prevent accidentally sending credentials to a user-supplied URL.
  if (!/^\/campaigns(?:\?(?:isDraft=false&)?limit=\d+(?:&afterId=\d+)?|\/[1-9]\d*\/leads(?:\/lookup)?)?$/.test(path)) {
    throw new Error('Use a documented campaign path');
  }
  for (let attempt = 0; attempt < 3; attempt++) {
    let response;
    try {
      response = await fetch(base + path, {
        method: body === undefined ? 'GET' : 'POST',
        headers: {
          Authorization: `Bearer ${key}`,
          ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
        },
        ...(body === undefined ? {} : { body: JSON.stringify(body) }),
        signal: AbortSignal.timeout(10_000),
        redirect: 'error',
      });
    } catch {
      if (attempt === 2) {
        // A lost response can follow successful registration. Reconcile before sending email.
        throw new Error('API response unavailable; registration may have completed');
      }
      await sleep(1000 * 2 ** attempt);
      continue;
    }
    if ([429, 503].includes(response.status) && attempt < 2) {
      const rawRetryAfter = response.headers.get('Retry-After');
      const retryAfter = rawRetryAfter === null ? NaN : Number(rawRetryAfter);
      const delay = response.status === 429 && Number.isFinite(retryAfter) && retryAfter >= 0
        ? retryAfter * 1000
        : 1000 * 2 ** attempt;
      await response.body?.cancel();
      await sleep(delay);
      continue;
    }
    const requestId = response.headers.get('X-Request-Id');
    let data;
    try { data = await response.json(); }
    catch { throw new ApreslyApiError(response.status, 'INVALID_RESPONSE', requestId); }
    if (!response.ok) {
      throw new ApreslyApiError(response.status, data.code || 'HTTP_ERROR', data.requestId || requestId);
    }
    return data;
  }
}

Call after a signup or purchase

The code below makes a real registration. Replace the campaign ID, page ID and test email before running it. Save it as integration.mjs, configure the key environment variable and run node integration.mjs. Do not expose this example as a public endpoint without your own authentication and validation.

import { apresly } from './apresly-client.mjs';

const campaigns = await apresly('/campaigns?isDraft=false&limit=50');
// Choose the intended campaign from campaigns.items, not blindly the first one.
const campaignId = 42; // Replace with your published campaign ID.
const email = '[email protected]'; // Replace with an email address you own.

const offer = await apresly(`/campaigns/${campaignId}/leads`, {
  email,
  language: 'en',
});

// Select the intended page ID from your campaign configuration.
const pageId = 17; // Replace with your own page ID.
const offerUrl = offer.offerLinks.find(link => link.pageId === pageId)?.url;
// Map offerUrl, offer.deadline and offer.mailTimerUrl to your email tool.
// Check warnings and missing values before sending. Do not log this object.

// Before a reminder: read without registering again.
const reminder = await apresly(`/campaigns/${campaignId}/leads/lookup`, {
  email,
  language: 'en',
});
const deadline = reminder.deadline === null ? null : Date.parse(reminder.deadline);
const canRemind = deadline !== null && Number.isFinite(deadline) && deadline > Date.now()
  && reminder.offerLinks.some(link => link.pageId === pageId);
// Only send when canRemind and your own delivery rules allow it.

Deliver email only once

Associate the source event, such as a purchase, with its stable ID in your database. Persist processing and delivery state, and use a queue or outbox. created alone cannot ensure exactly-once delivery: the first registration may succeed, its response may be lost, and a retry may return created: false.

If the module ends with an unavailable-response error, reconcile through lookup before further email delivery. Repeated registration still follows the campaign’s reset and cycle settings. Do not retry indefinitely or treat every 404 as a reason to register again.

Diagnostics and privacy

ApreslyApiError exposes status, code and requestId. Log those diagnostic fields without the complete response, email or links. Find the ID in Request history. For 400, 401, 409, 415 or 422, fix configuration instead of retrying automatically.

The code demonstrates a minimal client and checks before a reminder. Add the trigger, deduplication store, queue and email delivery appropriate to your application. The API reference describes all fields and constraints.

Next guideTroubleshooting

NEED A HAND?

Let’s work through your integration.

Tell us what you’re connecting. For a failed request, include the requestId and error code, without your API key or customer data.

Contact support