Developer API

Tài liệu API Verify PDF/XML

Hai cách xác thực PDF/XML, dùng chung API token. Chọn cách tích hợp phù hợp với ứng dụng của bạn.

01 · Tích hợp cơ bản

1. Gửi tài liệu và nhận kết quả ngay

API đồng bộ — phù hợp để bắt đầu tích hợp. Gửi tài liệu và chờ kết quả trong cùng HTTP request, không cần polling.

Gửi file Xử lý Nhận kết quả

MethodEndpointDữ liệu gửi
POST/api/v1/verify/fileMultipart với field file chứa PDF/XML.
POST/api/v1/verify/base64JSON: fileName, contentType, base64Content.

Dùng Authorization: Bearer YOUR_API_TOKEN. Các URL ví dụ dùng https://ca.9two.net; thay host theo môi trường được cấp. Request chịu giới hạn dung lượng, quota và rate limit.

File tối đa 25 MiB = 26.214.400 byte; request multipart tối đa 27 MiB = 28.311.552 byte để chừa chỗ cho metadata. Môi trường có thể đặt ngưỡng thấp hơn.

API này vẫn được hỗ trợ, không cần chuyển sang API nâng cao. Response dùng envelope success/requestId; đọc results[].outcome và documentSummary để xác định kết luận.

Ví dụ gửi file bằng cURL

Gửi một PDF/XML bằng multipart/form-data; tên field bắt buộc là file.

curl -X POST "https://ca.9two.net/api/v1/verify/file" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "[email protected]"
Ví dụ gửi Base64

base64Content là chuỗi Base64 thuần, không kèm tiền tố data:....

Giới hạn file sau giải mã là 25 MiB; toàn bộ JSON request tối đa 36 MiB = 37.748.736 byte vì Base64 tăng dung lượng khoảng 33%. IIS/proxy phải cho phép body tương ứng. Đây là endpoint đồng bộ hiện có; API theo job chỉ nhận multipart.

curl -X POST "https://ca.9two.net/api/v1/verify/base64" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "fileName": "contract.pdf", "contentType": "application/pdf", "base64Content": "JVBERi0xLjQK..." }'
Ví dụ tích hợp C#
using System.Net.Http.Headers;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_API_TOKEN");
using var form = new MultipartFormDataContent();
using var file = new StreamContent(File.OpenRead("contract.pdf"));
file.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(file, "file", "contract.pdf");
using var response = await client.PostAsync("https://ca.9two.net/api/v1/verify/file", form);
Console.WriteLine(await response.Content.ReadAsStringAsync());
response.EnsureSuccessStatusCode();
JSON phản hồi và các trường cần đọc

Ví dụ rút gọn một chữ ký chưa được tin cậy. success: true chỉ có nghĩa request được xử lý thành công.

{
  "success": true,
  "requestId": "00-...",
  "fileName": "contract.pdf",
  "fileType": "PDF",
  "signatureCount": 1,
  "documentSummary": {
    "label": "Thông tin tài liệu",
    "title": "Tài liệu còn nguyên vẹn và chưa bị chỉnh sửa",
    "message": "Có ít nhất 1 chữ ký số có nguồn Chưa tin cậy (hoặc có thể là self cert)",
    "status": "IntactWithUntrustedSignature",
    "isDocumentIntact": true,
    "hasUntrustedSignature": true,
    "isUsable": false
  },
  "results": [{
    "signatureIndex": 1,
    "outcome": "Invalid",
    "certificateTrustStatus": "Chưa tin cậy"
  }]
}

results[].outcome: Valid / Invalid / Indeterminate. documentSummary.isUsable là kết luận tổng hợp về khả năng sử dụng. Chuỗi mô tả được dịch chỉ phục vụ hiển thị.

Mã lỗi chuẩn hóa — API đồng bộ

Response lỗi dùng envelope success / requestId / errorCode / message. Không dùng schema lỗi async để parse response này.

