{
	"openapi": "3.2.0",
	"info": {
		"title": "Mockaton Control API",
		"version": "1.0.1"
	},
	"servers": [
		{
			"url": "http://localhost:2020"
		}
	],
	"paths": {
		"/mockaton/openapi": {
			"get": {
				"summary": "Get OpenAPI spec",
				"x-js-client-example": "await mockaton.getOpenAPI()",
				"responses": {
					"200": {
						"description": "OpenAPI spec",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"additionalProperties": true
								}
							}
						}
					}
				}
			}
		},

		"/mockaton/state": {
			"get": {
				"summary": "Get complete Mockaton state",
				"x-js-client-example": "await mockaton.getState()",
				"responses": {
					"200": {
						"description": "Mockaton state",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/State"
								}
							}
						}
					}
				}
			}
		},
		"/mockaton/write-mock": {
			"patch": {
				"summary": "Write a mock file",
				"description": "Writes a mock file to the mocks directory. Requires `config.readOnly = false` and `config.watcherEnabled = true`.",
				"x-js-client-example": "await mockaton.writeMock('api/user/friends.GET.200.json', '{ \"friends\": [] }')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "array",
								"minItems": 2,
								"maxItems": 2,
								"prefixItems": [
									{
										"$ref": "#/components/schemas/Filename"
									},
									{
										"type": "string",
										"description": "File content"
									}
								],
								"additionalItems": false
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK. The mock has been written and registered successfully."
					},
					"403": {
						"description": "Forbidden (read-only mode or outside mocksDir)"
					}
				}
			}
		},
		"/mockaton/delete-mock": {
			"patch": {
				"summary": "Delete a mock file",
				"description": "Deletes a mock file from the mocks directory. Requires `config.readOnly=false`",
				"x-js-client-example": "await mockaton.deleteMock('api/user/friends.GET.200.json')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/Filename"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"403": {
						"description": "Forbidden (read-only mode or outside mocksDir)"
					},
					"422": {
						"description": "Mock file does not exist"
					}
				}
			}
		},
		"/mockaton/reset": {
			"patch": {
				"summary": "Re-initialize Mockaton",
				"description": "The selected mocks, cookies, and delays go back to their defaults, but `proxyFallback`, `collectProxied`, and `corsAllowed` are not affected.",
				"x-js-client-example": "await mockaton.reset()",
				"responses": {
					"200": {
						"description": "OK"
					}
				}
			}
		},
		"/mockaton/select": {
			"patch": {
				"summary": "Select a mock file for a route",
				"x-js-client-example": "await mockaton.select('api/user/avatar.GET.json')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/Filename"
							}
						}
					}
				},
				"responses": {
					"200": {
						"$ref": "#/components/responses/Broker"
					},
					"422": {
						"description": "Mock file doesn’t exist",
						"content": {
							"application/json": {
								"schema": {
									"type": "string"
								},
								"example": "Mock file does not exist"
							}
						}
					}
				}
			}
		},
		"/mockaton/bulk-select-by-comment": {
			"patch": {
				"summary": "Select all mocks that have a particular comment",
				"x-js-client-example": "await mockaton.bulkSelectByComment('(demo-a)')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "string",
								"description": "Parentheses are optional, so you can pass a partial match. For example, passing `'demo-'` (without the final `a`) works too. On routes with many partial matches, their first mock in alphabetical order wins.",
								"example": "(demo-a)"
							}
						}
					}
				},
				"responses": {
					"200": {
						"$ref": "#/components/responses/Broker"
					}
				}
			}
		},
		"/mockaton/toggle-status": {
			"patch": {
				"summary": "Toggle a specific status for a route",
				"description": "Selects the first found mock with the given status, which could be the autogenerated one. Or, selects the default file.",
				"x-js-client-example": "await mockaton.toggleStatus('GET', '/api/user/friends', 500)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "array",
								"minItems": 3,
								"maxItems": 3,
								"prefixItems": [
									{
										"$ref": "#/components/schemas/Method"
									},
									{
										"$ref": "#/components/schemas/UrlMask"
									},
									{
										"type": "number",
										"description": "HTTP Status to toggle",
										"example": 500
									}
								],
								"additionalItems": false
							},
							"example": [
								"GET",
								"/api/user",
								500
							]
						}
					}
				},
				"responses": {
					"200": {
						"$ref": "#/components/responses/Broker"
					},
					"422": {
						"description": "Route does not exist"
					}
				}
			}
		},
		"/mockaton/delay": {
			"patch": {
				"summary": "Set whether a route is delayed",
				"x-js-client-example": "await mockaton.setRouteIsDelayed('GET', '/api/user/friends', true)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "array",
								"minItems": 3,
								"maxItems": 3,
								"prefixItems": [
									{
										"$ref": "#/components/schemas/Method"
									},
									{
										"$ref": "#/components/schemas/UrlMask"
									},
									{
										"type": "boolean",
										"description": "delayed",
										"example": true
									}
								],
								"additionalItems": false
							},
							"example": [
								"GET",
								"/api/user",
								true
							]
						}
					}
				},
				"responses": {
					"200": {
						"$ref": "#/components/responses/Broker"
					},
					"422": {
						"description": "Invalid request. Possible reasons:\n- Route does not exist\n- Expected boolean for `delayed`\n"
					}
				}
			}
		},
		"/mockaton/global-delay": {
			"patch": {
				"summary": "Set global delay for all responses",
				"x-js-client-example": "await mockaton.setGlobalDelay(1500)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "integer",
								"description": "Delay in milliseconds",
								"example": 1500
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Expected non-negative integer"
					}
				}
			}
		},
		"/mockaton/global-delay-jitter": {
			"patch": {
				"summary": "Set global delay jitter for all responses",
				"x-js-client-example": "await mockaton.setGlobalDelayJitter(0.5)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "number",
								"description": "0 to 3 float percent",
								"example": 0.5
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Expected 0 to 3 float"
					}
				}
			}
		},
		"/mockaton/cookies": {
			"patch": {
				"summary": "Select a cookie label",
				"x-js-client-example": "await mockaton.selectCookie('Normal User')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "string",
								"description": "Cookie key",
								"example": "Normal User"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Available cookie labels",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/CookieSelectionList"
								}
							}
						}
					},
					"422": {
						"description": "Cookie key not found"
					}
				}
			}
		},
		"/mockaton/cors": {
			"patch": {
				"summary": "Enable or disable CORS",
				"x-js-client-example": "await mockaton.setCorsAllowed(false)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "boolean",
								"description": "Is enabled?"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Expected boolean"
					}
				}
			}
		},
		"/mockaton/fallback": {
			"patch": {
				"summary": "Set proxy fallback address",
				"x-js-client-example": "await mockaton.setProxyFallback('http://example.test')",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "string",
								"description": "URL",
								"example": "https://example.test"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Invalid Proxy Fallback URL"
					}
				}
			}
		},
		"/mockaton/collect-proxied": {
			"patch": {
				"summary": "Enable or disable collection of proxied responses",
				"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
				"x-js-client-example": "await mockaton.setCollectProxied(true)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "boolean",
								"description": "Should Collect?",
								"example": true
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Expected boolean"
					}
				}
			}
		},
		"/mockaton/proxied": {
			"patch": {
				"summary": "Set whether a route is proxied",
				"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
				"x-js-client-example": "await mockaton.setRouteIsProxied('GET', '/api/user/friends', true)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "array",
								"minItems": 3,
								"maxItems": 3,
								"prefixItems": [
									{
										"$ref": "#/components/schemas/Method"
									},
									{
										"$ref": "#/components/schemas/UrlMask"
									},
									{
										"type": "boolean",
										"description": "proxied",
										"example": true
									}
								],
								"additionalItems": false
							},
							"example": [
								"GET",
								"/api/user",
								true
							]
						}
					}
				},
				"responses": {
					"200": {
						"$ref": "#/components/responses/Broker"
					},
					"422": {
						"description": "Invalid request. Possible reasons:\n- Route does not exist\n- Expected boolean for `proxied`\n- No `proxyFallback` configured\n"
					}
				}
			}
		},
		"/mockaton/watch-mocks": {
			"patch": {
				"summary": "Enable or disable mock file watching",
				"description": "Controls file watchers for mocksDir.",
				"x-js-client-example": "await mockaton.setWatchMocks(true)",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"type": "boolean",
								"description": "true to start watchers, false to stop them"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "OK"
					},
					"422": {
						"description": "Invalid input - expected boolean"
					}
				}
			}
		},
		"/mockaton/sync-version": {
			"get": {
				"summary": "Get sync version as SSE",
				"description": "A counter that’s incremented when a new mock is added, removed, or renamed. Also, when the internal state changes, e.g., when changing the mock file for a route.",
				"x-js-client-example": "await mockaton.getSyncVersion()",
				"responses": {
					"200": {
						"description": "Stream of sync version updates (SSE)",
						"content": {
							"text/event-stream": {
								"schema": {
									"type": "integer",
									"description": "Incremental sync version emitted per event"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"responses": {
			"Broker": {
				"description": "Updated broker state",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/ClientMockBroker"
						}
					}
				}
			}
		},
		"schemas": {
			"Method": {
				"type": "string",
				"description": "HTTP Method of the mock",
				"example": "GET"
			},
			"UrlMask": {
				"type": "string",
				"example": "/api/user/friends"
			},
			"Filename": {
				"type": "string",
				"description": "Mock filename. The convention is UrlMask.Method.StatusCode.Extension",
				"example": "api/user/friends.GET.200.json"
			},
			"ClientMockBroker": {
				"type": "object",
				"properties": {
					"mocks": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/Filename"
						}
					},
					"file": {
						"$ref": "#/components/schemas/Filename"
					},
					"status": {
						"type": "number"
					},
					"autoStatus": {
						"type": "number"
					},
					"isStatic": {
						"type": "boolean"
					},
					"delayed": {
						"type": "boolean"
					},
					"proxied": {
						"type": "boolean"
					}
				},
				"required": [
					"mocks",
					"file",
					"status",
					"autoStatus",
					"isStatic",
					"delayed",
					"proxied"
				]
			},
			"State": {
				"type": "object",
				"properties": {
					"brokersByMethod": {
						"type": "object",
						"additionalProperties": {
							"type": "object",
							"additionalProperties": {
								"$ref": "#/components/schemas/ClientMockBroker"
							}
						}
					},
					"cookies": {
						"$ref": "#/components/schemas/CookieSelectionList"
					},
					"comments": {
						"type": "array",
						"items": {
							"type": "string"
						}
					},
					"delay": {
						"type": "number"
					},
					"delayJitter": {
						"type": "number"
					},
					"collectProxied": {
						"type": "boolean"
					},
					"proxyFallback": {
						"type": "string"
					},
					"readOnly": {
						"type": "boolean"
					},
					"corsAllowed": {
						"type": "boolean"
					}
				},
				"required": [
					"brokersByMethod",
					"cookies",
					"comments",
					"delay",
					"delayJitter",
					"collectProxied",
					"proxyFallback",
					"readOnly",
					"corsAllowed"
				]
			},
			"CookieSelectionList": {
				"type": "array",
				"items": {
					"type": "array",
					"minItems": 2,
					"maxItems": 2,
					"prefixItems": [
						{
							"type": "string",
							"description": "Label"
						},
						{
							"type": "boolean",
							"description": "Selected"
						}
					],
					"additionalItems": false
				}
			}
		}
	}
}
