{
  "openapi": "3.1.0",
  "info": {
    "title": "FlowMask Cloud API",
    "version": "v1",
    "description": "Detect and mask personal data in a text. One operation: `POST /api/v1/check`. Every response is a JSON envelope; a refusal carries a stable `code` and `errorId`, listed in `x-flowmask-errors`."
  },
  "servers": [
    {
      "url": "https://api.jp.flowmask.ai"
    }
  ],
  "tags": [
    {
      "name": "Check",
      "description": "Detect and mask personal data in a text."
    }
  ],
  "security": [
    {
      "Bearer": []
    }
  ],
  "paths": {
    "/api/{version}/check": {
      "parameters": [
        {
          "name": "version",
          "in": "path",
          "required": true,
          "description": "API version. Another value answers 404 `api_version_not_supported`.",
          "schema": {
            "type": "string",
            "enum": [
              "v1"
            ]
          }
        }
      ],
      "post": {
        "operationId": "check",
        "tags": [
          "Check"
        ],
        "summary": "Check a text for personal data",
        "description": "Finds the personal data in `text` and returns each finding with its position, the verdict and the text with every finding replaced by its mask label. The data types that are checked are the ones enabled for the workspace in the console; the request cannot change them.\n\nA request that is refused before the limits (400, 401, 404) costs no rate-limit room and no quota. Only a 200 is charged: its `inputTokens` are added to the monthly quota. A 500 or 503 charges no tokens but may still count toward the per-minute rate limit.",
        "security": [
          {
            "Bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckRequest"
              },
              "example": {
                "text": "Call Taro Yamada at 090-1234-5678."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The text was checked.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Quota-Tokens-Remaining": {
                "description": "Input tokens left in the monthly quota of the workspace, after this request was charged. Never below 0. Input tokens are currently counted as UTF-8 bytes of `text` (an ASCII character is 1 token, a Japanese character usually 3). The counting method may change; `inputTokens` in the response is authoritative.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-Quota-Reset": {
                "$ref": "#/components/headers/X-Quota-Reset"
              },
              "X-Ignored-Fields": {
                "description": "The top-level request fields other than `text`, which the API ignored, joined with \", \": sorted, at most 10 names. A name longer than 64 characters or with a character outside `A-Z a-z 0-9 _ . -` is left out. Absent when the body had no other field.",
                "required": false,
                "schema": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_.-]{1,64}(, [A-Za-z0-9_.-]{1,64}){0,9}$"
                }
              },
              "Cache-Control": {
                "description": "The result is never cached.",
                "required": true,
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckSuccess"
                },
                "example": {
                  "data": {
                    "result": "UNSAFE",
                    "confidence": 0.99,
                    "findings": [
                      {
                        "text": "Taro Yamada",
                        "start": 5,
                        "end": 16,
                        "type": "person",
                        "group": "BASIC"
                      },
                      {
                        "text": "090-1234-5678",
                        "start": 20,
                        "end": 33,
                        "type": "phone_number",
                        "group": "BASIC"
                      }
                    ],
                    "maskedText": "Call [PERSON] at [PHONE].",
                    "inputTokens": 34,
                    "characters": 34,
                    "requestId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C",
                    "region": "JP",
                    "apiVersion": "v1"
                  },
                  "code": "http.200",
                  "message": "OK"
                }
              }
            }
          },
          "400": {
            "description": "The body was refused: `validation_failed` with one item in `errors` (see `x-flowmask-field-errors`). Not charged.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version-Optional"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region-Optional"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "validation_failed",
                  "message": "Request is invalid.",
                  "errorId": "FMS400",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C",
                  "errors": [
                    {
                      "field": "text",
                      "code": "text_too_long",
                      "message": "Text is 16,001 input tokens. The limit is 16,000 input tokens.",
                      "maxInputTokens": 16000,
                      "inputTokens": 16001
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: the `Authorization` header is missing or does not hold a usable key. A missing, malformed, unknown or deleted key and a key of another region all give the same answer. Not charged.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "unauthorized",
                  "message": "Authentication required.",
                  "errorId": "FMS401",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C"
                }
              }
            }
          },
          "404": {
            "description": "`api_version_not_supported` for a version that does not run (the answer has no `X-Api-Version` or `X-Region`); `not_found` for any other path or method under `/api/v1`. Not charged.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version-Optional"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region-Optional"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "api_version_not_supported",
                  "message": "API version v2 does not exist. Available versions: v1.",
                  "errorId": "FMC01",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: the workspace used all its requests of this clock minute. `monthly_quota_exceeded`: the monthly quota of input tokens is used up; it has no `X-RateLimit-Reset`. Wait `Retry-After` seconds before you retry. Not charged.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region"
              },
              "Retry-After": {
                "description": "Seconds to wait: to the next clock minute for `rate_limited`, to the quota reset for `monthly_quota_exceeded`.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "description": "Seconds to the next clock minute. Only on `rate_limited`; equal to `Retry-After`.",
                "required": false,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-Quota-Tokens-Remaining": {
                "description": "Input tokens left in the monthly quota of the workspace. `0` on `monthly_quota_exceeded`. Input tokens are currently counted as UTF-8 bytes of `text` (an ASCII character is 1 token, a Japanese character usually 3). The counting method may change; `inputTokens` in the response is authoritative.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-Quota-Reset": {
                "$ref": "#/components/headers/X-Quota-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "rate_limited",
                  "message": "Too many requests. Try again later.",
                  "errorId": "FMS429",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`: the server failed. No input tokens are charged to the monthly quota. A request that had already passed the rate-limit check still counts toward the per-minute rate limit of that minute; it is not given back. Retry later; quote `traceId` to support if it persists.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "internal_error",
                  "message": "Internal server error.",
                  "errorId": "FMS500",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`: the detection model or another part of the service did not answer in time. No input tokens are charged to the monthly quota. A request that had already passed the rate-limit check still counts toward the per-minute rate limit of that minute; it is not given back. Retry later with a backoff.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Api-Version": {
                "$ref": "#/components/headers/X-Api-Version-Optional"
              },
              "X-Region": {
                "$ref": "#/components/headers/X-Region-Optional"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "data": null,
                  "code": "service_unavailable",
                  "message": "Service temporarily unavailable.",
                  "errorId": "FMS503",
                  "traceId": "req_01JA8Z6Q3K7M2V9X4T5R8N1B0C"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key of the workspace, created in the console at https://jp.flowmask.ai: `Authorization: Bearer fm_key_...` (`fm_key_` and 43 letters or digits). The key is not accepted in any other header."
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "The id of this request, the same value as `traceId` or `data.requestId`. Quote it to support.",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "X-Api-Version": {
        "description": "The API version that answered.",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "v1"
          ]
        }
      },
      "X-Api-Version-Optional": {
        "description": "The API version that answered. Absent on `api_version_not_supported`; may also be absent on a 400 for an oversized body and on some 503 responses.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "v1"
          ]
        }
      },
      "X-Region": {
        "description": "The region that answered: `JP` for https://api.jp.flowmask.ai.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        }
      },
      "X-Region-Optional": {
        "description": "The region that answered: `JP` for https://api.jp.flowmask.ai. Absent on `api_version_not_supported`; may also be absent on a 400 for an oversized body and on some 503 responses.",
        "required": false,
        "schema": {
          "type": "string",
          "pattern": "^[A-Z]{2}$"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests the workspace can still send in this clock minute.",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "X-RateLimit-Reset": {
        "description": "Seconds to the next clock minute, when the minute limit starts again.",
        "required": true,
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "X-Quota-Reset": {
        "description": "When the monthly quota starts again: 00:00 on day 1 of the next month, region time, as RFC 3339 with the region offset (for example `2026-12-01T00:00:00+09:00`).",
        "required": true,
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "schemas": {
      "CheckRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "description": "The text to check, at most 16,000 input tokens; the whole body is at most 4 MiB (4,194,304 bytes). It must hold a character that is not white space. Positions in the answer count Unicode code points of this exact string. Input tokens are currently counted as UTF-8 bytes of `text` (an ASCII character is 1 token, a Japanese character usually 3). The counting method may change; `inputTokens` in the response is authoritative."
          }
        },
        "additionalProperties": true,
        "description": "A JSON object. Fields other than `text` are ignored and named in `X-Ignored-Fields`."
      },
      "CheckSuccess": {
        "type": "object",
        "required": [
          "data",
          "code",
          "message"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CheckData"
          },
          "code": {
            "type": "string",
            "const": "http.200"
          },
          "message": {
            "type": "string",
            "const": "OK"
          }
        }
      },
      "CheckData": {
        "type": "object",
        "required": [
          "result",
          "confidence",
          "findings",
          "maskedText",
          "inputTokens",
          "characters",
          "requestId",
          "region",
          "apiVersion"
        ],
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "SAFE",
              "UNSAFE"
            ],
            "description": "`SAFE` when `findings` is empty, `UNSAFE` otherwise."
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "How sure the model is of its verdict, with two decimals."
          },
          "findings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Finding"
            },
            "description": "The personal data found, in text order and without overlaps. Only types enabled for the workspace. Empty for `SAFE`."
          },
          "maskedText": {
            "type": "string",
            "description": "The text with each finding replaced by the mask label of its type, for example `[PHONE]`. The original text when there is no finding."
          },
          "inputTokens": {
            "type": "integer",
            "minimum": 0,
            "description": "Input tokens of the text: what this request costs in the monthly quota. Input tokens are currently counted as UTF-8 bytes of `text` (an ASCII character is 1 token, a Japanese character usually 3). The counting method may change; `inputTokens` in the response is authoritative."
          },
          "characters": {
            "type": "integer",
            "minimum": 1,
            "description": "Unicode code points of the text."
          },
          "requestId": {
            "type": "string",
            "pattern": "^req_[0-9A-HJKMNP-TV-Z]{26}$",
            "description": "The id of this request, the same value as `X-Request-Id`."
          },
          "region": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "The region that answered: `JP` for https://api.jp.flowmask.ai."
          },
          "apiVersion": {
            "type": "string",
            "enum": [
              "v1"
            ]
          }
        }
      },
      "Finding": {
        "type": "object",
        "required": [
          "text",
          "start",
          "end",
          "type",
          "group"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "The text of the finding."
          },
          "start": {
            "type": "integer",
            "minimum": 0,
            "description": "Offset of the first code point, from 0."
          },
          "end": {
            "type": "integer",
            "minimum": 1,
            "description": "Offset after the last code point (exclusive)."
          },
          "type": {
            "type": "string",
            "description": "The data type id, one of the ids listed at https://docs.flowmask.ai/en/docs/reference/data-types/."
          },
          "group": {
            "type": "string",
            "enum": [
              "BASIC",
              "SENSITIVE"
            ],
            "description": "The legal group of the data type."
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "data",
          "code",
          "message",
          "errorId",
          "traceId"
        ],
        "properties": {
          "data": {
            "type": "null"
          },
          "code": {
            "type": "string",
            "enum": [
              "validation_failed",
              "unauthorized",
              "api_version_not_supported",
              "not_found",
              "rate_limited",
              "monthly_quota_exceeded",
              "internal_error",
              "service_unavailable"
            ],
            "description": "Stable machine code; branch on it, not on `message`."
          },
          "message": {
            "type": "string",
            "description": "English text for a person. The texts are in `x-flowmask-errors`; `api_version_not_supported` and `monthly_quota_exceeded` fill in their placeholders."
          },
          "errorId": {
            "type": "string",
            "enum": [
              "FMS400",
              "FMS401",
              "FMC01",
              "FMS404",
              "FMS429",
              "FMQ01",
              "FMS500",
              "FMS503"
            ],
            "description": "Stable id of the error, one per `code`."
          },
          "traceId": {
            "type": "string",
            "description": "The id of this request, the same value as `X-Request-Id`. Quote it to support."
          },
          "errors": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/FieldError"
            },
            "description": "Only on 400 `validation_failed`: the field that was refused."
          }
        }
      },
      "FieldError": {
        "type": "object",
        "required": [
          "field",
          "code",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "enum": [
              "body",
              "text"
            ]
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_json",
              "text_empty",
              "text_must_be_string",
              "text_too_long"
            ],
            "description": "Stable machine code; the texts are in `x-flowmask-field-errors`."
          },
          "message": {
            "type": "string"
          },
          "maxInputTokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Only on `text_too_long`: the limit of input tokens per request."
          },
          "inputTokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Only on `text_too_long` when the text was counted: its input tokens. Absent when the body was over 4 MiB, because it was not read to the end."
          }
        }
      }
    }
  },
  "x-flowmask-errors": [
    {
      "status": 400,
      "code": "validation_failed",
      "errorId": "FMS400",
      "message": "Request is invalid."
    },
    {
      "status": 401,
      "code": "unauthorized",
      "errorId": "FMS401",
      "message": "Authentication required."
    },
    {
      "status": 404,
      "code": "api_version_not_supported",
      "errorId": "FMC01",
      "message": "API version {v} does not exist. Available versions: v1."
    },
    {
      "status": 404,
      "code": "not_found",
      "errorId": "FMS404",
      "message": "Not found."
    },
    {
      "status": 429,
      "code": "rate_limited",
      "errorId": "FMS429",
      "message": "Too many requests. Try again later."
    },
    {
      "status": 429,
      "code": "monthly_quota_exceeded",
      "errorId": "FMQ01",
      "message": "Monthly quota reached. 0 input tokens left. The quota resets at {reset}. To raise it, email {support}."
    },
    {
      "status": 500,
      "code": "internal_error",
      "errorId": "FMS500",
      "message": "Internal server error."
    },
    {
      "status": 503,
      "code": "service_unavailable",
      "errorId": "FMS503",
      "message": "Service temporarily unavailable."
    }
  ],
  "x-flowmask-field-errors": [
    {
      "field": "body",
      "code": "invalid_json",
      "message": "Request body must be a JSON object."
    },
    {
      "field": "text",
      "code": "text_empty",
      "message": "Text is empty."
    },
    {
      "field": "text",
      "code": "text_must_be_string",
      "message": "Text must be a string."
    },
    {
      "field": "text",
      "code": "text_too_long",
      "message": "Text is {inputTokens} input tokens. The limit is {maxInputTokens} input tokens."
    },
    {
      "field": "text",
      "code": "text_too_long",
      "message": "Text is too long. The limit is {maxInputTokens} input tokens."
    }
  ]
}
