Hướng dẫn dành cho khách hàng

Đưa tính năng chấm AI vào ứng dụng của bạn

AIHUB chấm bài viết và bài nói IELTS ngay trong ứng dụng của bạn. Xem cách bắt đầu, cách thử và khi nào phát sinh chi phí.

Lộ trình của bạn

  1. Đăng ký AIHUBTạo tài khoản cho doanh nghiệp
  2. Thử trướcThử chấm bài viết miễn phí
  3. Kết nối ứng dụngNhờ đội kỹ thuật thiết lập

Dành cho người phụ trách sản phẩm

Bạn cần biết trước khi bắt đầu

Người học tiếp tục làm bài trong ứng dụng của bạn; AIHUB nhận bài, chấm và gửi kết quả về để bạn hiển thị.

Ai cần tham gia?

Bạn tạo tài khoản AIHUB. Đội kỹ thuật kết nối ứng dụng để gửi bài làm và nhận kết quả.

Có thể thử trước không?

Có. Chế độ thử miễn phí hỗ trợ chấm bài viết trong hạn mức. Bài nói cần dùng kết nối có tính phí.

Khi nào tính phí?

Khi chuyển sang dùng thật, các lần chấm bắt đầu tính phí, kể cả lần kiểm tra đầu tiên.

Xem hướng dẫn cho đội kỹ thuật

Các bước chi tiết dành cho đội kỹ thuật

Gọi thử lần đầu với mã người học

Mỗi lần chấm cần một mã để biết bài làm thuộc về người học nào. Gửi mã đó trong X-User-Identity. JWKS (bộ khóa xác minh) là tuỳ chọn: nếu tổ chức chưa bật xác minh chữ ký, bạn chỉ cần gửi mã dạng văn bản.

  • Địa chỉ dùng thậthttps://api.aihubproduction.com
  • Khóa truy cậpHeader X-API-Key
  • Mã người họcHeader X-User-Identity

Chạy lệnh dưới đây trên máy chủ hoặc terminal của bạn với API key đã tạo. Khi kết quả có data và meta, kết nối đã hoạt động. Chưa có khóa? Xem cách tạo tài khoản và API key.

Lệnh curl gọi thử Writing Task 2 (Declared User ID)
# 1. Lưu key vào biến môi trường (chỉ trong terminal server, không commit)
export AIHUB_API_KEY="aihub_sk_..."

# 2. End-User ID của bạn — ổn định, 1–256 ký tự ASCII hiển thị, không khoảng trắng
export AIHUB_USER_IDENTITY="student_456"

# 3. Gọi thử một lần chấm Writing Task 2
curl -X POST 'https://api.aihubproduction.com/v1/ielts/writing/task2/grade' \
  -H "X-API-Key: $AIHUB_API_KEY" \
  -H "X-User-Identity: $AIHUB_USER_IDENTITY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "education",
    "question": "Some people believe that universities should only offer academic subjects, while others think vocational training is equally important. Discuss both views and give your own opinion.",
    "essay": "Universities have traditionally focused on academic subjects, yet the value of vocational training is increasingly recognised..."
  }'

AIHUB biết người học nào đang dùng bằng cách nào?

Mặc định, bạn gửi một mã người học ổn định. Chỉ cần thêm chữ ký nếu tổ chức của bạn đã bật xác minh nâng cao.

Trạng thái xác minhCách gửi mãGiá trị X-User-IdentityCần JWKS?
Chưa thiết lập xác minh chữ kýMã người học thông thườngMã người học của bạnKhông cần
Đã thiết lập nhưng đang tắtMã người học thông thườngMã người học của bạnKhông cần
Đang bật xác minh chữ kýMã có chữ kýMã do máy chủ của bạn ký, có hạn dùng ngắnCần

Xem trạng thái ở trang Identity của Organization trong Console. Cấu hình chỉ được lưu hoặc đã bị tắt thì chưa bật xác minh có chữ ký.

Mã người học thông thường (Declared User ID)

  • Đúng định dạng: 1–256 ký tự ASCII hiển thị, không có khoảng trắng. AIHUB so sánh đúng như bạn gửi: Student@X.com và student@x.com là hai người dùng.
  • Không đổi mã giữa các lần chấm: Gửi cùng một giá trị cho cùng một người, để mức dùng luôn tính về đúng người đó.
  • Tránh dùng email: Nên dùng ID không mang nghĩa (opaque). AIHUB lưu mã trong bản ghi sử dụng và chuyển tiếp nguyên văn trong quá trình xử lý; email sẽ trở thành dữ liệu cá nhân.

Khi cần xác minh nâng cao (Signed User Assertion)

  • Ai được cấu hình: Chỉ owner đang hoạt động của Organization lưu được cấu hình, bằng PUT /v1/organizations/{organization_id}/identity-config với Authorization: Bearer User Access JWT. Đây là credential của control plane, không phải API key dùng để chấm. Console có sẵn trang Identity làm việc này.
  • Chỉ cung cấp khóa công khai: Khai báo một issuer và đúng một nguồn khoá public: URL JWKS HTTPS truy cập công khai được (jwks_url), hoặc JWKS public dán trực tiếp (public_keys_jwks). Không bao giờ gửi private key cho AIHUB; private key ở lại backend của bạn. Có thể đặt thêm allowed_algorithms (RS256, ES256) và max_assertion_ttl_seconds.
  • Áp dụng toàn tổ chức: Cấu hình dùng ở mọi environment của Organization. Đừng đổi issuer giữa các request, và đừng gửi issuer hay JWKS như header khi chấm.
  • Bắt buộc xác minh khi đã bật: Mọi request phải mang một assertion hợp lệ. Assertion sai bị trả 401 INVALID_USER_IDENTITY, không có fallback xuống ID văn bản thuần, nên tích hợp lỗi ngay thay vì âm thầm đổi mức bảo đảm.
