Developer guide

Virgo

A Belumind product · Vietnamese contract review

Product page

Send a Vietnamese contract — a PDF whose text can be selected, a Word file, a text file, or just paste the text — and get back the clauses to read carefully, where they are on the page, and what to ask the other side. Each clause is checked against nine common risks, penalties and interest are checked against legal limits, and blanks left unfilled are flagged. Results are suggestions — they do not replace a lawyer or make the decision for you.

Address for your software (API)

https://api.belumind.com/virgo

Address for AI assistants (MCP)

https://mcp.belumind.com/virgo
Input
PDF, Word, text file or pasted text
Speed
~300 ms / page

Access keys

Every request sends two things: an API key for this product, made in the dashboard under API keys, and the access token of the signed-in user who owns it. The token lasts 15 minutes, so refresh it and send the new one. A key made for another product gets a 403 error. That means you can give an AI assistant a key that reaches exactly one tool and nothing else.

AI assistants send the same two headers when they connect to the address above.

contracts:analyze
How the keys are sent
X-API-Key: bm_live_7f3c…
Authorization: Bearer eyJhbGciOi…
Content-Type: application/json
POST/v1/analyzecontracts:analyze

Review a contract file

Splits the contract into clauses and returns the ones worth attention. Each one comes with its risk group (`code`), a level — `doc_ky` (read carefully) or `xem_xet` (worth a look) — the exact sentence behind it, its position on the page for highlighting, and advice: what to ask, what to note, and a clause you could add. Word and text files have no pages, so no positions are given; a scanned PDF whose text cannot be selected comes back empty, with a note in `document.warnings`. Sending the same file again soon gets an instant answer (`cached: true`). `POST /v1/analyze/stream` takes the same file and sends results page by page as they are ready, ending with this same result. Through MCP, `analyze_contract` gives each clause an id such as `c3`; `get_clause` returns that clause's full text, explanation, legal basis and advice, using the `analysis_id` printed at the end of the analysis.

AI assistant toolanalyze_contract

What to send

filefilerequired
The contract file: a PDF with selectable text, a Word .docx, or a .txt / .md text file. Old .doc files are not accepted (error 415).
include_allboolean · queryoptional
Also list clauses that have no suggestion. Off by default.
compactboolean · queryoptional
Send a shorter answer without the long explanations and legal references — you can look them up by `code` with `GET /v1/risk-types`. Between 20% and 65% smaller: the saving is largest on short contracts. Off by default.

What you get back

  • 200 OKIt worked. You get back what the example shows.
  • 400 Bad RequestSomething in what you sent was wrong or missing. The answer says which part.
  • 401 UnauthorizedThe API key or access token is missing, mistyped or no longer valid. Access tokens expire after 15 minutes — refresh it and send the new one.
  • 403 ForbiddenThe key works, but it was made for another product.
  • 402 Payment RequiredThe account is out of credits. Buy more in the dashboard, or turn on automatic top-up.
  • 429 Too Many RequestsToo many requests in a short time, or the key has reached the monthly limit set on it. Wait the number of seconds given in `Retry-After`, then try again.
  • 500 Internal Server ErrorSomething went wrong on our side. It is safe to send the same request again.
  • 503 Service UnavailableThe queue is full, or the dashboard that checks keys could not be reached. Try again a little later.

What you send

curl -X POST https://api.belumind.com/virgo/v1/analyze \
  -H "X-API-Key: $BELUMIND_API_KEY" \
  -H "Authorization: Bearer $BELUMIND_ACCESS_TOKEN" \
  -F "file=@hop_dong_lao_dong.pdf"

What you get back

