Skip to content

Partner API

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.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or access_token cookie
Content-Typeapplication/json
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown fields → 400
Related modulesorganizations, acl
Document versionv1 · 2026-05-20
AudienceInternal 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.

MethodPathAuthSummary
POST/v1/api-keysbearerCreate a new API key for an organization
GET/v1/api-keys/:organization_idbearerList all API keys owned by an organization
DELETE/v1/api-keys/:key_idbearerRevoke an API key
GET/v1/api-keys/:organization_id/statsbearerAPI key usage statistics per organization

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
FieldTypeRequiredNotes
organization_idstringID of the organization that owns the key
partner_namestringPartner display name (e.g. Moria Corporation)
permissionsstring[]optionalList of permission strings callable with this key (e.g. ['read-user'])
expires_atstring (ISO date)optionalExpiry time (IsDateString). Empty = never expires.
metadataobjectoptionalFree-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" }
}
{
"status": "success",
"statusCode": 200,
"message": "API key created successfully",
"data": {
"apiKey": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "660e8400-e29b-41d4-a716-446655440111",
"partner_name": "Acme Corp",
"permissions": ["read-account", "read-transaction"],
"is_active": true,
"expires_at": "2026-12-31T23:59:59.000Z",
"last_used_at": null,
"usage_count": 0,
"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.

StatusWhen 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

GET /v1/api-keys/:organization_id bearer

Section titled “GET /v1/api-keys/:organization_id ”

List all API keys owned by an organization. Only key metadata is returned — not plaintext.

bearer read-api-key
ParamTypeNotes
organization_idUUIDOrganization ID · validated via ParseUUIDPipe
{
"status": "success",
"statusCode": 200,
"message": "Partner API keys retrieved successfully",
"data": {
"apiKey": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "660e8400-e29b-41d4-a716-446655440111",
"partner_name": "Acme Corp",
"permissions": ["read-account", "read-transaction"],
"is_active": true,
"expires_at": "2026-12-31T23:59:59.000Z",
"last_used_at": "2026-05-19T16:20:00.000Z",
"usage_count": 42,
"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.

StatusWhen it occurs
400 Bad Requestorganization_id is not a UUID
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenCaller lacks read-api-key permission

DELETE /v1/api-keys/:key_id bearer

Section titled “DELETE /v1/api-keys/:key_id ”

Revoke an API key. Once revoked, all incoming requests with that key will be rejected. The operation cannot be undone.

bearer delete-api-key
ParamTypeNotes
key_idUUIDID of the API key to revoke · validated via ParseUUIDPipe
{
"status": "success",
"statusCode": 200,
"message": "API key revoked successfully"
}
StatusWhen it occurs
400 Bad Requestkey_id is not a UUID
401 UnauthorizedBearer/cookie token is invalid
403 ForbiddenCaller lacks delete-api-key permission
404 Not FoundAPI key not found

GET /v1/api-keys/:organization_id/stats bearer

Section titled “GET /v1/api-keys/:organization_id/stats ”

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
ParamTypeNotes
organization_idUUIDOrganization ID · validated via ParseUUIDPipe
{
"status": "success",
"statusCode": 200,
"message": "Partner API usage statistics retrieved successfully",
"data": {
"statistics": {
"total_calls": 1000,
"calls_last_24h": 50,
"active_keys": 3
}
}
}
StatusWhen it occurs
400 Bad Requestorganization_id is not a UUID
401 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)",
"statusCode": 400,
"error": "Bad Request"
}

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