TrườngGiá trịGhi chú
alg (header)RS256 hoặc ES256HS256 và alg none bị từ chối.
kid (header)Khớp đúng một khoá trong JWKSMột kid khớp nhiều khoá dùng được cũng bị từ chối.
issIssuer đã đăng kýKhớp từng ký tự.
aud"aihub"Hằng số.
subEnd-User IDCùng quy tắc 1–256 ký tự với Declared User ID.
jtiID duy nhất cho mỗi assertionVí dụ một UUID mới.
iatThời điểm ký, tính bằng giâyKhông được ở tương lai quá 60 giây.
expHết hạn, tính bằng giâyMặc định tối đa 300 giây (cấu hình max_assertion_ttl_seconds từ 1 đến 3600); ký ngay trước khi gọi.

Dạng ký chỉ đổi giá trị của header; phần còn lại của request giữ nguyên. Ký assertion ngay trước mỗi lần gọi ở backend, không lưu lâu và không đưa xuống trình duyệt hay mobile app trong ứng dụng thật. Bản demo công khai chỉ dùng assertion tạm thời cho test user theo contract v1 bên dưới.

Header X-User-Identity ở dạng ký
# SIGNED_USER_ASSERTION do backend của bạn ký ngay trước mỗi lần gọi
-H "X-User-Identity: $SIGNED_USER_ASSERTION"

Mẫu ký assertion ở backend của bạn (Node.js, Python, Java). Thay các giá trị <...> bằng issuer và kid đã đăng ký; nạp private key từ secret manager.

Node.js: ký Signed User Assertion
// backend/assertion.js — chạy trên server của bạn, không bao giờ trong trình duyệt
import { SignJWT, importPKCS8 } from "jose";
import { randomUUID } from "node:crypto";

const privateKey = await importPKCS8(process.env.SIGNING_KEY_PEM, "RS256");

export async function mintAssertion(userId) {
  return new SignJWT({})
    .setProtectedHeader({ alg: "RS256", kid: "<kid-co-trong-jwks>" })
    .setIssuer("<issuer-da-dang-ky>")
    .setAudience("aihub")
    .setSubject(userId)
    .setJti(randomUUID())
    .setIssuedAt()
    .setExpirationTime("5m")
    .sign(privateKey);
}
Python: ký Signed User Assertion
# backend/assertion.py — PyJWT
import time, uuid, jwt

def mint_assertion(user_id: str) -> str:
    now = int(time.time())
    return jwt.encode(
        {
            "iss": "<issuer-da-dang-ky>",
            "aud": "aihub",
            "sub": user_id,
            "jti": str(uuid.uuid4()),
            "iat": now,
            "exp": now + 300,
        },
        PRIVATE_KEY_PEM,  # nạp từ secret manager
        algorithm="RS256",
        headers={"kid": "<kid-co-trong-jwks>"},
    )
Java: ký Signed User Assertion
// com.nimbusds:nimbus-jose-jwt
var claims = new JWTClaimsSet.Builder()
    .issuer("<issuer-da-dang-ky>")
    .audience("aihub")
    .subject(userId)
    .jwtID(UUID.randomUUID().toString())
    .issueTime(new Date())
    .expirationTime(new Date(System.currentTimeMillis() + 300_000))
    .build();

var jwt = new SignedJWT(
    new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("<kid-co-trong-jwks>").build(),
    claims);
jwt.sign(new RSASSASigner(privateKey));
String assertion = jwt.serialize();

Endpoint assertion cho demo (contract v1)

