Testflinger API

Testflinger is Canonical's hardware test-orchestration service. Its REST API accepts test jobs against named device queues, reports job status and results, serves build artifacts, and exposes agent and queue inventory. The OpenAPI 3.0.3 contract is served live from the production host.

Operations 43

GET /v1/ Identify ourselves.
GET /v1/job Request a job to run from supported queues.
POST /v1/job Add a job to the queue.
GET /v1/job/search Search for jobs by tags.
GET /v1/agents/data Get all agent data.
POST /oidc/auth-init Initiate client request and proxy request to OIDC provider.
GET /v1/job/{job_id} Request the json job definition for a specified job, even if it has
POST /v1/oauth2/token Issue both access token and refresh token for a client.
GET /v1/agents/queues Get all advertised queues from this server.
POST /v1/agents/queues Tell testflinger the queue names that are being serviced.
POST /v1/agents/images Tell testflinger about known images for a specified queue
POST /v1/oauth2/revoke Revoke a refresh token. Only admins can perform this action.
POST /v1/oauth2/refresh Refresh access token using a valid refresh token.
GET /v1/result/{job_id} Return results for a specified job_id.
POST /v1/result/{job_id} Post a result for a specified job_id.
GET /v1/queues/wait_times Get wait time metrics - optionally take a list of queues.
GET /v1/restricted-queues List all agent's restricted queues and its owners.
GET /v1/client-permissions Retrieve all client permissions from database.
POST /v1/job/{job_id}/action Take action on the job status for a specified job ID.
POST /v1/job/{job_id}/events Post status updates from the agent to the server to be forwarded
GET /v1/agents/images/{queue} Get a dict of known images for a given queue.
GET /v1/job/{job_id}/position Return the position of the specified jobid in the queue.
GET /v1/result/{job_id}/status Return job state and phase exit codes for a specified job_id.
GET /v1/job/{job_id}/attachments Return the attachments bundle for a specified job_id.
POST /v1/job/{job_id}/attachments Post attachment bundle for a specified job_id.
GET /v1/result/{job_id}/artifact Return artifact bundle for a specified job_id.
POST /v1/result/{job_id}/artifact Post artifact bundle for a specified job_id.
GET /v1/agents/data/{agent_name} Get the information from a specified agent.
POST /v1/agents/data/{agent_name} Post information about the agent to the server.
GET /v1/queues/{queue_name}/jobs Get the jobs in a specified queue along with its state.
POST /oidc/auth-poll/{request_id} Poll for OIDC authentication result based on request ID.
GET /v1/queues/{queue_name}/agents Get the list of all data for agents listening to a specified queue.
GET /v1/restricted-queues/{queue_name} Get restricted queues for a specific agent.
POST /v1/restricted-queues/{queue_name} Add an owner to the specific restricted queue.
DELETE /v1/restricted-queues/{queue_name} Delete an owner from the specific restricted queue.
GET /v1/client-permissions/{client_id} Retrieve single client-permissions from database.
PUT /v1/client-permissions/{client_id} Add or create client permissions for a specified user.
DELETE /v1/client-permissions/{client_id} Delete client id along with its permissions.
PUT /v1/secrets/{client_id}/{path} Store a secret value for the specified client_id and path.
DELETE /v1/secrets/{client_id}/{path} Remove a secret value for the specified client_id and path.
POST /v1/agents/provision_logs/{agent_name} Post provision logs for the agent to the server.
GET /v1/result/{job_id}/log/{log_type} Get logs for a specified job_id.
POST /v1/result/{job_id}/log/{log_type} Post logs for a specified job ID.

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/testflinger-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