Mã lỗiHTTP statusÝ nghĩaHướng xử lý
UNAUTHORIZED401/403Request không có quyền truy cập API hoặc user bị khóa.Kiểm tra Bearer token và trạng thái tài khoản.
INVALID_TOKEN401Token sai format, sai hash hoặc không tồn tại.Tạo token mới trong trang API Tokens và gửi đúng header Authorization.
TOKEN_EXPIRED401Token đã vượt quá ngày hết hạn.Rotate hoặc tạo token mới, sau đó cập nhật client application.
TOKEN_REVOKED401Token đã bị user hoặc admin thu hồi.Dừng dùng token cũ và tạo token mới nếu còn nhu cầu.
QUOTA_EXCEEDED429Vượt giới hạn request theo ngày hoặc tháng.Chờ quota reset hoặc nâng hạn mức khi có paid plan.
INVALID_FILE_TYPE400File không phải PDF/XML hợp lệ, MIME sai hoặc magic header không khớp.Gửi file PDF/XML thật, content-type đúng và không đổi đuôi giả.
FILE_TOO_LARGE413File vượt giới hạn upload cấu hình.Giảm dung lượng file hoặc liên hệ để nâng giới hạn.
INVALID_BASE64400Trường base64Content không decode được.Encode lại nội dung file bằng Base64 chuẩn, không thêm prefix data URI.
NO_SIGNATURE_FOUND400Tài liệu hợp lệ nhưng không có chữ ký số hỗ trợ.Kiểm tra lại file nguồn hoặc quy trình ký số.
VERIFICATION_FAILED408/400Quá trình verify không hoàn tất, timeout hoặc dữ liệu chữ ký lỗi.Thử lại với file khác hoặc kiểm tra tình trạng CA/CRL/OCSP.
INTERNAL_ERROR500Lỗi hệ thống ngoài dự kiến.Ghi lại requestId và liên hệ support/admin.

02 · Tích hợp nâng cao

2. Nâng cao: Xác thực theo job

API bất đồng bộ — dành cho nhu cầu theo dõi tiến độ, retry và hủy. Gửi tài liệu một lần, nhận verificationId rồi lấy kết quả sau, không giữ request mở trong toàn bộ quá trình kiểm tra.

Gửi tài liệu Nhận mã yêu cầu Theo dõi Lấy kết quả

Môi trường đang phục vụ tài liệu này đã bật API nhận job async. Việc tiếp nhận vẫn phụ thuộc quota, capacity, worker và storage.

Xem OpenAPI 3.0.3 · Tải JSON để import Postman/sinh SDK

Khi tạo job, bắt buộc có Idempotency-Key. Lưu key trước khi gửi và dùng lại cùng payload nếu mất kết nối để tránh tạo yêu cầu trùng.

MethodEndpointÝ nghĩa
POST/api/v1/verificationsGửi một PDF/XML; 202 khi tiếp nhận mới, 200 khi replay.
GET/api/v1/verifications/{verificationId}Tra cứu trạng thái/tiến độ; 200 hoặc 304 khi ETag không đổi.
GET/api/v1/verifications/{verificationId}/result200 khi có kết quả, 202 khi chờ, 409 nếu không có kết quả hoàn chỉnh, 410 khi hết hạn.
POST/api/v1/verifications/{verificationId}/cancel200 khi đã cancelled, 202 khi đang yêu cầu hủy; terminal khác trả 409.

Mọi endpoint dùng Authorization: Bearer YOUR_API_TOKEN. Đây là opaque token, không phải JWT. Token khác cùng owner có thể tra cứu nếu còn quyền; owner khác nhận 404. Các URL ví dụ dùng https://ca.9two.net; thay host theo môi trường được cấp.