Nếu API key của Organization bật xác minh chữ ký, khách hàng có thể cấu hình endpoint trên backend của mình để cấp assertion ngắn hạn cho một test user cố định, không dùng trong production. Endpoint do Organization chọn; AIHUB không ký thay và không nhận private key.

  • Request từ trình duyệt: demo gửi POST không body tới URL HTTPS tuyệt đối. URL không được có username/password, query hoặc fragment; redirect bị từ chối. Request không mang cookie, Authorization, API key, user ID hay header xác thực tuỳ chỉnh. Trình duyệt chờ tối đa 15 giây cho cả response headers và body, sau đó cho phép thử lại.
  • CORS và bảo vệ endpoint: chỉ cho phép chính xác origin https://aihubproduction.com, không wildcard và không mở preview/local origin. Endpoint tự chọn test user cố định, dùng quyền tối thiểu và giới hạn tần suất; request không có secret của AIHUB để xác thực người gọi.
  • Response thành công: trả JSON với assertion và expires_at theo RFC 3339 UTC, dùng Z hoặc +00:00. expires_at phải khớp claim số nguyên exp; assertion tối đa 6 KiB, response body tối đa 8 KiB và token còn hạn. Header Cache-Control: no-store là bắt buộc.
  • Claims: ký bằng private key lưu ở backend; dùng aud: "aihub", issuer đã đăng ký, sub là test user cố định, jti duy nhất, iat/exp tính bằng giây và hạn dùng không vượt cấu hình Organization (1–3600 giây). Demo chỉ kiểm tra cấu trúc và thời hạn; AIHUB vẫn xác minh chữ ký và claims trên mọi thao tác live.
  • Lỗi ổn định: trả error.code và ghép đúng HTTP status: 429 RATE_LIMITED, 503 TEST_USER_UNAVAILABLE hoặc 502 ISSUER_ERROR. Demo hiện hướng dẫn an toàn chỉ khi cả mã lẫn status khớp; trường hợp khác dùng thông báo chung và không hiện body/message từ server.
  • Assertion dùng chung cho Writing và Speaking trong phiên demo, chỉ gửi trong X-User-Identity, không lưu vào browser storage, URL, log hay analytics. Đổi API key/môi trường hoặc rời phiên sẽ xóa URL và assertion. Có thể mở mục nhập thủ công nếu endpoint chưa dùng được.
Contract v1: response thành công
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Access-Control-Allow-Origin: https://aihubproduction.com

{
  "assertion": "<signed-user-assertion-jwt>",
  "expires_at": "2026-10-16T12:05:00Z"
}
Contract v1: response lỗi
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Access-Control-Allow-Origin: https://aihubproduction.com

{
  "error": {
    "code": "RATE_LIMITED"
  }
}

Tạo tài khoản và khóa truy cập

Làm ba bước này trước khi gửi yêu cầu chấm. API key chỉ hiện đầy đủ một lần sau khi tạo hoặc thay khóa.

  1. Đăng ký và xác minh email

    Đăng ký bằng email, username và mật khẩu, rồi mở email lấy link xác minh. Quên mật khẩu thì dùng khôi phục mật khẩu.

  2. Tạo tổ chức

    Tổ chức là nơi quản lý khóa truy cập và cách nhận diện người học. Tạo ở Console. Mỗi tài khoản tự tạo tối đa ba tổ chức; cần thêm thì liên hệ AIHUB.

  3. Tạo API key và lưu ngay

    Vào trang API keys của Organization, bấm Tạo API key, đặt tên và tick đúng scope cần dùng. Scope chưa bật cho Organization sẽ bị khoá — liên hệ AIHUB để mở. Lưu raw key vào secret manager rồi đóng dialog; sẽ không xem lại được.

Chọn tính năng chấm bạn cần

Chọn Writing hoặc Speaking rồi mở hướng dẫn chi tiết của tính năng đó. Mọi lần chấm đều cần mã người học X-User-Identity.

Tính năngĐường dẫn APIQuyền cần cấpGửi lại an toànDung lượng tối đaThời gian xử lýThời gian chờ nên đặt
Writing Task 1/v1/ielts/writing/task1/gradewriting.gradeBắt buộc256 KB60 giâyTối thiểu 90 giây
Writing Task 2/v1/ielts/writing/task2/gradewriting.gradeBắt buộc256 KB60 giâyTối thiểu 90 giây
Speaking multipart/v1/ielts/speaking/gradingspeaking.gradeKhông hỗ trợ (header bị bỏ qua)26 MiB60 giâyTối thiểu 90 giây
Speaking JSON-by-URL/v1/ielts/speaking/grading-jsonspeaking.gradeKhông hỗ trợ (header bị bỏ qua)256 KB30 giâyTối thiểu 60 giây

Lệnh chạy sẵn cho đúng API key của bạn nằm ở Quickstart của key trong Console.

Ba bảng dưới đây là hợp đồng của cả bốn endpoint: mọi thao tác chấm đều dùng chung X-User-Identity, và Writing có thêm Idempotency-Key.

HeaderÁp dụng choÝ nghĩa
X-API-KeyCả bốnKhóa của tổ chức bạn. Chỉ gọi từ máy chủ, không đưa vào trình duyệt.
X-User-IdentityCả bốnĐịnh danh người học: mã văn bản thuần, hoặc JWT có chữ ký nếu tổ chức bật xác minh.
Idempotency-KeyWriting Task 1 và 2Bắt buộc. Gán một UUID cho mỗi bài làm và dùng lại nguyên giá trị khi retry. Speaking không có idempotency nên header này bị bỏ qua.
Content-TypeTask 1, Task 2 và Speaking JSONapplication/json cho các endpoint gửi JSON. Endpoint Speaking multipart tự sinh content type kèm boundary, đừng đặt tay.

Gắn vào backend

Node.js: gọi Writing Task 2 từ backend
// backend/grading.js — gọi AIHUB từ server, không bao giờ từ trình duyệt
const AIHUB_HOST = "https://api.aihubproduction.com";

