# /api/lanes/frames (dashboard)

Source-derived dashboard endpoint contract, authentication, request validation, responses, and errors.

Source: https://docs.minds.sh/docs/api/platform/reference/dashboard--api-lanes-frames



This is the **dashboard** service route. Its origin and authentication are described in [Platform API overview](/docs/api/platform) and [Authentication](/docs/api/platform/authentication). A path shared by another service is a different endpoint.

| Property                       | Contract                                                                 |
| ------------------------------ | ------------------------------------------------------------------------ |
| Methods                        | POST, GET                                                                |
| Path                           | `/api/lanes/frames`                                                      |
| Path parameters                | None                                                                     |
| Query parameters read in route | None read directly; schema validation below may define additional fields |
| Headers read in route          | `idempotency-key`                                                        |

## Authentication and access [#authentication-and-access]

Dashboard session cookie through @repo/auth/server. Some auth-summary routes can redirect to sign-in; inspect the status branches below.

## Method behavior [#method-behavior]

The handler excerpt preserves field validation, response envelopes, cookie changes, and exception branches. Values returned by service helpers retain their named response type above; for passthrough routes, the upstream response is authoritative.

### POST [#post]

```typescript
export async function POST(request: NextRequest) {
  const session = await getSession();
  if (!session) {
    return NextResponse.json(
      {
        error: {
          code: 'UNAUTHENTICATED',
          message: 'Sign in at auth.l1fe.ai before attaching Lanes pack.',
        },
      },
      { status: 401, headers: noStoreHeaders() }
    );
  }

  let body: Record<string, unknown>;
  try {
    body = (await request.json()) as Record<string, unknown>;
  } catch {
    return NextResponse.json(
      {
        error: {
          code: 'INVALID_JSON',
          message: 'Request body must be JSON.',
        },
      },
      { status: 400, headers: noStoreHeaders() }
    );
  }

  const query =
    typeof body.query === 'string'
      ? body.query
      : typeof body.message === 'string'
        ? body.message
        : '';

  const cookieStore = await cookies();
  const activeTenant = cookieStore.get(ACTIVE_TENANT_COOKIE)?.value;

  const organizationId =
    (typeof body.organizationId === 'string' && body.organizationId) ||
    activeTenant ||
    session.user.orgId ||
    process.env.MINDS_ORG_ID ||
    process.env.MINDS_ORGANIZATION_ID ||
    '';
  const workspaceId =
    (typeof body.workspaceId === 'string' && body.workspaceId) ||
    process.env.MINDS_WORKSPACE_ID ||
    '';
  const projectId =
    (typeof body.projectId === 'string' && body.projectId) ||
    session.user.projectId ||
    process.env.MINDS_PROJECT_ID ||
    '';
  const environmentId =
    (typeof body.environmentId === 'string' && body.environmentId) ||
    process.env.MINDS_ENVIRONMENT_ID ||
    'env_prod';

  const tokenBudget =
    typeof body.tokenBudget === 'number' && Number.isFinite(body.tokenBudget)
      ? body.tokenBudget
      : undefined;
  const agentId =
    typeof body.agentId === 'string' && body.agentId.trim()
      ? body.agentId.trim()
      : undefined;
  const taskId =
    typeof body.taskId === 'string' && body.taskId.trim()
      ? body.taskId.trim()
      : undefined;
  const allowRawContext =
    typeof body.allowRawContext === 'boolean' ? body.allowRawContext : undefined;
  const idempotencyKey =
    (typeof body.idempotencyKey === 'string' && body.idempotencyKey) ||
    request.headers.get('idempotency-key') ||
    `minds-bind-001-${Date.now()}`;

  const result = await openLanesFrame({
    query,
    accessToken: session.accessToken,
    tokenBudget,
    agentId,
    taskId,
    allowRawContext,
    idempotencyKey,
    scope: {
      organizationId,
      workspaceId,
      projectId,
      environmentId,
      platformId: 'lanes',
    },
  });

  if (!result.ok) {
    return NextResponse.json(
      {
        error: result.error,
        lanes: {
          baseUrl: result.config.baseUrl,
          packId: result.config.packId,
          tipSha: result.config.tipSha,
          // scope ids only — never tokens
          scope: {
            platformId: result.config.scope.platformId,
            organizationId: result.config.scope.organizationId
              ? '[set]'
              : '[missing]',
            workspaceId: result.config.scope.workspaceId ? '[set]' : '[missing]',
            projectId: result.config.scope.projectId ? '[set]' : '[missing]',
            environmentId: result.config.scope.environmentId,
          },
        },
      },
      { status: result.status || 502, headers: noStoreHeaders() }
    );
  }

  return NextResponse.json(
    {
      frame: result.frame,
      lanes: {
        baseUrl: result.config.baseUrl,
        packId: result.config.packId,
        tipSha: result.config.tipSha,
      },
    },
    { status: result.status === 201 ? 201 : 200, headers: noStoreHeaders() }
  );
}
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:22`.

