Skip to content
Merged
37 changes: 37 additions & 0 deletions src/ConvertKit_API_Traits.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,43 @@ public function update_tag_name(int $tag_id, string $name)
return $this->put(sprintf('tags/%s', $tag_id), ['name' => $name]);
}

/**
* Tags the given subscribers with the given existing Tags.
*
* @param array<int,array<string>> $taggings Taggings, in the format:
* [
* [
* "tag_id" => 0,
* "subscriber_id" => 0
* ],
* [
* "tag_id" => 1,
* "subscriber_id" => 1
* ],
* ].
* @param string $callback_url URL to notify for large batch size when async processing complete.
*
* @since 2.2.1
*
* @see https://developers.kit.com/api-reference/tags/bulk-tag-subscribers
*
* @return false|mixed
*/
public function tag_subscribers(array $taggings, string $callback_url = '')
{
// Build parameters.
$options = ['taggings' => $taggings];
if (!empty($callback_url)) {
$options['callback_url'] = $callback_url;
}

// Send request.
return $this->post(
'bulk/tags/subscribers',
$options
);
}

/**
* Tags a subscriber with the given existing Tag.
*
Expand Down
67 changes: 67 additions & 0 deletions tests/ConvertKitAPIKeyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,73 @@ public function testCreateTagsThatExist()
);
}

/**
* Test that tag_subscribers() throws a ClientException when attempting
* to tag subscribers, as this is only supported using OAuth.
*
* @since 2.2.0
*
* @return void
*/
public function testTagSubscribers()
{
$this->expectException(ClientException::class);
$result = $this->api->tag_subscribers(
[
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
],
]
);
}

/**
* Test that tag_subscribers() throws a ClientException when an invalid
* tag ID is specified, as this is only supported using OAuth.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment makes it seem like tagging with an invalid is possible with OAuth. Can we drop or modify the last clause? Same goes for the comments on the following functions in this file. I'm also wondering if these test are necessary since we're testing the specific behavior in ConvertKitAPITest.php

Copy link
Copy Markdown
Contributor Author

@n7studios n7studios Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They can't be removed, as ConvertKitAPIKeyTest and ConvertKitAPIOAuthTest both extend ConvertKitAPITest, which is where most of the tests are contained (and therefore run) - otherwise, there'd be duplicate tests in two classes.

I've marked these methods to be skipped in ConvertKitAPIKeyTest, given testCreateTags checks for the ClientException.

Other redundant tests skipped in this PR.

*
* @since 2.2.1
*
* @return void
*/
public function testTagSubscribersWithInvalidTagID()
{
$this->expectException(ClientException::class);
$result = $this->api->tag_subscribers(
[
[
'tag_id' => 12345,
'subscriber_id' => (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']
],
]
);
}

/**
* Test that tag_subscribers() throws a ClientException when an invalid
* subscriber ID is specified, as this is only supported using OAuth.
*
* @since 2.2.1
*
* @return void
*/
public function testTagSubscribersWithInvalidSubscriberID()
{
$this->expectException(ClientException::class);
$result = $this->api->tag_subscribers(
[
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => 12345,
],
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => 67890,
],
]
);
}

/**
* Test that add_subscribers_to_forms() throws a ClientException when
* attempting to add subscribers to forms, as this is only supported
Expand Down
119 changes: 119 additions & 0 deletions tests/ConvertKitAPITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,125 @@ public function testGetSubscriberStatsWithInvalidSubscriberID()
$result = $this->api->get_subscriber_stats(12345);
}

/**
* Test that tag_subscribers() returns the expected data.
*
* @since 2.2.1
*
* @return void
*/
public function testTagSubscribers()
{
// Create subscribers.
$subscribers = [
[
'email_address' => str_replace('@kit.com', '-1@kit.com', $this->generateEmailAddress()),
],
[
'email_address' => str_replace('@kit.com', '-2@kit.com', $this->generateEmailAddress()),
],
];
$result = $this->api->create_subscribers($subscribers);

// Set subscriber_id to ensure subscriber is unsubscribed after test.
foreach ($result->subscribers as $i => $subscriber) {
$this->subscriber_ids[] = $subscriber->id;
}

// Tag subscribers.
$result = $this->api->tag_subscribers(
[
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => $this->subscriber_ids[0]
],
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => $this->subscriber_ids[1]
],
]
);

// Assert no failures.
$this->assertCount(0, $result->failures);

// Confirm result is an array comprising of each subscriber that was created.
$this->assertIsArray($result->subscribers);
$this->assertCount(2, $result->subscribers);
}

/**
* Test that tag_subscribers() returns failures when an invalid
* tag ID is specified.
*
* @since 2.2.1
*
* @return void
*/
public function testTagSubscribersWithInvalidTagID()
{
// Create subscribers.
$subscribers = [
[
'email_address' => str_replace('@kit.com', '-1@kit.com', $this->generateEmailAddress()),
],
[
'email_address' => str_replace('@kit.com', '-2@kit.com', $this->generateEmailAddress()),
],
];
$result = $this->api->create_subscribers($subscribers);

// Set subscriber_id to ensure subscriber is unsubscribed after test.
foreach ($result->subscribers as $i => $subscriber) {
$this->subscriber_ids[] = $subscriber->id;
}

// Tag subscribers.
$result = $this->api->tag_subscribers(
[
[
'tag_id' => 12345,
'subscriber_id' => $this->subscriber_ids[0]
],
[
'tag_id' => 12345,
'subscriber_id' => $this->subscriber_ids[1]
],
]
);

// Assert failures.
$this->assertCount(2, $result->failures);
}

/**
* Test that tag_subscribers() returns failures when an invalid
* subscriber ID is specified.
*
* @since 2.2.1
*
* @return void
*/
public function testTagSubscribersWithInvalidSubscriberID()
{
// Tag subscribers that do not exist.
$result = $this->api->tag_subscribers(
[
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => 12345,
],
[
'tag_id' => (int) $_ENV['CONVERTKIT_API_TAG_ID'],
'subscriber_id' => 67890,
],
]
);

// Assert failures.
$this->assertCount(2, $result->failures);
}

/**
* Test that tag_subscriber_by_email() returns the expected data.
*
Expand Down