diff --git a/src/CrowdinApiClient/Api/ApplicationApi.php b/src/CrowdinApiClient/Api/ApplicationApi.php
index 470ee7bd..6e1dd2f3 100644
--- a/src/CrowdinApiClient/Api/ApplicationApi.php
+++ b/src/CrowdinApiClient/Api/ApplicationApi.php
@@ -4,6 +4,7 @@
namespace CrowdinApiClient\Api;
+use CrowdinApiClient\Model\ApplicationConsent;
use CrowdinApiClient\Model\ApplicationData;
use CrowdinApiClient\Model\ApplicationInstallation;
use CrowdinApiClient\ModelCollection;
@@ -153,6 +154,62 @@ public function updateInstallation(ApplicationInstallation $installation): ?Appl
return $this->_update('applications/installations/' . $installation->getIdentifier(), $installation);
}
+ /**
+ * List Application Consents
+ * @link https://developer.crowdin.com/api/v2/#operation/api.applications.consents.getMany API Documentation
+ *
+ * @param array $params
+ * integer $params[limit]
+ * integer $params[offset]
+ * string $params[identifier]
+ * string $params[orderBy]
+ * @return ModelCollection
+ */
+ public function listConsents(array $params = []): ModelCollection
+ {
+ return $this->_list('applications/consents', ApplicationConsent::class, $params);
+ }
+
+ /**
+ * Add Application Consent
+ * @link https://developer.crowdin.com/api/v2/#operation/api.applications.consents.post API Documentation
+ *
+ * @param array $data
+ * string $data[identifier] required
+ * integer $data[installedBy] required
+ * string $data[status] required
+ * array $data[scopes]
+ * @return ApplicationConsent|null
+ */
+ public function addConsent(array $data): ?ApplicationConsent
+ {
+ return $this->_create('applications/consents', ApplicationConsent::class, $data);
+ }
+
+ /**
+ * Edit Application Consent
+ * @link https://developer.crowdin.com/api/v2/#operation/api.applications.consents.patch API Documentation
+ *
+ * @param ApplicationConsent $consent
+ * @return ApplicationConsent|null
+ */
+ public function updateConsent(ApplicationConsent $consent): ?ApplicationConsent
+ {
+ return $this->_update('applications/consents/' . $consent->getId(), $consent);
+ }
+
+ /**
+ * Delete Application Consent
+ * @link https://developer.crowdin.com/api/v2/#operation/api.applications.consents.delete API Documentation
+ *
+ * @param int $consentId
+ * @return mixed
+ */
+ public function deleteConsent(int $consentId)
+ {
+ return $this->_delete('applications/consents/' . $consentId);
+ }
+
/**
* Edit Application Data
* @link https://developer.crowdin.com/api/v2/#operation/api.applications.api.patch API Documentation
diff --git a/src/CrowdinApiClient/Model/ApplicationConsent.php b/src/CrowdinApiClient/Model/ApplicationConsent.php
new file mode 100644
index 00000000..bef06581
--- /dev/null
+++ b/src/CrowdinApiClient/Model/ApplicationConsent.php
@@ -0,0 +1,99 @@
+id = (int)$this->getDataProperty('id');
+ $this->installedBy = $this->getDataProperty('installedBy');
+ $this->identifier = (string)$this->getDataProperty('identifier');
+ $this->name = $this->getDataProperty('name');
+ $this->status = (string)$this->getDataProperty('status');
+ $this->scopes = (array)$this->getDataProperty('scopes');
+ $this->createdAt = (string)$this->getDataProperty('createdAt');
+ $this->updatedAt = (string)$this->getDataProperty('updatedAt');
+ }
+
+ public function getId(): int
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getInstalledBy(): ?array
+ {
+ return $this->installedBy;
+ }
+
+ public function getIdentifier(): string
+ {
+ return $this->identifier;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ public function getScopes(): array
+ {
+ return $this->scopes;
+ }
+
+ public function getCreatedAt(): string
+ {
+ return $this->createdAt;
+ }
+
+ public function getUpdatedAt(): string
+ {
+ return $this->updatedAt;
+ }
+
+ public function setStatus(string $status): void
+ {
+ $this->status = $status;
+ }
+
+ public function setScopes(array $scopes): void
+ {
+ $this->scopes = $scopes;
+ }
+}
diff --git a/tests/CrowdinApiClient/Api/ApplicationApiTest.php b/tests/CrowdinApiClient/Api/ApplicationApiTest.php
index a2cceef0..a904170d 100644
--- a/tests/CrowdinApiClient/Api/ApplicationApiTest.php
+++ b/tests/CrowdinApiClient/Api/ApplicationApiTest.php
@@ -4,6 +4,7 @@
namespace CrowdinApiClient\Tests\Api;
+use CrowdinApiClient\Model\ApplicationConsent;
use CrowdinApiClient\Model\ApplicationData;
use CrowdinApiClient\Model\ApplicationInstallation;
use CrowdinApiClient\ModelCollection;
@@ -54,6 +55,30 @@ protected function getInstallationResponseJson(): string
return json_encode(['data' => $this->getInstallationData()]);
}
+ protected function getConsentData(): array
+ {
+ return [
+ 'id' => 12,
+ 'installedBy' => [
+ 'id' => 1,
+ 'username' => 'admin',
+ 'fullName' => 'Admin User',
+ 'avatarUrl' => '',
+ ],
+ 'identifier' => 'my-app',
+ 'name' => 'My Application',
+ 'status' => 'granted',
+ 'scopes' => ['project', 'tm'],
+ 'createdAt' => '2026-07-24T10:00:00+00:00',
+ 'updatedAt' => '2026-07-24T10:00:00+00:00',
+ ];
+ }
+
+ protected function getConsentResponseJson(): string
+ {
+ return json_encode(['data' => $this->getConsentData()]);
+ }
+
public function testListInstallations(): void
{
$this->mockRequest([
@@ -131,6 +156,92 @@ public function testUpdateInstallation(): void
$this->assertEquals('my-app', $result->getIdentifier());
}
+ public function testListConsents(): void
+ {
+ $this->mockRequest([
+ 'uri' => 'https://api.crowdin.com/api/v2/applications/consents?identifier=my-app&orderBy=createdAt',
+ 'method' => 'get',
+ 'response' => json_encode([
+ 'data' => [
+ ['data' => $this->getConsentData()],
+ ],
+ 'pagination' => [['offset' => 0, 'limit' => 25]],
+ ]),
+ ]);
+
+ $list = $this->crowdin->application->listConsents([
+ 'identifier' => 'my-app',
+ 'orderBy' => 'createdAt',
+ ]);
+
+ $this->assertInstanceOf(ModelCollection::class, $list);
+ $this->assertCount(1, $list);
+ $this->assertInstanceOf(ApplicationConsent::class, $list[0]);
+ $this->assertEquals(12, $list[0]->getId());
+ $this->assertEquals('my-app', $list[0]->getIdentifier());
+ }
+
+ public function testAddConsent(): void
+ {
+ $requestBody = [
+ 'identifier' => 'my-app',
+ 'installedBy' => 1,
+ 'status' => 'granted',
+ 'scopes' => ['project', 'tm'],
+ ];
+
+ $this->mockRequest([
+ 'path' => '/applications/consents',
+ 'method' => 'post',
+ 'body' => json_encode($requestBody),
+ 'response' => $this->getConsentResponseJson(),
+ ]);
+
+ $consent = $this->crowdin->application->addConsent($requestBody);
+
+ $this->assertInstanceOf(ApplicationConsent::class, $consent);
+ $this->assertEquals(12, $consent->getId());
+ $this->assertEquals('granted', $consent->getStatus());
+ }
+
+ public function testUpdateConsent(): void
+ {
+ $consentData = $this->getConsentData();
+ $consent = new ApplicationConsent($consentData);
+ $consent->setStatus('denied');
+ $consent->setScopes(['project']);
+
+ $this->mockRequest([
+ 'path' => '/applications/consents/12',
+ 'method' => 'patch',
+ 'body' => json_encode([
+ [
+ 'op' => 'replace',
+ 'path' => '/status',
+ 'value' => 'denied',
+ ],
+ [
+ 'op' => 'replace',
+ 'path' => '/scopes',
+ 'value' => ['project'],
+ ],
+ ]),
+ 'response' => $this->getConsentResponseJson(),
+ ]);
+
+ $result = $this->crowdin->application->updateConsent($consent);
+
+ $this->assertInstanceOf(ApplicationConsent::class, $result);
+ $this->assertEquals(12, $result->getId());
+ }
+
+ public function testDeleteConsent(): void
+ {
+ $this->mockRequestDelete('/applications/consents/12');
+
+ $this->crowdin->application->deleteConsent(12);
+ }
+
public function testGetApplicationData(): void
{
$this->mockRequestGet(
diff --git a/tests/CrowdinApiClient/Model/ApplicationConsentTest.php b/tests/CrowdinApiClient/Model/ApplicationConsentTest.php
new file mode 100644
index 00000000..b14a9d77
--- /dev/null
+++ b/tests/CrowdinApiClient/Model/ApplicationConsentTest.php
@@ -0,0 +1,66 @@
+ 12,
+ 'installedBy' => [
+ 'id' => 1,
+ 'username' => 'admin',
+ 'fullName' => 'Admin User',
+ 'avatarUrl' => '',
+ ],
+ 'identifier' => 'my-app',
+ 'name' => 'My Application',
+ 'status' => 'granted',
+ 'scopes' => ['project', 'tm'],
+ 'createdAt' => '2026-07-24T10:00:00+00:00',
+ 'updatedAt' => '2026-07-24T10:00:00+00:00',
+ ];
+
+ public function testLoadData(): void
+ {
+ $consent = new ApplicationConsent($this->data);
+
+ $this->assertEquals(12, $consent->getId());
+ $this->assertEquals($this->data['installedBy'], $consent->getInstalledBy());
+ $this->assertEquals('my-app', $consent->getIdentifier());
+ $this->assertEquals('My Application', $consent->getName());
+ $this->assertEquals('granted', $consent->getStatus());
+ $this->assertEquals(['project', 'tm'], $consent->getScopes());
+ $this->assertEquals($this->data['createdAt'], $consent->getCreatedAt());
+ $this->assertEquals($this->data['updatedAt'], $consent->getUpdatedAt());
+ }
+
+ public function testLoadDataWithoutOptionalFields(): void
+ {
+ $consent = new ApplicationConsent([
+ 'id' => 12,
+ 'identifier' => 'my-app',
+ 'status' => 'denied',
+ 'scopes' => [],
+ 'createdAt' => '2026-07-24T10:00:00+00:00',
+ 'updatedAt' => '2026-07-24T10:00:00+00:00',
+ ]);
+
+ $this->assertNull($consent->getInstalledBy());
+ $this->assertNull($consent->getName());
+ }
+
+ public function testSetData(): void
+ {
+ $consent = new ApplicationConsent($this->data);
+ $consent->setStatus('denied');
+ $consent->setScopes(['project']);
+
+ $this->assertEquals('denied', $consent->getStatus());
+ $this->assertEquals(['project'], $consent->getScopes());
+ }
+}