Coperniq sites API
The sites API from Coperniq — 3 operation(s) for sites.
The sites API from Coperniq — 3 operation(s) for sites.
openapi: 3.1.0
info:
title: API Key accounts sites API
version: 1.0.0
servers:
- url: https://api.coperniq.io/v1
description: Production server
tags:
- name: sites
paths:
/sites:
get:
operationId: list-sites
summary: List Sites
description: 'Retrieve a paginated list of sites. Results can be filtered by account, state, city, or zipcode.
'
tags:
- sites
parameters:
- name: accountId
in: query
description: Filter by account ID.
required: false
schema:
type: integer
- name: state
in: query
description: Filter by state, case-insensitive (e.g. `ca` or `CA`).
required: false
schema:
type: string
- name: city
in: query
description: Filter by city, case-insensitive substring match (e.g. `francisco` matches `San Francisco`).
required: false
schema:
type: string
- name: zipcode
in: query
description: Filter by zip code, case-insensitive.
required: false
schema:
type: string
- name: page
in: query
description: Page number (1-based)
required: false
schema:
type: integer
default: 1
- name: page_size
in: query
description: Number of items per page (max 100)
required: false
schema:
type: integer
default: 20
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: List of sites
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Site'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ListSitesRequestBadRequestError'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/ListSitesRequestUnauthorizedError'
post:
operationId: create-site
summary: Create Site
description: 'Create a new site record belonging to an account.
Required fields:
- `accountId`: The account this site belongs to.
- `fullAddress` or `geoLocation`: At least one must be provided. The backend geocodes `fullAddress` to derive coordinates.
If a site with the same address already exists for the same account, the existing site is returned instead of creating a duplicate (`200` instead of `201`).
'
tags:
- sites
parameters:
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: Duplicate site found — existing site returned
content:
application/json:
schema:
$ref: '#/components/schemas/Site'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSiteRequestBadRequestError'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSiteRequestUnauthorizedError'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SiteCreate'
/sites/{siteId}:
get:
operationId: get-site
summary: Get Site
description: Retrieve a specific site by ID. Returns 404 if not found or if the site belongs to a different company.
tags:
- sites
parameters:
- name: siteId
in: path
required: true
schema:
type: string
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: Site details
content:
application/json:
schema:
$ref: '#/components/schemas/Site'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/GetSiteRequestBadRequestError'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/GetSiteRequestUnauthorizedError'
'404':
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/GetSiteRequestNotFoundError'
patch:
operationId: update-site
summary: Update Site
description: 'Update an existing site. All fields are optional — only provided fields are changed.
> **Moving a site to a different account:** Use `POST /sites/{siteId}/move` instead. `accountId` cannot be changed on a site update.
> **Note on `geoLocation` and `fullAddress`:** Sending either field on update triggers geocoding — the backend may overwrite the stored coordinates with what Google Maps snaps to for the nearest address, so the values you send may not be stored as-is. There is currently no way to update coordinates without allowing the geocoder to potentially adjust them.
'
tags:
- sites
parameters:
- name: siteId
in: path
required: true
schema:
type: string
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: Site updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Site'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateSiteRequestBadRequestError'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateSiteRequestUnauthorizedError'
'404':
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateSiteRequestNotFoundError'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SiteUpdate'
delete:
operationId: delete-site
summary: Delete Site
description: Delete a specific site by ID. Returns 404 if not found.
tags:
- sites
parameters:
- name: siteId
in: path
required: true
schema:
type: string
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: Successful response
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteSiteRequestUnauthorizedError'
'404':
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteSiteRequestNotFoundError'
/sites/{siteId}/move:
post:
operationId: move-site
summary: Move Site to Another Account
description: 'Move a site and all its associated records to a different account.
**A new site is created with a new ID.** The original site (`siteId` in the path) is permanently
deleted. The response contains the new site object — update any stored site IDs accordingly.
The `fullAddress` and `title` are copied from the original site to the new one. The backend geocodes the address on the new site, so all address components (street, city, state, zipcode) are derived from geocoding.
**Migrated records:** opportunities, projects, work orders, forms, services, systems, assets, and financial documents.
> ⚠️ **This operation is irreversible.** The original site and its ID are permanently deleted.
> Any references to the old `siteId` will no longer resolve.
> Service plan instance links are removed — plans are account-scoped and cannot be transferred.
'
tags:
- sites
parameters:
- name: siteId
in: path
description: Site identifier
required: true
schema:
type: integer
- name: x-api-key
in: header
required: true
schema:
type: string
responses:
'200':
description: The newly created site on the target account. This site has a new `id` — the original site has been deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/Site'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/MoveSiteRequestBadRequestError'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/MoveSiteRequestUnauthorizedError'
'404':
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/MoveSiteRequestNotFoundError'
requestBody:
content:
application/json:
schema:
type: object
properties:
accountId:
type: integer
description: The target account ID to move the site to.
required:
- accountId
components:
schemas:
SitesSiteIdDeleteResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- NOT_FOUND
title: SitesSiteIdDeleteResponsesContentApplicationJsonSchemaCode
UpdateSiteRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdPatchResponsesContentApplicationJsonSchemaCode'
title: UpdateSiteRequestUnauthorizedError
SitesGetResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- UNAUTHORIZED
title: SitesGetResponsesContentApplicationJsonSchemaCode
CreateSiteRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesPostResponsesContentApplicationJsonSchemaCode'
title: CreateSiteRequestUnauthorizedError
GetSiteRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdGetResponsesContentApplicationJsonSchemaCode'
title: GetSiteRequestUnauthorizedError
GetSiteRequestBadRequestError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdGetResponsesContentApplicationJsonSchemaCode'
field:
type: string
description: Field that caused the validation error (if applicable)
title: GetSiteRequestBadRequestError
SitesSiteIdGetResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- NOT_FOUND
title: SitesSiteIdGetResponsesContentApplicationJsonSchemaCode
SitesSiteIdMovePostResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- NOT_FOUND
title: SitesSiteIdMovePostResponsesContentApplicationJsonSchemaCode
SitesPostResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- UNAUTHORIZED
title: SitesPostResponsesContentApplicationJsonSchemaCode
ListSitesRequestBadRequestError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesGetResponsesContentApplicationJsonSchemaCode'
field:
type: string
description: Field that caused the validation error (if applicable)
title: ListSitesRequestBadRequestError
MoveSiteRequestBadRequestError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdMovePostResponsesContentApplicationJsonSchemaCode'
field:
type: string
description: Field that caused the validation error (if applicable)
title: MoveSiteRequestBadRequestError
DeleteSiteRequestNotFoundError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdDeleteResponsesContentApplicationJsonSchemaCode'
title: DeleteSiteRequestNotFoundError
UpdateSiteRequestNotFoundError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdPatchResponsesContentApplicationJsonSchemaCode'
title: UpdateSiteRequestNotFoundError
Site:
type: object
properties:
id:
type: integer
description: Unique site ID.
accountId:
type: integer
description: ID of the account this site belongs to.
title:
type:
- string
- 'null'
description: Human-readable label for the site (e.g. "Main Office").
fullAddress:
type:
- string
- 'null'
description: Full formatted address string.
street:
type:
- string
- 'null'
city:
type:
- string
- 'null'
state:
type:
- string
- 'null'
zipcode:
type:
- string
- 'null'
geoLocation:
type:
- string
- 'null'
description: Latitude/longitude as "lat,lng" (e.g. "37.7749,-122.4194").
timezone:
type:
- string
- 'null'
description: IANA timezone string (e.g. "America/Los_Angeles").
createdAt:
type:
- string
- 'null'
format: date-time
updatedAt:
type:
- string
- 'null'
format: date-time
title: Site
ListSitesRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesGetResponsesContentApplicationJsonSchemaCode'
title: ListSitesRequestUnauthorizedError
UpdateSiteRequestBadRequestError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdPatchResponsesContentApplicationJsonSchemaCode'
field:
type: string
description: Field that caused the validation error (if applicable)
title: UpdateSiteRequestBadRequestError
SiteUpdate:
type: object
properties:
fullAddress:
type: string
description: Full address string. Backend will re-geocode when updated.
geoLocation:
type: string
description: 'Latitude/longitude as "lat,lng". **On update**, sending this triggers a full re-geocode — the backend snaps to the nearest address via Google Maps, so the stored value may differ from what you sent.
'
title:
type: string
street:
type: string
city:
type: string
state:
type: string
zipcode:
type: string
timezone:
type: string
description: 'IANA timezone string (e.g. "America/Los_Angeles"). Use this to manually override the timezone if the geocoded value is incorrect or you are migrating from a system that has an explicit timezone already set.
'
description: 'All fields are optional. To move a site to a different account, use `POST /sites/{siteId}/move` instead of setting `accountId` here.
'
title: SiteUpdate
MoveSiteRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdMovePostResponsesContentApplicationJsonSchemaCode'
title: MoveSiteRequestUnauthorizedError
SiteCreate:
type: object
properties:
accountId:
type: integer
description: ID of the account this site belongs to.
fullAddress:
type: string
description: Full address string. Either `fullAddress` or `geoLocation` must be provided.
geoLocation:
type: string
description: Latitude/longitude as "lat,lng". Either `fullAddress` or `geoLocation` must be provided.
title:
type: string
description: Human-readable label for the site.
street:
type: string
city:
type: string
state:
type: string
zipcode:
type: string
required:
- accountId
title: SiteCreate
SitesSiteIdPatchResponsesContentApplicationJsonSchemaCode:
type: string
enum:
- NOT_FOUND
title: SitesSiteIdPatchResponsesContentApplicationJsonSchemaCode
CreateSiteRequestBadRequestError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesPostResponsesContentApplicationJsonSchemaCode'
field:
type: string
description: Field that caused the validation error (if applicable)
title: CreateSiteRequestBadRequestError
GetSiteRequestNotFoundError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdGetResponsesContentApplicationJsonSchemaCode'
title: GetSiteRequestNotFoundError
DeleteSiteRequestUnauthorizedError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdDeleteResponsesContentApplicationJsonSchemaCode'
title: DeleteSiteRequestUnauthorizedError
MoveSiteRequestNotFoundError:
type: object
properties:
message:
type: string
code:
$ref: '#/components/schemas/SitesSiteIdMovePostResponsesContentApplicationJsonSchemaCode'
title: MoveSiteRequestNotFoundError
securitySchemes:
BasicAuth:
type: http
scheme: basic