> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nullpass.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Access Management

> Get and update service entitlements

## Endpoints

```
GET /api/services
POST /api/services
```

## GET /api/services

Retrieves service entitlements for the authenticated user. Can be filtered by service type.

### Query Parameters

<ParamField query="service" type="string">
  Optional. Filter by service: `DROP`, `MAILS`, `VAULT`, or `DB`
</ParamField>

### Response

<ResponseField name="entitlements" type="array">
  Array of service entitlement objects
</ResponseField>

## POST /api/services

Creates or updates a service entitlement for the authenticated user. Uses upsert logic (creates if doesn't exist, updates if exists).

### Request

<ParamField body="service" type="ServiceIdentifier" required>
  Service identifier: `DROP`, `MAILS`, `VAULT`, or `DB`
</ParamField>

<ParamField body="tier" type="string">
  Access tier (e.g., "free", "premium", "enterprise")
</ParamField>

<ParamField body="isPremium" type="boolean">
  Premium access flag
</ParamField>

<ParamField body="accessFlags" type="object">
  Custom access flags (JSON object)
</ParamField>

<ParamField body="metadata" type="object">
  Service-specific metadata (JSON object)
</ParamField>

<ParamField body="customStorageLimit" type="number">
  Custom storage limit in bytes
</ParamField>

<ParamField body="customApiKeyLimit" type="number">
  Custom API key limit
</ParamField>

<ParamField body="polarCustomerId" type="string">
  Polar customer ID
</ParamField>

<ParamField body="polarSubscriptionId" type="string">
  Polar subscription ID
</ParamField>

<ParamField body="polarSubscriptionStatus" type="string">
  Polar subscription status
</ParamField>

### Response

<ResponseField name="entitlement" type="object">
  Created or updated entitlement object
</ResponseField>

## Implementation Details

### Code Reference

```23:114:nullpass_clean/src/app/api/services/route.ts theme={null}
export async function GET(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const { searchParams } = new URL(request.url)
    const service = searchParams.get('service') as 'DROP' | 'MAILS' | 'VAULT' | 'DB' | null

    const where: any = { userId: auth.userId }
    if (service) {
      where.service = service
    }

    const entitlements = await prisma.userServiceEntitlement.findMany({
      where,
    })

    return jsonResponse({ entitlements }, 200, request.headers.get('origin'))
  } catch (error) {
    logger.error('Get service access error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}

export async function POST(request: NextRequest) {
  const corsResponse = handleCors(request)
  if (corsResponse) return corsResponse

  const blocked = await protectRoute(request)
  if (blocked) return blocked

  const auth = await requireAuth(request)
  if ('error' in auth) return auth.error

  try {
    const body = await request.json()
    const validated = updateServiceSchema.parse(body)

    logger.ups('Service entitlement update:', auth.userId, validated.service)

    const entitlement = await prisma.userServiceEntitlement.upsert({
      where: {
        userId_service: {
          userId: auth.userId,
          service: validated.service,
        },
      },
      update: {
        ...(validated.tier !== undefined && { tier: validated.tier }),
        ...(validated.isPremium !== undefined && { isPremium: validated.isPremium }),
        ...(validated.accessFlags !== undefined && { accessFlags: validated.accessFlags }),
        ...(validated.metadata !== undefined && { metadata: validated.metadata }),
        ...(validated.customStorageLimit !== undefined && { customStorageLimit: validated.customStorageLimit }),
        ...(validated.customApiKeyLimit !== undefined && { customApiKeyLimit: validated.customApiKeyLimit }),
        ...(validated.polarCustomerId !== undefined && { polarCustomerId: validated.polarCustomerId }),
        ...(validated.polarSubscriptionId !== undefined && { polarSubscriptionId: validated.polarSubscriptionId }),
        ...(validated.polarSubscriptionStatus !== undefined && { polarSubscriptionStatus: validated.polarSubscriptionStatus }),
        updatedAt: new Date(),
      },
      create: {
        userId: auth.userId,
        service: validated.service,
        tier: validated.tier || 'free',
        isPremium: validated.isPremium || false,
        accessFlags: validated.accessFlags || undefined,
        metadata: validated.metadata || undefined,
        customStorageLimit: validated.customStorageLimit || null,
        customApiKeyLimit: validated.customApiKeyLimit || null,
        polarCustomerId: validated.polarCustomerId || null,
        polarSubscriptionId: validated.polarSubscriptionId || null,
        polarSubscriptionStatus: validated.polarSubscriptionStatus || null,
      },
    })

    logger.info('Service entitlement updated:', auth.userId, validated.service)

    return jsonResponse({ entitlement }, 200, request.headers.get('origin'))
  } catch (error: any) {
    if (error.name === 'ZodError') {
      logger.warn('Service update validation error:', error.errors)
      return errorResponse(error.errors[0].message, 400, request.headers.get('origin'))
    }
    logger.error('Update service access error:', error)
    return errorResponse('Internal server error', 500, request.headers.get('origin'))
  }
}
```

## Status Codes

<ResponseField name="200" type="OK">
  Success
</ResponseField>

<ResponseField name="400" type="Bad Request">
  Validation error
</ResponseField>

<ResponseField name="401" type="Unauthorized">
  Missing or invalid authentication token
</ResponseField>

## Example Requests

### Get All Services

```bash theme={null}
curl -X GET https://auth.nullpass.xyz/api/services \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Get Specific Service

```bash theme={null}
curl -X GET "https://auth.nullpass.xyz/api/services?service=DROP" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

### Update Service Entitlement

```bash theme={null}
curl -X POST https://auth.nullpass.xyz/api/services \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "DROP",
    "tier": "premium",
    "isPremium": true,
    "customStorageLimit": 10737418240,
    "customApiKeyLimit": 100
  }'
```

## Upsert Behavior

The POST endpoint uses Prisma's `upsert` operation:

* **If entitlement exists**: Updates only provided fields, preserves others
* **If entitlement doesn't exist**: Creates new entitlement with provided values and defaults
* **Unique constraint**: `userId` + `service` combination must be unique

## Default Values

When creating a new entitlement:

* `tier`: "free"
* `isPremium`: false
* `connected`: true (set by database default)
* `customStorageLimit`: null
* `customApiKeyLimit`: null
* Polar fields: null


## OpenAPI

````yaml GET /services
openapi: 3.1.0
info:
  title: Null Pass API
  description: >-
    Internal API documentation for Null Pass authentication and service
    management system
  version: 1.0.0
servers:
  - url: https://auth.nullpass.xyz/api
security: []
paths:
  /services:
    get:
      tags:
        - Services
      summary: Get Service Entitlements
      description: Retrieve service entitlements for authenticated user
      parameters:
        - name: service
          in: query
          schema:
            type: string
            enum:
              - DROP
              - MAILS
              - VAULT
              - DB
      responses:
        '200':
          description: Service entitlements
          content:
            application/json:
              schema:
                type: object
                properties:
                  entitlements:
                    type: array
                    items:
                      $ref: '#/components/schemas/ServiceEntitlement'
      security:
        - bearerAuth: []
components:
  schemas:
    ServiceEntitlement:
      type: object
      properties:
        id:
          type: string
        userId:
          type: string
        service:
          type: string
          enum:
            - DROP
            - MAILS
            - VAULT
            - DB
        tier:
          type: string
          example: premium
        isPremium:
          type: boolean
        accessFlags:
          type: object
          additionalProperties: true
        metadata:
          type: object
          additionalProperties: true
        customStorageLimit:
          type: integer
          nullable: true
        customApiKeyLimit:
          type: integer
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````