application/json200 OK
{
  "version": "1.11.0",
  "model_version": "v1.11",
  "document": {
    "file_name": "hop_dong_lao_dong.pdf",
    "pages": [
      {
        "index": 0,
        "width": 595.3,
        "height": 841.9,
        "rotation": 0
      }
    ],
    "n_clauses": 2,
    "n_chars": 132,
    "is_credit_institution": false,
    "is_labour_contract": false,
    "warnings": [],
    "domain": "dieu_kien_giao_dich"
  },
  "summary": {
    "n_flagged_clauses": 1,
    "n_blank_fields": 1,
    "n_capped": 0,
    "by_risk": {
      "DON_PHUONG_CHAM_DUT": {
        "doc_ky": 1,
        "xem_xet": 0
      },
      "CHUA_DIEN": {
        "doc_ky": 0,
        "xem_xet": 1
      }
    },
    "levels": {
      "doc_ky": "Nên đọc kỹ",
      "xem_xet": "Nên xem xét"
    }
  },
  "clauses": [
    {
      "id": "c0",
      "index": 0,
      "heading": "Điều 3. Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
      "text": "Điều 3. Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
      "page_start": 0,
      "page_end": 0,
      "max_level": "doc_ky",
      "risks": [
        {
          "code": "DON_PHUONG_CHAM_DUT",
          "label": "Một bên được đơn phương chấm dứt",
          "level": "doc_ky",
          "score": 0.9035,
          "source": "model",
          "evidence": {
            "text": "Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
            "boxes": [
              {
                "page": 0,
                "x0": 72,
                "y0": 96.4,
                "x1": 523.3,
                "y1": 110.2
              }
            ]
          }
        }
      ],
      "advice": {
        "code": "DON_PHUONG_CHAM_DUT",
        "label": "Một bên được đơn phương chấm dứt",
        "level": "doc_ky",
        "card_id": "dpcd_tuy_y",
        "title": "Chấm dứt tuỳ ý, không cần lý do",
        "ask": [
          "Bên kia được chấm dứt trong những trường hợp cụ thể nào? \"Khi có nhu cầu / khi xét thấy cần thiết\" nghĩa là gì?",
          "Phải báo trước bao nhiêu ngày, bằng văn bản hay chỉ thông báo?",
          "Khi bị chấm dứt sớm, bạn được hoàn lại / bồi thường những khoản nào?"
        ],
        "notes": [
          "Quyền chấm dứt không cần căn cứ khiến thời hạn hợp đồng gần như không có giá trị bảo đảm cho bạn.",
          "Kiểm tra bạn có quyền chấm dứt tương đương không; nếu chỉ một bên có quyền thì hợp đồng đang lệch."
        ],
        "add_clause": "\"Mỗi bên chỉ được đơn phương chấm dứt Hợp đồng khi bên kia vi phạm nghĩa vụ [liệt kê] và không khắc phục trong [15] ngày kể từ ngày nhận thông báo; trường hợp chấm dứt không do lỗi của bên kia, bên chấm dứt phải báo trước [30] ngày và bồi thường thiệt hại thực tế phát sinh.\"",
        "legal_basis": "Điều 428 Bộ luật Dân sự 2015 (đơn phương chấm dứt thực hiện hợp đồng); Điều 25 Luật Bảo vệ quyền lợi người tiêu dùng 2023 (hợp đồng theo mẫu)",
        "similarity": 0.9103,
        "safe_margin": -0.0315
      }
    }
  ],
  "disclaimer": "Đây là GỢI Ý các điều khoản nên đọc kỹ, do mô hình máy học tự động đề xuất; không phải tư vấn pháp lý…"
}

Try it

Try it

Example mode
POST https://api.belumind.com/virgo/v1/analyze

The platform is in early access, so this box shows the example answer instead of contacting the live service. Ask us for a key and the same box will send real requests.

POST/v1/analyze/textcontracts:analyze

Review contract text

The same review for text you already have — for example, copied from Word or an email, or read from a scan. The answer looks the same as `/v1/analyze`, except there are no pages: instead, `char_start` / `char_end` say where each part sits in your text. Takes the same `include_all` and `compact` options; the example below uses `compact=true`.

AI assistant toolanalyze_contract

What to send

textstringrequired
The full contract text. Text that is too long gives error 413 (`text_too_large`); empty text gives error 422 (`empty_text`).
file_namestringoptional
A name shown back in the result (`document.file_name`). Defaults to `contract.txt`.
include_allboolean · queryoptional
Also list clauses that have no suggestion. Off by default.
compactboolean · queryoptional
Send a shorter answer without the long explanations and legal references — you can look them up by `code` with `GET /v1/risk-types`. Off by default.

