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

# Get permit insights

> The aggregate view of an organisation's permit risk: permit counts by risk band,
when the permits that still carry risk expire, and counts by each of the two
Street Manager statuses.

Everything is computed from the permits the same filter would list, so a figure
here and a page of permits under the same filter describe the same set. The filter
carries the organisation and may carry any of the permit list's other terms to
narrow it.

Risk bands are evaluated at read time rather than read from each permit's stored
band. The stored band is written nightly, so a permit closed or cancelled since
then would otherwise still be reported as carrying risk.

A sibling collection rather than a sub-resource of a permit, because the figures
belong to the organisation rather than to any one permit. The organisation is a
filter term rather than a path segment, so that everything selecting the permits
travels in one parameter.

Served by a hand-written handler rather than a generated one, because the
resource is computed rather than stored.




## OpenAPI

````yaml /api-reference/openapi.yaml get /v3/permit-insights
openapi: 3.1.0
info:
  contact:
    email: support@ctrl-hub.com
    name: Ctrl Hub
    url: https://www.ctrl-hub.com
  description: >
    Ctrl Hub is the all-in-one platform for high-risk industries like utilities,
    construction, infrastructure, and renewables. We help teams manage
    everything from risk assessments and HAVS exposure to vehicle and equipment
    checks, with a guaranteed minimum of 200% ROI.
  license:
    name: MIT License
    url: https://opensource.org/licenses/MIT
  summary: An API for managing your compliance and risk posture
  termsOfService: https://www.ctrl-hub.com/terms-conditions
  title: Ctrl Hub
  version: 1.0.0
servers:
  - description: Production
    url: https://api.ctrl-hub.com
  - description: Staging
    url: https://api.ctrl-hub.dev
  - description: Development
    url: https://api.ctrl-hub.run
security: []
tags:
  - description: |
      Actions are follow-ups assigned to users and teams, produced manually or
      by domain producers such as the data-capture workflow runner.
    name: Actions
  - description: |
      Audit events are the events that are logged by the system.
    name: Audit Events
  - description: |
      View the platform's health and availability.
    name: Status
  - description: >
      User-owned dashboards composed of cards on a fixed-slot bento layout.
      Cards come from a per-domain registry; the API stores their config as
      opaque JSON.
    name: Dashboards
  - description: >
      A record of documents the platform generated and handed over, retained as
      evidence of what

      was issued. `subject_type` says what kind of records an export covers, and
      it is what decides

      how the request is authorised: there is no export permission of its own,
      so if you can see the

      records you can export them.
    name: Exports
  - description: >
      Scheduled delivery of a saved query's results, as a CSV attachment or a
      summary email. Every run is retained with the file it sent, so what a
      recipient received stays retrievable. A report runs as the person who
      created it, under their own access.
    name: Reports
  - description: |
      Manage appointments for work to be carried out with your customers
    name: Customer Appointments
  - description: |
      Manage interactions you have with your customers
    name: Customer Interactions
  - description: |
      Manage accounts for your customers
    name: Customer Accounts and Contacts
  - description: |
      Qualifications are the skills and knowledge that an organisation requires.
    name: Qualifications
  - description: |
      Workflows allow you to automate your processes.
    name: Workflows
  - description: |
      Manage documents
    name: Documents
  - description: |
      Manage documents
    name: Folders
  - description: |
      Manage documents
    name: Document Reviews
  - description: |
      Manage feature configurations for an organisation.
    name: Feature Configurations
  - description: |
      Equipment are the physical assets that an organisation manages.
    name: Equipment
  - description: |
      The central cost register: time-versioned rate codes for material, labour,
      contractor and miscellaneous spend, used to cost work against the job
      hierarchy.
    name: Costs
  - description: >
      Locations are places an organisation manages, optionally classified by a
      location type.
    name: Locations
  - description: |
      Manage your forms and their schemas
    name: Forms, Schemas and Categories
  - description: |
      Create and view form submissions
    name: Submissions
  - description: |
      View the roles available in the system.
    name: IAM Roles
  - description: >
      IAM role groups can be assigned to principals to manage authorisation
      centrally.
    name: IAM Role Groups
  - description: |
      Manage service accounts which can access the API programmatically.
    name: Service Accounts
  - description: |
      Manage bridges between organisations.
    name: Bridges
  - description: |
      Manage settings for an organisation.
    name: Settings
  - description: |
      Manage teams within an organisation.
    name: Teams
  - description: |
      Manage job roles within an organisation.
    name: Job Roles
  - description: |
      Manage users and accounts.
    name: Users
  - description: |
      Invite and manage invitations to organisations.
    name: Invitations
  - description: >
      IAM grants are the asignment of roles or permissions to principals to
      manage resource access.
    name: IAM Grants
  - description: |
      View the permissions available in the system.
    name: IAM Permissions
  - description: |
      SSO providers are the identity providers for an organisation.
    name: SSO Providers
  - description: |
      Whoami returns information about the currently authenticated principal.
    name: Whoami
  - description: |
      Manage your images
    name: Images
  - description: >
      AI agent personas that synthesise data into role-specific intelligent
      briefings.
    name: Agents
  - description: |
      Briefings generated by AI agents, including reasoning traces.
    name: Briefings
  - description: >
      Organisations are the center point for most resources in the platform.
      Most other endpoints are subresources of an organisation.
    name: Organisations
  - description: |
      Permits managements, integrated with street manager.
    name: Permits
  - description: |
      Projects manage your work and governance.
    name: Projects
  - description: >
      Import templates allow users to save and reuse their CSV importer
      configuration as named templates.
    name: Import Templates
  - description: |
      Properties are the physical locations.
    name: Properties
  - description: |
      Search across schemes, work orders, and operations.
    name: Search
  - description: |
      Provides the API specification in JSON and YAML formats
    name: Specifications
  - description: |
      Streets are the physical roads.
    name: Streets
  - description: |
      Integration with street manager
    name: Street Manager
  - description: |
      Vehicles are the physical vehicles that an organisation manages.
    name: Vehicles
  - description: >
      Scheme contracts (also known as regions) group schemes allocated from the
      network to a contractor.
    name: Scheme Contracts
  - description: >
      Scheme shares allow you to share your schemes with other organisations
      across bridges.
    name: Scheme Shares
  - description: |
      Schemes are large programmes of work
    name: Schemes
  - description: |
      Work orders the component parts of a scheme.
    name: Work Orders
  - description: |
      Operations are the work to be carried out within work orders.
    name: Operations
