The Partner API Keys module manages the lifecycle of API keys used by external partners to call the Moria API on behalf of a specific organization: create , list , revoke , and usage statistics . All endpoints live at /api-keys, require Bearer JWT, and are guarded by @Permissions(...) ACL. These endpoints are administrative — not for use from end-user mobile apps.
Property Value Base URL {HOST}/v1Auth Bearer JWT (header Authorization) or access_token cookie Content-Type application/jsonError envelope { "message": string | string[], "statusCode": number, "error": string }Validation Global ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown fields → 400 Related modules organizations, acl Document version v1 · 2026-05-20 Audience Internal FE devs (web admin / partner portal)
An FE admin creates an API key with POST /api-keys (returns the plaintext key once only — stored hashed in the DB), then lists keys per organization via GET /api-keys/:organization_id, revokes a specific key via DELETE /api-keys/:key_id, and retrieves usage summary via GET /api-keys/:organization_id/stats.
Method Path Auth Summary POST /v1/api-keysbearer Create a new API key for an organization GET /v1/api-keys/:organization_idbearer List all API keys owned by an organization DELETE /v1/api-keys/:key_idbearer Revoke an API key GET /v1/api-keys/:organization_id/statsbearer API key usage statistics per organization
Auth + permissions notes
All endpoints require Bearer JWT. No @Roles(...) — access control is entirely via @Permissions(...).
Permissions used: create-api-key, read-api-key, delete-api-key, read-api-stats. Make sure the caller’s role in ACL has these permissions.
The plaintext API key is only returned when POST succeeds. After that the DB only stores the hash; FE must display the key to the admin once and prompt them to copy it.
organization_id and key_id are validated via ParseUUIDPipe — requests with an invalid UUID → 400.
Create a new API key for a partner organization. The server will generate the key string, store its hashed version, and return the plaintext key once only in the response.
bearer
create-api-key
Field Type Required Notes organization_idstring ✓ ID of the organization that owns the key partner_namestring ✓ Partner display name (e.g. Moria Corporation) permissionsstring[] optional List of permission strings callable with this key (e.g. ['read-user']) expires_atstring (ISO date) optional Expiry time (IsDateString). Empty = never expires. metadataobject optional Free-form metadata (e.g. { environment: 'production', team: 'engineering' })
"organization_id" : " 660e8400-e29b-41d4-a716-446655440111 " ,
"partner_name" : " Acme Corp " ,
"permissions" : [ " read-account " , " read-transaction " ],
"expires_at" : " 2026-12-31T23:59:59.000Z " ,
"metadata" : { "environment" : " production " }
"message" : " API key created successfully " ,
"id" : " 550e8400-e29b-41d4-a716-446655440000 " ,
"organization_id" : " 660e8400-e29b-41d4-a716-446655440111 " ,
"partner_name" : " Acme Corp " ,
"permissions" : [ " read-account " , " read-transaction " ],
"expires_at" : " 2026-12-31T23:59:59.000Z " ,
"created_at" : " 2026-05-20T08:30:00.000Z "
The plaintext API key (raw string) only appears in the create response — included by the service in the apiKey field. After this the DB only stores the hashed version; it cannot be retrieved again. FE must display the key to the admin only once.
Status When it occurs 400 Bad RequestValidation failure (organization_id empty, expires_at not ISO date, unknown field) 401 UnauthorizedBearer/cookie token is invalid 403 ForbiddenCaller lacks create-api-key permission
List all API keys owned by an organization. Only key metadata is returned — not plaintext.
bearer
read-api-key
Param Type Notes organization_idUUID Organization ID · validated via ParseUUIDPipe
"message" : " Partner API keys retrieved successfully " ,
"id" : " 550e8400-e29b-41d4-a716-446655440000 " ,
"organization_id" : " 660e8400-e29b-41d4-a716-446655440111 " ,
"partner_name" : " Acme Corp " ,
"permissions" : [ " read-account " , " read-transaction " ],
"expires_at" : " 2026-12-31T23:59:59.000Z " ,
"last_used_at" : " 2026-05-19T16:20:00.000Z " ,
"created_at" : " 2026-01-15T08:30:00.000Z "
The response field (data.apiKey) is an array despite its singular name — this is intentional in the service mapper.
Status When it occurs 400 Bad Requestorganization_id is not a UUID401 UnauthorizedBearer/cookie token is invalid 403 ForbiddenCaller lacks read-api-key permission
Revoke an API key. Once revoked, all incoming requests with that key will be rejected. The operation cannot be undone.
bearer
delete-api-key
Param Type Notes key_idUUID ID of the API key to revoke · validated via ParseUUIDPipe
"message" : " API key revoked successfully "
Status When it occurs 400 Bad Requestkey_id is not a UUID401 UnauthorizedBearer/cookie token is invalid 403 ForbiddenCaller lacks delete-api-key permission 404 Not FoundAPI key not found
Aggregate statistics of API key usage for an organization: total calls, calls in the last 24 hours, and number of active keys.
bearer
read-api-stats
Param Type Notes organization_idUUID Organization ID · validated via ParseUUIDPipe
"message" : " Partner API usage statistics retrieved successfully " ,
Status When it occurs 400 Bad Requestorganization_id is not a UUID401 UnauthorizedBearer/cookie token is invalid 403 ForbiddenCaller lacks read-api-stats permission
create-api-key — create a new key
read-api-key — list keys per organization
delete-api-key — revoke a key
read-api-stats — read usage statistics
true — key active, incoming requests accepted
false — key already revoked / disabled
null — never expires
ISO 8601 timestamp — expiry date
"message" : " Validation failed (uuid is expected) " ,
message can be a string or an array of strings (multi-field validation error).
400 UUID / DTO validation failed
401 token expired / missing
403 ACL permission mismatch
404 API key not found
500 internal — show a generic toast