Files
as213905-sdk/openapi/openapi.yaml
T

708 lines
28 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: 17 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: 17 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}