// idempotencyKey: sinh MỘT lần cho mỗi bài làm (ví dụ randomUUID()), lưu cùng bài làm,
// và dùng lại đúng key này ở mọi lần retry. Đừng sinh key mới bên trong hàm này.
export async function gradeTask2({ question, topic, essay, endUserId, idempotencyKey }) {
  const response = await fetch(`${AIHUB_HOST}/v1/ielts/writing/task2/grade`, {
    method: "POST",
    signal: AbortSignal.timeout(90_000), // ngân sách AIHUB là 60 giây; client chờ tối thiểu 90 giây
    headers: {
      "X-API-Key": process.env.AIHUB_API_KEY,
      "X-User-Identity": endUserId, // Declared User ID, hoặc Signed User Assertion nếu Organization bật xác minh
      "Idempotency-Key": idempotencyKey,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ question, topic, essay }),
  });

  // Mọi response thành công đều bọc trong { data, meta }.
  if (response.ok) return (await response.json()).data;

  // Lỗi: rẽ nhánh theo error.code, quyết định retry theo retryable / retry_after_ms.
  const { error } = await response.json();
  throw Object.assign(new Error(`AIHUB ${response.status} ${error.code}`), {
    code: error.code,
    retryable: error.retryable,
    retryAfterMs: error.retry_after_ms,
    requestId: error.request_id, // gửi cho hỗ trợ, đừng gửi kèm key hay bài làm
  });
}

Body và response của từng API

POSTWriting Task 1/v1/ielts/writing/task1/grade
FieldKiểuBắt buộcMô tả
questionstringBẮT BUỘCĐề bài mô tả biểu đồ, 1–2.000 ký tự.
chart_typestringBẮT BUỘCLoại biểu đồ, phân biệt HOA THƯỜNG, đúng một trong: Bar Chart | Line Graph | Pie Chart | Table | Map | Process Diagram | Multiple Graphs.
essaystringBẮT BUỘCBài làm Task 1, 1–20.000 ký tự.
image_urlstring (uri)BẮT BUỘCẢnh biểu đồ dạng HTTP(S), tối đa 2.000 ký tự. Phải trỏ tới ảnh thật mà AIHUB có thể truy cập.
languagestringtuỳ chọnNgôn ngữ nhận xét. Hiện chỉ nhận vi; nhận xét luôn trả về bằng tiếng Việt.

Response: Envelope { data, meta } của AIHUB. data khớp hợp đồng chấm bài: overall_band chạy từ 0 đến 9 theo bội số 0.5, criteria luôn có đúng 4 phần tử theo thứ tự cố định (bắt đầu bằng task_achievement), kèm summary, suggestions, next_steps và annotations trích nguyên văn câu trong bài. meta cho request_id để báo hỗ trợ, service, operation và các số đo thời gian; correlation_id chỉ xuất hiện khi bạn gửi X-Correlation-Id.

{
  "data": {
    "overall_band": 6.5,
    "language": "vi",
    "criteria": [
      {
        "id": "task_achievement",
        "name": "Task Achievement",
        "band": 6.5,
        "band_reason": "Bài có overview và so sánh các nước, nhưng dùng từ chỉ mức tăng cho biểu đồ chỉ có một năm và chưa nêu số của Russia.",
        "strengths": [
          "Mở bài nêu đúng đại lượng và năm 2010.",
          "Có câu Overall nêu đặc điểm chính của biểu đồ."
        ],
        "improvements": [
          "Nêu số của Russia và mô tả chênh lệch thay cho xu hướng tăng."
        ]
      },
      {
        "id": "coherence_cohesion",
        "name": "Coherence and Cohesion",
        "band": 6.5,
        "band_reason": "Ý đi từ số lớn đến số nhỏ và có câu Overall, nhưng toàn bài dồn vào một đoạn.",
        "strengths": [
          "Câu Overall tóm tắt đặc điểm chính."
        ],
        "improvements": [
          "Tách mở bài và overview khỏi phần số liệu."
        ]
      },
      {
        "id": "lexical_resource",
        "name": "Lexical Resource",
        "band": 6,
        "band_reason": "Dùng đúng production capacity nhưng big increase of production vừa sai ngữ cảnh vừa thiếu chính xác.",
        "strengths": [
          "Dùng đúng estimated production capacity thay cho số lượng dài dòng."
        ],
        "improvements": [
          "Dùng a large gap in production capacity để nói về chênh lệch giữa các nước."
        ]
      },
      {
        "id": "grammatical_range_accuracy",
        "name": "Grammatical Range and Accuracy",
        "band": 6.5,
        "band_reason": "Các câu so sánh phần lớn dễ hiểu, nhưng một câu thừa động từ were sau produced.",
        "strengths": [
          "Cụm almost three times the figure for Russia diễn đạt so sánh rõ."
        ],
        "improvements": [
          "Bỏ were sau produced trong câu về Norway và Mexico."
        ]
      }
    ],
    "summary": "Bài đạt band 6.5 và có overview, nhưng nói về mức tăng dù biểu đồ chỉ có năm 2010, bỏ số của Russia và có một câu sai cấu trúc.",
    "suggestions": [
      "Bổ sung số ước tính của Russia và tách phần overview thành đoạn riêng.",
      "Dùng từ chỉ chênh lệch thay cho increase ở biểu đồ một năm."
    ],
    "next_steps": [
      "Luyện mô tả biểu đồ cột chỉ có một mốc thời gian.",
      "Rà lại các câu so sánh để tránh thừa động từ."
    ],
    "annotations": [
      {
        "criterion": "lexical_resource",
        "issue": "collocation_error",
        "quote": "a big increase of production",
        "explanation": "Biểu đồ chỉ có năm 2010 nên không thể kết luận sản lượng tăng; dùng a large gap in production capacity để nói về chênh lệch."
      },
      {
        "criterion": "grammatical_range_accuracy",
        "issue": "inappropriate_structure",
        "quote": "Norway and Mexico produced were considerably less",
        "explanation": "Thừa were sau produced. Viết Norway and Mexico produced considerably less."
      }
    ]
  },
  "meta": {
    "request_id": "req_01M23NT8MJW22AYX6FVT17F5XF",
    "service": "writing",
    "timing": {
      "downstream_ms": 41820,
      "gateway_overhead_ms": 184,
      "total_ms": 42004
    },
    "operation": "writing.task1.grade"
  }
}

