{
  "openapi": "3.1.0",
  "info": {
    "title": "MojiCare API",
    "summary": "NG word moderation API for Japanese text",
    "description": "Moderates Japanese text and returns the masked text along with what was detected.\n\n- **Authentication**: `Authorization: Bearer mc_live_xxxxx` (Enterprise test keys use `mc_test_xxxxx`). Issue keys from your dashboard.\n- **Server-side only**: CORS is not enabled. Never ship an API key to a browser or a public repository.\n- **Requests**: `Content-Type: application/json`. The body is limited to 64KB (65536 bytes) and `text` to 5000 characters.\n- **Error language**: responses are in Japanese when `Accept-Language` contains `ja`, and in English otherwise.\n- **We do not store your text**: submitted text is used for moderation only and is never stored on our side. The response does carry your input back, though, as part of the result (`masked`, `matches`, and `segments` — concatenating the segments reproduces the original text). Keep that in mind before sending responses to logs or third-party services.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.moji-care.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Moderation",
      "description": "Text moderation"
    },
    {
      "name": "Health",
      "description": "Health check"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/moderations": {
      "post": {
        "operationId": "createModeration",
        "tags": [
          "Moderation"
        ],
        "summary": "Moderate text",
        "description": "Moderates text and returns the masked text, the detected NG words, and the segmented result.\n\nOnly successful moderations count toward your usage (error responses do not), and calls made with an Enterprise test key (`mc_test_xxxxx`) are never counted.\nThe `id` in the response is a request identifier you can quote in support requests; it matches the `X-Request-Id` header.",
        "parameters": [
          {
            "name": "Accept-Language",
            "in": "header",
            "required": false,
            "description": "Language for error messages. Japanese when the value contains `ja`, English otherwise (including when the header is absent). The moderation result itself is unaffected by this header.",
            "schema": {
              "type": "string",
              "examples": [
                "ja",
                "en"
              ]
            }
          }
        ],
        "requestBody": {
          "description": "The text to moderate and how to mask it (JSON, up to 64KB).",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ModerationRequest"
              },
              "examples": {
                "default": {
                  "summary": "Moderate text",
                  "value": {
                    "text": "これはうんこです",
                    "options": {
                      "mask_char": "*",
                      "mask_mode": "repeat"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moderation result",
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModerationResponse"
                },
                "examples": {
                  "default": {
                    "summary": "Moderation result",
                    "value": {
                      "id": "mod_9f8a7b6c5d4e",
                      "flagged": true,
                      "masked": "これは***です",
                      "matches": [
                        "うんこ"
                      ],
                      "segments": [
                        {
                          "text": "これは",
                          "clean": true,
                          "start": 0,
                          "end": 3
                        },
                        {
                          "text": "うんこ",
                          "clean": false,
                          "start": 3,
                          "end": 6
                        },
                        {
                          "text": "です",
                          "clean": true,
                          "start": 6,
                          "end": 8
                        }
                      ],
                      "usage": {
                        "chars": 8
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `invalid_request` — Missing or wrongly typed `text`, unknown keys or an invalid `mask_mode` in `options`, or `text` containing ill-formed Unicode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "Missing or wrongly typed `text`, unknown keys or an invalid `mask_mode` in `options`, or `text` containing ill-formed Unicode",
                    "value": {
                      "error": {
                        "code": "invalid_request",
                        "message": "Invalid request: {detail}"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "- `missing_api_key` — The `Authorization` header is missing or is not in the form `Bearer <api key>`\n- `invalid_api_key` — The API key does not exist or has been revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "missing_api_key": {
                    "summary": "The `Authorization` header is missing or is not in the form `Bearer <api key>`",
                    "value": {
                      "error": {
                        "code": "missing_api_key",
                        "message": "Missing Authorization header (Bearer <api key>)"
                      }
                    }
                  },
                  "invalid_api_key": {
                    "summary": "The API key does not exist or has been revoked",
                    "value": {
                      "error": {
                        "code": "invalid_api_key",
                        "message": "Invalid API key"
                      }
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "- `payment_required` — The account has no payment method registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "payment_required": {
                    "summary": "The account has no payment method registered",
                    "value": {
                      "error": {
                        "code": "payment_required",
                        "message": "No payment method registered. Please register one from the dashboard"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              }
            }
          },
          "403": {
            "description": "- `account_suspended` — The account is past the payment failure grace period, has been suspended, or has been closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "account_suspended": {
                    "summary": "The account is past the payment failure grace period, has been suspended, or has been closed",
                    "value": {
                      "error": {
                        "code": "account_suspended",
                        "message": "This account is suspended. Please check your dashboard"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              }
            }
          },
          "413": {
            "description": "- `text_too_long` — `text` is longer than 5000 Unicode code points\n- `body_too_large` — The request body is larger than 65536 bytes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "text_too_long": {
                    "summary": "`text` is longer than 5000 Unicode code points",
                    "value": {
                      "error": {
                        "code": "text_too_long",
                        "message": "text is too long ({actual} chars, limit {limit})"
                      }
                    }
                  },
                  "body_too_large": {
                    "summary": "The request body is larger than 65536 bytes",
                    "value": {
                      "error": {
                        "code": "body_too_large",
                        "message": "Request body too large (limit {limit} bytes)"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "429": {
            "description": "- `rate_limit_exceeded` — Exceeded the plan's requests-per-second limit\n- `monthly_quota_exceeded` — Included requests for the current cycle are exhausted and overage billing is disabled\n- `spend_cap_reached` — Overage charges reached the monthly spend cap you configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "rate_limit_exceeded": {
                    "summary": "Exceeded the plan's requests-per-second limit",
                    "value": {
                      "error": {
                        "code": "rate_limit_exceeded",
                        "message": "Too many requests. The limit is {limit} requests per second"
                      }
                    }
                  },
                  "monthly_quota_exceeded": {
                    "summary": "Included requests for the current cycle are exhausted and overage billing is disabled",
                    "value": {
                      "error": {
                        "code": "monthly_quota_exceeded",
                        "message": "Monthly included requests exhausted. Enable overage billing or upgrade your plan"
                      }
                    }
                  },
                  "spend_cap_reached": {
                    "summary": "Overage charges reached the monthly spend cap you configured",
                    "value": {
                      "error": {
                        "code": "spend_cap_reached",
                        "message": "Your monthly spend cap for overage has been reached"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "How long to wait before retrying, in seconds. Present on every 429 response: until the next second for `rate_limit_exceeded`, and until the next billing cycle for the quota errors.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "500": {
            "description": "- `internal_error` — An internal error in the gateway",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "internal_error": {
                    "summary": "An internal error in the gateway",
                    "value": {
                      "error": {
                        "code": "internal_error",
                        "message": "Internal server error"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "- `upstream_error` — The moderation engine did not respond within 5 seconds, or returned an error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "upstream_error": {
                    "summary": "The moderation engine did not respond within 5 seconds, or returned an error",
                    "value": {
                      "error": {
                        "code": "upstream_error",
                        "message": "The moderation engine is temporarily unavailable. Please retry later"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "required": true,
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "required": true,
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "- `service_unavailable` — An internal dependency is temporarily unavailable; retrying may succeed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "service_unavailable": {
                    "summary": "An internal dependency is temporarily unavailable; retrying may succeed",
                    "value": {
                      "error": {
                        "code": "service_unavailable",
                        "message": "The service is temporarily unavailable. Please retry later"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "X-Request-Id": {
                "description": "The request identifier, identical to `id` in the response body. Present on every response once the API key has been verified, including errors. Not present on responses rejected before authentication.",
                "schema": {
                  "type": "string",
                  "pattern": "^mod_[0-9A-Za-z]{12}$",
                  "examples": [
                    "mod_9f8a7b6c5d4e"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "The plan's requests-per-second limit (Enterprise test keys are limited to 1). Only present once rate limiting has been evaluated, i.e. not on responses returned before the plan is known.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Remaining requests in the current one-second bucket (never negative). Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset": {
                "description": "Epoch seconds at which the next one-second bucket starts. Present under the same conditions as `X-RateLimit-Limit`.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Health"
        ],
        "summary": "Health check",
        "description": "No authentication required. Returns only `{\"status\":\"ok\"}` — no internal details, and none of the rate limit headers.",
        "security": [],
        "parameters": [
          {
            "name": "Accept-Language",
            "in": "header",
            "required": false,
            "description": "Language for error messages. Japanese when the value contains `ja`, English otherwise (including when the header is absent). The moderation result itself is unaffected by this header.",
            "schema": {
              "type": "string",
              "examples": [
                "ja",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The gateway is responding",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                },
                "examples": {
                  "default": {
                    "summary": "The gateway is responding",
                    "value": {
                      "status": "ok"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send an API key issued from your dashboard as `Authorization: Bearer mc_live_xxxxx`. Treat the key as a secret. **Do not paste a live key into the authorization field on this page** — try the API with curl from your own server instead, or use an Enterprise test key (`mc_test_xxxxx`)."
      }
    },
    "schemas": {
      "ModerationRequest": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "description": "The text to moderate (1-5000 characters). Length is counted in Unicode code points, which is not the same as JavaScript `String.length`. Anything longer than 5000 characters returns 413 `text_too_long`."
          },
          "options": {
            "type": "object",
            "properties": {
              "mask_char": {
                "default": "*",
                "type": "string",
                "description": "The character used for masking (default `*`). It must be 1-8 Unicode code points; anything else returns 400 `invalid_request`. This range is deliberately not expressed as JSON Schema `minLength` / `maxLength`, because those count UTF-16 units and would disagree with the code-point based check for inputs such as emoji."
              },
              "mask_mode": {
                "default": "repeat",
                "type": "string",
                "enum": [
                  "repeat",
                  "fixed"
                ],
                "description": "How to mask. `repeat` repeats the mask character for the length of the NG word (default). `fixed` replaces each NG word with a single mask character, so with a multi-character mask the length of `masked` will not match the original text."
              }
            },
            "additionalProperties": false,
            "description": "Masking options. Unknown keys here return 400 `invalid_request`, so typos such as `mask_charr` are not silently ignored."
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false
      },
      "ModerationResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Request identifier. Same value as the `X-Request-Id` response header; quote it in support requests."
          },
          "flagged": {
            "type": "boolean",
            "description": "True when at least one NG word was detected."
          },
          "masked": {
            "type": "string",
            "description": "The text with detected NG words masked."
          },
          "matches": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The detected NG words, as they appeared in the input."
          },
          "segments": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string",
                  "description": "The text of this segment."
                },
                "clean": {
                  "type": "boolean",
                  "description": "True when this segment contains no NG word."
                },
                "start": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Start offset of the segment, in Unicode code points. JavaScript `slice` counts UTF-16 units, so this offset cannot be passed to it directly for text containing emoji and similar characters."
                },
                "end": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "End offset of the segment, in Unicode code points (exclusive)."
                }
              },
              "required": [
                "text",
                "clean",
                "start",
                "end"
              ]
            },
            "description": "The input split into moderation segments. Concatenating them reproduces the original text."
          },
          "usage": {
            "type": "object",
            "properties": {
              "chars": {
                "type": "integer",
                "minimum": 0,
                "description": "Length of the input, in Unicode code points."
              }
            },
            "required": [
              "chars"
            ],
            "description": "Usage for this call."
          }
        },
        "required": [
          "id",
          "flagged",
          "masked",
          "matches",
          "segments",
          "usage"
        ]
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "ok",
            "description": "Always `ok`."
          }
        },
        "required": [
          "status"
        ]
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "missing_api_key",
                  "invalid_api_key",
                  "payment_required",
                  "account_suspended",
                  "text_too_long",
                  "body_too_large",
                  "rate_limit_exceeded",
                  "monthly_quota_exceeded",
                  "spend_cap_reached",
                  "upstream_error",
                  "service_unavailable",
                  "internal_error"
                ],
                "description": "Machine-readable error code. Branch on this value rather than on the wording of `message`."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation, following `Accept-Language`. The curly-brace parts in the examples below are replaced with actual values in real responses."
              }
            },
            "required": [
              "code",
              "message"
            ],
            "description": "Details of the error."
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