What you get back

  • 200 OKIt worked. You get back what the example shows.
  • 400 Bad RequestSomething in what you sent was wrong or missing. The answer says which part.
  • 401 UnauthorizedThe API key or access token is missing, mistyped or no longer valid. Access tokens expire after 15 minutes — refresh it and send the new one.
  • 403 ForbiddenThe key works, but it was made for another product.
  • 402 Payment RequiredThe account is out of credits. Buy more in the dashboard, or turn on automatic top-up.
  • 429 Too Many RequestsToo many requests in a short time, or the key has reached the monthly limit set on it. Wait the number of seconds given in `Retry-After`, then try again.
  • 500 Internal Server ErrorSomething went wrong on our side. It is safe to send the same request again.
  • 503 Service UnavailableThe queue is full, or the dashboard that checks keys could not be reached. Try again a little later.

What you send

curl -X POST https://api.belumind.com/virgo/v1/analyze/text \
  -H "X-API-Key: $BELUMIND_API_KEY" \
  -H "Authorization: Bearer $BELUMIND_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Điều 3. Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.\nĐiều 4. Mức lương: ........ đồng/tháng.",
  "file_name": "hop_dong_lao_dong.txt"
}'

What you get back

application/json200 OK
{
  "version": "1.11.0",
  "model_version": "v1.11",
  "compact": true,
  "document": {
    "format": "text",
    "file_name": "hop_dong_lao_dong.txt",
    "pages": [],
    "n_clauses": 2,
    "n_chars": 132,
    "is_credit_institution": false,
    "is_labour_contract": false,
    "warnings": [],
    "domain": "dieu_kien_giao_dich"
  },
  "summary": {
    "n_flagged_clauses": 1,
    "n_blank_fields": 1,
    "n_capped": 0,
    "by_risk": {
      "DON_PHUONG_CHAM_DUT": {
        "doc_ky": 1,
        "xem_xet": 0
      },
      "CHUA_DIEN": {
        "doc_ky": 0,
        "xem_xet": 1
      }
    },
    "levels": {
      "doc_ky": "Nên đọc kỹ",
      "xem_xet": "Nên xem xét"
    }
  },
  "clauses": [
    {
      "id": "c0",
      "index": 0,
      "heading": "Điều 3. Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
      "text": "Điều 3. Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
      "char_start": 0,
      "char_end": 93,
      "boxes": [],
      "max_level": "doc_ky",
      "risks": [
        {
          "code": "DON_PHUONG_CHAM_DUT",
          "level": "doc_ky",
          "score": 0.9035,
          "source": "model",
          "evidence": {
            "text": "Công ty có quyền đơn phương chấm dứt hợp đồng bất cứ lúc nào mà không cần báo trước.",
            "char_start": 8,
            "char_end": 92,
            "boxes": []
          }
        }
      ]
    },
    {
      "id": "c1",
      "index": 1,
      "heading": "Điều 4. Mức lương: ........ đồng/tháng.",
      "text": "Điều 4. Mức lương: ........ đồng/tháng.",
      "char_start": 93,
      "char_end": 132,
      "boxes": [],
      "max_level": "xem_xet",
      "risks": [
        {
          "code": "CHUA_DIEN",
          "level": "xem_xet",
          "score": 1,
          "source": "rule",
          "evidence": {
            "text": "1 chỗ chưa điền: Điều 4. Mức lương: đồng/tháng.",
            "char_start": 112,
            "char_end": 120,
            "boxes": []
          }
        }
      ]
    }
  ],
  "timing_ms": 82
}

Try it

Try it

Example mode
POST https://api.belumind.com/virgo/v1/analyze/text

The platform is in early access, so this box shows the example answer instead of contacting the live service. Ask us for a key and the same box will send real requests.

GET/v1/risk-typescontracts:analyze

See all risk groups

Every risk Virgo checks for, by `code`: its name, what it means, what to check, the law to look at, and whether it is currently switched on. Use it to fill in the details of a shorter (`compact`) answer. It needs both credentials but uses no credits. The example shows three of the thirteen groups.

AI assistant toollist_risk_types

What you get back

  • 200 OKIt worked. You get back what the example shows.
  • 400 Bad RequestSomething in what you sent was wrong or missing. The answer says which part.
  • 401 UnauthorizedThe API key or access token is missing, mistyped or no longer valid. Access tokens expire after 15 minutes — refresh it and send the new one.
  • 403 ForbiddenThe key works, but it was made for another product.
  • 402 Payment RequiredThe account is out of credits. Buy more in the dashboard, or turn on automatic top-up.
  • 429 Too Many RequestsToo many requests in a short time, or the key has reached the monthly limit set on it. Wait the number of seconds given in `Retry-After`, then try again.
  • 500 Internal Server ErrorSomething went wrong on our side. It is safe to send the same request again.
  • 503 Service UnavailableThe queue is full, or the dashboard that checks keys could not be reached. Try again a little later.