Trường và ví dụ đầy đủ, kèm mọi lỗi có thể gặp: mở trang Writing Task 1.

POSTWriting Task 2/v1/ielts/writing/task2/grade
FieldKiểuBắt buộcMô tả
questionstringBẮT BUỘCĐề bài luận, 1–2.000 ký tự.
topicstringBẮT BUỘCChủ đề thật của bài như education hoặc technology, 1–200 ký tự. Đây là môn chủ đề, không phải loại biểu đồ.
essaystringBẮT BUỘCBài làm Task 2, 1–20.000 ký tự.
languagestringtuỳ chọnNgôn ngữ nhận xét. Hiện chỉ nhận vi; nhận xét luôn trả về bằng tiếng Việt.

Response: Envelope { data, meta } của AIHUB. data khớp hợp đồng chấm bài: overall_band chạy từ 0 đến 9 theo bội số 0.5, criteria luôn có đúng 4 phần tử theo thứ tự cố định (bắt đầu bằng task_response), kèm summary, suggestions, next_steps và annotations trích nguyên văn câu trong bài. meta cho request_id để báo hỗ trợ, service, operation và các số đo thời gian; correlation_id chỉ xuất hiện khi bạn gửi X-Correlation-Id.

{
  "data": {
    "overall_band": 7,
    "language": "vi",
    "criteria": [
      {
        "id": "task_response",
        "name": "Task Response",
        "band": 7,
        "band_reason": "Bài bàn cả hai quan điểm và kết luận ủng hộ đào tạo nghề với điều kiện được coi trọng ngang các môn học thuật; ví dụ minh họa còn khái quát.",
        "strengths": [
          "Nêu quan điểm cá nhân ở câu cuối thay vì để ngỏ.",
          "Câu kết nêu rõ điều kiện same status and the same faculty."
        ],
        "improvements": [
          "Thêm ví dụ cụ thể về một chương trình đào tạo nghề trong đại học."
        ]
      },
      {
        "id": "coherence_cohesion",
        "name": "Coherence and Cohesion",
        "band": 7,
        "band_reason": "Các ý nối theo trình tự rõ, nhưng toàn bài dồn vào một đoạn và câu Neither path is complete alone quá dài.",
        "strengths": [
          "Các câu nối trực tiếp từ lợi ích học thuật sang đào tạo nghề."
        ],
        "improvements": [
          "Tách phần học thuật và phần đào tạo nghề thành hai đoạn."
        ]
      },
      {
        "id": "lexical_resource",
        "name": "Lexical Resource",
        "band": 7,
        "band_reason": "Từ vựng học thuật phù hợp, nhưng turns that analysis into a specific craft là phép diễn đạt trừu tượng.",
        "strengths": [
          "Dùng chính xác vocational training và academic subject theo nghĩa của đề."
        ],
        "improvements": [
          "Nêu kỹ năng thực hành cụ thể thay cho specific craft."
        ]
      },
      {
        "id": "grammatical_range_accuracy",
        "name": "Grammatical Range and Accuracy",
        "band": 7,
        "band_reason": "Câu ghép và mệnh đề điều kiện dùng đúng, nhưng câu Neither path is complete alone chứa quá nhiều ý.",
        "strengths": [
          "Cấu trúc provided it carries the same status là câu chủ động phức đúng ngữ pháp."
        ],
        "improvements": [
          "Tách câu Neither path is complete alone thành hai câu."
        ]
      }
    ],
    "summary": "Bài đạt band 7.0, nêu rõ quan điểm và điều kiện trong kết luận. Cần ví dụ cụ thể hơn, chia đoạn và rút ngắn một câu dài.",
    "suggestions": [
      "Thêm ví dụ về chương trình đào tạo nghề trong đại học.",
      "Tách phần học thuật và đào tạo nghề thành các đoạn riêng."
    ],
    "next_steps": [
      "Viết lại bài với các đoạn riêng và một ví dụ thực tế cho quan điểm cá nhân."
    ],
    "annotations": [
      {
        "criterion": "lexical_resource",
        "issue": "imprecise_word_choice",
        "quote": "turns that analysis into a specific craft",
        "explanation": "Cách diễn đạt trừu tượng; nêu rõ đào tạo nghề giúp người học phát triển kỹ năng thực hành cụ thể."
      },
      {
        "criterion": "task_response",
        "issue": "underdeveloped_example",
        "quote": "provided it carries the same status and the same faculty as academic study",
        "explanation": "Điều kiện đã rõ nhưng cần một ví dụ về cách đại học trao vị thế tương đương cho đào tạo nghề."
      }
    ]
  },
  "meta": {
    "request_id": "req_01M23NT8MJW22AYX6FVT17F5XF",
    "service": "writing",
    "timing": {
      "downstream_ms": 41820,
      "gateway_overhead_ms": 184,
      "total_ms": 42004
    },
    "operation": "writing.task2.grade"
  }
}