Gửi tài liệu và nhận mã yêu cầu — cURL, JSON
curl -i -X POST "https://ca.9two.net/api/v1/verifications" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Idempotency-Key: 53228d8e-0bfa-45c8-9e54-8779982cdb34" \
  -F "[email protected];type=application/pdf" \
  -F "languageCode=vi-VN"
  • Nhận một file và languageCode tùy chọn. XML dùng application/xml hoặc text/xml.
  • Tối đa file 25 MiB = 26.214.400 byte, request multipart 27 MiB = 28.311.552 byte. Môi trường có thể đặt ngưỡng thấp hơn; proxy cũng có giới hạn riêng.
  • Ngôn ngữ: vi-VN (mặc định), en-US, zh-CN, ko-KR, ja-JP.
  • Không gửi base64, URL tải file, policy hay trust root tùy ý vào endpoint async này. Base64 hiện là endpoint đồng bộ riêng.
  • 202 chỉ được trả sau khi input và job đã lưu bền vững; thời gian upload không phải thời gian engine verify.
HTTP/1.1 202 Accepted
Location: /api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13
Retry-After: 3
Cache-Control: no-store
{
  "verificationId": "8d13f38e-b908-40d3-a08d-067249f73f13",
  "status": "queued",
  "acceptedAt": "2026-10-30T08:00:00Z",
  "startedAt": null,
  "completedAt": null,
  "progress": { "stage": "queued", "processedSignatures": 0, "totalSignatures": null },
  "resultAvailability": "not_ready",
  "resultExpiresAt": null,
  "retryAfterSeconds": 3,
  "version": "76c69727-c4c7-4a39-bded-746380787783",
  "errorCode": null,
  "errorCategory": null,
  "links": {
    "status": "/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13",
    "result": "/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13/result"
  },
  "inputDeletedAt": null,
  "inputRetentionStatus": "retained_for_processing"
}
Theo dõi trạng thái — polling, Retry-After và ETag
curl -i "https://ca.9two.net/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Trạng thái: queued, running, retry_scheduled, completed, failed, cancelled. Ba trạng thái cuối là terminal. Với failed, đọc errorCode/errorCategory; GET status vẫn 200 vì tra cứu thành công.

totalSignatures có thể null trước parse. Xử lý đủ số chữ ký chưa đồng nghĩa đã công bố kết quả; dựa vào status. Completed không có nghĩa chữ ký hợp lệ.

Poll theo Retry-After/retryAfterSeconds, thêm jitter/backoff và ngân sách chờ ở client. Ví dụ mặc định gợi ý 3 giây; không gọi vòng lặp liên tục. Dừng khi terminal.

GET status trả ETag. Gửi lại đúng ETag đó qua If-None-Match; 304 không có body, không parse JSON từ response 304.

curl -i "https://ca.9two.net/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H 'If-None-Match: "76c69727c4c74a39bded746380787783"'
Lấy kết quả và đọc kết luận
curl -i "https://ca.9two.net/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13/result" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Khi chờ, response 202 dùng Job body, Location về status và Retry-After. Khi hoàn tất/còn hạn, response 200 dùng Result body khác schema với Job.

Ví dụ dưới đây rút gọn phần details nhưng vẫn giữ một kết quả tương ứng signatureCount = 1; hash và nhà cung cấp chỉ là minh họa.

