diff --git a/src/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj b/src/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj
index 50e7262..389510b 100644
--- a/src/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj
+++ b/src/Piwigo.Client.Tests/Piwigo.Client.Tests.csproj
@@ -41,6 +41,21 @@
ImageApiTests.cs
+
+ Always
+
+
+ TagApiTests.cs
+
+
+ Always
+
+
+ TagApiTests.cs
+
+
+ TagApiTests.cs
+
diff --git a/src/Piwigo.Client.Tests/TagApi.GetAdminList.json b/src/Piwigo.Client.Tests/TagApi.GetAdminList.json
new file mode 100644
index 0000000..8988376
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApi.GetAdminList.json
@@ -0,0 +1,19 @@
+{
+ "stat": "ok",
+ "result": {
+ "tags": [
+ {
+ "id": "1",
+ "name": "tag1",
+ "url_name": "tag1",
+ "lastmodified": "2022-10-27 21:17:59"
+ },
+ {
+ "id": "2",
+ "name": "tag2",
+ "url_name": "tag2",
+ "lastmodified": "2022-10-27 21:23:34"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Tests/TagApi.GetList.json b/src/Piwigo.Client.Tests/TagApi.GetList.json
new file mode 100644
index 0000000..37a068a
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApi.GetList.json
@@ -0,0 +1,15 @@
+{
+ "stat": "ok",
+ "result": {
+ "tags": [
+ {
+ "id": 1,
+ "name": "tag1",
+ "url_name": "tag1",
+ "lastmodified": "2022-10-27 21:17:59",
+ "counter": 1,
+ "url": "http://localhost:8080/index.php?/tags/1-tag1"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Tests/TagApiTests.AddAsync_should_add_tag_and_return_correct_result.verified.txt b/src/Piwigo.Client.Tests/TagApiTests.AddAsync_should_add_tag_and_return_correct_result.verified.txt
new file mode 100644
index 0000000..dd8a748
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApiTests.AddAsync_should_add_tag_and_return_correct_result.verified.txt
@@ -0,0 +1,6 @@
+{
+ info: Keyword "test3" has been added,
+ id: 3,
+ name: test3,
+ url_name: test3
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client.Tests/TagApiTests.GetAdminListAsync_should_return_all_tags.verified.txt b/src/Piwigo.Client.Tests/TagApiTests.GetAdminListAsync_should_return_all_tags.verified.txt
new file mode 100644
index 0000000..8ecc262
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApiTests.GetAdminListAsync_should_return_all_tags.verified.txt
@@ -0,0 +1,14 @@
+[
+ {
+ id: 1,
+ name: tag1,
+ url_name: tag1,
+ lastmodified: DateTime_1
+ },
+ {
+ id: 2,
+ name: tag2,
+ url_name: tag2,
+ lastmodified: DateTime_2
+ }
+]
\ No newline at end of file
diff --git a/src/Piwigo.Client.Tests/TagApiTests.GetListAsync_should_return_all_tags.verified.txt b/src/Piwigo.Client.Tests/TagApiTests.GetListAsync_should_return_all_tags.verified.txt
new file mode 100644
index 0000000..5e6ae85
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApiTests.GetListAsync_should_return_all_tags.verified.txt
@@ -0,0 +1,10 @@
+[
+ {
+ counter: 1,
+ url: http://localhost:8080/index.php?/tags/1-tag1,
+ id: 1,
+ name: tag1,
+ url_name: tag1,
+ lastmodified: DateTime_1
+ }
+]
\ No newline at end of file
diff --git a/src/Piwigo.Client.Tests/TagApiTests.cs b/src/Piwigo.Client.Tests/TagApiTests.cs
new file mode 100644
index 0000000..a054d90
--- /dev/null
+++ b/src/Piwigo.Client.Tests/TagApiTests.cs
@@ -0,0 +1,54 @@
+using Microsoft.Extensions.Logging.Abstractions;
+using Piwigo.Client.Tags;
+
+namespace Piwigo.Client.Tests;
+
+[TestFixture]
+public class TagApiTests : ApiTestsBase
+{
+ private ITagApi _tagApi = null!;
+ private const string ApiToken = "37ff55f201cf54eadf11f734a74f1d0e";
+
+ protected override void OnSetUp()
+ {
+ base.OnSetUp();
+ _tagApi = new TagApi(Context, new NullLogger());
+ }
+
+ [Test]
+ public async Task AddAsync_should_add_tag_and_return_correct_result()
+ {
+ SetJsonResult(@"{""stat"":""ok"",""result"":{""info"":""Keyword \""test3\"" has been added"",""id"":3,""name"":""test3"",""url_name"":""test3""}}");
+
+ var response = await _tagApi.AddAsync("test3");
+
+ CorrectMethodShouldGetCalled("pwg.tags.add");
+ CorrectParamShouldGetSent("name", "test3");
+
+ await Verify(response);
+ }
+
+ [Test]
+ public async Task GetAdminListAsync_should_return_all_tags()
+ {
+ await SetJsonResultFromFileAsync("TagApi.GetAdminList.json");
+
+ var response = await _tagApi.GetAdminListAsync();
+
+ CorrectMethodShouldGetCalled("pwg.tags.getAdminList");
+
+ await Verify(response);
+ }
+
+ [Test]
+ public async Task GetListAsync_should_return_all_tags()
+ {
+ await SetJsonResultFromFileAsync("TagApi.GetList.json");
+
+ var response = await _tagApi.GetListAsync();
+
+ CorrectMethodShouldGetCalled("pwg.tags.getList");
+
+ await Verify(response);
+ }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/AssignedTag.cs b/src/Piwigo.Client/Tags/AssignedTag.cs
new file mode 100644
index 0000000..b80a0d8
--- /dev/null
+++ b/src/Piwigo.Client/Tags/AssignedTag.cs
@@ -0,0 +1,12 @@
+using Newtonsoft.Json;
+
+namespace Piwigo.Client.Tags;
+
+public record AssignedTag : Tag
+{
+ [JsonProperty("counter")]
+ public int Counter { get; init; }
+
+ [JsonProperty("url")]
+ public string Url { get; init; }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/ITagApi.cs b/src/Piwigo.Client/Tags/ITagApi.cs
index 9eeb9e2..2aa1ca6 100644
--- a/src/Piwigo.Client/Tags/ITagApi.cs
+++ b/src/Piwigo.Client/Tags/ITagApi.cs
@@ -2,4 +2,32 @@ namespace Piwigo.Client.Tags;
public interface ITagApi
{
+ ///
+ /// Adds a new tag to the gallery
+ ///
+ /// the name of the tag
+ ///
+ ///
+ ///
+ /// Information about the uploaded tag
+ Task AddAsync(string name, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets a list of all known tags. This call is only available for admins
+ ///
+ ///
+ ///
+ ///
+ /// all available tags
+ Task> GetAdminListAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Gits a list of known tags that are at least assigned to one image
+ ///
+ /// sorts the result by the number of assigned images of a tag
+ ///
+ ///
+ ///
+ /// all tags with at least one image assigned
+ Task> GetListAsync(bool? sortByCounter = null, CancellationToken cancellationToken = default);
}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/Tag.cs b/src/Piwigo.Client/Tags/Tag.cs
new file mode 100644
index 0000000..b4ed8b4
--- /dev/null
+++ b/src/Piwigo.Client/Tags/Tag.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+using Newtonsoft.Json;
+
+namespace Piwigo.Client.Tags;
+
+[SuppressMessage("ReSharper", "StringLiteralTypo")]
+public record Tag
+{
+ [JsonProperty("id")]
+ public int Id { get; init; }
+
+ [JsonProperty("name")]
+ public string? Name { get; init; }
+
+ [JsonProperty("url_name")]
+ public string? UrlName { get; init; }
+
+ [JsonProperty("lastmodified")]
+ public DateTime? LastModified { get; init; }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/TagAdded.cs b/src/Piwigo.Client/Tags/TagAdded.cs
new file mode 100644
index 0000000..27cf591
--- /dev/null
+++ b/src/Piwigo.Client/Tags/TagAdded.cs
@@ -0,0 +1,18 @@
+using Newtonsoft.Json;
+
+namespace Piwigo.Client.Tags;
+
+public record TagAdded
+{
+ [JsonProperty("info")]
+ public string? Info { get; init; }
+
+ [JsonProperty("id")]
+ public int Id { get; init; }
+
+ [JsonProperty("name")]
+ public string? Name { get; init; }
+
+ [JsonProperty("url_name")]
+ public string? UrlName { get; init; }
+}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/TagApi.cs b/src/Piwigo.Client/Tags/TagApi.cs
index dae0886..1b8385b 100644
--- a/src/Piwigo.Client/Tags/TagApi.cs
+++ b/src/Piwigo.Client/Tags/TagApi.cs
@@ -1,5 +1,40 @@
+using Microsoft.Extensions.Logging;
+
namespace Piwigo.Client.Tags;
public class TagApi : ITagApi
{
+ private readonly IPiwigoContext _context;
+ private readonly ILogger _logger;
+
+ public TagApi(IPiwigoContext context, ILogger logger)
+ {
+ _context = context ?? throw new ArgumentNullException(nameof(context));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public async Task AddAsync(string name, CancellationToken cancellationToken = default)
+ {
+ var formParams = new Dictionary
+ {
+ { "name", name }
+ };
+ var response = await _context.PostAsync>(_logger, "pwg.tags.add", formParams, cancellationToken);
+ return response.Result;
+ }
+
+ public async Task> GetAdminListAsync(CancellationToken cancellationToken = default)
+ {
+ var response = await _context.PostAsync>>(_logger, "pwg.tags.getAdminList", new Dictionary(), cancellationToken);
+ return response.Result.Tags;
+ }
+
+ public async Task> GetListAsync(bool? sortByCounter = null, CancellationToken cancellationToken = default)
+ {
+ var formParams = new Dictionary();
+ formParams.AddIfValueNotNull("sort_by_counter", sortByCounter?.ToString());
+
+ var response = await _context.PostAsync>>(_logger, "pwg.tags.getList", formParams, cancellationToken);
+ return response.Result.Tags;
+ }
}
\ No newline at end of file
diff --git a/src/Piwigo.Client/Tags/TagList.cs b/src/Piwigo.Client/Tags/TagList.cs
new file mode 100644
index 0000000..c8bfab2
--- /dev/null
+++ b/src/Piwigo.Client/Tags/TagList.cs
@@ -0,0 +1,9 @@
+using Newtonsoft.Json;
+
+namespace Piwigo.Client.Tags;
+
+internal record TagList
+{
+ [JsonProperty("tags")]
+ public IReadOnlyCollection Tags { get; init; } = null!;
+}
\ No newline at end of file