Skip to content

Get Subscribers

Fans who opt in through a smart link — email capture or a presave — are stored as opt-ins linked to a fan record. Both are readable through the API, which is how you sync an artist's audience into an ESP, CRM, or warehouse.

Data model

TableHolds
optinsOne row per opt-in event: which artist, which link, which channel, what value, current delivery status.
fansThe person: email, location, Spotify connection. One fan can have many opt-ins.
presave_subscriptionsPresave signups tied to a specific release link.
optoutsUnsubscribe records, per artist and platform.

Opt-ins are read-only through the API. They are created by visitors on the link pages, not by API clients.

List an artist's subscribers

graphql
query Subscribers($artist_id: uuid!, $limit: Int = 100, $offset: Int = 0) {
  optins(
    where: { artist_id: { _eq: $artist_id } }
    order_by: { created_at: desc }
    limit: $limit
    offset: $offset
  ) {
    id
    type
    value
    status
    platform
    country
    region
    city
    future_releases
    imported
    created_at
    link { id domain path title }
    fan {
      id
      email
      country
      spotify_connected
    }
  }
  optins_aggregate(where: { artist_id: { _eq: $artist_id } }) {
    aggregate { count }
  }
}

optins fields

FieldTypeDescription
iduuid!Opt-in ID.
artist_iduuidArtist the fan opted in to.
typeStringCapture channel.
valueStringThe captured email address.
statusStringDelivery/subscription state.
platformStringWhere the opt-in originated.
country, region, cityStringGeolocation at capture time.
future_releasesBooleanFan agreed to be told about future releases.
importedBooleanRow came from a bulk import rather than a live capture.
fail_countIntFailed delivery attempts.
last_trytimestamptzLast delivery attempt.
created_attimestamptzWhen the fan opted in.
link_iduuidLink where the opt-in happened.
source_link_iduuidOriginating link, when it differs.
link_destination_iduuidDestination button that triggered it.
fan_iduuidThe fans row.

fans fields

FieldTypeDescription
iduuid!Fan ID.
emailStringEmail address.
country, region, cityStringLocation.
spotify_connectedBooleanFan connected a Spotify account.
spotify_emailStringEmail from the connected Spotify account.

You can query fans directly; visibility is scoped to fans who have opted in to or presaved for one of your artists.

graphql
query FansWithSpotify {
  fans(where: { spotify_connected: { _eq: true } }, limit: 100) {
    id
    email
    country
    spotify_connected
  }
}

Common filters

graphql
# Email subscribers only
where: { artist_id: { _eq: $artist_id }, type: { _eq: "email" } }

# Opted in to future releases
where: { artist_id: { _eq: $artist_id }, future_releases: { _eq: true } }

# Captured since a timestamp — the basis of an incremental sync
where: { artist_id: { _eq: $artist_id }, created_at: { _gt: $since } }

# From one link
where: { link_id: { _eq: $link_id } }

# One market
where: { artist_id: { _eq: $artist_id }, country: { _eq: "US" } }

# Exclude bulk-imported rows
where: { artist_id: { _eq: $artist_id }, imported: { _eq: false } }

Incremental export

Walk forward by created_at rather than paging with offset — it stays correct while new opt-ins arrive mid-export.

graphql
query ExportSince($artist_id: uuid!, $since: timestamptz!) {
  optins(
    where: {
      artist_id: { _eq: $artist_id }
      created_at: { _gt: $since }
    }
    order_by: { created_at: asc }
    limit: 500
  ) {
    id
    type
    value
    country
    created_at
    fan { email }
  }
}

Store the highest created_at you received and pass it as $since next run. Keep the page size modest; these are large tables.

Counting

graphql
query Counts($artist_id: uuid!) {
  total: optins_aggregate(where: { artist_id: { _eq: $artist_id } }) {
    aggregate { count }
  }
  email: optins_aggregate(
    where: { artist_id: { _eq: $artist_id }, type: { _eq: "email" } }
  ) {
    aggregate { count }
  }
}

Presaves

Presave signups are tracked separately, per release link:

graphql
query Presaves($artist_id: uuid!) {
  presave_subscriptions(
    where: { artist_id: { _eq: $artist_id } }
    order_by: { created_at: desc }
    limit: 100
  ) {
    id
    country
    region
    city
    created_at
    link { id path title }
  }
}

Richer per-fan presave detail, including status and delivery attempts, is in presaves:

graphql
query PresaveDetail($link_id: uuid!) {
  presaves(where: { link_id: { _eq: $link_id } }) {
    id
    type
    status
    emails
    future_releases
    fail_count
    last_try
    country
    created_at
    fan { id email }
  }
}

Opt-outs

Always reconcile against opt-outs before sending anything.

graphql
query OptOuts($artist_id: uuid!) {
  optouts(where: { artist_id: { _eq: $artist_id } }) {
    id
    platform
    created_at
  }
}

WARNING

Opt-in records are personal data. Exporting them makes you responsible for handling under GDPR, CCPA, and the consent terms the fan actually agreed to — which is per-artist, not per-account. Honour optouts, respect future_releases, and do not merge one artist's list into another's.

Not available through the API

Opt-ins cannot be created, edited, or deleted through the API — the permission model grants manager read access only. Capture happens on the link pages; deletion requests go through support so that suppression records are kept correctly.

ArtistHub developer documentation