{
  "verificationId": "8d13f38e-b908-40d3-a08d-067249f73f13",
  "resultVersion": "1.0",
  "status": "completed",
  "documentOutcome": "invalid",
  "signatureCount": 1,
  "file": {
    "name": "contract.pdf", "fileType": "PDF", "sizeBytes": 1048576,
    "sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  },
  "summary": { "valid": 0, "invalid": 1, "indeterminate": 0 },
  "verifiedAt": "2026-10-30T08:00:42Z",
  "policyVersion": "2976205a-8bdb-4073-a191-aeb567e306f9",
  "trustSnapshotId": "context-hash",
  "resultExpiresAt": "2026-11-06T08:00:42Z",
  "timing": { "queueWaitMs": 2000, "processingTimeMs": 40000, "totalLatencyMs": 42000 },
  "results": [{
    "signatureIndex": 1, "outcome": "invalid", "durationMs": 39000,
    "evidenceObservedAt": "2026-10-30T08:00:41Z",
    "certificate": { "providerId": "example-ca", "providerName": "CA minh họa", "trustType": "bundle", "subjectType": "unknown", "keyAlgorithm": "RSA", "keySize": 2048 },
    "details": { "signatureIndex": 1, "outcome": "Invalid", "timestamp": { "status": "Absent" }, "warnings": [] }
  }]
}
TrườngCách dùng
documentOutcomevalid / invalid / indeterminate / unsigned, tách khỏi trạng thái xử lý.
signatureCount, summary, results[]Số lượng và kết quả từng chữ ký. File 10 chữ ký vẫn là một yêu cầu tài liệu.
results[].outcomeMã canonical chữ thường valid / invalid / indeterminate để tích hợp máy.
results[].detailsIssuer/subject, integrity, revocation, timestamp, historicalValidation, warnings. Enum tương thích có thể PascalCase; chuỗi dịch chỉ để hiển thị.
file.sha256Đối soát tài liệu đã nộp, không phải link tải file gốc.
verifiedAt, policyVersion, trustSnapshotIdMốc xác thực và ngữ cảnh đã dùng. Thời điểm nhận upload không phải thời điểm đã verify.
timingqueueWaitMs / processingTimeMs / totalLatencyMs; tổng latency không phải crypto thuần.
resultExpiresAtHết hạn: GET result trả 410 khi metadata còn; metadata đã dọn có thể là 404.

Không có chữ ký có thể là completed/unsigned, không được xem là hợp lệ. API đồng bộ cũ vẫn có hành vi NO_SIGNATURE_FOUND riêng. Xem schema Result/Signature đầy đủ trong OpenAPI.

Gửi lại an toàn — Idempotency-Key và retry

Lưu Idempotency-Key trước khi gửi. Cùng owner/token/key và cùng file, tên file, MIME chuẩn hóa, ngôn ngữ sẽ replay 200 cùng job, có Idempotency-Replayed: true. Payload khác trả 409 idempotency_conflict. Không tạo key mới chỉ vì chưa nhận được response.

Key được giữ tối thiểu 24 giờ và không hết hạn khi job còn hoạt động. Key mới là lần nộp mới. Đổi token làm thay đổi scope key: lưu job ID để tra cứu thay vì tự POST lại bằng token mới.

Khi gặp 429/503, tôn trọng Retry-After và chính sách quota. Worker tự retry lỗi có thể phục hồi trong giới hạn cấu hình; client không cần tạo job mới. Kết luận invalid/indeterminate không được retry chỉ để tìm kết quả hợp lệ.

Hủy yêu cầu, quota và thời hạn lưu dữ liệu
curl -i -X POST "https://ca.9two.net/api/v1/verifications/8d13f38e-b908-40d3-a08d-067249f73f13/cancel" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Hủy là hợp tác: 202 xác nhận đã yêu cầu, tiếp tục tra cứu tới terminal. Cancelled hủy lặp lại trả 200; completed/failed trả 409. Dừng chờ/hủy HTTP ở client không tự hủy job đã nhận.

  • Job mới giữ một quota reservation. Polling, replay và retry worker không trừ thêm lượt; vẫn có rate limit/capacity.
  • Invalid/indeterminate/unsigned vẫn tính lượt. Hủy trước khi bắt đầu hoặc lỗi hạ tầng cuối cùng được hoàn theo chính sách; hủy sau khi đã bắt đầu vẫn tính lượt.
  • Input mã hóa được giữ qua queue/running/retry và lên lịch xóa ngay khi terminal. Cleanup thử lại định kỳ, không cung cấp tải file gốc.
  • inputRetentionStatus: retained_for_processing / deletion_pending / deleted / not_stored. inputDeletedAt chỉ có sau xác nhận xóa.
  • Cấu hình môi trường phục vụ tài liệu: result 7 ngày, metadata 30 ngày, cleanup mỗi 300 giây. Đọc thời hạn cụ thể từ từng job.

Chính sách bảo mật — API async. Schema có sẵn không đồng nghĩa môi trường đã bật producer/worker.

Ví dụ C# đầy đủ — gửi → chờ → lấy kết quả

Đặt API_TOKEN, thay host và file. Lưu key/job ID để tiếp tục tra cứu nếu chương trình dừng; có thể truyền lại key đã lưu qua IDEMPOTENCY_KEY. Khi retry POST do mất kết nối/503, tạo lại stream/form nhưng dùng cùng key và payload.

using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

using var client = new HttpClient { BaseAddress = new Uri("https://ca.9two.net/") };
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("API_TOKEN")
        ?? throw new InvalidOperationException("Missing API_TOKEN"));
