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

# Register an OAuth client from the dashboard

> Create an OAuth client with an operator behind it. This is the
dashboard-managed counterpart to the public RFC 7591
`POST /oauth/register`: the row records `registeredById`, so it reports
`registrationSource: dashboard` while self-registered clients report
`dynamic`.

**Scopes are silently filtered.** Submitted `scopes` are passed through
`filterKnownHeadlessScopes` before storage — anything outside the
headless catalog is dropped without an error. If every submitted scope
is unknown, the request fails with `400` ("At least one valid scope is
required"). Check the `scopes` array on the response to see what was
actually granted.

**`clientSecret` is returned exactly once**, and only for confidential
clients (`isPublic: false`). Public clients (the default) use PKCE and
get no secret.

Requires the `system.manage_integrations` permission.




## OpenAPI

````yaml https://api.nexspace365.com/api/openapi.json post /api/oauth-clients
openapi: 3.0.3
info:
  title: NexSpace 365 API
  description: >
    Multi-industry enterprise workforce management platform — scheduling, HR,

    payroll, CRM, AI, and messaging suites.


    ## Authentication

    Four credentials, all but the first sent as `Authorization: Bearer <token>`:

    session cookie, JWT bearer, API key (`nex_live_…` / `nex_test_…` /

    `nex_pat_…`), and OAuth 2.1 access token. AI agents should use an API key or

    an OAuth token with the appropriate scopes.


    ## Authorization

    Endpoints require specific permissions via the RBAC system:

    - **Internal team**: Full platform access

    - **Facility users**: Scoped to their org/facilities

    - **Staff**: Limited to own data


    ## Error Contract

    All errors return `{ error: { message, code?, suggestion?, retryable? },
    requestId? }`.

    The `suggestion` field hints at how to fix the request; `retryable` signals
    whether

    retry-with-backoff is appropriate.


    ## Versioning

    Pin your integration to a specific API version via the `NexSpace-Version`
    header

    (date-based, e.g. `2026-05-10`). When absent, the latest version is assumed.

    Deprecated versions include `Sunset` and `Deprecation` headers. Discovery at

    `GET /.well-known/api-versions`.


    ## Idempotency

    Write operations (POST/PUT/PATCH) accept an `Idempotency-Key` header. If

    the same key + body combination is sent again within 24 hours, the original

    response is replayed. Different body with the same key returns 409 Conflict.


    ## MCP (Model Context Protocol)

    AI agents interact via `POST /mcp` using JSON-RPC 2.0. Call `tools/list` to

    discover available verbs, then `tools/call` to invoke them.


    MCP is served on its own host — `https://mcp.nexspace365.com`, not the API

    host. `api.nexspace365.com` rejects `/mcp`, `/mcp/*` and `/.well-known/mcp`

    with `403 MCP_HOST_NOT_ALLOWED`, and those operations pin the correct host

    with an operation-level `servers` override. The

    `/.well-known/oauth-protected-resource*` operations are also tagged MCP but

    are **not** covered by the host guard — they answer on either host.
  version: 1.0.0
  contact:
    name: NexSpace
    email: admin@nexspace365.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
  - url: https://api.nexspace365.com
    description: Production server
security:
  - bearerAuth: []
  - apiKeyAuth: []
tags:
  - name: Authentication
    description: User authentication and session management
  - name: Facilities
    description: Facility management
  - name: Staff
    description: Staff member management and profiles
  - name: Credentials
    description: Staff credential tracking — licenses, certifications, and training
  - name: Shifts
    description: Shift scheduling and management
  - name: Agents
    description: Headless agent runs, human-in-the-loop approvals, and automation triggers.
  - name: Dashboard
    description: Dashboard statistics and widgets
  - name: API Keys
    description: API key lifecycle — create, list, revoke, rotate
  - name: MCP
    description: Model Context Protocol endpoint for AI agent tool calling
  - name: OAuth
    description: OAuth 2.1 authorization server — DCR, authorize, token
  - name: Webhooks
    description: Outbound webhook subscription management
  - name: Analytics
    description: API usage analytics and observability
  - name: Sandbox
    description: Sandbox (`nex_test_`) data lifecycle for the same production host
  - name: Platform
    description: Version discovery and platform metadata.
paths:
  /api/oauth-clients:
    post:
      tags:
        - OAuth
      summary: Register an OAuth client from the dashboard
      description: |
        Create an OAuth client with an operator behind it. This is the
        dashboard-managed counterpart to the public RFC 7591
        `POST /oauth/register`: the row records `registeredById`, so it reports
        `registrationSource: dashboard` while self-registered clients report
        `dynamic`.

        **Scopes are silently filtered.** Submitted `scopes` are passed through
        `filterKnownHeadlessScopes` before storage — anything outside the
        headless catalog is dropped without an error. If every submitted scope
        is unknown, the request fails with `400` ("At least one valid scope is
        required"). Check the `scopes` array on the response to see what was
        actually granted.

        **`clientSecret` is returned exactly once**, and only for confidential
        clients (`isPublic: false`). Public clients (the default) use PKCE and
        get no secret.

        Requires the `system.manage_integrations` permission.
      operationId: createOauthClient
      parameters:
        - $ref: '#/components/parameters/NexSpaceVersionHeader'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - clientName
                - redirectUris
                - scopes
              properties:
                clientName:
                  type: string
                  minLength: 1
                  maxLength: 120
                redirectUris:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    format: uri
                scopes:
                  type: array
                  minItems: 1
                  items:
                    type: string
                  description: >-
                    Requested scopes. Filtered to the headless catalog
                    (`shared/headless-scopes.ts`) before storage.
                isPublic:
                  type: boolean
                  default: true
                  description: >-
                    `true` (default) mints a public PKCE client with no secret.
                    `false` mints a confidential client and returns a one-time
                    `clientSecret`.
                clientUri:
                  type: string
                  format: uri
                logoUri:
                  type: string
                  format: uri
              example:
                clientName: Ops automation
                redirectUris:
                  - https://ops.example.com/callback
                scopes:
                  - shifts:read
                  - staff:read
                isPublic: true
      responses:
        '201':
          description: Client registered
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/OauthClient'
                  - type: object
                    properties:
                      clientSecret:
                        type: string
                        description: >-
                          Confidential clients only. Shown exactly once — it is
                          stored hashed and can never be read back.
        '400':
          description: >-
            Validation failure, or every submitted scope was outside the
            headless catalog.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          description: Missing the `system.manage_integrations` permission
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth: []
components:
  parameters:
    NexSpaceVersionHeader:
      name: NexSpace-Version
      in: header
      required: false
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
      description: >
        Pin the request to a dated API version (e.g. `2026-05-10`). Applied by
        app-level middleware to every `/api` route (`server/routes/index.ts` →
        `apiVersionMiddleware`), which echoes the resolved value back in the
        `NexSpace-Version` response header. Omit to get the latest version.
        Discover the catalog at `GET /.well-known/api-versions`.
    IdempotencyKeyHeader:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: >
        Replay guard for write requests. Applied by app-level middleware to
        every `/api` route (`server/routes/index.ts` → `idempotencyMiddleware`),
        which only acts on POST/PUT/PATCH — GET, DELETE and OPTIONS ignore the
        header. Re-sending the same key with an identical body within 24 hours
        replays the original response; the same key with a different body
        returns `409`.
  schemas:
    OauthClient:
      type: object
      description: >
        A registered OAuth client, as returned by the dashboard-managed
        `/api/oauth-clients` endpoints. The client secret is never included —
        only its one-time value on creation carries it.
      properties:
        id:
          type: integer
          description: Row id, used in `DELETE /api/oauth-clients/{id}`.
        clientId:
          type: string
          description: Public client identifier (`cli_…`).
        clientName:
          type: string
        redirectUris:
          type: array
          items:
            type: string
            format: uri
        isPublic:
          type: boolean
          description: Public (PKCE, no secret) vs confidential (secret-bearing).
        scopes:
          type: array
          items:
            type: string
          description: Granted scopes, already filtered to the headless catalog.
        metadata:
          type: object
          additionalProperties: true
          nullable: true
          description: '`client_uri` / `logo_uri` as submitted.'
        registeredById:
          type: integer
          nullable: true
          description: The operator who registered it; null for RFC 7591 self-registration.
        registeredByName:
          type: string
          nullable: true
        registrationSource:
          type: string
          enum:
            - dashboard
            - dynamic
          description: >-
            `dashboard` when `registeredById` is set (created via `POST
            /api/oauth-clients`), `dynamic` when the client self-registered via
            `POST /oauth/register`.
        createdAt:
          type: string
          format: date-time
          nullable: true
        updatedAt:
          type: string
          format: date-time
          nullable: true
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
              description: Human-readable error message
            code:
              type: string
              description: Machine-readable error code (e.g. VALIDATION_FAILED, NOT_FOUND)
            details:
              type: object
              description: Structured details (e.g. Zod validation issues)
            suggestion:
              type: string
              description: Hint for agents on how to recover
            retryable:
              type: boolean
              description: Whether the same request might succeed on retry
          required:
            - message
        requestId:
          type: string
          description: Correlation ID for logs and Sentry
      required:
        - error
  responses:
    UnauthorizedError:
      description: Missing, invalid, expired, or revoked credential.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              message: Invalid or revoked API key
              code: UNAUTHENTICATED
              suggestion: >-
                Send a valid `Authorization: Bearer <token>` (nex_live_/nex_pat_
                key, JWT, or OAuth access token).
              retryable: false
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token authentication
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: NexSpaceApiKey
      description: |
        API key or personal access token (NEX-1042). Send as
        `Authorization: Bearer <token>`. Token format:
          - `nex_live_…`  — live API key (server-to-server)
          - `nex_test_…`  — sandbox API key
          - `nex_pat_…`   — personal access token (acts as the issuing user)

        Authorization is decided by the key's `scopes` array, not the
        owning user's RBAC role. See `POST /api/api-keys` to mint a key.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.