{
  "openapi": "3.1.0",
  "info": {
    "title": "Blackoutly Console — organisation API",
    "version": "1.0.0",
    "summary": "Let your own systems (Intune, scripts) see and remove Blackoutly activations of your organisation's computers.",
    "description": "For **Blackoutly Enterprise** organisations. Typical use: when a computer is retired in Microsoft Intune, your script removes its Blackoutly activation, so the seat is free for another computer.\n\n## Tokens\n\nThe organisation's administrator creates a token at https://console.blackoutly.com/my → *API for your systems*:\n\n- shown **once** (the console keeps only its SHA-256 hash) and starts with `bo_`;\n- valid for 30, 90, 180 or 365 days — **never more than a year**; make a new one before it ends;\n- may *read only* (`GET /api/v1/org/devices`) or *read and remove activations* (also `POST /api/v1/org/remove`);\n- works **only from the IP addresses / networks given** for it (IPv4 or IPv6, at most 20; changeable without a new token);\n- can be revoked at any time; at most 10 tokens an organisation.\n\nSend it in the header `Authorization: Bearer bo_…` over HTTPS. No cookies are used or set.\n\n## Computers\n\nA computer is named by its **computer ID** — 64 hexadecimal characters, the same the program shows in Settings → License and `BlackoutlyCli.exe computer-id` prints (`short_id` is its first 12 characters, as `A1B2-C3D4-E5F6`) — or, when removing, by its **BIOS serial number** (as Intune lists it). Computer names are never collected.\n\n## Log and limits\n\nEvery call — answered or refused — is kept for 90 days (time, token name, address, path, result, number of computers) and shown to the organisation's administrator at /my. At most 30 calls a minute from one address (then `429`), at most 200 computers in one call.\n\n## Errors\n\nAn error answer is `{\"error\": \"<code>\"}`:\n\n| Status | Code | Meaning |\n|---|---|---|\n| 400 | `request` | Not JSON, no `computers`, or more than 200 |\n| 401 | `token` | No token, or not a token of ours |\n| 401 | `revoked` | The token was revoked |\n| 401 | `expired` | The token's time is over — make a new one |\n| 403 | `edition` | The organisation has no running Blackoutly Enterprise license now (or is suspended) |\n| 403 | `address` | The call came from an address not allowed for this token |\n| 403 | `scope` | The token may only read |\n| 429 | `busy` | Too many calls from this address — wait a minute |",
    "contact": {
      "name": "Blackoutly support",
      "url": "https://console.blackoutly.com"
    }
  },
  "servers": [
    {
      "url": "https://api.blackoutly.com",
      "description": "Blackoutly Console (EU)"
    }
  ],
  "security": [
    {
      "bearerToken": []
    }
  ],
  "tags": [
    {
      "name": "Computers",
      "description": "The organisation's activated computers"
    }
  ],
  "paths": {
    "/api/v1/org/devices": {
      "get": {
        "tags": ["Computers"],
        "operationId": "listActivatedComputers",
        "summary": "The activated computers",
        "description": "Every computer of the organisation whose activation is counted now (removed ones are not listed). Needs a token that may read.",
        "responses": {
          "200": {
            "description": "The activated computers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DevicesResponse"
                },
                "example": {
                  "devices": [
                    {
                      "computer_id": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",
                      "short_id": "A1B2-C3D4-E5F6",
                      "edition": "enterprise",
                      "program": "0.0.10",
                      "last_seen": "2026-10-20T08:14:03+00:00"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/Busy"
          }
        }
      }
    },
    "/api/v1/org/remove": {
      "post": {
        "tags": ["Computers"],
        "operationId": "removeActivations",
        "summary": "Remove activations",
        "description": "Removes the activations of these computers (by serial number or computer ID): their seats are free for other computers at once. Each computer learns it at its next weekly check-in and runs as Blackoutly Free from its next start; it does not activate itself again — someone presses *Activate* in Settings → License (if a seat is free). Needs a token that may remove activations.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveRequest"
              },
              "example": {
                "computers": ["5CG1234XYZ", "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result for each computer given, in the same order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveResponse"
                },
                "example": {
                  "results": [
                    {"computer": "5CG1234XYZ", "result": "removed"},
                    {"computer": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90", "result": "not_activated"}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Not JSON, no `computers`, an empty list or more than 200",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/Error"},
                "example": {"error": "request"}
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/Busy"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bo_…",
        "description": "A token from https://console.blackoutly.com/my → API for your systems"
      }
    },
    "schemas": {
      "Device": {
        "type": "object",
        "required": ["computer_id", "short_id", "edition", "program", "last_seen"],
        "properties": {
          "computer_id": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "The full computer ID (as the program shows it in Settings → License)"
          },
          "short_id": {
            "type": "string",
            "pattern": "^[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}$",
            "description": "Its first 12 characters, for people"
          },
          "edition": {
            "type": "string",
            "enum": ["pro", "enterprise"]
          },
          "program": {
            "type": "string",
            "description": "The Blackoutly version the computer reported last (empty: not reported)",
            "examples": ["0.0.10"]
          },
          "last_seen": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Its activation or last weekly check-in (UTC)"
          }
        }
      },
      "DevicesResponse": {
        "type": "object",
        "required": ["devices"],
        "properties": {
          "devices": {
            "type": "array",
            "items": {"$ref": "#/components/schemas/Device"}
          }
        }
      },
      "RemoveRequest": {
        "type": "object",
        "required": ["computers"],
        "properties": {
          "computers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "description": "BIOS serial numbers (as Intune lists them) or full computer IDs",
            "items": {"type": "string", "maxLength": 200}
          }
        }
      },
      "RemoveResult": {
        "type": "object",
        "required": ["computer", "result"],
        "properties": {
          "computer": {
            "type": "string",
            "description": "As given (at most 80 characters)"
          },
          "result": {
            "type": "string",
            "enum": ["removed", "not_activated", "invalid"],
            "description": "removed — its activation was removed; not_activated — no counted activation of this organisation; invalid — neither a serial number nor a computer ID"
          }
        }
      },
      "RemoveResponse": {
        "type": "object",
        "required": ["results"],
        "properties": {
          "results": {
            "type": "array",
            "items": {"$ref": "#/components/schemas/RemoveResult"}
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "enum": ["request", "token", "revoked", "expired", "edition", "address", "scope", "busy"]
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "`token` — no token or not ours; `revoked`; `expired` — make a new token",
        "headers": {
          "WWW-Authenticate": {
            "schema": {"type": "string", "const": "Bearer"}
          }
        },
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/Error"},
            "example": {"error": "expired"}
          }
        }
      },
      "Forbidden": {
        "description": "`edition` — no running Blackoutly Enterprise license; `address` — this address is not allowed for the token; `scope` — the token may only read",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/Error"},
            "example": {"error": "address"}
          }
        }
      },
      "Busy": {
        "description": "More than 30 calls a minute from this address",
        "content": {
          "application/json": {
            "schema": {"$ref": "#/components/schemas/Error"},
            "example": {"error": "busy"}
          }
        }
      }
    }
  }
}