using var waitBudget = new CancellationTokenSource(TimeSpan.FromMinutes(10));
var ct = waitBudget.Token; // Hết thời gian chờ không tự hủy job trên server.
var key = Environment.GetEnvironmentVariable("IDEMPOTENCY_KEY") ?? Guid.NewGuid().ToString();
Console.WriteLine($"Idempotency-Key: {key}");
using var form = new MultipartFormDataContent();
using var file = new StreamContent(File.OpenRead("contract.pdf"));
file.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
form.Add(file, "file", "contract.pdf");
form.Add(new StringContent("vi-VN"), "languageCode");
using var create = new HttpRequestMessage(HttpMethod.Post, "api/v1/verifications") { Content = form };
create.Headers.Add("Idempotency-Key", key);
using var accepted = await client.SendAsync(create, ct);
if (accepted.StatusCode != HttpStatusCode.Accepted && accepted.StatusCode != HttpStatusCode.OK)
{
    Console.WriteLine(await accepted.Content.ReadAsStringAsync(ct));
    accepted.EnsureSuccessStatusCode();
}
using var admission = JsonDocument.Parse(await accepted.Content.ReadAsStringAsync(ct));
var id = admission.RootElement.GetProperty("verificationId").GetGuid();
Console.WriteLine($"verificationId: {id}");
var path = $"api/v1/verifications/{id:D}";
while (true)
{
    using var statusResponse = await client.GetAsync(path, ct);
    var delay = RetryDelay(statusResponse);
    if (statusResponse.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.ServiceUnavailable)
    { await Task.Delay(delay, ct); continue; }
    statusResponse.EnsureSuccessStatusCode();
    using var statusJson = JsonDocument.Parse(await statusResponse.Content.ReadAsStringAsync(ct));
    var state = statusJson.RootElement.GetProperty("status").GetString();
    if (state is "failed" or "cancelled")
    { Console.WriteLine(statusJson.RootElement); break; }
    if (state == "completed")
    {
        using var result = await client.GetAsync(path + "/result", ct);
        if (result.StatusCode == HttpStatusCode.OK)
        { Console.WriteLine(await result.Content.ReadAsStringAsync(ct)); break; }
        if (result.StatusCode is not (HttpStatusCode.Accepted or HttpStatusCode.TooManyRequests or HttpStatusCode.ServiceUnavailable))
        { Console.WriteLine(await result.Content.ReadAsStringAsync(ct)); result.EnsureSuccessStatusCode(); }
        delay = RetryDelay(result);
    }
    await Task.Delay(delay, ct);
}

static TimeSpan RetryDelay(HttpResponseMessage response)
{
    var requested = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(3);
    return TimeSpan.FromMilliseconds(Math.Max(1000, requested.TotalMilliseconds)
        + Random.Shared.Next(0, 500));
}
Mã lỗi nâng cao và cách xử lý

RFC 9457 Problem Details

application/problem+json, mã chữ thường, không dùng envelope success/errorCode/message của API đồng bộ. Với job failed, GET status vẫn 200 và chứa lỗi của job trong body.