### GET [#get]

```typescript
export async function GET() {
  const live = await probeLanesLive();
  return NextResponse.json(
    {
      bindGate: 'health/live',
      live,
      note: 'Ready 503 is OK for day-one; do not gate bind on /health/ready. Frame prove after Nash tip-image swap.',
    },
    { status: live.ok ? 200 : 503, headers: noStoreHeaders() }
  );
}
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:156`.

## Response and error branches [#response-and-error-branches]

Literal HTTP statuses in the route: 400, 401. Shared management error classes are defined in [Errors and responses](/docs/api/platform/errors).

```typescript
NextResponse.json(
      {
        error: {
          code: 'UNAUTHENTICATED',
          message: 'Sign in at auth.l1fe.ai before attaching Lanes pack.',
        },
      },
      { status: 401, headers: noStoreHeaders() }
    )
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:25`.

```typescript
request.json()
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:38`.

```typescript
NextResponse.json(
      {
        error: {
          code: 'INVALID_JSON',
          message: 'Request body must be JSON.',
        },
      },
      { status: 400, headers: noStoreHeaders() }
    )
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:40`.

```typescript
NextResponse.json(
      {
        error: result.error,
        lanes: {
          baseUrl: result.config.baseUrl,
          packId: result.config.packId,
          tipSha: result.config.tipSha,
          // scope ids only — never tokens
          scope: {
            platformId: result.config.scope.platformId,
            organizationId: result.config.scope.organizationId
              ? '[set]'
              : '[missing]',
            workspaceId: result.config.scope.workspaceId ? '[set]' : '[missing]',
            projectId: result.config.scope.projectId ? '[set]' : '[missing]',
            environmentId: result.config.scope.environmentId,
          },
        },
      },
      { status: result.status || 502, headers: noStoreHeaders() }
    )
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:119`.

```typescript
NextResponse.json(
    {
      frame: result.frame,
      lanes: {
        baseUrl: result.config.baseUrl,
        packId: result.config.packId,
        tipSha: result.config.tipSha,
      },
    },
    { status: result.status === 201 ? 201 : 200, headers: noStoreHeaders() }
  )
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:142`.

```typescript
NextResponse.json(
    {
      bindGate: 'health/live',
      live,
      note: 'Ready 503 is OK for day-one; do not gate bind on /health/ready. Frame prove after Nash tip-image swap.',
    },
    { status: live.ok ? 200 : 503, headers: noStoreHeaders() }
  )
```

Source: `minds-ui/apps/app/app/api/lanes/frames/route.ts:158`.

## Service dependencies [#service-dependencies]

* `minds-ui/apps/app/lib/organizations.ts`

## Evidence [#evidence]

Generated from the local source snapshot on 2026-09-12. This page documents implemented code and does not certify a deployed service, permissions configuration, or successful provider operation.

Source file: `minds-ui/apps/app/app/api/lanes/frames/route.ts`. SHA-256: `1f53cf1ed7cefb8859f551dab7b9239bd7d898b99aaa4e8edfb942a5b33359cb`.
