Shukr Docs
Shukr Public Media Service

Authentication

The two authentication schemes — user JWT and internal service key — plus ownership rules and error codes.

Every /api/v1/media/* endpoint is guarded. A request authenticates with either a user access token or an internal service key — you never send both. Playback and webhook endpoints are not behind this guard (they rely on asset visibility and provider signatures respectively).

Scheme 1 — User JWT (BearerAuth)

Send a Shukr user access token as a bearer token:

Authorization: Bearer <access_token>

The token is verified locally (HS256, no network round-trip). It must:

  • be signed with the service's ACCESS_TOKEN_SECRET,
  • carry a user_id claim, and
  • have type: "access" — refresh tokens are rejected.

The resolved user_id becomes the owner of any asset created in that request, and scopes every list/read/delete to that user.

Scheme 2 — Service key (ServiceKeyAuth)

Internal services authenticate with a shared API key header (case-insensitive):

X-Service-Key: <service_api_key>

Because there is no user in the token, a service caller must name the owner explicitly by passing owner_user_id — in the request body for writes, or as a query parameter for GET /media.

Omitting owner_user_id on a service-key request fails validation with 400 BAD_REQUEST. Service-key callers also bypass per-asset ownership checks, so treat the key as a trusted server-to-server secret and never ship it to a browser or mobile client.

Same call, both schemes

curl -X POST https://media.example.com/api/v1/media/upload-url \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "photo.jpg", "content_type": "place", "mime_type": "image/jpeg" }'

Ownership

For single-asset operations (GET, download-url, playback, DELETE, complete, …) a user caller may only act on assets they own; anything else returns 403 FORBIDDEN. A service caller is trusted and skips this check — which is exactly why it must state the owner_user_id.

Error codes

Authentication and authorization failures use the standard error envelope. The error.error_code field is stable and safe to switch on:

HTTPerror_codeWhen
400BAD_REQUESTMissing/invalid input — e.g. a service call without owner_user_id, or a disallowed mime_type.
401UNAUTHORIZEDNo credentials, malformed token, wrong secret, refresh token used, or bad service key.
403FORBIDDENAuthenticated, but the asset belongs to another user.
404NOT_FOUNDAsset (or route) does not exist.
500INTERNAL_ERRORUnexpected server error.
{
  "success": false,
  "message": "Missing or invalid access token",
  "code": 401,
  "error": { "error_code": "UNAUTHORIZED", "details": null },
  "request_id": "req_01J9Z0X8Q2",
  "timestamp": "2026-07-29T04:15:30.000Z"
}

On this page