{
  "type": "https://ca.9two.net/problems/idempotency_conflict",
  "title": "idempotency_conflict",
  "status": 409,
  "detail": "Idempotency-Key đã được dùng với nội dung khác.",
  "instance": "/api/v1/verifications",
  "errorCode": "idempotency_conflict",
  "traceId": "server-generated-trace-id"
}
errorCodeHTTPXử lý
missing_idempotency_key / invalid_idempotency_key400Gửi đúng một key dài 8–128 ký tự ASCII.
missing_file / invalid_multipart / invalid_filename / invalid_language400Sửa file, multipart hoặc tham số, không lặp lại request sai.
unauthorized / forbidden401/403Kiểm tra Bearer token và trạng thái/quyền tài khoản.
not_found404ID không tồn tại hoặc không thuộc owner; biết ID không đủ quyền truy cập.
idempotency_conflict409Key đã dùng cho payload khác. Chỉ đổi key khi thực sự tạo lần nộp mới.
already_finished / not_async / result_not_available409Đọc status, thao tác không phù hợp trạng thái/loại yêu cầu.
result_expired410Kết quả đã hết hạn; cần lần xác thực mới nếu vẫn cần kết quả.
file_too_large413Giới hạn file và giới hạn toàn bộ request là hai ngưỡng khác nhau.
unsupported_type415Dùng multipart/form-data và PDF/XML có nội dung phù hợp.
quota_exceeded / owner_capacity_exceeded / rate_limit_exceeded429Tôn trọng Retry-After/quota; polling không trừ quota nhưng có rate limit.
request_cancelled408Nếu chưa biết đã tiếp nhận hay chưa, gửi lại cùng key/file/options để đối soát.
async_disabled503Môi trường chưa bật producer; liên hệ quản trị, không coi job đã được nhận.
queue_full / staging_expired / dependency_unavailable503Chờ Retry-After, gửi lại POST bằng cùng key và payload.

Lỗi job như invalid_document / queue_timeout / execution_timeout / attempts_exhausted / owner_inactive / storage_integrity_failed / storage_size_limit được đọc từ status. Object đã mã hóa tối đa 64 MiB (độc lập giới hạn file gốc 25 MiB); lỗi kích thước/integrity cố định không được retry như lỗi mạng.

worker_interrupted có thể xuất hiện khi chờ retry; không tạo POST mới chỉ vì worker đang phục hồi.

Cách đọc kết quả xác thực

Xử lý thành công không đồng nghĩa chữ ký hợp lệ.

  • Nhận kết quả ngay: đọc results[].outcome và documentSummary, không chỉ success.
  • Theo job: đọc documentOutcome và kết quả từng chữ ký; completed chỉ là trạng thái hoàn tất xử lý.

Kết luận có thể hợp lệ, không hợp lệ hoặc chưa đủ cơ sở. Job không có chữ ký có thể hoàn tất với unsigned, không phải hợp lệ.

Tìm hiểu chi tiết kết quả xác thực — OCSP, CRL và timestamp

Các trường bên dưới thuộc results[] của API đồng bộ; với API theo job, đọc trong results[].details.

results[].revocation gồm mode (OcspFirst / OcspOnly / CrlOnly / CrlFirst), configurationVersion, unknownPolicy, source, fallbackReason, và crl/ocsp với status, failure, revocationTime.

Trạng thái nguồn: Good, Revoked, Unknown, Unavailable, InvalidResponse, Skipped. Nguồn bị bỏ qua theo chế độ có trạng thái Skipped. Các mã này độc lập ngôn ngữ; các trường mô tả CRL/OCSP cũ vẫn được giữ.

results[].timestamp.status: Absent, Unverified, Trusted, Invalid. Chỉ Trusted được dùng làm mốc lịch sử. claimedSigningTime là thời gian khai báo; timestamp.time là thời gian trong token, không mặc nhiên đáng tin cậy.

historicalValidation có status, referenceTime, missingEvidence. Trạng thái: NotRequired, ValidAtTimestamp, InvalidAtReferenceTime, Indeterminate. Cảnh báo trả trong warnings[]; invalidReason chỉ dùng khi kết luận không hợp lệ.

revocationReason, revocationReasonName, invalidityTime phân biệt thu hồi/đình chỉ và thời điểm mất hiệu lực. certificateHold (mã 6) cần lịch sử đình chỉ/khôi phục để kết luận lịch sử; ngày đình chỉ hiện tại không tự chứng minh chữ ký quá khứ hợp lệ.