externalDocs:
  description: More documentation and resources
  url: https://docs.ctrl-hub.com
paths:
  /v3/permit-insights:
    get:
      tags:
        - Permits
      description: >
        The aggregate view of an organisation's permit risk: permit counts by
        risk band,

        when the permits that still carry risk expire, and counts by each of the
        two

        Street Manager statuses.


        Everything is computed from the permits the same filter would list, so a
        figure

        here and a page of permits under the same filter describe the same set.
        The filter

        carries the organisation and may carry any of the permit list's other
        terms to

        narrow it.


        Risk bands are evaluated at read time rather than read from each
        permit's stored

        band. The stored band is written nightly, so a permit closed or
        cancelled since

        then would otherwise still be reported as carrying risk.


        A sibling collection rather than a sub-resource of a permit, because the
        figures

        belong to the organisation rather than to any one permit. The
        organisation is a

        filter term rather than a path segment, so that everything selecting the
        permits

        travels in one parameter.


        Served by a hand-written handler rather than a generated one, because
        the

        resource is computed rather than stored.
      operationId: GetPermitInsights
      parameters:
        - $ref: '#/components/parameters/permit_insights_filter'
        - $ref: '#/components/parameters/permit_insights_horizon'
      responses:
        '200':
          $ref: '#/components/responses/GetPermitInsights'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorised'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - Session: []
        - OAuth2: []
        - Cookie: []
