708 lines
28 KiB
YAML
708 lines
28 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: AS213905 API
|
||
version: 1.2.0
|
||
description: |
|
||
Customer automation API for organisations, invoices, services, traffic,
|
||
tunnels and RFC 8805 geofeeds. Create a scoped key in Panel → Settings.
|
||
contact:
|
||
name: AS213905 NOC
|
||
email: noc@as213905.com
|
||
url: https://as213905.com/docs/api
|
||
license:
|
||
name: Proprietary API; SDKs licensed under MIT
|
||
url: https://git.datacoria.com/Phylex/as213905-sdk/src/branch/main/LICENSE
|
||
servers:
|
||
- url: https://as213905.com
|
||
tags:
|
||
- name: Organisation
|
||
description: The organisation bound to the API key.
|
||
- name: Invoices
|
||
description: Billing documents and payment state.
|
||
- name: Services
|
||
description: Customer services, BGP VM metrics and power actions.
|
||
- name: Traffic
|
||
description: Organisation and per-tunnel traffic accounting.
|
||
- name: Tunnels
|
||
description: Transit tunnel inventory and lifecycle.
|
||
- name: Geofeeds
|
||
description: RFC 8805 public geofeed management.
|
||
security:
|
||
- apiKey: []
|
||
paths:
|
||
/api/v1/organization:
|
||
get:
|
||
tags: [Organisation]
|
||
summary: Get the API key's organisation
|
||
description: Returns the single organisation permanently bound to the bearer key. Requires `organizations:read`.
|
||
operationId: getOrganization
|
||
x-required-scope: organizations:read
|
||
responses:
|
||
'200':
|
||
description: Organisation
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/OrganizationResponse'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
/api/v1/invoices:
|
||
get:
|
||
tags: [Invoices]
|
||
summary: List invoices, newest first
|
||
description: Returns all billing documents for the key organisation, ordered from newest to oldest. Requires `invoices:read`.
|
||
operationId: listInvoices
|
||
x-required-scope: invoices:read
|
||
responses:
|
||
'200':
|
||
description: Invoice list
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [invoices]
|
||
properties:
|
||
invoices: {type: array, items: {$ref: '#/components/schemas/Invoice'}}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
/api/v1/services:
|
||
get:
|
||
tags: [Services]
|
||
summary: List services
|
||
description: Returns every customer service and its lifecycle state. Requires `services:read`.
|
||
operationId: listServices
|
||
x-required-scope: services:read
|
||
responses:
|
||
'200':
|
||
description: Service list
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [services]
|
||
properties:
|
||
services: {type: array, items: {$ref: '#/components/schemas/Service'}}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
/api/v1/services/{id}:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
get:
|
||
tags: [Services]
|
||
summary: Get one service
|
||
description: Returns one service owned by the key organisation. A foreign identifier is reported as not found. Requires `services:read`.
|
||
operationId: getService
|
||
x-required-scope: services:read
|
||
responses:
|
||
'200':
|
||
description: Service
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [service]
|
||
properties:
|
||
service: {$ref: '#/components/schemas/Service'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/services/{id}/metrics:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
get:
|
||
tags: [Services]
|
||
summary: Read a BGP VM metric series
|
||
description: Reads a sampled metric series from a controllable BGP VM. Requires `services:read`.
|
||
operationId: getServiceMetrics
|
||
x-required-scope: services:read
|
||
parameters:
|
||
- name: target
|
||
in: query
|
||
description: Metric to read. Network values are returned as bytes per second; percentage and load values use their native units.
|
||
example: net_tx
|
||
schema:
|
||
type: string
|
||
enum: [cpu_load, mem_usage_percent, net_rx, net_tx, iops, df.root.used]
|
||
default: cpu_load
|
||
- name: range
|
||
in: query
|
||
description: Time window ending at the current time.
|
||
example: 6h
|
||
schema: {type: string, enum: [1h, 6h, 24h, 7d], default: 24h}
|
||
responses:
|
||
'200':
|
||
description: Metric series
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/MetricSeries'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/services/{id}/power:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
post:
|
||
tags: [Services]
|
||
summary: Start, stop or restart a BGP VM
|
||
description: Queues an asynchronous power command for a controllable BGP VM. Requires `services:write`.
|
||
operationId: powerService
|
||
x-required-scope: services:write
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [action]
|
||
properties:
|
||
action: {type: string, enum: [start, stop, restart], examples: [restart], description: Power transition to queue.}
|
||
examples:
|
||
restart: {value: {action: restart}}
|
||
responses:
|
||
'202':
|
||
description: Command accepted
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [status, action]
|
||
properties:
|
||
status: {type: string, const: accepted}
|
||
action: {type: string, enum: [start, stop, restart]}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
'429': {$ref: '#/components/responses/RateLimited'}
|
||
/api/v1/traffic:
|
||
get:
|
||
tags: [Traffic]
|
||
summary: Get organisation transit usage
|
||
description: Returns transferred bytes and 95th-percentile traffic for either the last N days or an explicit UTC date interval. `days` is mutually exclusive with `from`/`to`. Requires `traffic:read`.
|
||
operationId: getTraffic
|
||
x-required-scope: traffic:read
|
||
parameters:
|
||
- {$ref: '#/components/parameters/Days'}
|
||
- {$ref: '#/components/parameters/From'}
|
||
- {$ref: '#/components/parameters/To'}
|
||
responses:
|
||
'200':
|
||
description: Traffic summary
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/Traffic'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
/api/v1/tunnels:
|
||
get:
|
||
tags: [Tunnels]
|
||
summary: List tunnels
|
||
description: Returns all transit tunnels and their provisioning state. Requires `tunnels:read`.
|
||
operationId: listTunnels
|
||
x-required-scope: tunnels:read
|
||
responses:
|
||
'200':
|
||
description: Tunnel list
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [tunnels]
|
||
properties:
|
||
tunnels: {type: array, items: {$ref: '#/components/schemas/Tunnel'}}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
post:
|
||
tags: [Tunnels]
|
||
summary: Request a transit tunnel
|
||
description: Records a Frankfurt transit tunnel for asynchronous provisioning. Currently `fra` is the only available location. Requires `tunnels:write`.
|
||
operationId: createTunnel
|
||
x-required-scope: tunnels:write
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/CreateTunnelRequest'}
|
||
examples:
|
||
frankfurt:
|
||
summary: Frankfurt GRE tunnel
|
||
value: {peer_endpoint: 203.0.113.10, location: fra, label: fra-primary, announce: true}
|
||
responses:
|
||
'202':
|
||
description: Tunnel recorded for provisioning
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [tunnel]
|
||
properties:
|
||
tunnel: {$ref: '#/components/schemas/Tunnel'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
/api/v1/tunnels/{id}:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
delete:
|
||
tags: [Tunnels]
|
||
summary: Remove a tunnel asynchronously
|
||
description: Queues teardown and returns before router configuration has completed. Requires `tunnels:write`.
|
||
operationId: deleteTunnel
|
||
x-required-scope: tunnels:write
|
||
responses:
|
||
'202':
|
||
description: Removal accepted
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/StatusResponse'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/tunnels/{id}/traffic:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
get:
|
||
tags: [Traffic, Tunnels]
|
||
summary: Get one tunnel's usage
|
||
description: Returns transferred bytes and 95th-percentile traffic for one tunnel. `days` is mutually exclusive with `from`/`to`. Requires `traffic:read`.
|
||
operationId: getTunnelTraffic
|
||
x-required-scope: traffic:read
|
||
parameters:
|
||
- {$ref: '#/components/parameters/Days'}
|
||
- {$ref: '#/components/parameters/From'}
|
||
- {$ref: '#/components/parameters/To'}
|
||
responses:
|
||
'200':
|
||
description: Tunnel traffic
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/TunnelTraffic'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/geofeeds:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/OrganizationId'}
|
||
get:
|
||
tags: [Geofeeds]
|
||
summary: List geofeeds
|
||
description: Lists the organisation's public RFC 8805 feeds. Each organisation can own at most two. Requires `geofeeds:read`.
|
||
operationId: listGeofeeds
|
||
x-required-scope: geofeeds:read
|
||
responses:
|
||
'200':
|
||
description: Geofeed list
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/GeofeedList'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
post:
|
||
tags: [Geofeeds]
|
||
summary: Create a geofeed (maximum two per organisation)
|
||
description: Creates an empty feed and an unguessable public CSV URL. Requires `geofeeds:write`.
|
||
operationId: createGeofeed
|
||
x-required-scope: geofeeds:write
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [name]
|
||
properties:
|
||
name: {type: string, minLength: 1, maxLength: 100, examples: [Customer prefixes], description: Human-readable display name.}
|
||
examples:
|
||
default: {value: {name: Customer prefixes}}
|
||
responses:
|
||
'201':
|
||
description: Created
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/GeofeedList'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'409': {$ref: '#/components/responses/Conflict'}
|
||
/api/v1/geofeeds/{id}:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/OrganizationId'}
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
get:
|
||
tags: [Geofeeds]
|
||
summary: Get a geofeed and its records
|
||
description: Returns feed metadata and all current RFC 8805 records. Requires `geofeeds:read`.
|
||
operationId: getGeofeed
|
||
x-required-scope: geofeeds:read
|
||
responses:
|
||
'200':
|
||
description: Geofeed
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [geofeed]
|
||
properties:
|
||
geofeed: {$ref: '#/components/schemas/GeofeedWithRecords'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
patch:
|
||
tags: [Geofeeds]
|
||
summary: Rename a geofeed
|
||
description: Changes only the display name; the public URL remains valid. Requires `geofeeds:write`.
|
||
operationId: updateGeofeed
|
||
x-required-scope: geofeeds:write
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [name]
|
||
properties:
|
||
name: {type: string, minLength: 1, maxLength: 100, examples: [Production geofeed], description: New display name.}
|
||
examples:
|
||
rename: {value: {name: Production geofeed}}
|
||
responses:
|
||
'200':
|
||
description: Updated
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/StatusResponse'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
delete:
|
||
tags: [Geofeeds]
|
||
summary: Delete a geofeed
|
||
description: Permanently deletes the feed, its records and public CSV URL. Requires `geofeeds:write`.
|
||
operationId: deleteGeofeed
|
||
x-required-scope: geofeeds:write
|
||
responses:
|
||
'200':
|
||
description: Deleted
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/StatusResponse'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/geofeeds/{id}/records:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/OrganizationId'}
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
put:
|
||
tags: [Geofeeds]
|
||
summary: Atomically replace all RFC 8805 records
|
||
description: Validates the complete array and replaces all records in one transaction. No partial update is committed. Requires `geofeeds:write`.
|
||
operationId: replaceGeofeedRecords
|
||
x-required-scope: geofeeds:write
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [records]
|
||
properties:
|
||
records:
|
||
type: array
|
||
maxItems: 1000
|
||
items: {$ref: '#/components/schemas/GeofeedRecordInput'}
|
||
examples:
|
||
frankfurt:
|
||
value:
|
||
records:
|
||
- {prefix: 203.0.113.0/24, country_code: DE, region_code: DE-HE, city: Frankfurt am Main, postal_code: '60311'}
|
||
responses:
|
||
'200':
|
||
description: Saved
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/StatusResponse'}
|
||
'400': {$ref: '#/components/responses/BadRequest'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
/api/v1/geofeeds/{id}/rotate:
|
||
parameters:
|
||
- {$ref: '#/components/parameters/OrganizationId'}
|
||
- {$ref: '#/components/parameters/ResourceId'}
|
||
post:
|
||
tags: [Geofeeds]
|
||
summary: Rotate the unguessable public CSV URL
|
||
description: Invalidates the previous URL immediately and issues a new public slug. Requires `geofeeds:write`.
|
||
operationId: rotateGeofeedUrl
|
||
x-required-scope: geofeeds:write
|
||
responses:
|
||
'200':
|
||
description: Rotated
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/GeofeedList'}
|
||
'401': {$ref: '#/components/responses/Unauthenticated'}
|
||
'403': {$ref: '#/components/responses/Forbidden'}
|
||
'404': {$ref: '#/components/responses/NotFound'}
|
||
components:
|
||
securitySchemes:
|
||
apiKey:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: as213905_…
|
||
description: API key from Panel → Settings. Query-string keys are rejected.
|
||
parameters:
|
||
ResourceId:
|
||
name: id
|
||
in: path
|
||
required: true
|
||
description: UUID returned by the corresponding list endpoint.
|
||
example: 38fce115-83e6-4122-a65a-9c70c9187794
|
||
schema: {type: string, format: uuid}
|
||
OrganizationId:
|
||
name: X-Organization-ID
|
||
in: header
|
||
required: true
|
||
description: Organisation UUID. An API key can only address its own organisation.
|
||
example: 9f44e0e7-13d4-4af3-bc6e-76b91c93b752
|
||
schema: {type: string, format: uuid}
|
||
Days:
|
||
name: days
|
||
in: query
|
||
description: Last N days; mutually exclusive with from/to.
|
||
example: 7
|
||
schema: {type: integer, minimum: 1, maximum: 366, default: 7}
|
||
From:
|
||
name: from
|
||
in: query
|
||
description: First UTC calendar date in an explicit interval. Use together with `to`; mutually exclusive with `days`.
|
||
example: '2026-08-01'
|
||
schema: {type: string, format: date}
|
||
To:
|
||
name: to
|
||
in: query
|
||
description: Inclusive whole-day end.
|
||
example: '2026-08-07'
|
||
schema: {type: string, format: date}
|
||
responses:
|
||
BadRequest:
|
||
description: Invalid request
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
invalid_parameter: {value: {error: invalid_parameter, message: "days cannot be combined with from/to"}}
|
||
Unauthenticated:
|
||
description: Missing, malformed, expired, revoked or unknown API key
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
missing: {value: {error: api_key_required, message: "Authorization header is required"}}
|
||
invalid: {value: {error: api_key_invalid, message: "The API key is invalid or expired"}}
|
||
Forbidden:
|
||
description: Missing scope or disallowed source address
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
scope: {value: {error: scope_required, message: "This endpoint requires traffic:read", required_scope: traffic:read}}
|
||
address: {value: {error: address_not_allowed, message: "The request source is not allowed"}}
|
||
NotFound:
|
||
description: Resource not found
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
resource: {value: {error: not_found, message: "Resource not found"}}
|
||
Conflict:
|
||
description: Resource limit or state conflict
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
limit: {value: {error: geofeed_limit_reached, message: "An organisation can have at most two geofeeds"}}
|
||
RateLimited:
|
||
description: Rate limit exceeded
|
||
headers:
|
||
Retry-After:
|
||
description: Seconds before another attempt should be made.
|
||
schema: {type: integer, minimum: 1, examples: [30]}
|
||
content:
|
||
application/json:
|
||
schema: {$ref: '#/components/schemas/APIError'}
|
||
examples:
|
||
limited: {value: {error: rate_limited, message: "Too many requests", retry_after: 30}}
|
||
schemas:
|
||
APIError:
|
||
type: object
|
||
required: [error]
|
||
properties:
|
||
error: {type: string, examples: [scope_required]}
|
||
message: {type: string}
|
||
required_scope: {type: string}
|
||
retry_after: {type: integer}
|
||
StatusResponse:
|
||
type: object
|
||
required: [status]
|
||
properties: {status: {type: string}}
|
||
OrganizationResponse:
|
||
type: object
|
||
required: [organization]
|
||
properties: {organization: {$ref: '#/components/schemas/Organization'}}
|
||
Organization:
|
||
type: object
|
||
required: [id, asn, name, status, commit_kbps]
|
||
properties:
|
||
id: {type: string, format: uuid}
|
||
asn: {type: integer, minimum: 1, maximum: 4294967295}
|
||
name: {type: string}
|
||
status: {type: string}
|
||
commit_kbps: {type: integer, format: int64}
|
||
examples:
|
||
- {id: 9f44e0e7-13d4-4af3-bc6e-76b91c93b752, asn: 213905, name: Example Networks GmbH, status: active, commit_kbps: 1000000}
|
||
Invoice:
|
||
type: object
|
||
required: [id, number, status, currency, total_cents]
|
||
properties:
|
||
id: {type: string, format: uuid}
|
||
number: {type: string}
|
||
status: {type: string, enum: [draft, issued, paid, overdue, cancelled]}
|
||
currency: {type: string, minLength: 3, maxLength: 3}
|
||
total_cents: {type: integer, format: int64}
|
||
period_from: {type: [string, 'null'], format: date}
|
||
period_to: {type: [string, 'null'], format: date}
|
||
issued_at: {type: [string, 'null'], format: date-time}
|
||
due_at: {type: [string, 'null'], format: date-time}
|
||
examples:
|
||
- {id: c089452d-9127-48b6-a1b8-407f9691a658, number: INV-2026-0042, status: paid, currency: EUR, total_cents: 900, period_from: '2026-08-01', period_to: '2026-08-31', issued_at: '2026-08-01T09:00:00Z', due_at: '2026-08-08T09:00:00Z'}
|
||
Service:
|
||
type: object
|
||
required: [id, kind, label, status, monthly_cents]
|
||
properties:
|
||
id: {type: string, format: uuid}
|
||
kind: {type: string}
|
||
label: {type: string}
|
||
status: {type: string, enum: [awaiting_payment, provisioning, active, suspended, terminating, terminated]}
|
||
monthly_cents: {type: integer, format: int64}
|
||
next_due_on: {type: [string, 'null'], format: date}
|
||
controllable: {type: boolean}
|
||
location: {type: string}
|
||
configuration: {type: object, additionalProperties: true}
|
||
examples:
|
||
- {id: 38fce115-83e6-4122-a65a-9c70c9187794, kind: bgp_vm, label: BGP Small, status: active, monthly_cents: 900, next_due_on: '2026-09-01', controllable: true, location: fra, configuration: {vcpu: 2, memory_gib: 4, disk_gib: 50}}
|
||
MetricPoint:
|
||
type: object
|
||
required: [t]
|
||
properties:
|
||
t: {type: string, format: date-time}
|
||
v: {type: number}
|
||
examples:
|
||
- {t: '2026-08-14T14:00:00Z', v: 42.7}
|
||
MetricSeries:
|
||
type: object
|
||
required: [metric, target, range, points]
|
||
properties:
|
||
metric: {type: string}
|
||
target: {type: string}
|
||
range: {type: string}
|
||
points: {type: array, items: {$ref: '#/components/schemas/MetricPoint'}}
|
||
examples:
|
||
- {metric: Network transmit, target: net_tx, range: 6h, points: [{t: '2026-08-14T14:00:00Z', v: 1250000}, {t: '2026-08-14T14:05:00Z', v: 1310000}]}
|
||
Period:
|
||
type: object
|
||
required: [from, to, label]
|
||
properties:
|
||
from: {type: string, format: date-time}
|
||
to: {type: string, format: date-time}
|
||
label: {type: string}
|
||
examples:
|
||
- {from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z', label: 1–7 August 2026}
|
||
Traffic:
|
||
type: object
|
||
required: [p95_kbps, total_bytes, total_gb, period]
|
||
properties:
|
||
p95_kbps: {type: integer, format: int64}
|
||
commit_kbps: {type: integer, format: int64}
|
||
over_commit: {type: boolean}
|
||
total_bytes: {type: integer, format: int64}
|
||
total_gb: {type: number}
|
||
period: {$ref: '#/components/schemas/Period'}
|
||
examples:
|
||
- {p95_kbps: 428500, commit_kbps: 1000000, over_commit: false, total_bytes: 482901204887, total_gb: 449.74, period: {from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z', label: 1–7 August 2026}}
|
||
Tunnel:
|
||
type: object
|
||
required: [id, status, location, peer_endpoint]
|
||
properties:
|
||
id: {type: string, format: uuid}
|
||
status: {type: string, enum: [provisioning, configuring, awaiting_peer, active, error, suspended, removed]}
|
||
label: {type: string}
|
||
location: {type: string, examples: [fra]}
|
||
peer_endpoint: {type: string, format: ip}
|
||
ifname: {type: string}
|
||
inner_v4: {type: string}
|
||
inner_v6: {type: string}
|
||
announce: {type: boolean}
|
||
examples:
|
||
- {id: a5247bdd-e2d0-4c25-a1e6-c84e290cbfa1, status: active, label: fra-primary, location: fra, peer_endpoint: 203.0.113.10, ifname: gre-customer-1, inner_v4: 169.254.10.2/30, inner_v6: 'fe80::2/64', announce: true}
|
||
CreateTunnelRequest:
|
||
type: object
|
||
required: [peer_endpoint, location]
|
||
properties:
|
||
peer_endpoint: {type: string, format: ip, examples: [203.0.113.10], description: Public remote GRE endpoint.}
|
||
location: {type: string, enum: [fra], examples: [fra], description: Service location; currently Frankfurt only.}
|
||
label: {type: string, maxLength: 100, examples: [fra-primary], description: Optional customer-visible name.}
|
||
announce: {type: boolean, default: false, examples: [true], description: Request route announcement after configuration.}
|
||
examples:
|
||
- {peer_endpoint: 203.0.113.10, location: fra, label: fra-primary, announce: true}
|
||
TunnelTraffic:
|
||
allOf:
|
||
- {$ref: '#/components/schemas/Traffic'}
|
||
- type: object
|
||
required: [tunnel]
|
||
properties: {tunnel: {$ref: '#/components/schemas/Tunnel'}}
|
||
Geofeed:
|
||
type: object
|
||
required: [id, name, slug, record_count, created_at, updated_at]
|
||
properties:
|
||
id: {type: string, format: uuid}
|
||
name: {type: string}
|
||
slug: {type: string}
|
||
record_count: {type: integer, minimum: 0}
|
||
created_at: {type: string, format: date-time}
|
||
updated_at: {type: string, format: date-time}
|
||
examples:
|
||
- {id: 661e65fd-a498-4418-af2f-dbc8e4b24535, name: Production geofeed, slug: geo-Q4cW8sR1.csv, record_count: 1, created_at: '2026-08-14T10:00:00Z', updated_at: '2026-08-14T14:00:00Z'}
|
||
GeofeedRecordInput:
|
||
type: object
|
||
required: [prefix, country_code]
|
||
properties:
|
||
prefix: {type: string, examples: [203.0.113.0/24]}
|
||
country_code: {type: string, pattern: '^[A-Z]{2}$', examples: [DE]}
|
||
region_code: {type: string, maxLength: 128, examples: [DE-HE]}
|
||
city: {type: string, maxLength: 128, examples: [Frankfurt am Main]}
|
||
postal_code: {type: string, maxLength: 128, examples: ['60311']}
|
||
examples:
|
||
- {prefix: 203.0.113.0/24, country_code: DE, region_code: DE-HE, city: Frankfurt am Main, postal_code: '60311'}
|
||
GeofeedRecord:
|
||
allOf:
|
||
- {$ref: '#/components/schemas/GeofeedRecordInput'}
|
||
- type: object
|
||
properties: {id: {type: string, format: uuid}}
|
||
GeofeedWithRecords:
|
||
allOf:
|
||
- {$ref: '#/components/schemas/Geofeed'}
|
||
- type: object
|
||
required: [records]
|
||
properties:
|
||
records: {type: array, items: {$ref: '#/components/schemas/GeofeedRecord'}}
|
||
GeofeedList:
|
||
type: object
|
||
required: [geofeeds, limit]
|
||
properties:
|
||
geofeeds: {type: array, items: {$ref: '#/components/schemas/Geofeed'}}
|
||
limit: {type: integer, const: 2}
|
||
examples:
|
||
- {geofeeds: [{id: 661e65fd-a498-4418-af2f-dbc8e4b24535, name: Production geofeed, slug: geo-Q4cW8sR1.csv, record_count: 1, created_at: '2026-08-14T10:00:00Z', updated_at: '2026-08-14T14:00:00Z'}], limit: 2}
|