Trường và ví dụ đầy đủ, kèm mọi lỗi có thể gặp: mở trang Writing Task 2.

POSTSpeaking multipart/v1/ielts/speaking/grading
FieldKiểuBắt buộcMô tả
audiofileBẮT BUỘCMột tệp wav, mp3, m4a, webm hoặc ogg; tối thiểu 100 byte, tối đa 25 MiB. Ví dụ trỏ tới tệp trên máy bạn; muốn chạy thử không cần tệp, dùng POST /v1/ielts/speaking/grading-json với Published sample answer của AIHUB.
partintegerBẮT BUỘCPart của bài Speaking: 1, 2 hoặc 3.
question_idstringBẮT BUỘCMã câu hỏi lấy từ endpoint lấy câu hỏi; thay bằng câu hỏi bạn sắp trả lời.
prompt_textstringtuỳ chọnNội dung câu hỏi, dùng để đánh giá độ liên quan.
test_typestringtuỳ chọnChỉ nhận Practice hoặc Full-test. Bỏ trống thì AIHUB dùng Practice.
test_codestringtuỳ chọnMã chung cho các câu hỏi thuộc cùng một bài kiểm tra đầy đủ.
transcriptstringtuỳ chọnTranscript có sẵn, dùng khi nhà cung cấp hỗ trợ bỏ qua bước chuyển giọng nói thành chữ.

Response: Envelope { data, meta } của AIHUB. data khớp hợp đồng chấm Speaking: scorability cho biết lượt nói có chấm được không, estimated_band gồm 5 tiêu chí mỗi tiêu chí là số chẵn hoặc nửa từ 0 đến 9, transcript cho nội dung đã nghe kèm số từ và thời lượng, relevance cho mức độ đúng chủ đề, fluency_metrics cho nhịp nói và ngừng, pronunciation_detail cho chất lượng từng từ và âm, language_analysis cho lỗi ngữ pháp cùng nâng cấp từ vựng, và feedback cho tóm tắt cùng kế hoạch cải thiện. meta cho request_id để báo hỗ trợ, service và operation.

{
  "data": {
    "test_type": "Practice",
    "question_id": "p1_do-you-enjoy-living-in-your-city-or-hometown",
    "scorability": {
      "is_scorable": true,
      "confidence": "high",
      "display_band": true,
      "message_vi": null
    },
    "estimated_band": {
      "overall": 6,
      "fluency_coherence": 6,
      "lexical_resource": 6.5,
      "grammatical_range_accuracy": 5.5,
      "pronunciation": 6
    },
    "transcript": {
      "text": "I live in a small town about thirty kilometres from the city.",
      "word_count": 12,
      "duration_seconds": 6.4
    },
    "relevance": {
      "on_topic": true,
      "score": 0.93,
      "feedback_vi": "Câu trả lời đi thẳng vào đề hỏi về quê hương."
    },
    "fluency_metrics": {
      "speech_rate_wpm": 112,
      "pause_count": 2,
      "mean_length_run_words": 6.1
    },
    "pronunciation_detail": {
      "summary": {
        "good_count": 9,
        "fair_count": 2,
        "poor_count": 1
      },
      "words": []
    },
    "language_analysis": {
      "grammar_errors": [],
      "vocabulary_upgrades": []
    },
    "feedback": {
      "summary_vi": "Lượt nói rõ ràng, tốc độ hơi nhanh ở câu cuối.",
      "strong_point_vi": "Nói đủ ý và tự nhiên.",
      "action_plan_vi": "Luyện ngắt hơi giữa các ý để tăng điểm trôi chảy."
    }
  },
  "meta": {
    "request_id": "req_01M23NT8MJW22AYX6FVT17F5XF",
    "service": "speaking",
    "operation": "speaking.grading",
    "timing": {
      "downstream_ms": 22140,
      "gateway_overhead_ms": 163,
      "total_ms": 22303
    }
  }
}

Trường và ví dụ đầy đủ, kèm mọi lỗi có thể gặp: mở trang Speaking multipart.

POSTSpeaking JSON-by-URL/v1/ielts/speaking/grading-json
FieldKiểuBắt buộcMô tả
audio_urlstringBẮT BUỘCURL HTTPS trên đúng host s3.wispace.app, dài tối đa 2.048 ký tự, có thể kèm query cho signed object URL. AIHUB kiểm tra URL nhưng không tải — nhà cung cấp tự tải và không theo redirect.
partintegerBẮT BUỘCPart của bài Speaking: 1, 2 hoặc 3.
question_idstringBẮT BUỘCMã câu hỏi lấy từ endpoint lấy câu hỏi; thay bằng câu hỏi bạn sắp trả lời.
prompt_textstringtuỳ chọnNội dung câu hỏi, dùng để đánh giá độ liên quan.
test_typestringtuỳ chọnChỉ nhận Practice hoặc Full-test. Bỏ trống thì AIHUB dùng Practice.
test_codestringtuỳ chọnMã chung cho các câu hỏi thuộc cùng một bài kiểm tra đầy đủ.
transcriptstringtuỳ chọnTranscript có sẵn, dùng khi nhà cung cấp hỗ trợ bỏ qua bước chuyển giọng nói thành chữ.

