API reference¶
The public surface of every module. Anything not listed here (names
prefixed with _) is an internal implementation detail, not covered by
this package's compatibility guarantees.
django_tenant_apikeys.models¶
No DRF or Ninja dependency — importable with just the base package.
AbstractTenantAPIKey¶
Abstract model. Subclass it and add a tenant relation (see
Multi-tenancy).
Fields: name, prefix, hashed_key, scopes, environment,
rate_limit, rate_limit_window, allowed_ips, metadata, is_active,
created_at, expires_at, last_used_at, revoked_at, revoked_reason.
Class methods
generate_key(cls, *, prefix="tak", **kwargs) -> tuple[instance, raw_key]— creates and saves a new key.**kwargsgo straight to the model constructor (name,scopes,expires_at,environment,rate_limit,rate_limit_window,allowed_ips,metadata, atenantrelation, etc). RaisesValueErrorifscopes/allowed_ipsisn't a list of non-empty strings (eachallowed_ipsentry must also parse as an IP or CIDR network), ifrate_limitisn't a positive integer, or ifrate_limit_window/environmentisn't a valid choice.
Instance methods
verify_key(self, raw_key: str) -> bool— constant-time check against the stored hash.has_scope(self, required_scope: str) -> bool— see Scopes.is_ip_allowed(self, client_ip: str) -> bool— see IP restrictions.record_usage(self) -> None— updateslast_used_at, throttled byLAST_USED_THRESHOLD. Called automatically by both framework integrations on every successful authentication.revoke(self, *, reason: str = "") -> Nonereactivate(self) -> Nonerotate(self, *, prefix="tak", **overrides) -> tuple[instance, raw_key]— raisesValueErrorif the key is already inactive or expired.
Properties
is_expired: boolis_valid: bool—is_active and not is_expired
Class attributes
LAST_USED_THRESHOLD: ClassVar[timedelta]— defaulttimedelta(minutes=5), overridable per subclass.
See Key lifecycle for all of the above in context.
TenantAPIKeyManager¶
The default manager (.objects) on AbstractTenantAPIKey.
get_from_key(self, raw_key: str) -> AbstractTenantAPIKey— indexed prefix lookup only; does not verify the secret. Callverify_key()on the result. RaisesDoesNotExistif no row matches.get_usable_keys(self) -> QuerySet[AbstractTenantAPIKey]— active, unexpired keys only.
Environment¶
TextChoices: PRODUCTION (default), STAGING, DEVELOPMENT, TEST. See
Environments.
RateLimitWindow¶
TextChoices: SECOND, MINUTE (default), HOUR, DAY. See
Rate limiting.
Module-level functions¶
generate_api_key(prefix: str = "tak", *, environment: str = "production") -> tuple[str, str, str]— returns(full_key, key_prefix, hashed_key). RaisesValueErrorifprefixis too long to fitAbstractTenantAPIKey.prefix'smax_length=32, or ifenvironmentisn't a valid choice.hash_key(raw_key: str) -> str— SHA-256 hex digest ofraw_key.get_api_key_model() -> type[AbstractTenantAPIKey]— resolvessettings.TENANT_API_KEY_MODEL. RaisesImproperlyConfiguredif unset or invalid.
django_tenant_apikeys.ip¶
No optional dependency required.
get_client_ip(request) -> str— see IP restrictions.
django_tenant_apikeys.ratelimit¶
No optional dependency required.
check_rate_limit(api_key) -> RateLimitResult— records one request and checks it againstapi_key.rate_limit. See Rate limiting.RateLimitResult— a frozen dataclass:allowed: bool,limit: int | None,remaining: int | None,reset_at: int | None(Unix timestamp),retry_after: int | None(seconds; set only whenallowedisFalse). The last four are allNonewhen the key has norate_limitconfigured.RateLimitBackend— theProtocola custom backend implements: one method,hit(key: str, limit: int, window_seconds: int) -> RateLimitResult.CacheRateLimitBackend(cache: BaseCache | None = None)— the default backend. See Rate limiting.get_rate_limit_backend() -> RateLimitBackend— resolvesTENANT_API_KEY_RATE_LIMIT_BACKEND, defaulting toCacheRateLimitBackend.
django_tenant_apikeys.authentication (needs [drf])¶
TenantAPIKeyAuthentication— DRFBaseAuthenticationsubclass.keyword = "Api-Key",model: type[AbstractTenantAPIKey] | None = Noneget_model(self) -> type[AbstractTenantAPIKey]— returnsself.modelorget_api_key_model().authenticate(self, request) -> tuple[None, AbstractTenantAPIKey] | Noneauthenticate_credentials(self, raw_key, request=None) -> tuple[None, AbstractTenantAPIKey]— the lower-level methodauthenticate()delegates to after parsing the header;requestis optional, and tenant attachment is skipped if it'sNone.authenticate_header(self, request) -> str
API_KEY_KEYWORD—"Api-Key", the scheme both integrations parse.
django_tenant_apikeys.permissions (needs [drf])¶
All three are BasePermission subclasses with a has_permission(self, request, view) -> bool
method, meant to compose in permission_classes — see
How the policies interact.
HasAPIKeyScope— enforces the view'srequired_scopesagainstrequest.auth.has_scope(...). See Scopes.HasAllowedIP— enforcesrequest.auth.allowed_ips. See IP restrictions.WithinRateLimit— enforcesrequest.auth.rate_limit; raises DRF'sThrottled(429) once exceeded, and setsX-RateLimit-*response headers. See Rate limiting.
All three return False if request.auth isn't an AbstractTenantAPIKey
instance, so each is meant to be paired with TenantAPIKeyAuthentication,
not used alone.
django_tenant_apikeys.ninja (needs [ninja])¶
TenantAPIKeyAuth— NinjaAPIKeyHeadersubclass.param_name = "Authorization",openapi_scheme = "apikey",model: type[AbstractTenantAPIKey] | None = Noneget_model(self) -> type[AbstractTenantAPIKey]authenticate(self, request, key: str | None) -> AbstractTenantAPIKey | None
See Django Ninja.
django_tenant_apikeys.admin¶
TenantAPIKeyAdmin—ModelAdminsubclass. See Admin forlist_display,list_filter,readonly_fields, themasked_key/statusdisplay methods, therevoke_selectedaction, andsave_model()'s one-time key reveal.
Management commands¶
Require django_tenant_apikeys in INSTALLED_APPS.
tenant_api_key_revoke <prefix> [--reason TEXT]tenant_api_key_rotate <prefix>
See Management commands.
Settings¶
See Configuration for the complete list:
TENANT_API_KEY_MODEL, TENANT_API_KEY_TRUSTED_PROXY_HEADER,
TENANT_API_KEY_RATE_LIMIT_CACHE, TENANT_API_KEY_RATE_LIMIT_BACKEND.