openapi: "3.0.2" info: title: Page Server API description: Neon Pageserver API version: "1.0" license: name: "Apache" url: https://github.com/neondatabase/neon/blob/main/LICENSE servers: - url: "" paths: /v1/status: description: Healthcheck endpoint get: description: Healthcheck security: [] responses: "200": description: OK content: application/json: schema: type: object required: - id properties: id: type: integer /v1/disk_usage_eviction/run: put: description: Do an iteration of disk-usage-based eviction to evict a given amount of disk space. security: [] requestBody: content: application/json: schema: type: object required: - evict_bytes properties: evict_bytes: type: integer responses: "200": description: | The run completed. This does not necessarily mean that we actually evicted `evict_bytes`. Examine the returned object for detail, or, just watch the actual effect of the call using `du` or `df`. content: application/json: schema: type: object /v1/reload_auth_validation_keys: post: description: Reloads the JWT public keys from their pre-configured location on disk. responses: "200": description: The reload completed successfully. /v1/tenant/{tenant_id}: parameters: - name: tenant_id in: path required: true schema: type: string get: description: Get tenant status responses: "200": description: Currently returns the flag whether the tenant has inprogress timeline downloads content: application/json: schema: $ref: "#/components/schemas/TenantInfo" delete: description: | Attempts to delete specified tenant. 500, 503 and 409 errors should be retried. Deleting a non-existent tenant is considered successful (returns 200). responses: "200": description: Tenant was successfully deleted, or was already not found. "503": description: Service is unavailable, or tenant is already being modified (perhaps concurrently deleted) /v1/tenant/{tenant_id}/time_travel_remote_storage: parameters: - name: tenant_id in: path required: true schema: type: string - name: travel_to in: query required: true schema: type: string format: date-time - name: done_if_after in: query required: true schema: type: string format: date-time put: description: Time travel the tenant's remote storage responses: "200": description: OK content: application/json: schema: type: string /v1/tenant/{tenant_id}/timeline: parameters: - name: tenant_id in: path required: true schema: type: string get: description: Get timelines for tenant responses: "200": description: TimelineInfo content: application/json: schema: type: array items: $ref: "#/components/schemas/TimelineInfo" /v1/tenant/{tenant_id}/timeline/{timeline_id}: parameters: - name: tenant_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex get: description: Get info about the timeline responses: "200": description: TimelineInfo content: application/json: schema: $ref: "#/components/schemas/TimelineInfo" delete: description: "Attempts to delete specified timeline. 500 and 409 errors should be retried" responses: "404": description: Timeline not found. This is the success path. content: application/json: schema: $ref: "#/components/schemas/NotFoundError" "409": description: Deletion is already in progress, continue polling content: application/json: schema: $ref: "#/components/schemas/ConflictError" "412": description: Tenant is missing, or timeline has children content: application/json: schema: $ref: "#/components/schemas/PreconditionFailedError" /v1/tenant/{tenant_id}/timeline/{timeline_id}/get_timestamp_of_lsn: parameters: - name: tenant_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex get: description: Get timestamp for a given LSN parameters: - name: lsn in: query required: true schema: type: string format: hex description: A LSN to get the timestamp responses: "200": description: OK content: application/json: schema: type: string format: date-time /v1/tenant/{tenant_id}/timeline/{timeline_id}/get_lsn_by_timestamp: parameters: - name: tenant_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex get: description: Get LSN by a timestamp parameters: - name: timestamp in: query required: true schema: type: string format: date-time description: A timestamp to get the LSN - name: with_lease in: query required: false schema: type: boolean description: Whether to grant a lease to the corresponding LSN. Default to false. responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/LsnByTimestampResponse" /v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/lsn_lease: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex post: description: Obtains a lease for the given LSN. requestBody: content: application/json: schema: type: object required: - lsn properties: lsn: description: A LSN to obtain the lease for. type: string format: hex responses: "200": description: OK content: application/json: schema: $ref: "#/components/schemas/LsnLease" /v1/tenant/{tenant_id}/timeline/{timeline_id}/do_gc: parameters: - name: tenant_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex put: description: Garbage collect given timeline responses: "200": description: OK content: application/json: schema: type: string /v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/block_gc: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex post: description: Persistently add a gc blocking at the tenant level because of this timeline responses: "200": description: OK /v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/unblock_gc: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string format: hex post: description: Persistently remove a tenant level gc blocking for this timeline responses: "200": description: OK /v1/tenant/{tenant_shard_id}/location_config: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: flush_ms in: query required: false schema: type: integer - name: lazy in: query required: false schema: type: boolean description: Set to true for attaches to queue up until activated by compute. Eager (false) is the default. put: description: | Configures a _tenant location_, that is how a particular pageserver handles a particular tenant. This includes _attached_ tenants, i.e. those ingesting WAL and page service requests, and _secondary_ tenants, i.e. those which are just keeping a warm cache in anticipation of transitioning to attached state in the future. This is a declarative, idempotent API: there are not separate endpoints for different tenant location configurations. Rather, this single endpoint accepts a description of the desired location configuration, and makes whatever changes are required to reach that state. In imperative terms, this API is used to attach and detach tenants, and to transition tenants to and from secondary mode. This is a synchronous API: there is no 202 response. State transitions should always be fast (milliseconds), with the exception of requests setting `flush_ms`, in which case the caller controls the runtime of the request. In some state transitions, it makes sense to flush dirty data to remote storage: this includes transitions to AttachedStale and Detached. Flushing is never necessary for correctness, but is an important optimization when doing migrations. The `flush_ms` parameter controls whether flushing should be attempted, and how much time is allowed for flushing. If the time limit expires, the requested transition will continue without waiting for any outstanding data to flush. Callers should use a duration which is substantially less than their HTTP client's request timeout. It is safe to supply flush_ms irrespective of the request body: in state transitions where flushing doesn't make sense, the server will ignore it. It is safe to retry requests, but if one receives a 409 or 503 response, it is not useful to retry aggressively: there is probably an existing request still ongoing. requestBody: required: false content: application/json: schema: $ref: "#/components/schemas/TenantLocationConfigRequest" responses: "200": description: Tenant is now in requested state content: application/json: schema: $ref: "#/components/schemas/TenantLocationConfigResponse" "409": description: | The tenant is already being modified, perhaps by a concurrent call to this API content: application/json: schema: $ref: "#/components/schemas/ConflictError" /v1/tenant/{tenant_id}/timeline/{timeline_id}/preserve_initdb_archive: parameters: - name: tenant_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string post: description: | Marks the initdb archive for preservation upon deletion of the timeline or tenant. This is meant to be part of the disaster recovery process. responses: "202": description: Tenant scheduled to load successfully /v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/archival_config: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string put: description: | Either archives or unarchives the given timeline. An archived timeline may not have any non-archived children. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ArchivalConfigRequest" responses: "200": description: Timeline (un)archived successfully "409": description: | The tenant/timeline is already being modified, perhaps by a concurrent call to this API content: application/json: schema: $ref: "#/components/schemas/ConflictError" "500": description: Generic operation error content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: Temporarily unavailable, please retry. content: application/json: schema: $ref: "#/components/schemas/ServiceUnavailableError" /v1/tenant/{tenant_id}/synthetic_size: parameters: - name: tenant_id in: path required: true schema: type: string - name: inputs_only in: query required: false schema: type: boolean description: | When true, skip calculation and only provide the model inputs (for debugging). Defaults to false. - name: retention_period in: query required: false schema: type: integer description: | Override the default retention period (in bytes) used for size calculation. get: description: | Calculate tenant's size, which is a mixture of WAL (bytes) and logical_size (bytes). responses: "200": description: OK, content: application/json: schema: $ref: "#/components/schemas/SyntheticSizeResponse" text/html: schema: type: string description: SVG representation of the tenant and its timelines. "401": description: Unauthorized Error content: application/json: schema: $ref: "#/components/schemas/UnauthorizedError" "403": description: Forbidden Error content: application/json: schema: $ref: "#/components/schemas/ForbiddenError" "500": description: Generic operation error content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: Temporarily unavailable, please retry. content: application/json: schema: $ref: "#/components/schemas/ServiceUnavailableError" /v1/tenant/{tenant_shard_id}/heatmap_upload: parameters: - name: tenant_shard_id in: path required: true schema: type: string post: description: | If the location is in an attached mode, upload the current state to the remote heatmap responses: "200": description: Success /v1/tenant/{tenant_shard_id}/secondary/download: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: wait_ms description: If set, we will wait this long for download to complete, and if it isn't complete then return 202 in: query required: false schema: type: integer post: description: | If the location is in secondary mode, download latest heatmap and layers responses: "200": description: Success content: application/json: schema: $ref: "#/components/schemas/SecondaryProgress" "202": description: Download has started but not yet finished content: application/json: schema: $ref: "#/components/schemas/SecondaryProgress" /v1/tenant/{tenant_id}/timeline/: parameters: - name: tenant_id in: path required: true schema: type: string post: description: | Create a timeline. Returns new timeline id on success. Recreating the same timeline will succeed if the parameters match the existing timeline. If no pg_version is specified, assume DEFAULT_PG_VERSION hardcoded in the pageserver. To ensure durability, the caller must retry the creation until success. Just because the timeline is visible via other endpoints does not mean it is durable. Future versions may stop showing timelines that are not yet durable. requestBody: content: application/json: schema: type: object required: - new_timeline_id properties: new_timeline_id: type: string format: hex ancestor_timeline_id: type: string format: hex ancestor_start_lsn: type: string format: hex pg_version: type: integer existing_initdb_timeline_id: type: string format: hex import_pgdata: $ref: "#/components/schemas/TimelineCreateRequestImportPgdata" responses: "201": description: Timeline was created, or already existed with matching parameters content: application/json: schema: $ref: "#/components/schemas/TimelineInfo" "406": description: Permanently unsatisfiable request, don't retry. content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: Timeline already exists, with different parameters. Creation cannot proceed. content: application/json: schema: $ref: "#/components/schemas/ConflictError" "429": description: A creation request was sent for the same Timeline Id while a creation was already in progress. Back off and retry. content: application/json: schema: $ref: "#/components/schemas/Error" /v1/tenant/{tenant_shard_id}/timeline/{timeline_id}/detach_ancestor: parameters: - name: tenant_shard_id in: path required: true schema: type: string - name: timeline_id in: path required: true schema: type: string put: description: | Detach a timeline from its ancestor and reparent all ancestors timelines with lower `ancestor_lsn`. Current implementation might not be retryable across failure cases, but will be enhanced in future. Detaching should be expected to be expensive operation. Timeouts should be retried. responses: "200": description: | The timeline has been detached from it's ancestor (now or earlier), and at least the returned timelines have been reparented. If any timelines were deleted after reparenting, they might not be on this list. content: application/json: schema: $ref: "#/components/schemas/AncestorDetached" "400": description: | Number of early checks meaning the timeline cannot be detached now: - the ancestor of timeline has an ancestor: not supported, see RFC content: application/json: schema: $ref: "#/components/schemas/Error" "404": description: Tenant or timeline not found. content: application/json: schema: $ref: "#/components/schemas/NotFoundError" "409": description: | The timeline can never be detached: - timeline has no ancestor, implying that the timeline has never had an ancestor content: application/json: schema: $ref: "#/components/schemas/ConflictError" "500": description: | Transient error, for example, pageserver shutdown happened while processing the request but we were unable to distinguish that. Must be retried. content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: | Temporarily unavailable, please retry. Possible reasons: - another timeline detach for the same tenant is underway, please retry later - detected shutdown error content: application/json: schema: $ref: "#/components/schemas/ServiceUnavailableError" /v1/tenant/: get: description: Get tenants list responses: "200": description: TenantInfo content: application/json: schema: type: array items: $ref: "#/components/schemas/TenantInfo" post: description: | Create a tenant. Returns new tenant id on success. If no new tenant id is specified in parameters, it would be generated. It's an error to recreate the same tenant. Invalid fields in the tenant config will cause the request to be rejected with status 400. requestBody: content: application/json: schema: $ref: "#/components/schemas/TenantCreateRequest" responses: "201": description: New tenant created successfully content: application/json: schema: type: string "409": description: Tenant already exists, creation skipped content: application/json: schema: $ref: "#/components/schemas/ConflictError" /v1/tenant/config: put: description: | Update tenant's config. Invalid fields in the tenant config will cause the request to be rejected with status 400. requestBody: content: application/json: schema: $ref: "#/components/schemas/TenantConfigRequest" responses: "200": description: OK content: application/json: schema: type: array items: $ref: "#/components/schemas/TenantInfo" /v1/tenant/{tenant_id}/config/: parameters: - name: tenant_id in: path required: true schema: type: string get: description: | Returns tenant's config description: specific config overrides a tenant has and the effective config. responses: "200": description: Tenant config, specific and effective content: application/json: schema: $ref: "#/components/schemas/TenantConfigResponse" /v1/utilization: get: description: | Returns the pageservers current utilization and fitness score for new tenants. responses: "200": description: Pageserver utilization and fitness score content: application/json: schema: $ref: "#/components/schemas/PageserverUtilization" components: securitySchemes: JWT: type: http scheme: bearer bearerFormat: JWT schemas: TenantInfo: type: object required: - id - attachment_status properties: id: type: string current_physical_size: type: integer attachment_status: description: | Status of this tenant's attachment to this pageserver. - `maybe` means almost nothing, don't read anything into it except for the fact that the pageserver _might_ be already writing to the tenant's S3 state, so, DO NOT ATTACH the tenant to any other pageserver, or we risk split-brain. - `attached` means that the attach operation has completed, successfully - `failed` means that attach has failed. For reason check corresponding `reason` failed. `failed` is the terminal state, retrying attach call wont resolve the issue. For example this can be caused by s3 being unreachable. The retry may be implemented with call to detach, though it would be better to not automate it and inspec failed state manually before proceeding with a retry. type: object required: - slug - data properties: slug: type: string enum: [ "maybe", "attached", "failed" ] data: type: object properties: reason: type: string TenantCreateRequest: allOf: - $ref: '#/components/schemas/TenantConfig' - $ref: '#/components/schemas/TenantLoadRequest' - type: object required: - new_tenant_id properties: new_tenant_id: type: string TenantLoadRequest: type: object properties: generation: type: integer description: Attachment generation number. TenantConfigRequest: allOf: - $ref: '#/components/schemas/TenantConfig' - type: object required: - tenant_id properties: tenant_id: type: string TenantLocationConfigRequest: type: object required: - mode properties: mode: type: string enum: ["AttachedSingle", "AttachedMulti", "AttachedStale", "Secondary", "Detached"] description: Mode of functionality that this pageserver will run in for this tenant. generation: type: integer description: Attachment generation number, mandatory when `mode` is an attached state secondary_conf: $ref: '#/components/schemas/SecondaryConfig' tenant_conf: $ref: '#/components/schemas/TenantConfig' TenantLocationConfigResponse: type: object required: - shards properties: shards: description: Pageservers where this tenant's shards are attached. Not populated for secondary locations. type: array items: $ref: "#/components/schemas/TenantShardLocation" stripe_size: description: If multiple shards are present, this field contains the sharding stripe size, else it is null. type: integer nullable: true TenantShardLocation: type: object required: - node_id - shard_id properties: node_id: description: Pageserver node ID where this shard is attached type: integer shard_id: description: Tenant shard ID of the shard type: string SecondaryConfig: type: object properties: warm: type: boolean description: Whether to poll remote storage for layers to download. If false, secondary locations don't download anything. ArchivalConfigRequest: type: object required: - state properties: state: description: The archival state of a timeline type: string enum: ["Archived", "Unarchived"] TenantConfig: type: object properties: gc_period: type: string gc_horizon: type: integer pitr_interval: type: string checkpoint_distance: type: integer checkpoint_timeout: type: string compaction_target_size: type: integer compaction_period: type: string compaction_threshold: type: string image_creation_threshold: type: integer walreceiver_connect_timeout: type: string lagging_wal_timeout: type: string max_lsn_wal_lag: type: integer heatmap_period: type: string TenantConfigResponse: type: object properties: tenant_specific_overrides: $ref: "#/components/schemas/TenantConfig" effective_config: $ref: "#/components/schemas/TenantConfig" TimelineCreateRequestImportPgdata: type: object required: - location - idempotency_key properties: idempotency_key: type: string location: $ref: "#/components/schemas/TimelineCreateRequestImportPgdataLocation" TimelineCreateRequestImportPgdataLocation: type: object properties: AwsS3: $ref: "#/components/schemas/TimelineCreateRequestImportPgdataLocationAwsS3" TimelineCreateRequestImportPgdataLocationAwsS3: type: object properties: region: type: string bucket: type: string key: type: string required: - region - bucket - key TimelineInfo: type: object required: - timeline_id - tenant_id - last_record_lsn - disk_consistent_lsn - state - latest_gc_cutoff_lsn properties: timeline_id: type: string format: hex tenant_id: type: string last_record_lsn: type: string format: hex disk_consistent_lsn: type: string format: hex remote_consistent_lsn: type: string format: hex remote_consistent_lsn_visible: type: string format: hex ancestor_timeline_id: type: string format: hex ancestor_lsn: type: string format: hex prev_record_lsn: type: string format: hex current_logical_size: type: integer current_physical_size: type: integer wal_source_connstr: type: string last_received_msg_lsn: type: string format: hex last_received_msg_ts: type: integer state: type: string latest_gc_cutoff_lsn: type: string format: hex SyntheticSizeResponse: type: object required: - id - size - segment_sizes - inputs properties: id: type: string format: hex size: type: integer nullable: true description: | Size metric in bytes or null if inputs_only=true was given. segment_sizes: type: array items: $ref: "#/components/schemas/SegmentSize" inputs: type: object properties: segments: type: array items: $ref: "#/components/schemas/SegmentData" timeline_inputs: type: array items: $ref: "#/components/schemas/TimelineInput" SegmentSize: type: object required: - method - accum_size properties: method: type: string accum_size: type: integer SegmentData: type: object required: - segment properties: segment: type: object required: - lsn properties: parent: type: integer lsn: type: integer size: type: integer needed: type: boolean timeline_id: type: string format: hex kind: type: string TimelineInput: type: object required: - timeline_id properties: ancestor_id: type: string ancestor_lsn: type: string timeline_id: type: string format: hex LsnByTimestampResponse: type: object required: - lsn - kind properties: lsn: type: string format: hex kind: type: string enum: [past, present, future, nodata] valid_until: type: string format: date-time description: The expiration time of the granted lease. LsnLease: type: object required: - valid_until properties: valid_until: type: string format: date-time PageserverUtilization: type: object required: - disk_usage_bytes - free_space_bytes - utilization_score properties: disk_usage_bytes: type: integer format: int64 minimum: 0 description: The amount of disk space currently used. free_space_bytes: type: integer format: int64 minimum: 0 description: The amount of usable disk space left. utilization_score: type: integer format: int64 minimum: 0 maximum: 9223372036854775807 default: 9223372036854775807 description: | Lower is better score for how good this pageserver would be for the next tenant. The default or maximum value can be returned in situations when a proper score cannot (yet) be calculated. SecondaryProgress: type: object required: - heatmap_mtime - layers_downloaded - layers_total - bytes_downloaded - bytes_total properties: heatmap_mtime: type: string format: date-time description: Modification time of the most recently downloaded layer heatmap (RFC 3339 format) layers_downloaded: type: integer format: int64 description: How many layers from the latest layer heatmap are present on disk bytes_downloaded: type: integer format: int64 description: How many bytes of layer content from the latest layer heatmap are present on disk layers_total: type: integer format: int64 description: How many layers were in the latest layer heatmap bytes_total: type: integer format: int64 description: How many bytes of layer content were in the latest layer heatmap AncestorDetached: type: object required: - reparented_timelines properties: reparented_timelines: type: array description: Set of reparented timeline ids items: type: string format: hex description: TimelineId Error: type: object required: - msg properties: msg: type: string UnauthorizedError: type: object required: - msg properties: msg: type: string ForbiddenError: type: object required: - msg properties: msg: type: string ServiceUnavailableError: type: object required: - msg properties: msg: type: string NotFoundError: type: object required: - msg properties: msg: type: string ConflictError: type: object required: - msg properties: msg: type: string PreconditionFailedError: type: object required: - msg properties: msg: type: string security: - JWT: []