Response: Envelope { data, meta } của AIHUB. data khớp hợp đồng chấm Speaking: scorability cho biết lượt nói có chấm được không, estimated_band gồm 5 tiêu chí mỗi tiêu chí là số chẵn hoặc nửa từ 0 đến 9, transcript cho nội dung đã nghe kèm số từ và thời lượng, relevance cho mức độ đúng chủ đề, fluency_metrics cho nhịp nói và ngừng, pronunciation_detail cho chất lượng từng từ và âm, language_analysis cho lỗi ngữ pháp cùng nâng cấp từ vựng, và feedback cho tóm tắt cùng kế hoạch cải thiện. meta cho request_id để báo hỗ trợ, service và operation.

{
  "data": {
    "test_type": "Practice",
    "question_id": "p1_do-you-enjoy-living-in-your-city-or-hometown",
    "scorability": {
      "is_scorable": true,
      "confidence": "high",
      "display_band": true,
      "message_vi": null
    },
    "estimated_band": {
      "overall": 6,
      "fluency_coherence": 6,
      "lexical_resource": 6.5,
      "grammatical_range_accuracy": 5.5,
      "pronunciation": 6
    },
    "transcript": {
      "text": "I live in a small town about thirty kilometres from the city.",
      "word_count": 12,
      "duration_seconds": 6.4
    },
    "relevance": {
      "on_topic": true,
      "score": 0.93,
      "feedback_vi": "Câu trả lời đi thẳng vào đề hỏi về quê hương."
    },
    "fluency_metrics": {
      "speech_rate_wpm": 112,
      "pause_count": 2,
      "mean_length_run_words": 6.1
    },
    "pronunciation_detail": {
      "summary": {
        "good_count": 9,
        "fair_count": 2,
        "poor_count": 1
      },
      "words": []
    },
    "language_analysis": {
      "grammar_errors": [],
      "vocabulary_upgrades": []
    },
    "feedback": {
      "summary_vi": "Lượt nói rõ ràng, tốc độ hơi nhanh ở câu cuối.",
      "strong_point_vi": "Nói đủ ý và tự nhiên.",
      "action_plan_vi": "Luyện ngắt hơi giữa các ý để tăng điểm trôi chảy."
    }
  },
  "meta": {
    "request_id": "req_01M23NT8MJW22AYX6FVT17F5XF",
    "service": "speaking",
    "operation": "speaking.grading",
    "timing": {
      "downstream_ms": 22140,
      "gateway_overhead_ms": 163,
      "total_ms": 22303
    }
  }
}

Trường và ví dụ đầy đủ, kèm mọi lỗi có thể gặp: mở trang Speaking JSON-by-URL.

Thử chấm Writing miễn phí

Chỉ dành cho Writing Task 1 và Task 2. Bạn cần khóa riêng để dùng môi trường thử.

Lấy khóa dùng thử

Chủ sở hữu hoặc quản trị viên đang hoạt động tạo khóa bằng lệnh bên dưới. Lệnh đã chọn môi trường thử và có thể cấp quyền chấm Writing, Speaking hoặc cả hai nếu Organization đã được bật các dịch vụ tương ứng.

Console hiện chỉ tạo khóa dùng thật.

Hạn mức miễn phí

25lượt / tổ chức

500lượt / toàn bộ môi trường thử

Tính theo tháng UTC. Hạn mức chung gồm cả lượt demo của AIHUB nên có thể hết trước hạn mức của tổ chức bạn.

Mã người học: Môi trường thử dùng cùng cách nhận diện người học đang bật cho tổ chức: mã thông thường (Declared User ID) hoặc mã có chữ ký (Signed User Assertion).

Request cấp Customer Sandbox API key
POST /v1/organizations/{organization_id}/api-keys
Authorization: Bearer <User Access JWT của owner hoặc admin>
Content-Type: application/json

{
  "name": "scoring-demo-sandbox",
  "scopes": ["writing.grade", "speaking.grade"],
  "allowed_environments": ["sandbox"]
}

Demo assertion không dành cho khách

Bạn không cần gọi đường dẫn này. POST /v1/sandbox/assertions chỉ dành cho Organization demo của AIHUB và khóa Sandbox do máy chủ AIHUB giữ. Khóa của bạn không gọi được đường dẫn này. Nếu tổ chức bật xác minh có chữ ký, hãy ký bằng khóa riêng của chính bạn.

