{
  "openapi": "3.0.0",
  "info": {
    "title": "Nira HTTP API",
    "description": "### Getting Started\nBefore getting started with Nira's HTTP API, be sure you know your organization's Nira URL prefix (e.g. `your-organization.nira.app`). Visit the [login page](https://nira.app/login) if you need any help locating it.\n\nYou will also need an API key. To generate one, [log in](https://nira.app/login) to your Nira organization as an administrator, click the Hamburger menu (Upper left corner), then API Keys → Add. Your API key must be included as the value of the `x-api-key` HTTP header with every request that you make.\n\nYour API requests should be made to your Nira organization's hostname. For example, if your Nira organization's hostname is `your-organization.nira.app`, the full request URL to retrieve a list of your user groups would be `https://your-organization.nira.app/api/groups`\n\n### OpenAPI specification\nThe specification for this API is available as [YAML](/openapi/index.yaml) and [JSON](/openapi/index.json) for use with Postman, Swagger UI and client generators. The `x-api-key` header is declared as the `ApiKeyAuth` security scheme.\n\n### Can't find the API call that you need?\nNira's HTTP API is intentionally simple, as our most requested programmability is exposed in the [JavaScript SDK](/js-sdk) Viewer class. Additional API calls can be added upon request -- simply [reach out](https://nira.app/contact) with a brief description of your use case.\n\n### Upload automation\nFor automation of asset uploads, we typically suggest using our python-based [niraclient uploader](/niraclient). If you'd like to implement upload automation using your own code, please [reach out](https://nira.app/contact) with a brief description of your use case and we will provide our low-level upload API documentation.\n\n#### Enterprise plan required\nHTTP API access requires a customized Enterprise plan. Please [contact us](https://nira.app/contact) with any questions.\n\n#### Software vendor integrations\nIf you are a software vendor interested in adding Nira upload capabilities into your application, please [contact us](https://nira.app/contact).\n"
  },
  "tags": [
    {
      "name": "Callouts",
      "x-displayName": "Callout",
      "description": "The `callout` object represents a Callout in Nira."
    },
    {
      "name": "Assets",
      "x-displayName": "Asset",
      "description": "The `asset` object represents an asset in Nira."
    }
  ],
  "servers": [
    {
      "url": "https://YOUR_ORGANIZATION_GOES_HERE.nira.app"
    }
  ],
  "components": {
    "schemas": {
      "Callout": {
        "type": "object",
        "x-tags": [
          "Callouts"
        ],
        "title": "The Callout object",
        "description": "The unit of the Callout Import/Export feature. This is both the format\nproduced by the export endpoint and the format accepted by the import\nendpoint. Exported JSON is an array of these objects.\n\nWhat an import does with these objects depends on the import endpoint's\n`mode` parameter.\n\nIn the default `add` mode every object becomes a new callout. Any\n`uuid`, `numericId` or `alphanumericId` in the payload is ignored. Nira\nassigns all of them, and returns the uuid for each new callout in the\nresponse's `results` array.\nSending the same request again therefore adds a second copy of every\ncallout.\n\nIn `replace` mode every callout on the asset is removed first, including\nphoto callouts, and then every object in the request is created as in\n`add` mode. Use it when the request should become the asset's entire\nset of callouts. The `removeCalloutsBeforeImport=1` parameter is\ndeprecated in favour of `mode=replace` and cannot be combined with\n`mode`.\n\nIn `update` mode no callouts are created. Each object is matched against\nan existing callout on the asset by the field named in the endpoint's\n`matchField` parameter, either `uuid` or `externalId`, and matched\ncallouts are updated in place using only the fields the object actually\ncarries, so omitted fields keep their current values. When matching by\n`externalId`, any `uuid` an object carries is ignored. An update cannot change what kind of callout it is, so\nchanging `type`, or adding polyline geometry or a photo callout to one\nthat has none, is rejected. Remove the callout and import it again\ninstead. Objects that match nothing are reported in `results` with a\n`skipped` outcome.\n\nThe import response returns one `results` entry per submitted object, in\nthe same order, each carrying an `outcome` and, where a callout was\naffected, its `uuid`.\n\nA request either applies in full or changes nothing: if it returns an\nerror status, nothing was written. A single request accepts at most 60000\ncallouts, so larger imports must be split across several requests.\n\nWhat each response status means:\n\n- 2xx: the import applied.\n- 4xx or 556: the request was rejected and will be rejected again if\n  resent unchanged. Correct it before sending it again.\n- 401: refresh the credentials once, then retry.\n- 429: wait for the time in the `Retry-After` header, then retry.\n- 500, 502, 503, or 52x: retry as it stands.\n- 504 or 524: retry only when the original request carried an\n  `Idempotency-Key`, because the import may already have applied.\n\nEvery error body carries a stable `reason` string on each entry of\n`errors`, such as `import-over-limit`, so clients can branch on that\ninstead of on the message text.\n\nTo make a retry safe after a timeout or a lost connection, send an\n`Idempotency-Key` header and repeat the same key on the retry. If the\nfirst attempt did apply, the retry returns that attempt's original\nresponse and imports nothing further.\n\nEvery exported callout carries a `uuid`, which is what `update` mode\nmatches on. On import every field is optional, apart from the match field\nin `update` mode. A callout with no `type` defaults to `CameraBookmark`.\n\nIn CSV and TSV, callouts are represented as one row per callout with a\nheader row of field names, and empty cells are treated as omitted. The\n`content` and `defectType` fields are encoded as JSON\nstrings; the display color is split across the `viewColorR`, `viewColorG`,\nand `viewColorB` columns; photo callouts use the `photoCalloutFilename`,\n`photoCalloutTop`, `photoCalloutLeft`, `photoCalloutWidth`, and\n`photoCalloutHeight` columns; and polyline points use zero-indexed\n`point0X`, `point0Y`, `point0Z`, `point1X`, … columns.\n",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Nira's unique identifier for the callout. Always present in exported\ncallouts.\n\nWhen importing callouts:\n  - In `add` and `replace` mode, the uuid is optional and any uuid you supply is ignored. Nira generates the new callout's uuid and returns it in the response's `results` array, so you can use it to update that callout later.\n  - In `update` mode with `matchField=uuid`, the uuid selects which callout on the asset to update. Every object must carry one, and no two objects may repeat the same one. A malformed uuid rejects the whole request with a `400`. A well-formed uuid that matches nothing on the asset leaves that object alone, which the response reports as a `skipped` outcome, and the rest of the import still applies.\n  - In `update` mode with `matchField=externalId`, the uuid is ignored and the `externalId` selects the callout instead.\n",
            "example": "9f8e7d6c-5b4a-4392-8171-0e2d3c4b5a69"
          },
          "numericId": {
            "type": "integer",
            "readOnly": true,
            "example": 1,
            "description": "The callout's place in its asset's label sequence, counting from 1.\nPresent in exports and ignored on import, where Nira always assigns\nit.\n\nOn import, Nira assigns these in payload order, so the row order in\nyour file decides the numbering. In `replace` mode the sequence restarts\nfrom 1. Otherwise it carries on after the callouts already on the\nasset. A number is never reused, so deleting\na callout leaves a gap in the sequence. `update` mode never changes\none.\n"
          },
          "alphanumericId": {
            "type": "string",
            "readOnly": true,
            "example": "A1",
            "description": "The callout's label as users see it in the 3D view, the Layers panel\nand PDF reports. It is `numericId` in a shorter, readable form, so 1\nis A1 and 2 is A2. Present in exports and ignored on import.\n\nThe sequence counts in base 35, running 1 to 9 and then A to Z, so 9\nis A9, 10 is AA, 36 is B1, and 911 is AA1. Sorting a spreadsheet by\nthis column only matches the real order for the first 910 callouts on\nan asset, because as text AA1 sorts before AZ. Sort by `numericId`\ninstead.\n"
          },
          "title": {
            "type": "string",
            "description": "Title for Annotation and Camera Bookmarks. Also see defectTitle parameter.",
            "example": "North pier, west face"
          },
          "poiDescription": {
            "type": "string",
            "description": "Description text for CameraBookmark callouts. This is the text box shown in the 3D view next to the callout. Whether the box is displayed is controlled by `showDescriptionInView`. Has no effect on other callout types.",
            "example": "Hairline crack running the full height of the pier."
          },
          "externalId": {
            "type": "string",
            "maxLength": 255,
            "description": "Your own identifier for the callout, up to 255 characters, unique\namong the live callouts of an asset. Nira stores and returns it but\nnever generates one. Returned in every export, empty when unset.\n\nWhen importing callouts:\n  - In `add` and `replace` mode, it is stored on the new callout. A value already on another callout of the asset rejects the whole request with a `400`.\n  - In `update` mode with `matchField=externalId`, it selects which callout to update. Every object must carry one, and no two objects may repeat the same one.\n  - In `update` mode with `matchField=uuid`, it is written to the matched callout. In JSON, `null` clears it. In CSV/TSV an empty cell leaves it unchanged.\n",
            "example": "inspection-2026-0042"
          },
          "content": {
            "type": "array",
            "description": "Rich-text body content for Annotation callouts, produced by Nira's\nSlate-based editor. It is an array of block nodes, each of the form\n`{ \"type\": \"p\", \"children\": [ { \"text\": \"...\" } ] }`. If provided, it\nmust be non-empty. In CSV/TSV it is encoded as a JSON string.\n",
            "items": {
              "type": "object"
            },
            "example": [
              {
                "type": "p",
                "children": [
                  {
                    "text": "Inspect the corroded flange."
                  }
                ]
              }
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "Annotation",
              "Defect",
              "Measurement",
              "CameraBookmark"
            ],
            "default": "CameraBookmark",
            "description": "Specifies the type of callout. Measurement callouts are also referred\nto as Polyline Callouts. These values correspond to the Callout Type\nlabels shown in the Nira app as follows: `CameraBookmark` = \"Basic\nText\", `Annotation` = \"Rich Text Window\", `Defect` = \"Defect Window\",\n`Measurement` = \"Measurement / Defect Window\". The app then prefixes\nthe label according to the callout's geometry, so the same\n`CameraBookmark` reads as \"3D Callout - Basic Text\" when it is a\nsingle point and \"Polyline - Basic Text\" when it has polyline\ngeometry.\n",
            "example": "Defect"
          },
          "schemaVersion": {
            "type": "integer",
            "example": 2,
            "description": "The version of the callout data format. Always specify 2. Version 1,\nor omitting this field, is a legacy format and should not be used.\n"
          },
          "posX": {
            "type": "number",
            "format": "double",
            "description": "X coordinate for point callouts. When `coordsys` is EPSG:4326, this is the longitude.",
            "example": -84.268241
          },
          "posY": {
            "type": "number",
            "format": "double",
            "description": "Y coordinate for point callouts. When `coordsys` is EPSG:4326, this is the latitude.",
            "example": 39.344056
          },
          "posZ": {
            "type": "number",
            "format": "double",
            "description": "Z coordinate for point callouts. Elevation is always expressed in the coordinate system of the asset's source geometry files (e.g. meters if the source files use meters). It is not converted between vertical datums.",
            "example": 211.843078
          },
          "camPosX": {
            "type": "number",
            "format": "double",
            "description": "Camera position X coordinate.",
            "example": -84.267912
          },
          "camPosY": {
            "type": "number",
            "format": "double",
            "description": "Camera position Y coordinate.",
            "example": 39.343688
          },
          "camPosZ": {
            "type": "number",
            "format": "double",
            "description": "Camera position Z coordinate.",
            "example": 238.512443
          },
          "camTarX": {
            "type": "number",
            "format": "double",
            "description": "Camera target X coordinate.",
            "example": -84.268241
          },
          "camTarY": {
            "type": "number",
            "format": "double",
            "description": "Camera target Y coordinate.",
            "example": 39.344056
          },
          "camTarZ": {
            "type": "number",
            "format": "double",
            "description": "Camera target Z coordinate.",
            "example": 211.843078
          },
          "defectTitle": {
            "type": "string",
            "description": "Title used when the callout's type is 'Defect'.",
            "example": "Spalling at pier base"
          },
          "defectDescription": {
            "type": "string",
            "description": "Defect description",
            "example": "Concrete spalling with exposed rebar, roughly 40 cm across."
          },
          "defectSeverity": {
            "type": "integer",
            "nullable": true,
            "description": "Numeric value indicating the severity of the defect. One of 1–5, or\nnull when unclassified. The values correspond to the labels shown in\nthe app: null = Unclassified, 1 = Low, 2 = Normal, 3 = Medium,\n4 = High, 5 = Critical.\n",
            "example": 4
          },
          "defectType": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "Corrosion",
              "Spalling"
            ],
            "description": "The callout's list of tags, shown as \"Observation Tags\" in the app.\nDespite the field name, tags are supported on all callout types, not\njust Defects. In the app's Layers panel, callouts can be grouped and\nfiltered by these tags, and each tag becomes an autocomplete\nsuggestion for future tagging across the organization.\n"
          },
          "measurementClosed": {
            "type": "boolean",
            "description": "Indicates whether the polyline forms a closed shape. Measurement / polyline callouts only.",
            "example": false
          },
          "measurementLength": {
            "type": "number",
            "format": "double",
            "readOnly": true,
            "description": "The sum of the lengths of all segments of the polyline. Measurement /\npolyline callouts only. Nira computes this from the callout's points\nand uses it for display, such as measurement labels in the orthophoto\nviewer and the defect dialog. It is ignored on import, and is\nrecomputed the next time the asset is opened.\n",
            "example": 0.8870510458946228
          },
          "measurementArea": {
            "type": "number",
            "format": "double",
            "readOnly": true,
            "description": "The area enclosed by the polyline. Closed measurement / polyline\ncallouts only. Nira computes this from the callout's points as a\ntriangulated surface area, so it follows the geometry rather than\nflattening it. It is ignored on import, and is recomputed the next\ntime the asset is opened.\n",
            "example": 0
          },
          "points": {
            "type": "array",
            "description": "An array of ordered 3D points comprising the polyline. Measurement /\npolyline callouts only. When `schemaVersion` is 2 or greater, point\ncoordinates follow the declared `coordsys`. For closed polylines the\nexport omits the duplicate closing point. In CSV/TSV, points are\nencoded as zero-indexed column triplets `point0X`, `point0Y`,\n`point0Z`, `point1X`, ….\n\n`update` mode can replace a polyline's points but cannot add geometry\nto a callout that has none, or remove it from one that does. An empty\narray is rejected. Omit the field to leave the geometry as it is.\n",
            "items": {
              "type": "array",
              "items": {
                "type": "number",
                "format": "double",
                "minItems": 3,
                "maxItems": 3
              }
            },
            "example": [
              [
                -84.268241,
                39.344056,
                211.843078
              ],
              [
                -84.268236,
                39.344062,
                212.239078
              ]
            ]
          },
          "coordsys": {
            "type": "string",
            "description": "The coordinate system used for the callout's 3D positional data\n(`posX/Y/Z`, `camPosX/Y/Z`, `camTarX/Y/Z`, and `points`), in EPSG\nformat or 'local' for local coordinates. 'local' can only be used with\nnon-georeferenced assets. For latitude/longitude coordinates,\nEPSG:4326 should be specified.\n\nOn export, the value is set from the export request's `coordsys` query\nparameter and is `local`, the asset's EPSG code, or `EPSG:4326`. On\nimport, if omitted, the coordinates are assumed to be in the asset's\ninternal/local coordinate space and no validation or transformation is\nperformed. This is how legacy exports produced before `schemaVersion`\n2 behave.\n\nAxis order for EPSG:4326: X values (`posX`, `camPosX`, `camTarX`, and\nthe first component of each `points` entry) are longitude, and Y\nvalues are latitude. Z values are elevation in the asset's source-file\nunits and are passed through unconverted.\n",
            "example": "EPSG:4326"
          },
          "viewColorR": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 255,
            "example": 255,
            "description": "The red component of the callout's display color, in the range 0-255 (not 0.0-1.0)."
          },
          "viewColorG": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 255,
            "example": 191,
            "description": "The green component of the callout's display color, in the range 0-255."
          },
          "viewColorB": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "maximum": 255,
            "example": 0,
            "description": "The blue component of the callout's display color, in the range 0-255."
          },
          "showDescriptionInView": {
            "type": "boolean",
            "default": true,
            "description": "For CameraBookmark callouts, controls whether the callout's `poiDescription` text box is displayed next to its marker: pinned open in the 2D orthophoto view, and shown when the callout is selected or highlighted in the 3D view. When false, only the marker and title are shown. Corresponds to the \"Show Description in View\" checkbox in the app. Has no effect on other callout types.",
            "example": true
          },
          "photocallouts": {
            "type": "array",
            "description": "A list of photo callouts associated with this callout. Currently, only\none photo callout per callout is supported, and they are only\napplicable to callouts of type Defect. The `filename` of each photo\ncallout must refer to an image that has already been uploaded to the\nasset. In CSV/TSV, photo callouts are encoded using the\n`photoCalloutFilename`, `photoCalloutTop`, `photoCalloutLeft`,\n`photoCalloutWidth`, and `photoCalloutHeight` columns.\n\n`update` mode can change a photo callout but cannot add or remove\none. An empty array is rejected. Omit the field to leave the photo\ncallout as it is.\n",
            "items": {
              "type": "object",
              "required": [
                "filename",
                "top",
                "left",
                "width",
                "height"
              ],
              "properties": {
                "filename": {
                  "type": "string",
                  "description": "The image filename for the photo callout. An image with this name must exist on the asset.",
                  "example": "P0002105.jpg"
                },
                "top": {
                  "type": "number",
                  "format": "float",
                  "description": "The y coordinate of the top-left corner of the photo callout's rectangle, in pixels.",
                  "example": 4026
                },
                "left": {
                  "type": "number",
                  "format": "float",
                  "description": "The x coordinate of the top-left corner of photo callout's rectangle, in pixels.",
                  "example": 5076
                },
                "width": {
                  "type": "number",
                  "format": "float",
                  "description": "The width of the photo callout's rectangle, in pixels.",
                  "example": 441
                },
                "height": {
                  "type": "number",
                  "format": "float",
                  "description": "The height of the photo callout's rectangle, in pixels.",
                  "example": 619
                },
                "url": {
                  "type": "string",
                  "readOnly": true,
                  "example": "https://example.nira.app/a/140d1779/P0002105.jpg?signature=b4f1c2e8a90d4d17&expires=1788366600000",
                  "description": "A signed download URL for the photo callout's image. Present\nonly in exports requested with an API key. It cannot be\nspecified on import and is ignored if present. Imported photo\ncallouts reference their image by `filename` instead.\n\nThe URL expires between 30 and 60 minutes after the export,\nand its `expires` query parameter carries the deadline as a\nmillisecond epoch. Treat 30 minutes as the safe figure: the\nsignature is issued on a half-hour boundary so that repeated\nexports return a cacheable URL, which means the remaining\nlifetime depends on where in that window the export landed.\nRe-export rather than reusing URLs from an older response.\n"
                }
              }
            }
          },
          "pdfReportScreenshotId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier of the screenshot image shown for this defect callout in PDF reports. Presence of this attribute implies that this callout was configured to be shown in PDF reports. This attribute is only effective when imported into the same Nira organization from which it was exported.",
            "example": "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
          }
        }
      },
      "Asset": {
        "type": "object",
        "title": "The Asset object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the stage (auto-incremented)",
            "readOnly": true,
            "example": 4213
          },
          "name": {
            "type": "string",
            "description": "Name of the asset",
            "example": "Kings Island Bridge Survey"
          },
          "uuid": {
            "type": "string",
            "format": "uuid",
            "description": "UUID for the asset",
            "example": "140d1779-2bb4-4443-bc25-ab8c6f43bc1b"
          },
          "createdBy": {
            "type": "integer",
            "description": "ID of the user who created the stage",
            "example": 17
          },
          "usesvt": {
            "type": "boolean",
            "description": "Indicates if the stage uses a specific technology (SVT)",
            "nullable": true,
            "example": false
          },
          "type": {
            "type": "string",
            "enum": [
              "default",
              "sculpt",
              "photogrammetry",
              "volumetric_video"
            ],
            "description": "Type of the stage",
            "default": "default",
            "example": "photogrammetry"
          },
          "passwordRequired": {
            "type": "boolean",
            "description": "Indicates if a password is required to access the stage",
            "nullable": true,
            "example": false
          },
          "dccname": {
            "type": "string",
            "description": "Name of the DCC software associated with the stage",
            "nullable": true,
            "example": "RealityScan"
          },
          "hideBranding": {
            "type": "boolean",
            "example": false
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    }
  },
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/api/assets/{suuid}": {
      "delete": {
        "summary": "Delete an asset",
        "tags": [
          "Assets"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suuid",
            "description": "Assets's base64 encoded uuid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns a deleted record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true,
                      "description": "Whether the API call was successful."
                    },
                    "result": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed",
            "content": {}
          },
          "404": {
            "description": "Not found",
            "content": {}
          }
        }
      }
    },
    "/api/assets/{suuid}/sharing": {
      "patch": {
        "summary": "Change an asset's visibility",
        "description": "This controls whether an asset is publicly accessible or private.",
        "tags": [
          "Assets"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suuid",
            "description": "Assets's base64 encoded uuid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "private",
                      "publicly_accessible"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Returns a patched record.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true,
                      "description": "Whether the API call was successful."
                    },
                    "result": {
                      "$ref": "#/components/schemas/Asset"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed",
            "content": {}
          },
          "404": {
            "description": "Not found",
            "content": {}
          }
        }
      }
    },
    "/api/assets/{asset_suuid}/callouts/import": {
      "post": {
        "tags": [
          "Callouts"
        ],
        "summary": "Import callouts for a specific asset",
        "description": "Imports callouts onto an asset from a JSON, CSV, or TSV payload. The\npayload format must match the `Content-Type` header: `application/json`,\n`text/csv`, or `text/tab-separated-values`.\n\nFor JSON, the request body is an array of Callout objects, the same\nformat the export endpoint produces, so an exported file is accepted\nas-is. See the Callout schema for the full field reference, including\nthe CSV/TSV column encodings.\n\nThe `mode` query parameter selects what the import does. In the default\n`add` mode every row becomes a new callout: any `uuid` or\n`numericId` or `alphanumericId` in the payload is ignored. Nira\nassigns all of them.\nIn `replace` mode every callout on the asset is removed first,\nincluding photo callouts, and then every row in the request is created.\nIn `update` mode no callouts are created. Each row is matched against\nan existing callout on the asset by the field named in `matchField`\n(`uuid` or `externalId`), and matched callouts are updated in place\nwith only the fields the row actually carries. Rows that match nothing\nare skipped and reported as such in the response. On import all fields\nare optional.\n\n`externalId` is a caller-owned identifier that Nira stores and returns\nbut never generates. In CSV/TSV an empty `externalId` cell leaves the\nstored value unchanged, the same as every other omitted column. In JSON\na `null` clears it.\n\nA malformed match value rejects the whole request with a 400. A\nwell-formed one that matches nothing only skips that row.\n\nThis endpoint requires the organization to be on the Growth or\nEnterprise plan.\n\nA request either applies in full or changes nothing. If it returns an\nerror status, nothing was written, including any callouts\n`mode=replace` had already removed, so a failed\nrequest is safe to retry once the payload is corrected.\n\nWhat each response status means:\n\n- 2xx: the import applied.\n- 4xx or 556: the request was rejected and will be rejected again if\n  resent unchanged. Correct it before sending it again.\n- 401: refresh the credentials once, then retry.\n- 429: wait for the time in the `Retry-After` header, then retry.\n- 500, 502, 503, or 52x: retry as it stands.\n- 504 or 524: retry only when the original request carried an\n  `Idempotency-Key`, because the import may already have applied.\n\nEvery error body carries a stable `reason` string on each entry of\n`errors`, such as `import-over-limit`, so clients can branch on that\ninstead of on the message text.\n\nTo make a retry safe after a timeout or a lost connection, send an\n`Idempotency-Key` header and repeat the same key on the retry. If the\nfirst attempt did apply, the retry returns that attempt's original\nresponse and imports nothing further.\n\nA single request accepts at most 60000 callouts. Larger imports must be\nsplit into several requests. Each one is atomic on its own, so send\nthem one at a time and check each response before sending the next.\n\nBody size limits are 40 MB for JSON and 100 MB for CSV/TSV.\n",
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Content-Type",
            "in": "header",
            "required": true,
            "description": "The content type of the request body. Must be one of 'application/json', 'text/csv', or 'text/tab-separated-values'.",
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/csv",
                "text/tab-separated-values"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "An optional identifier you choose for this import, 1 to 200\ncharacters. Repeating a request with the same key on the same\nasset returns the original response and imports nothing further.\nUsing a key that was already used for a different request on the\nsame asset returns a 409.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asset_suuid",
            "in": "path",
            "required": true,
            "description": "The 22-character asset short UUID, as it appears in the asset's URL in the Nira app. Numeric asset ids are not accepted.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "What the import does:\n\n- \"add\" (the default) creates a new callout for every row,\n  ignoring any supplied uuid.\n- \"replace\" removes every callout on the asset, including photo\n  callouts, then creates every row in the request.\n- \"update\" only updates callouts that already exist on the asset,\n  and never creates or deletes anything.\n",
            "schema": {
              "type": "string",
              "enum": [
                "add",
                "replace",
                "update"
              ],
              "default": "add"
            }
          },
          {
            "name": "matchField",
            "in": "query",
            "required": false,
            "description": "The callout field used to match payload rows to existing callouts.\nRequired when mode=update, and ignored in the other modes. In update\nmode every row must carry a value for it, and no two rows may repeat\na value.\n\n- \"uuid\" matches by the uuid Nira assigned to each callout.\n- \"externalId\" matches by the externalId you assigned to each\n  callout when it was imported or created. Use it when your own\n  system's identifiers are the ones you track. Any uuid a row\n  carries is then ignored.\n",
            "schema": {
              "type": "string",
              "enum": [
                "uuid",
                "externalId"
              ]
            }
          },
          {
            "name": "removeCalloutsBeforeImport",
            "in": "query",
            "required": false,
            "description": "Deprecated. \"1\" is equivalent to mode=replace, and is kept for\ncallers that already send it. Defaults to \"0\". Cannot be combined\nwith `mode`.\n",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "0"
              ],
              "default": "0"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Callout"
                }
              }
            },
            "text/csv": {
              "schema": {
                "type": "string",
                "description": "CSV text with a header row of Callout field names and one callout per row. See the Callout schema for the CSV/TSV column encodings."
              }
            },
            "text/tab-separated-values": {
              "schema": {
                "type": "string",
                "description": "Tab-separated text with a header row of Callout field names and one callout per row. See the Callout schema for the CSV/TSV column encodings."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The import ran. Individual rows may still have been skipped or\nfailed, so check the counts and `results`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "assetId": {
                      "type": "integer",
                      "example": 4213,
                      "description": "The asset's internal numeric id."
                    },
                    "suuid": {
                      "type": "string",
                      "example": "FmITrR_IR1KIsfhXJireEQ",
                      "description": "The asset's short UUID."
                    },
                    "mode": {
                      "type": "string",
                      "description": "The import mode that was applied. Reported as \"replace\" when the deprecated removeCalloutsBeforeImport=1 was used.",
                      "enum": [
                        "add",
                        "replace",
                        "update"
                      ]
                    },
                    "matchField": {
                      "type": "string",
                      "nullable": true,
                      "example": null,
                      "description": "The match field that was applied in update mode, or null in the other modes."
                    },
                    "removeCalloutsBeforeImport": {
                      "type": "boolean",
                      "description": "Whether existing callouts were removed before the import ran. True in replace mode."
                    },
                    "removedCount": {
                      "type": "integer",
                      "example": 0,
                      "description": "Callouts removed in replace mode. Always 0 in the other modes."
                    },
                    "createdCount": {
                      "type": "integer",
                      "example": 2,
                      "description": "Callouts created. Always 0 in update mode."
                    },
                    "updatedCount": {
                      "type": "integer",
                      "example": 0,
                      "description": "Callouts updated in place. Always 0 in add and replace mode."
                    },
                    "skippedCount": {
                      "type": "integer",
                      "example": 0,
                      "description": "Rows whose match value found no callout on the asset. Always 0 in add and replace mode."
                    },
                    "results": {
                      "type": "array",
                      "description": "One entry per submitted row, in the same order as the payload, so\nentry `i` describes submitted row `i`. The submitted callouts are\nnot echoed back.\n",
                      "items": {
                        "type": "object",
                        "properties": {
                          "outcome": {
                            "type": "string",
                            "enum": [
                              "created",
                              "updated",
                              "skipped"
                            ]
                          },
                          "uuid": {
                            "type": "string",
                            "example": "9f8e7d6c-5b4a-4392-8171-0e2d3c4b5a69",
                            "description": "The callout's uuid. On a create this is the uuid Nira\ngenerated, which is how you map new callouts back to your own\nrecords and what you pass as a match value later. On an update\nit is the uuid of the callout that was matched. Absent on a\nskip, since no callout was affected.\n"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request. Causes include: an unsupported `Content-Type`; more\nthan 60000 callouts in one request; an unknown `mode` or\n`matchField`; `mode=update` without a `matchField`; `mode`\ncombined with the deprecated `removeCalloutsBeforeImport=1`; an empty\n`photocallouts` or `points` array in update mode; a row with a\nmissing, malformed, or duplicated match value in update mode; an\n`externalId` that is not a string of at most 255 characters without\ncontrol characters; an `externalId` repeated within the request or\nalready on another callout of the asset; an\nunrecognized value for an enumerated field; a numeric field, point\ncoordinate, or photo callout dimension that is not a finite\nnumber; a callout `coordsys` that does not match the asset's\ncoordinate system; a `photocallouts` entry on a callout whose type\nis not Defect; a row that changes an existing callout's type; a row\nthat adds polyline geometry to a callout that has none; or a photo\ncallout `filename` that is not a file on this asset.\n"
          },
          "403": {
            "description": "The organization's plan does not permit callout import, or the API key lacks permission to modify this asset."
          },
          "404": {
            "description": "No asset on this organization has the supplied asset short UUID."
          },
          "409": {
            "description": "The `Idempotency-Key` was already used for a different import request on this asset."
          },
          "500": {
            "description": "Internal server error."
          }
        }
      }
    },
    "/api/assets/{asset_suuid}/callouts/export": {
      "get": {
        "tags": [
          "Callouts"
        ],
        "summary": "Export callouts for a specific asset",
        "description": "Exports callouts related to the specified asset in the requested\nformat.\n\nWhat each response status means:\n\n- 2xx: the export is complete.\n- 4xx or 556: the request was rejected and will be rejected again if\n  resent unchanged. Correct it before sending it again.\n- 401: refresh the credentials once, then retry.\n- 429: wait for the time in the `Retry-After` header, then retry.\n- 500, 502, 503, or 52x: retry as it stands.\n\nEvery error body carries a stable `reason` string on each entry of\n`errors`, such as `export-invalid-coordsys`, so clients can branch on that\ninstead of on the message text.\n",
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asset_suuid",
            "in": "path",
            "required": true,
            "description": "The unique identifier of the asset.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "The format of the export. One of 'json', 'csv', or 'tsv'. Defaults to 'json' when omitted.",
            "schema": {
              "type": "string",
              "default": "json",
              "enum": [
                "json",
                "csv",
                "tsv"
              ]
            }
          },
          {
            "name": "coordsys",
            "in": "query",
            "required": false,
            "description": "The coordinate system that will be used for the exported callouts 3D positional data (`posX/Y/Z`, `camPosX/Y/Z`, `camTarX/Y/Z`, and `points`). Must be `asset`, `latlong`, or `local`. It is optional. When omitted it defaults to `latlong` for georeferenced assets and `local` otherwise. Requesting `asset` or `latlong` for a non-georeferenced asset returns a 400.\n- `asset`: 3D Positional data will be represented using the coordinate system of the asset. For example, if your asset uses the EPSG:27700 coordinate system, then all 3D positional data will be represented in EPSG:27700. If the asset is not georeferenced, then 3D Positional data will be represented using the coordinate system of the asset's geometry files (i.e. the .obj files).\n- `latlong`: The X and Y coordinate of all 3D Positional data will be represented using latitude / longitude coordinates, also known as EPSG:4326. Elevation (Z) will be represented using coordinate system of your asset's source files. For example, if the .obj files for the asset use meters to store elevation, then the Z coordinates of the callout positional data will use meters.\n- `local`: 3D Positional data will be represented using the coordinate system of the asset's geometry files (i.e. the .obj files). This should only be used for non-georeferenced assets. It is almost always better to specify `asset` instead of `local`.\n",
            "schema": {
              "type": "string",
              "enum": [
                "local",
                "asset",
                "latlong"
              ]
            }
          },
          {
            "name": "include",
            "in": "query",
            "description": "Selects which categories of callout data to include in the export.\nWhen omitted, all categories are included. When provided, only the\nlisted categories are included, and any category not listed is\nexcluded. Values are matched case-insensitively.\n- `pointCallouts` Includes 3D point callouts.\n- `polylineCallouts` Includes 3D polyline / measurement callouts.\n- `photoCallouts` Includes 2D photo callouts.\n- `pdfReportScreenshotIds` Preserves *Include in PDF Report* checkbox options and their associated Screenshot IDs. PDF Report Screenshot IDs are only compatible with your own Nira organization. When importing your callout data into a different Nira organization, the *Include in PDF Report* checkbox will need to be manually checked for any 3D callouts that you wish to include in PDF reports.\n",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pointCallouts",
                  "polylineCallouts",
                  "photoCallouts",
                  "pdfReportScreenshotIds"
                ]
              },
              "default": [
                "pointCallouts",
                "polylineCallouts",
                "photoCallouts",
                "pdfReportScreenshotIds"
              ]
            },
            "style": "form",
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "Successful export of callouts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Callout"
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "text/tab-separated-values": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid format specified"
          },
          "403": {
            "description": "The organization's plan does not permit callout export, or the API key lacks permission to access this asset."
          },
          "404": {
            "description": "Asset not found"
          },
          "500": {
            "description": "Server error"
          }
        }
      }
    },
    "/api/assets/": {
      "delete": {
        "summary": "Delete multiple assets",
        "description": "Delete multiple assets that were created prior to a particular point in time. This can be useful for cleaning up older assets.",
        "tags": [
          "Assets"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "before",
            "description": "A timestamp (milliseconds since epoch) or a relative date string in the format \"Nd\", where N is a positive integer and \"d\" represents days. For example, \"30d\" would delete assets created before 30 days ago, and the timestamp \"1741201439963\" would delete all assets created before 2025-03-05 19:03:59 UTC.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d+d?$"
            },
            "example": "30d"
          },
          {
            "in": "query",
            "name": "confirm",
            "description": "If param is set to 'true', the deletion will be executed.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(true|false)$"
            },
            "example": "true"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true,
                      "description": "Whether the API call was successful."
                    },
                    "result": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "number",
                            "example": 1
                          },
                          "uuid": {
                            "type": "string",
                            "example": "550e8400-e29b-41d4-a716-446655440000"
                          },
                          "name": {
                            "type": "string",
                            "example": "Asset"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/users/sessions": {
      "delete": {
        "summary": "Expire all of a user's sessions",
        "description": "Expire all of the login sessions of a user account with the specified email address. Please note, it can take up to 10 minutes after making this request for the user to be forcibly logged out of an ongoing session.",
        "tags": [
          "Users"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "x-api-key",
            "in": "header",
            "required": true,
            "description": "Your API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "email",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "example@gmail.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Returns a successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true,
                      "description": "Whether the API call was successful."
                    },
                    "result": {
                      "type": "array",
                      "description": "Array of expired session objects.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "uuid": {
                            "type": "string",
                            "format": "uuid",
                            "description": "Session UUID."
                          },
                          "email": {
                            "type": "string",
                            "format": "email",
                            "description": "User account's email address."
                          },
                          "iat": {
                            "type": "integer",
                            "description": "Creation time of the session in seconds since the epoch."
                          },
                          "exp": {
                            "type": "integer",
                            "description": "Originally scheduled expiration time of the session in seconds since the epoch."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not allowed",
            "content": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false,
                      "description": "Whether the API call was successful."
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}