What you send

curl -X GET https://api.belumind.com/virgo/v1/risk-types \
  -H "X-API-Key: $BELUMIND_API_KEY" \
  -H "Authorization: Bearer $BELUMIND_ACCESS_TOKEN"

What you get back

application/json200 OK
{
  "PHAT_VUOT_TRAN_8": {
    "label": "Mức phạt vi phạm có thể vượt 8%",
    "explanation": "Mức phạt vi phạm được nêu cao hơn 8% giá trị phần nghĩa vụ bị vi phạm. Trần 8% chỉ áp dụng cho hợp đồng thương mại; hợp đồng dân sự do các bên tự thoả thuận.",
    "suggestion": "Kiểm tra hợp đồng là thương mại hay dân sự, mức phạt tính trên giá trị nào, và bên kia có chịu mức phạt tương tự không.",
    "legal_basis": "Điều 301 Luật Thương mại 2005; Điều 418 Bộ luật Dân sự 2015",
    "enabled": true
  },
  "DON_PHUONG_CHAM_DUT": {
    "label": "Một bên được đơn phương chấm dứt",
    "explanation": "Một bên được chấm dứt/huỷ hợp đồng, khoá tài khoản hoặc ngừng dịch vụ TUỲ Ý: không cần căn cứ, căn cứ do chính bên đó tự quyết định, hoặc không báo trước/không bồi thường dù bên kia không có lỗi.",
    "suggestion": "Hỏi rõ bên kia được chấm dứt trong trường hợp nào, có báo trước bao lâu, và bạn được hoàn tiền / bồi thường gì nếu bị chấm dứt.",
    "legal_basis": "Điều 428 Bộ luật Dân sự 2015; Điều 25 Luật Bảo vệ quyền lợi người tiêu dùng 2023",
    "enabled": true
  },
  "CHUA_DIEN": {
    "label": "Chưa điền",
    "explanation": "Chỗ trống (dòng chấm, \"…\", \"____\") chưa điền thông tin — thường gặp ở mẫu hợp đồng. Đây là nhắc điền thông tin, không phải đánh giá rủi ro của điều khoản.",
    "suggestion": "Điền đầy đủ và kiểm tra lại các chỗ trống trước khi ký; không để trống để bên kia tự điền sau.",
    "legal_basis": "Điều 398 Bộ luật Dân sự 2015 (nội dung của hợp đồng)",
    "enabled": true
  }
}

Try it

Try it

Example mode
GET https://api.belumind.com/virgo/v1/risk-types

The platform is in early access, so this box shows the example answer instead of contacting the live service. Ask us for a key and the same box will send real requests.

Use with an AI assistant

Connected to an AI assistant, Virgo becomes a first check on any contract a user shares in the chat: the assistant explains the flagged clauses in plain language and drafts the questions to send. It never says a clause is unlawful — it points at what deserves a closer look. Virgo cannot open files on the user's computer, so the assistant sends it the file itself or the pasted text.

You ask

“Review this employment contract before I sign — what should I ask HR about?”

The assistant

  1. 01Sends the PDF to Virgo for review
  2. 02Groups flagged clauses by level: read carefully first, then worth a look
  3. 03Asks Virgo for the advice on each clause, then drafts follow-up questions with page numbers
Settings to paste into your AI assistant
{
  "mcpServers": {
    "virgo": {
      "url": "https://mcp.belumind.com/virgo",
      "headers": {
        "X-API-Key": "bm_live_7f3c…",
        "Authorization": "Bearer eyJhbGciOi…"
      }
    }
  }
}

When something goes wrong

When a request fails, you get a standard error number and a short code that your software can act on, next to a plain message for people to read. Each request can carry an Idempotency-Key, so sending it again after a problem on our side is safe — it won't be done twice.

What an error looks like4xx / 5xx
{
  "error": {
    "code": "invalid_parameter",
    "message": "`url` must be a reachable http(s) URL.",
    "param": "url",
    "request_id": "req_4d91c0b7"
  }
}

Ready to use it for real?

While the platform is in early access, the "Try it" box shows example answers. Tell us what you are building and we will get you a key.