---
title: "Create site"
method: POST
path: "/api/v1/sites"
tags: ["Sites"]
---

# Create site

`POST /api/v1/sites`

## Request

- Base URL: `https://app.scanopy.net`
- URL: `https://app.scanopy.net/api/v1/sites`
- Auth: one of:
  - API key in header `Authorization`
  - API key in cookie `session_id`

## Request body

- Site
  - `credential_ids` string[], nullable: Credential IDs assigned to this site (hydrated from the junction table, so always present in a response). On create, omitting it assigns the organization's site-wide default credentials and a list, even an empty one, is used exactly. On update, omitting it leaves the assignments unchanged.
  - `name` string, required: Human-facing name for this site.
  - `organization_id` string, uuid, required: The organization that owns this record.
  - `stale_after_hours` integer, nullable, required: How long a discovery-managed entity on this site may go unobserved before it reads as stale. `None` = unset; callers resolve the effective value through [`Site::stale_after`], never by reading this directly. Site-scoped because staleness is only meaningful relative to scan cadence, and cadence is a property of a site's discoveries.
  - `tags` string[], required: Tags assigned to this entity.
  - `created_at` string, date-time, required: When this record was first created.
  - `effective_stale_after_hours` integer: `stale_after_hours` with the server's default already applied. Computed, never stored (excluded from `to_params`). Published so the frontend derives staleness from the *same* number the digest uses rather than re-declaring the default in TypeScript, where the two could drift and a host could read stale in the app but current in the digest email.
  - `id` string, uuid, required: Server-assigned unique identifier.
  - `updated_at` string, date-time, required: When this record was last modified.

## Response `200`

Site created

- ApiResponseSite: Envelope for a successful response. Failures are sent with a non-2xx status and an `ApiErrorResponse` body instead.
  - `data` object: The result payload. Omitted on failure.
    - `credential_ids` string[], nullable: Credential IDs assigned to this site (hydrated from the junction table, so always present in a response). On create, omitting it assigns the organization's site-wide default credentials and a list, even an empty one, is used exactly. On update, omitting it leaves the assignments unchanged.
    - `name` string, required: Human-facing name for this site.
    - `organization_id` string, uuid, required: The organization that owns this record.
    - `stale_after_hours` integer, nullable, required: How long a discovery-managed entity on this site may go unobserved before it reads as stale. `None` = unset; callers resolve the effective value through [`Site::stale_after`], never by reading this directly. Site-scoped because staleness is only meaningful relative to scan cadence, and cadence is a property of a site's discoveries.
    - `tags` string[], required: Tags assigned to this entity.
    - `created_at` string, date-time, required: When this record was first created.
    - `effective_stale_after_hours` integer: `stale_after_hours` with the server's default already applied. Computed, never stored (excluded from `to_params`). Published so the frontend derives staleness from the *same* number the digest uses rather than re-declaring the default in TypeScript, where the two could drift and a host could read stale in the app but current in the digest email.
    - `id` string, uuid, required: Server-assigned unique identifier.
    - `updated_at` string, date-time, required: When this record was last modified.
  - `error` string, nullable: Not sent on a successful response. Failure messages arrive in an `ApiErrorResponse`.
  - `meta` ApiMeta, required: API metadata included in all responses
    - `api_version` integer, required: API version (integer, increments on breaking changes)
    - `server_version` string, required: Server version (semver)
  - `success` boolean, required: Always `true` on a successful response.

## Changes

- **2026-10-12** `c305a1f5ae29`: 2 breaking, 2 info
  - the response property `data/allOf[#/components/schemas/SiteBase]/credential_ids` became nullable for the status `200`
  - the response property `data/allOf[#/components/schemas/SiteBase]/credential_ids` became optional for the status `200`
  - the request property `allOf[#/components/schemas/SiteBase]/credential_ids` became nullable
  - the request property `allOf[#/components/schemas/SiteBase]/credential_ids` became optional
- **2026-10-11** `a6ea2ab6fbb9`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/scanopy/apis/scanopy-api/changes/api/v1/sites/post.md)

---

[API](https://skmtc.dev/scanopy/apis/scanopy-api.md) - [All operations](https://skmtc.dev/scanopy/apis/scanopy-api/llms.txt) - [OpenAPI document](https://skmtc.dev/scanopy/apis/scanopy-api/revisions/966ba786bdb5?raw)