Khi yêu cầu chấm bị lỗi

  • Writing bắt buộc có Idempotency-Key, lưu trạng thái replay 24 giờ. Key dài 1–255 byte UTF-8. Retry thì dùng lại đúng key và đúng body: AIHUB trả kết quả đã lưu thay vì chấm lại. Đổi body, hoặc gửi lại khi request cũ vẫn đang chạy, có thể trả 409 IDEMPOTENCY_CONFLICT.
  • Speaking không có idempotency. Mỗi lần gửi lại là một lần chấm mới, tính phí thêm. Đừng retry mù quáng; hãy quyết định có chấp nhận chấm lại hay không.
  • Lưu mã để được hỗ trợ: Kết quả thành công có dạng { data, meta }. Giữ meta.request_id của mỗi lần gọi để AIHUB tra khi cần. Không gửi API key, assertion, bài viết hay âm thanh cho hỗ trợ.
  • Chỉ thử lại khi được phép: Lỗi có dạng { error: { code, retryable, ... } }. Rẽ nhánh theo code, không theo message. Chỉ retry khi retryable là true, và đợi retry_after_ms khi có (nằm trong body, không phải header Retry-After). Đừng retry mọi lỗi 5xx theo status.
  • Giới hạn số lần thử lại: Tối đa hai hoặc ba lần, với thời gian chờ tăng dần và thêm một khoảng ngẫu nhiên. RATE_LIMITED nghĩa là chậm lại; CONCURRENCY_LIMIT nghĩa là giảm số request chạy cùng lúc; QUOTA_EXCEEDED có thể phải chờ tới tháng UTC sau.
  • Để đủ thời gian tải âm thanh: Ngân sách của Speaking multipart gồm cả thời gian tải lên. File lớn trên đường truyền chậm cần timeout client lớn hơn mức tối thiểu ở bảng thao tác.

Bảng dưới là tóm tắt chung. Lỗi riêng và response mẫu của từng thao tác nằm ở trang API Reference, link ở bảng thao tác.

StatusMãNguyên nhânViệc cần làm
400INVALID_REQUESTBody sai quy tắc hoặc có trường lạ.Sửa request. Retry vô ích.
401UNAUTHORIZEDAPI key sai, đã thu hồi hoặc hết hạn.Kiểm tra key.
401USER_IDENTITY_REQUIREDThiếu X-User-Identity trên thao tác chấm.Gửi X-User-Identity.
401INVALID_USER_IDENTITYDeclared User ID vi phạm quy tắc, hoặc Signed User Assertion không qua được xác minh.Declared: sửa giá trị. Signed: ký assertion mới, kiểm tra issuer, kid và JWKS.
403FORBIDDENKey thiếu scope của thao tác.Cấp key có scope phù hợp.
403ENVIRONMENT_NOT_ALLOWEDKey không dùng được ở hostname này.Dùng key đi đúng cặp với host của nó.
409IDEMPOTENCY_CONFLICTWriting dùng lại Idempotency-Key với body khác, hoặc request giống hệt vẫn đang chạy.Body khác: lấy key mới. Đang chạy: chờ rồi thử lại với cùng key và cùng body.
413PAYLOAD_TOO_LARGEBody vượt trần của thao tác.Giảm kích thước bài viết hoặc âm thanh.
429RATE_LIMITEDQuá nhiều request mỗi phút.Chờ retry_after_ms rồi thử lại.
429CONCURRENCY_LIMITQuá nhiều request chạy cùng lúc.Giảm số request song song, dùng hàng đợi có số worker cố định.
429QUOTA_EXCEEDEDHết hạn mức của kỳ hiện tại.Đừng retry ngay: chờ tới lúc hạn mức hồi (có thể là đầu tháng UTC sau) hoặc liên hệ AIHUB.
502 / 503AI_SERVICE_*Yêu cầu chấm có thể gặp lỗi, quá tải, gián đoạn hoặc trả về kết quả không đúng định dạng.Retry có backoff khi retryable là true. Với CONTRACT_VIOLATION thì không retry, báo request_id.
503IDENTITY_PROVIDER_UNAVAILABLEAIHUB không đọc được Identity configuration (cả hai dạng định danh đều có thể gặp), hoặc không lấy được khoá public từ nguồn JWKS (chỉ dạng ký).Retry. Với dạng ký, kiểm tra nguồn JWKS đã cấu hình. API key không có lỗi.
504AI_SERVICE_TIMEOUTChấm vượt ngân sách của thao tác.Writing: retry với cùng Idempotency-Key. Speaking: chỉ retry khi chấp nhận một lần chấm mới.
500INTERNAL_ERRORLỗi phía AIHUB.Retry sau, báo request_id nếu lặp lại.

Kiểm tra trước khi dùng thật

  • Key có đúng scope: writing.grade cho Writing, speaking.grade cho Speaking.
  • Mỗi key đi cặp với host của nó: production với api.aihubproduction.com, Customer Sandbox với sandbox.aihubproduction.com.
  • Đã xác định dạng định danh của Organization và mọi request gửi đúng dạng đó.
  • API key, Signed User Assertion và private key chỉ nằm trên server hoặc trong secret manager.
  • Timeout phía client từ 90 giây (Writing, Speaking multipart) hoặc 60 giây (Speaking JSON-by-URL).
  • Writing: một Idempotency-Key cho mỗi bài làm, retry với cùng key và cùng body. Speaking: không retry tự động.
  • Retry dựa trên retryable và retry_after_ms, tối đa hai hoặc ba lần, có backoff ngẫu nhiên.
  • Ghi log meta.request_id của mọi lần gọi. Key production riêng, có ngày hết hạn (tối đa 50 key mỗi Organization).

Bước tiếp theo