Skip to the documentation
PHP

API keys

`keys->list`, `listAll`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log, activity and stats readers.

Every method

keys.php
use OpenEmail\Constants\ApiScopes; $key = $client->keys->create([    'name' => 'Billing sender',    'scopes' => [ApiScopes::EMAILS_SEND],    'domainAllowlist' => ['billing.acme.com'],    'expiresInMinutes' => 60 * 24 * 90,]); file_put_contents('.openemail-billing-key', $key['token']); $client->keys->update($key['id'], ['enabled' => false]); $rotated = $client->keys->rotate($key['id']);file_put_contents('.openemail-billing-key', $rotated['token']); $client->keys->revoke($key['id'], ['reason' => 'Replaced']);$client->keys->delete($key['id']);

create and rotate are the only calls that return a secret, in token, once. Every read returns maskedKey instead. update switches a key off and on with enabled, which is the reversible alternative to revoke, and delete only removes a key that has been revoked: any other key is a 409 not_revoked, thrown as a ConflictException. Reading needs keys:read and every change needs keys:manage.

list returns one OpenEmail\Result\Page, listAll returns every key in one array, and iterate returns a Generator that yields one key at a time. create and update take the body as one array under the API’s names, and revoke takes an optional array with reason. An OAuth access token reaches these calls only when the person who connected the app owns the workspace. Anyone else’s gets a PermissionException with errorCode set to owner_only.

The package never retries create or rotate. A retry after a lost response would mint a second key, or invalidate the secret the first attempt returned. update and revoke are retried after a network failure, because repeating them leaves the same key, and delete is not.

Never wider than the caller

A key never makes or reaches a key wider than itself. Scopes, role, expiry, mode and send scope all have to sit inside the calling key, or the call throws a PermissionException with errorCode set to beyond_caller_authority, and param names the axis. A key narrowed to some domains or addresses only sees the keys inside its own send scope. rotate on the calling key also works with keys:write, like $client->me->rotate().

Step-up verification cannot apply to a call made with a key, so keys:manage is a credential that makes credentials. Give it only to automation that provisions keys, give that key a role, a send scope and an expiry, and watch listWorkspaceActivity, where everything it does is recorded against it.

Request log and activity

key_logs.php
$failures = $client->keys->listRequests(    '4c1b257a66287fd113bd89d0',    failedOnly: true,    since: new \DateTimeImmutable('-1 day'),);echo count($failures), PHP_EOL; foreach ($client->keys->iterateWorkspaceActivity() as $change) {    echo $change['keyName'], ' ', $change['type'], ' ', $change['actor']['label'] ?? 'system', PHP_EOL;}

listRequests and listActivity read one key, and listWorkspaceRequests and listWorkspaceActivity read every key or the ones keyIds: names, as an array or one comma-separated string. Each has a listAll and an iterate twin beside it, such as listAllRequests and iterateRequests. They take since: and until: as a DateTimeInterface or an ISO 8601 string, and the request readers also take failedOnly:.

stats reads what the keys did inside one window: the mail they sent and what became of it, the calls refused, the requests they made and the routes they called. It takes the same keyIds:, since: and until:, plus grain: and offsetMinutes:, covers the 30 days before now when you name no window, and needs emails:read beside keys:read.

until: has to be later than since:, or the call throws an InvalidRequestException with errorCode set to invalid_parameter and param set to until. A DateTimeInterface is sent in UTC, and a string is sent as written, so it has to be an ISO 8601 timestamp such as 2026-09-01T00:00:00Z.