openapi: 3.1.0 info: title: AS213905 API version: 1.1.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 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 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 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 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 operationId: getServiceMetrics x-required-scope: services:read parameters: - name: target in: query schema: type: string enum: [cpu_load, mem_usage_percent, net_rx, net_tx, iops, df.root.used] default: cpu_load - name: range in: query 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 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]} 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 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 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 operationId: createTunnel x-required-scope: tunnels:write requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/CreateTunnelRequest'} 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 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 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 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) 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} 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 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 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} 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 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 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'} 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 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 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. schema: {type: string, format: uuid} Days: name: days in: query description: Last N days; mutually exclusive with from/to. schema: {type: integer, minimum: 1, maximum: 366} From: name: from in: query schema: {type: string, format: date} To: name: to in: query description: Inclusive whole-day end. schema: {type: string, format: date} responses: BadRequest: description: Invalid request content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} Unauthenticated: description: Missing, malformed, expired, revoked or unknown API key content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} Forbidden: description: Missing scope or disallowed source address content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} NotFound: description: Resource not found content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} Conflict: description: Resource limit or state conflict content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} RateLimited: description: Rate limit exceeded headers: Retry-After: {schema: {type: integer}} content: {application/json: {schema: {$ref: '#/components/schemas/APIError'}}} 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} 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} 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} MetricPoint: type: object required: [t] properties: t: {type: string, format: date-time} v: {type: number} 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'}} Period: type: object required: [from, to, label] properties: from: {type: string, format: date-time} to: {type: string, format: date-time} label: {type: string} 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'} 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} CreateTunnelRequest: type: object required: [peer_endpoint, location] properties: peer_endpoint: {type: string, format: ip} location: {type: string, examples: [fra]} label: {type: string, maxLength: 100} announce: {type: boolean, default: false} 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} 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} 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}