API keys
`keys.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log and activity readers.
Every method
from pathlib import Path from openemail import openemail key = openemail.keys.create({ 'name': 'Billing sender', 'scopes': ['emails:send'], 'domainAllowlist': ['billing.acme.com'], 'expiresInMinutes': 60 * 24 * 90,}) secret = Path('.openemail-billing-key')secret.touch(mode=0o600)secret.write_text(key['token']) openemail.keys.update(key['id'], {'enabled': False}) rotated = openemail.keys.rotate(key['id'])secret.write_text(rotated['token']) openemail.keys.revoke(key['id'], {'reason': 'Replaced'})openemail.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. Reading needs keys:read and every change needs keys:manage.
The client 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 like reads, 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 is refused with 403 beyond_caller_authority. 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 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 list_workspace_activity, where everything it does is recorded against it.
Request log and activity
from datetime import datetime, timedelta, timezone from openemail import openemail failures = openemail.keys.list_requests( '4c1b257a66287fd113bd89d0', failed_only=True, since=datetime.now(timezone.utc) - timedelta(days=1),)for request in failures['items']: print(request['status'], request['method'], request['path']) for change in openemail.keys.iterate_workspace_activity(): actor = change['actor'] print(change['keyName'], change['type'], actor['label'] if actor else None)list_requests and list_activity read one key, list_workspace_requests and list_workspace_activity read every key or the ones key_ids names, and each has a list_all_… and an iterate_… beside it. They take since and until, and the request readers also take failed_only.
since and until take a datetime or an ISO 8601 string. A datetime is sent in UTC, and a naive one is read as local time first.
Reference
keys.list()Full referencekeys.list_all()Full referencekeys.iterate()Full referencekeys.get()Full referencekeys.create()Full referencekeys.update()Full referencekeys.delete()Full referencekeys.rotate()Full referencekeys.revoke()Full referencekeys.list_requests()Full referencekeys.list_all_requests()Full referencekeys.iterate_requests()Full referencekeys.list_activity()Full referencekeys.list_all_activity()Full referencekeys.iterate_activity()Full referencekeys.list_workspace_requests()Full referencekeys.list_all_workspace_requests()Full referencekeys.iterate_workspace_requests()Full referencekeys.list_workspace_activity()Full referencekeys.list_all_workspace_activity()Full referencekeys.iterate_workspace_activity()Full reference