canonical-testflinger-api-openapi.json Raw ↑
{"info": {"title": "Testflinger API", "version": "1.0.0"}, "tags": [{"name": "V1"}, {"name": "Oidc_Api"}], "servers": [{"url": "https://testflinger.ps7.canonical.com/"}], "paths": {"/v1/": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "Identify ourselves."}}, "/v1/job": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Job"}}}, "description": "Successful response"}, "204": {"content": {"application/json": {"schema": {"type": "object", "properties": {}}}}, "description": "No job found"}}, "tags": ["V1"], "summary": "Request a job to run from supported queues.", "description": "The agent must identify itself via the ``agent_name`` cookie. One or more\n``queue`` query parameters must be supplied; the server returns the first\navailable job across those queues.\n\nAny secrets referenced in the job are resolved against the secrets store\nat this point. Secrets that are inaccessible (store unreachable, path not\nfound, or insufficient permissions) are silently resolved to an empty\nstring rather than causing the request to fail.  Agents must therefore\nhandle the possibility of empty secret values."}, "post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/JobId"}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Add a job to the queue.", "description": "The ``job_queue`` field in the submitted JSON determines which queue the\njob is placed on. All other fields are passed through to the agent\nunchanged.\n\nReturns HTTP 422 if the job references secrets that are inaccessible at\nsubmission time (e.g. the secrets store is unreachable or the secret path\ndoes not exist for the submitting client).", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Job"}}}}}}, "/v1/job/search": {"get": {"parameters": [{"in": "query", "name": "tags", "description": "List of tags to search for", "schema": {"type": "array", "items": {"type": "string"}}, "required": false, "explode": true, "style": "form"}, {"in": "query", "name": "match", "description": "Match mode - 'all' or 'any' (default 'any')", "schema": {"type": "string", "enum": ["any", "all"]}, "required": false}, {"in": "query", "name": "state", "description": "List of job states to include", "schema": {"type": "array", "items": {"type": "string", "enum": ["setup", "provision", "firmware_update", "test", "allocate", "allocated", "reserve", "cleanup", "cancelled", "completed", "active"]}}, "required": false, "explode": true, "style": "form"}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/JobSearchResponse"}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Search for jobs by tags."}}, "/v1/agents/data": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentOut"}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "Get all agent data."}}, "/oidc/auth-init": {"post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}}, "tags": ["Oidc_Api"], "summary": "Initiate client request and proxy request to OIDC provider.", "description": ":return: dict containing required OIDC parameters for client to\nauthenticate with provider"}}, "/v1/job/{job_id}": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/JobOut"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Request the json job definition for a specified job, even if it has", "description": "already run.\n\n:param job_id:\nUUID as a string for the job\n:return:\nJSON data for the job or error string and http error"}}, "/v1/oauth2/token": {"post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "Issue both access token and refresh token for a client.", "description": "Get JWT with priority and queue permissions.\n\nBefore being encrypted, the JWT can contain fields like:\n{\nexp: <Expiration DateTime of Token>,\niat: <Issuance DateTime of Token>,\nsub: <Subject Field of Token>,\npermissions: {\nmax_priority: <Queue to Priority Level Dict>,\nallowed_queues: <List of Allowed Restricted Queues>,\nmax_reservation_time: <Queue to Max Reservation Time Dict>,\n}\n}"}}, "/v1/agents/queues": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"type": "object", "additionalProperties": {"type": "string"}, "example": {"device001": "Queue for device001", "some-queue": "some other queue"}}}}, "description": "Mapping of queue names and descriptions"}}, "tags": ["V1"], "summary": "Get all advertised queues from this server.", "description": "Returns a dict of queue names and descriptions, ex:\n{\n\"some_queue\": \"A queue for testing\",\n\"other_queue\": \"A queue for something else\"\n}"}, "post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Tell testflinger the queue names that are being serviced.", "description": "Some agents may want to advertise some of the queues they listen on so that\nthe user can check which queues are valid to use.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/QueuesIn"}}}}}}, "/v1/agents/images": {"post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Tell testflinger about known images for a specified queue", "description": "images will be stored in a dict of key/value pairs as part of the queues\ncollection. That dict will contain image_name:provision_data mappings, ex:\n{\n\"some_queue\": {\n\"core22\": \"http://cdimage.ubuntu.com/.../core-22.tar.gz\",\n\"jammy\": \"http://cdimage.ubuntu.com/.../ubuntu-22.04.tar.gz\"\n},\n\"other_queue\": {\n...\n}\n}.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImagesIn"}}}}}}, "/v1/oauth2/revoke": {"post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Revoke a refresh token. Only admins can perform this action.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RefreshTokenIn"}}}}}}, "/v1/oauth2/refresh": {"post": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}}, "tags": ["V1"], "summary": "Refresh access token using a valid refresh token.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RefreshTokenIn"}}}}}}, "/v1/result/{job_id}": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResultGet"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}, "204": {"content": {}, "description": "No result found"}}, "tags": ["V1"], "summary": "Return results for a specified job_id.", "description": "Results are reconstructed from the log storage system to maintain\nbackward compatibility.  Phase exit codes are combined with captured\nlog data and returned as a flat structure:\n\n- ``{phase}_status``: exit code for each phase\n- ``{phase}_output``: stdout log for that phase (if available)\n- ``{phase}_serial``: serial console log for that phase (if available)\n- Additional metadata fields such as ``device_info`` and ``job_state``\n\n:param job_id: UUID as a string for the job\n:raises HTTPError: If the job_id is not a valid UUID"}, "post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post a result for a specified job_id.", "description": ":param job_id: UUID as a string for the job\n:raises HTTPError: If the job_id is not a valid UUID", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResultPost"}}}}}}, "/v1/queues/wait_times": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "Get wait time metrics - optionally take a list of queues."}}, "/v1/restricted-queues": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RestrictedQueueOut"}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "List all agent's restricted queues and its owners."}}, "/v1/client-permissions": {"get": {"parameters": [], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClientPermissionsOut"}}}, "description": "Successful response"}}, "tags": ["V1"], "summary": "Retrieve all client permissions from database."}}, "/v1/job/{job_id}/action": {"post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Take action on the job status for a specified job ID.", "description": ":param job_id:\nUUID as a string for the job", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ActionIn"}}}}}}, "/v1/job/{job_id}/events": {"post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post status updates from the agent to the server to be forwarded", "description": "to the server-configured webhook url.\n\nThe json sent to this endpoint may contain data such as the following:\n{\n\"agent_id\": \"<string>\",\n\"job_queue\": \"<string>\",\n\"job_status_webhook\": \"<URL as string>\",\n\"events\": [\n{\n\"event_name\": \"<string enum of events>\",\n\"timestamp\": \"<datetime>\",\n\"detail\": \"<string>\"\n},\n...\n]\n}\n\n:param job_id: UUID as a string for the job\n:param json_data: JSON data containing the status updates and webhook URL", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusUpdate"}}}}}}, "/v1/agents/images/{queue}": {"get": {"parameters": [{"in": "path", "name": "queue", "schema": {"type": "string"}, "required": true}], "responses": {"404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}, "200": {"content": {"application/json": {"schema": {"type": "object", "additionalProperties": {"type": "string"}, "example": {"core22": "url: http://.../core22.img.xz", "server-22.04": "url: http://.../ubuntu-22.04.img.xz"}}}}, "description": "Mapping of image names and provision data"}}, "tags": ["V1"], "summary": "Get a dict of known images for a given queue."}}, "/v1/job/{job_id}/position": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Return the position of the specified jobid in the queue."}}, "/v1/result/{job_id}/status": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResultStatus"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}, "204": {"content": {}, "description": "No result found"}}, "tags": ["V1"], "summary": "Return job state and phase exit codes for a specified job_id.", "description": "This is a lightweight alternative to GET /result/<job_id> that omits\nlog data (output and serial).  Use this when only the job state or\nphase statuses are needed.\n\n:param job_id: UUID as a string for the job\n:raises HTTPError: If the job_id is not a valid UUID"}}, "/v1/job/{job_id}/attachments": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Return the attachments bundle for a specified job_id.", "description": ":param job_id:\nUUID as a string for the job\n:return:\nsend_file stream of attachment tarball to download"}, "post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post attachment bundle for a specified job_id.", "description": ":param job_id:\nUUID as a string for the job"}}, "/v1/result/{job_id}/artifact": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Return artifact bundle for a specified job_id.", "description": ":param job_id:\nUUID as a string for the job\n:return:\nsend_file stream of artifact tarball to download"}, "post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post artifact bundle for a specified job_id.", "description": ":param job_id:\nUUID as a string for the job"}}, "/v1/agents/data/{agent_name}": {"get": {"parameters": [{"in": "path", "name": "agent_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentOut"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Get the information from a specified agent.", "description": ":param agent_name:\nString with the name of the agent to retrieve information from.\n:return:\nJSON data with the specified agent information."}, "post": {"parameters": [{"in": "path", "name": "agent_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post information about the agent to the server.", "description": "The json sent to this endpoint may contain data such as the following:\n{\n\"state\": string, # State the device is in\n\"queues\": array[string], # Queues the device is listening on\n\"location\": string, # Location of the device\n\"job_id\": string, # Job ID the device is running, if any\n\"log\": array[string], # push and keep only the last 100 lines\n}", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentIn"}}}}}}, "/v1/queues/{queue_name}/jobs": {"get": {"parameters": [{"in": "path", "name": "queue_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Get the jobs in a specified queue along with its state.", "description": ":param queue_name\nString with the queue name where to perform the query.\n:return:\nJSON data with the jobs allocated to the specified queue."}}, "/oidc/auth-poll/{request_id}": {"post": {"parameters": [{"in": "path", "name": "request_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["Oidc_Api"], "summary": "Poll for OIDC authentication result based on request ID.", "description": ":param request_id: unique request ID generated during auth initiation\n:return: dict containing authentication result and user info if successful"}}, "/v1/queues/{queue_name}/agents": {"get": {"parameters": [{"in": "path", "name": "queue_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentOut"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Get the list of all data for agents listening to a specified queue."}}, "/v1/restricted-queues/{queue_name}": {"get": {"parameters": [{"in": "path", "name": "queue_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RestrictedQueueOut"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Get restricted queues for a specific agent."}, "post": {"parameters": [{"in": "path", "name": "queue_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Add an owner to the specific restricted queue.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RestrictedQueueIn"}}}}}, "delete": {"parameters": [{"in": "path", "name": "queue_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Delete an owner from the specific restricted queue.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RestrictedQueueIn"}}}}}}, "/v1/client-permissions/{client_id}": {"get": {"parameters": [{"in": "path", "name": "client_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClientPermissionsOut"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Retrieve single client-permissions from database."}, "put": {"parameters": [{"in": "path", "name": "client_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Add or create client permissions for a specified user.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClientPermissionsIn"}}}}}, "delete": {"parameters": [{"in": "path", "name": "client_id", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Delete client id along with its permissions."}}, "/v1/secrets/{client_id}/{path}": {"put": {"parameters": [{"in": "path", "name": "client_id", "schema": {"type": "string"}, "required": true}, {"in": "path", "name": "path", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SecretOut"}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Store a secret value for the specified client_id and path.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SecretIn"}}}}}, "delete": {"parameters": [{"in": "path", "name": "client_id", "schema": {"type": "string"}, "required": true}, {"in": "path", "name": "path", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Remove a secret value for the specified client_id and path."}}, "/v1/agents/provision_logs/{agent_name}": {"post": {"parameters": [{"in": "path", "name": "agent_name", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post provision logs for the agent to the server.", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProvisionLogsIn"}}}}}}, "/v1/result/{job_id}/log/{log_type}": {"get": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}, {"in": "path", "name": "log_type", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LogGet"}}}, "description": "Successful response"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Get logs for a specified job_id.", "description": "Logs are persistent and may be retrieved multiple times.  Results are\norganised by phase.  Each phase entry contains:\n\n- ``last_fragment_number``: highest fragment number stored for that phase\n- ``log_data``: combined log text from all matching fragments\n\nOptional query parameters for filtering:\n\n- ``phase``: restrict results to a single test phase\n- ``start_fragment``: return only fragments from this number onwards\n- ``start_timestamp``: return only fragments created after this\nISO 8601 timestamp\n\n:param job_id: UUID as a string for the job\n:param log_type: LogType enum value for the type of log requested\n:raises HTTPError: If the job_id is not a valid UUID or if invalid query\n:return: Dictionary with log data"}, "post": {"parameters": [{"in": "path", "name": "job_id", "schema": {"type": "string"}, "required": true}, {"in": "path", "name": "log_type", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"content": {"application/json": {"schema": {}}}, "description": "Successful response"}, "422": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ValidationError"}}}, "description": "Validation error"}, "404": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/HTTPError"}}}, "description": "Not found"}}, "tags": ["V1"], "summary": "Post logs for a specified job ID.", "description": "Agents stream log data in sequential fragments.  Each request must\ninclude:\n\n- ``fragment_number``: sequential integer starting from 0\n- ``timestamp``: ISO 8601 timestamp when the fragment was created\n- ``phase``: test phase name (setup, provision, firmware_update, test,\nallocate, reserve, cleanup)\n- ``log_data``: the log content for this fragment\n\n:param job_id: UUID as a string for the job\n:param log_type: LogType enum value for the type of log being posted\n:raises HTTPError: If the job_id is not a valid UUID\n:param json_data: Dictionary with log data", "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LogPost"}}}}}}}, "openapi": "3.0.3", "components": {"schemas": {"JobId": {"type": "object", "properties": {"job_id": {"type": "string"}}, "required": ["job_id"], "additionalProperties": false}, "ValidationError": {"properties": {"detail": {"type": "object", "properties": {"<location>": {"type": "object", "properties": {"<field_name>": {"type": "array", "items": {"type": "string"}}}}}}, "message": {"type": "string"}}, "type": "object"}, "ProvisionData": {"type": "object", "properties": {}, "additionalProperties": false}, "Attachment": {"type": "object", "properties": {"agent": {"type": "string"}, "device": {"type": "string"}}, "required": ["agent"], "additionalProperties": false}, "TestData": {"type": "object", "properties": {"test_cmds": {"type": "string"}, "attachments": {"type": "array", "items": {"$ref": "#/components/schemas/Attachment"}}, "test_username": {"type": "string"}, "test_password": {"type": "string"}, "secrets": {"type": "object", "additionalProperties": {"type": "string", "pattern": "^[a-zA-Z0-9_/-]+$"}}}, "additionalProperties": false}, "ReserveData": {"type": "object", "properties": {"ssh_keys": {"type": "array", "items": {"type": "string", "pattern": "^(lp|gh):(\\S+)$"}}, "timeout": {"nullable": true}}, "additionalProperties": false}, "Job": {"type": "object", "properties": {"job_id": {"type": "string"}, "parent_job_id": {"type": "string"}, "name": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, "job_queue": {"type": "string", "minLength": 1}, "global_timeout": {"type": "integer"}, "output_timeout": {"type": "integer"}, "allocation_timeout": {"type": "integer"}, "provision_data": {"anyOf": [{"type": "object", "nullable": true}, {"$ref": "#/components/schemas/ProvisionData"}]}, "firmware_update_data": {"type": "object", "additionalProperties": {}}, "test_data": {"$ref": "#/components/schemas/TestData"}, "allocate_data": {"type": "object", "additionalProperties": {}}, "reserve_data": {"$ref": "#/components/schemas/ReserveData"}, "job_status_webhook": {"type": "string"}, "job_priority": {"type": "integer"}, "exclude_agents": {"type": "array", "items": {"type": "string"}}, "debug": {"type": "boolean"}}, "required": ["job_queue"], "additionalProperties": false}, "JobSearchResponse": {"type": "object", "properties": {"jobs": {"type": "array", "items": {"$ref": "#/components/schemas/Job"}}}, "required": ["jobs"], "additionalProperties": false}, "AgentJob": {"type": "object", "properties": {"job_id": {"type": "string"}, "submitted_by": {"type": "string", "default": null, "nullable": true}, "created_at": {"type": "string", "format": "date-time"}, "started_at": {"type": "string", "format": "date-time"}, "job_queue": {"type": "string"}, "job_state": {"type": "string"}, "job_priority": {"type": "integer"}, "tags": {"type": "array", "items": {"type": "string"}}}, "required": ["job_id"], "additionalProperties": false}, "AgentOut": {"type": "object", "properties": {"name": {"type": "string"}, "job_id": {"type": "string"}, "state": {"type": "string"}, "queues": {"type": "array", "items": {"type": "string"}}, "location": {"type": "string"}, "provision_type": {"type": "string"}, "comment": {"type": "string"}, "restricted_to": {"type": "object", "additionalProperties": {}}, "job": {"anyOf": [{"type": "object", "nullable": true}, {"$ref": "#/components/schemas/AgentJob"}]}}, "required": ["name"], "additionalProperties": false}, "JobOut": {"type": "object", "properties": {"job_id": {"type": "string"}, "parent_job_id": {"type": "string"}, "name": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, "job_queue": {"type": "string", "minLength": 1}, "global_timeout": {"type": "integer"}, "output_timeout": {"type": "integer"}, "allocation_timeout": {"type": "integer"}, "provision_data": {"anyOf": [{"type": "object", "nullable": true}, {"$ref": "#/components/schemas/ProvisionData"}]}, "firmware_update_data": {"type": "object", "additionalProperties": {}}, "test_data": {"$ref": "#/components/schemas/TestData"}, "allocate_data": {"type": "object", "additionalProperties": {}}, "reserve_

# --- truncated at 32 KB (38 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/canonical/refs/heads/main/openapi/canonical-testflinger-api-openapi.json