components:
  parameters:
    permit_insights_filter:
      name: filter
      in: query
      description: >
        Selects the permits the figures are computed over. Required, because it
        carries

        the organisation.


        - `eq(organisation,<uuid>)`: **Required.** The organisation whose
        permits the
          figures cover. Exactly one.
        - `eq(id,<uuid>)` / `in(id,[<uuid>,…])`: filter by permit id

        - `eq(source_permit.reference,<ref>)` /
        `in(source_permit.reference,[…])`: filter
          by the Street Manager work reference
        - `in(source_permit.status,[granted,closed,…])`: filter by permit status

        - `in(source_permit.work_status,[planned,completed,…])`: filter by work
        status

        - `in(risk.status,[low,medium,high,none])`: filter by the permit's
        **stored** risk
          band
        - `not(…)`: negates the term it wraps, so
          `not(in(source_permit.status,[closed,cancelled]))` excludes those statuses

        Terms are comma separated and combine with AND. The field names are the
        permit

        resource's own, so a nested field is dotted: `source_permit.status` is
        the state of

        the permit, `source_permit.work_status` the state of the works. The two
        are

        independent, and either being terminal means the permit carries no risk.


        Note that `risk.status` filters on each permit's stored band, which is
        written

        nightly, whereas `bands` in the response is evaluated at read time.
        Combining the

        two asks for "permits whose band was X last night, banded as they are
        now".


        `assigned_to_me` is not offered here. It takes its value from the caller
        rather

        than the query, so it has no expression in this syntax; use the permits
        list for it.


        For more information on filtering, see [the
        docs](https://docs.ctrl-hub.com/api-reference/features#filtering)
      required: true
      schema:
        type: string
        example: eq(organisation,b951325b-d02a-43f5-ba7f-c08c62ec52e4)
    permit_insights_horizon:
      name: horizon
      in: query
      description: >
        How many days ahead the expiry series should cover. Permits ending
        beyond it are

        still counted in the bands and the status counts; they just have no
        place on the

        chart.
      required: false
      schema:
        type: integer
        default: 30
        minimum: 1
        maximum: 365
  responses:
    GetPermitInsights:
      description: The aggregate permit risk figures for the organisation.
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            allOf:
              - type: object
                required:
                  - data
                properties:
                  data:
                    $ref: '#/components/schemas/PermitInsights'
              - $ref: '#/components/schemas/JSONAPI'
    BadRequest:
      description: >
        There was an error with the request - this could be due to an invalid
        body, query parameters,

        or headers that were sent to the API.
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: 98ca4a78-b66f-4234-9719-aaf832ee6669
            status: '400'
            title: A validation error was encountered
            source:
              parameter: include
            meta:
              resource: wrong_value
    Unauthorised:
      description: Authentication failed
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: 05fc9c8d-73b9-4697-9337-57f7a567a48f
            status: '401'
            title: You are not authorised to access this resource
            detail: In order to access this resource, you need the 'admin' role.
            code: AUTH.001
    InternalServerError:
      description: There was a problem handling the request on the server side
      headers:
        Content-Type:
          $ref: '#/components/headers/content-type'
        Content-Length:
          $ref: '#/components/headers/content-length'
        X-Request-ID:
          $ref: '#/components/headers/x-request-id'
      content:
        application/vnd.api+json:
          schema:
            type: object
            properties:
              errors:
                type: array
                items:
                  $ref: '#/components/schemas/Error'
          example:
            id: fe9d9a69-f0a7-4fdc-bb2c-176027f316c5
            status: '500'
            title: Internal Server Error
            detail: An unexpected error occurred on the server.
  headers:
    content-type:
      description: The content type of the response
      schema:
        type: string
      example: application/vnd.api+json
    content-length:
      description: The length of the response body in bytes
      schema:
        type: integer
        format: int32
      example: 1234
    x-request-id:
      description: >-
        An ID that can be provided when reporting bugs to help identify the
        issue
      schema:
        type: string
      example: 8470f56af4cf25e22be08e72c70dbbdc
  schemas:
    PermitInsights:
      type: object
      description: >
        The aggregate view of an organisation's permit risk. A synthetic,
        read-only

        resource: it is computed from the permits the caller can see, never
        stored, and

        its identifier is the organisation the figures were computed for.


        Every figure is derived from the same set of permits in the same read,
        so the

        band counts, the expiry series and the status counts always agree with
        each

        other. Risk bands are evaluated at read time rather than read from each
        permit's

        stored band, so a permit whose status changed since the nightly
        recalculation is

        banded on what it is now.
      required:
        - id
        - type
        - attributes
        - relationships
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the organisation the figures cover.
        type:
          type: string
          const: permit-insights
        attributes:
          $ref: '#/components/schemas/PermitInsightsAttributes'
        relationships:
          $ref: '#/components/schemas/PermitInsightsRelationships'
    JSONAPI:
      type: object
      description: JSON API response object
      required:
        - jsonapi
      properties:
        jsonapi:
          type: object
          required:
            - version
          properties:
            version:
              type: string
              description: The version of the JSON API specification
              examples:
                - '1.0'
    Error:
      type: object
      description: An error response
      properties:
        id:
          description: >-
            A unique identifier for this particular occurrence of the problem.
            If you encounter this, please provide us with the error ID and we
            can investigate it on our side.
          type: string
          format: uuid
          examples:
            - 05fc9c8d-73b9-4697-9337-57f7a567a48f
        status:
          description: >-
            The status code for the error. This might not match the HTTP status
            code if there are more that one errors to return with different
            status codes.
          type: string
          examples:
            - '401'
            - '500'
        title:
          description: A human readable title for the error.
          type: string
          examples:
            - You are not authorised to access this resource
        detail:
          description: >-
            Where there is more detail that we can provide outside of the title,
            we will provide it here.
          type: string
          examples:
            - In order to access this resource, you need the 'admin' role.
        code:
          description: >-
            A unique code for the error that may help us to diagnose the issue.
            Not all errors have codes, so this is usually empty.
          type: string
          examples:
            - AUTH.001
        source:
          description: A JSON object containing additional information about the error.
          type: object
          properties:
            pointer:
              description: >-
                A JSON Pointer to the value in the request that caused the
                error.
              type: string
              examples:
                - /data/attributes/email
            parameter:
              description: >-
                A string indicating which query parameter in the request caused
                the error.
              type: string
              examples:
                - include
      required:
        - id
        - status
        - title
    PermitInsightsAttributes:
      type: object
      required:
        - as_of
        - horizon_days
        - bands
        - expiry
        - statuses
        - truncated
      properties:
        as_of:
          type: string
          format: date-time
          description: >
            The instant the bands were evaluated at. Risk is a function of time,
            so the

            figures are only true as of this moment and a reader can say what
            they were

            computed against.
        horizon_days:
          type: integer
          description: The number of days ahead the expiry series covers.
        bands:
          type: object
          description: >
            Permit counts by risk band, evaluated at `as_of`. The four counts
            sum to

            every permit the filter reached, so a permit appears in exactly one
            band.
          required:
            - none
            - low
            - medium
            - high
          properties:
            none:
              type: integer
              description: >
                Permits that can no longer carry risk, because the works are
                over or the

                permit itself is closed, cancelled, refused or revoked.
            low:
              type: integer
            medium:
              type: integer
            high:
              type: integer
        expiry:
          type: object
          description: >
            When the permits that still carry risk run out, for an expiry chart.
            Permits

            in the `none` band are excluded: a closed permit has no meaningful
            expiry

            and plotting one would put a dead permit on a chart of what needs
            attention.
          required:
            - expired
            - series
          properties:
            expired:
              type: integer
              description: >
                Permits whose end date has already passed. Counted separately
                rather

                than as a bucket, so a negative day index can never fold back
                into the

                horizon and read as a permit with days left.
            series:
              type: array
              description: >
                One entry per day of the horizon that holds at least one permit.
                Empty

                days are omitted: a chart draws the gaps from `horizon_days`,
                and a wide

                horizon is mostly gaps.
              items:
                type: object
                required:
                  - day
                  - count
                  - band
                properties:
                  day:
                    type: integer
                    description: >-
                      Whole days from `as_of` until those permits end, from 0 to
                      `horizon_days`.
                  count:
                    type: integer
                    description: How many permits end on that day.
                  band:
                    type: string
                    description: >
                      The worst band held by any permit in the bucket. A
                      bucket's colour

                      cannot understate the risk in it, which colouring by an
                      arbitrary

                      member could.
                    enum:
                      - none
                      - low
                      - medium
                      - high
        statuses:
          type: object
          description: >
            Permit counts by each of the two independent Street Manager
            statuses. Every

            status the API models is present, including the ones no permit
            currently

            holds, so a caller reading a count never has to tell absent from
            zero.


            A status recorded on a permit that the API does not model is carried
            under

            its own name rather than dropped, and permits with no status
            recorded are

            counted under `unknown`.
          required:
            - permit
            - work
          properties:
            permit:
              type: object
              description: The state of the permit itself, keyed by status.
              additionalProperties:
                type: integer
            work:
              type: object
              description: The state of the works, keyed by status.
              additionalProperties:
                type: integer
        truncated:
          type: boolean
          description: >
            True when more permits matched the filter than one request will
            aggregate, so

            every figure above covers only part of them. A partial aggregate is
            reported as

            partial rather than served as though it were the whole answer.
    PermitInsightsRelationships:
      type: object
      description: Relationships for a permit insights aggregate
      required:
        - organisation
      properties:
        organisation:
          type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/OrganisationRelationship'
    OrganisationRelationship:
      type: object
      description: Represents a relationship to an organisation
      required:
        - id
        - type
      properties:
        id:
          type: string
          format: uuid
          description: The unique identifier of the organisation
        type:
          type: string
          const: organisations
  securitySchemes:
    Session:
      description: |
        Session token for authentication.
      in: header
      name: X-Session-Token
      type: apiKey
    OAuth2:
      description: |
        OAuth2 token for authentication.
      flows:
        clientCredentials:
          scopes: {}
          tokenUrl: https://auth.ctrl-hub.com/oauth2/token
      type: oauth2
    Cookie:
      description: |
        Cookie token for authentication.
      in: cookie
      name: ctrl_hub_session
      type: apiKey

````