Lewati ke konten utama

Error API

Halaman ini mendefinisikan kontrak error lintas fungsi untuk setiap rute HTTP dan SSE saat ini. Skema request dan response khusus rute tersedia di query dan pencarian, conversation dan memori, serta ingesti dan dokumen.

Skema error​

Response error biasa​

Sebagian besar error non-validasi mengembalikan JSON dengan satu string detail.

{
"detail": "Invalid or missing API key"
}
FieldTipeMakna
detailstringAlasan yang dapat dibaca manusia. Aman ditampilkan setelah peninjauan copy produk, tetapi jangan membuat percabangan berdasarkan prosa persisnya kecuali halaman rute mendokumentasikan prosa tersebut sebagai stabil.

Tidak ada field code, request_id, atau errors standar pada error HTTP biasa.

Response error validasi (422)​

Error skema request menggunakan bentuk validasi default FastAPI: detail adalah array.

{
"detail": [
{
"loc": ["body", "query"],
"msg": "Field required",
"type": "missing"
}
]
}
FieldTipeMakna
detailarraySatu atau beberapa masalah validasi.
detail[].locarrayLokasi field yang tidak valid, biasanya dimulai dengan body, query, atau path.
detail[].msgstringPesan validasi yang dapat dibaca manusia.
detail[].typestringKategori validasi FastAPI/Pydantic.
detail[].inputanyDapat ada ketika validator menyertakan input yang ditolak.

Client harus menangani kedua bentuk karena detail dapat berupa string atau array.

Penolakan timeout model dan anggaran pada /query serta /query/stream mengikuti ADR-0041; pengecualian ini berlaku pada panduan 502 dan SSE error di bawah.

Panduan retry​

Status atau kegagalanCoba lagi?Perilaku client
400Tidak, sampai request berubahPerbaiki isi request, seperti teks query yang hanya berisi spasi, teks dokumen kosong, manifest batch yang tidak valid, atau URL yang ditolak.
401Tidak, sampai kredensial atau injeksi identitas berubahSegarkan konfigurasi API key sisi server atau header identitas. Jangan retry dari kode browser dengan shared key yang terekspos.
403Tidak, sampai konfigurasi platform atau peran caller berubahTiga penggunaan saat ini: ingesti URL dinonaktifkan oleh feature flag; DELETE /documents/{document_id} ditolak karena principal terverifikasi bukan admin; DELETE /workspaces/{workspace_id}/documents/{document_id} ditolak karena caller adalah viewer. Sembunyikan atau nonaktifkan fitur untuk environment atau peran tersebut.
404Biasanya tidakUntuk resource yang dibatasi kepemilikan, resource yang tidak dikenal dan yang bukan milik pengguna sama-sama mengembalikan 404. Minta pengguna memilih resource yang dimilikinya atau memulai conversation baru.
409Tidak, sampai unggahan selesaiPenghapusan workspace saat unggahan tujuan masih diproses atau ada unggahan aktif di workspace yang belum memiliki id dokumen tersimpan. Tidak ada yang berubah. Coba lagi setelah selesai.
413Tidak, sampai query/context berubahKurangi context conversation atau ajukan pertanyaan yang lebih sempit.
422Tidak, sampai skema request berubahPerbaiki field yang hilang, nilai enum yang tidak valid, tipe parameter path yang salah, field tambahan yang dilarang, atau pelanggaran ukuran list.
500Bergantung pada detailPenghapusan mungkin sebagian atau hasilnya belum pasti. Ikuti panduan pemulihan untuk penghapusan admin global atau penghapusan workspace.
502Ya, dengan backoff terbatasDependency model, embedding, reranker, atau orkestrasi gagal. Retry beberapa kali, lalu tampilkan pesan layanan sementara.
503Ya, dengan backoff terbatasObject storage tidak tersedia untuk rute upload/batch. Retry setelah jeda singkat atau minta pengguna mencoba lagi nanti.
Timeout jaringan sebelum response apa punMungkinGunakan kebijakan timeout gateway Anda. Untuk /query/stream, izinkan jeda panjang sebelum dan antar-frame progres; lihat kontrak stream.
Disconnect/cancel clientTidak ada retry otomatisPerlakukan sebagai pembatalan pengguna kecuali UI Anda secara eksplisit menawarkan resume atau retry.

Pola retry yang direkomendasikan untuk response 5xx yang dapat di-retry:

  1. retry paling banyak 2-3 kali;
  2. gunakan exponential backoff dengan jitter;
  3. jangan menduplikasi tindakan pengguna yang terlihat, seperti upload file, tanpa state UI yang jelas;
  4. untuk /query, pertahankan conversation_id yang sama hanya ketika pengguna sengaja melakukan retry pada turn yang sama.

Perilaku 404 berdasarkan kepemilikan​

API menghindari kebocoran keberadaan resource milik pengguna.

ResourcePerilaku
ConversationGET atau DELETE /conversations/{conversation_id} dan POST /query dengan conversation_id mengembalikan 404 baik untuk ID yang tidak dikenal maupun conversation yang dimiliki subject lain.
MemoriDELETE /memories/{memory_id} mengembalikan 404 baik untuk ID yang tidak dikenal maupun memori yang dimiliki subject lain.
Daftar conversation dan memoriGET /conversations dan GET /memories hanya mengembalikan resource milik principal yang telah diselesaikan; resource milik subject lain dihilangkan.

Jangan tampilkan copy kepada pengguna seperti "Anda tidak memiliki izin ke resource yang sudah ada" untuk 404 ini. Gunakan copy netral seperti "Item tersebut tidak ditemukan atau tidak lagi tersedia."

Perilaku pencarian kosong​

POST /search mengembalikan 200 bahkan ketika tidak ada passage yang cocok.

{
"mode": "hybrid",
"results": []
}

Ini bukan error. Tampilkan empty state dan biarkan pengguna menyempurnakan query.

collection_ids dan document_ids yang well-formed juga dapat mempersempit hasil menjadi set kosong. Collection/dokumen yang tidak dikenal, dilarang, atau tidak dapat diakses menghasilkan nol row, bukan 403, sehingga client tidak dapat menggunakan hasil kosong untuk membedakan kasus-kasus tersebut. Entri document_ids yang malformed berbeda: ini adalah validasi request dan mengembalikan 422.

POST /query mengembalikan 404 dengan string detail biasa jika tidak ada konten ter-index yang terlihat untuk menjawab, termasuk ketika fokus dokumen yang well-formed tidak menghasilkan chunk yang cocok. Untuk pengecualian /query/v2, lihat Galat pada kontrak v2. Untuk /query/stream, kegagalan yang sama menjadi error HTTP sebelum streaming atau error SSE terminal setelah progres dimulai; lihat kontrak stream.

Kegagalan pra-stream vs dalam-stream​

Kontrak Query stream mendefinisikan batas antara error setup HTTP dan error SSE terminal setelah progres langsung dimulai.

Kegagalan pra-stream​

Kegagalan sebelum stream dimulai mengembalikan status HTTP normal dan error JSON. Client harus memeriksa response.ok sebelum membaca stream.

Contoh:

KegagalanStatus
API key hilang atau tidak valid401
Query kosong400
conversation_id tidak dikenal atau lintas pengguna404
Tidak ada konten ter-index yang cocok (/query, atau /query/v2 ketika selection yang berisi dokumen tidak menemukan apa pun)404
Selection bernama kosong (/query/v2)Lihat Galat v2
Context window melampaui batas413
Kegagalan model atau orkestrasi502
Validasi skema request422

guard.blocked bukan error. Ini adalah hasil policy yang selesai dan diikuti done dengan finish_reason: "guard_blocked".

Kegagalan dalam stream​

Setelah streaming dimulai, kegagalan pipeline dan fault framing tak terduga mengirim event terminal error. Fault framing menggunakan:

event: error
data: {"code":"stream_failed","message":"..."}

Ketika error muncul, done tidak menyusul. Perlakukan sebagai terminal dan tawarkan retry. Disconnect client adalah pembatalan, bukan frame error server.

Lihat kontrak event stream lengkap di Query stream.

Matriks rute dan status​

401 pada rute data berarti header API key hilang/salah atau, pada rute yang membawa principal ketika strict identity diaktifkan, identitas tepercaya hilang/malformed.

RuteBerhasilError clientError server/dependencyCatatan
GET /healthz200--Tidak memerlukan API key.
GET /readyz200-503Tidak memerlukan API key. 503 mencantumkan setiap dependency yang gagal.
GET /openapi.json200--Tidak memerlukan API key. Tersedia di setiap environment.
GET /docs200 di luar production404 di production-Hanya Swagger UI; bukan API aplikasi.
POST /query200400, 401, 404, 413, 422502404 mencakup conversation yang tidak dikenal/lintas pengguna dan tidak adanya konten ter-index.
POST /query/stream200 SSE400, 401, 404, 413, 422502 sebelum stream; SSE error setelah stream dimulaiBody request dan kegagalan setup sama seperti /query.
POST /search200400, 401, 422502Hasil kosong adalah 200 dengan results: [].
POST /conversations201401, 422-Membuat conversation yang dimiliki principal yang telah diselesaikan.
GET /conversations200401-Hanya mengembalikan conversation milik principal yang telah diselesaikan.
GET /conversations/{conversation_id}200401, 404, 422-ID yang tidak dikenal dan lintas pengguna sama-sama mengembalikan 404.
DELETE /conversations/{conversation_id}204401, 404, 422-Menghapus conversation milik pemanggil beserta semua pesannya; ID yang tidak dikenal dan lintas pengguna sama-sama mengembalikan 404.
GET /memories200401-Hanya mengembalikan memori non-terhapus milik principal yang telah diselesaikan.
DELETE /memories/{memory_id}204401, 404, 422-ID yang tidak dikenal dan lintas pengguna sama-sama mengembalikan 404.
DELETE /memories200401-Penghapusan forget-me untuk semua memori milik principal yang telah diselesaikan.
POST /documents/txt200400, 401, 422502Ingesti teks inline; konten ter-parse kosong adalah 400, kegagalan dependency model adalah 502.
POST /documents200400, 401, 422503Upload multipart; kegagalan storage adalah 503.
GET /documents200401, 422-Filter status opsional; nilai status yang tidak valid divalidasi sebagai 422.
GET /documents/{document_id}200401, 404-ID dokumen yang tidak dikenal mengembalikan 404.
DELETE /documents/{document_id}204401, 403, 404500Hanya admin ketika autentikasi terverifikasi aktif; operator/user mendapat 403. ID yang tidak dikenal, malformed, dan sudah dihapus semuanya mengembalikan 404. Lihat Ingesti dan dokumen.
DELETE /workspaces/{workspace_id}/documents/{document_id}204401, 403, 404, 409500Lihat Workspace sources untuk otorisasi, pemeriksaan unggahan aktif, dan pemulihan.
POST /ingestion/batch200400, 401, 422503Tepat salah satu dari prefix atau keys wajib ada. Kegagalan list/enqueue storage adalah 503.
POST /ingestion/url200 ketika aktif400, 401, 403, 422-403 berarti ingesti URL dinonaktifkan. Skema, host, ekstensi, atau penolakan allow-list yang buruk adalah 400.
GET /ingestion/jobs/{job_id}200401, 404-ID job yang tidak dikenal mengembalikan 404.

Makna status umum​

StatusMakna
200Request selesai. Untuk /search, ini dapat menyertakan array results kosong. Untuk /query/stream, ini memulai stream SSE.
201Conversation dibuat.
204Penghapusan conversation, memori, atau dokumen berhasil dan tidak mengembalikan body.
400Bentuk request valid secara sintaksis, tetapi input bisnis tidak valid.
401API key hilang/salah, atau kegagalan identitas tepercaya yang ketat pada rute yang membawa principal.
403Fitur dinonaktifkan untuk environment (ingesti URL), atau principal terverifikasi tidak memiliki peran yang diperlukan (DELETE /documents/{document_id} hanya untuk admin; DELETE /workspaces/{workspace_id}/documents/{document_id} hanya untuk owner dan partner).
404Resource hilang, resource milik pengguna tidak dapat diakses, atau tidak ada konten ter-index untuk query.
409Unggahan tujuan masih diproses atau unggahan aktif di workspace belum memiliki id dokumen tersimpan (hapus dokumen workspace). Tidak ada yang berubah.
413Context query melampaui anggaran model/sintesis.
422Error validasi request FastAPI/Pydantic.
500Penghapusan gagal; lihat panduan pemulihan per rute di atas.
502Kegagalan dependency model/orchestrator upstream.
503Dependency object storage tidak tersedia untuk ingesti.

Parsing error TypeScript​

Gunakan parser yang menangani error biasa dan error validasi, serta tetap memberikan output yang berguna jika intermediary mengembalikan non-JSON.

type PlainApiError = { detail: string };

type ValidationIssue = {
loc?: Array<string | number>;
msg?: string;
type?: string;
input?: unknown;
};

type ValidationApiError = { detail: ValidationIssue[] };

type ApiErrorBody = PlainApiError | ValidationApiError | Record<string, unknown>;

export class ApiHttpError extends Error {
constructor(
message: string,
readonly status: number,
readonly body: ApiErrorBody | string | null,
) {
super(message);
this.name = "ApiHttpError";
}
}

function isPlainApiError(value: unknown): value is PlainApiError {
return (
typeof value === "object" &&
value !== null &&
typeof (value as { detail?: unknown }).detail === "string"
);
}

function isValidationApiError(value: unknown): value is ValidationApiError {
return (
typeof value === "object" &&
value !== null &&
Array.isArray((value as { detail?: unknown }).detail)
);
}

function summarizeValidation(error: ValidationApiError): string {
return error.detail
.map((issue) => {
const path = issue.loc?.join(".") ?? "request";
return `${path}: ${issue.msg ?? issue.type ?? "invalid value"}`;
})
.join("; ");
}

export async function readApiError(response: Response): Promise<string> {
const contentType = response.headers.get("content-type") ?? "";

let body: ApiErrorBody | string | null = null;
if (contentType.includes("application/json")) {
body = (await response.json().catch(() => null)) as ApiErrorBody | null;
} else {
body = await response.text().catch(() => null);
}

let message: string;
if (isPlainApiError(body)) {
message = body.detail;
} else if (isValidationApiError(body)) {
message = summarizeValidation(body);
} else if (typeof body === "string" && body.trim()) {
message = body.trim();
} else {
message = response.statusText || "Request failed";
}

throw new ApiHttpError(`HTTP ${response.status}: ${message}`, response.status, body);
}

Gunakan ini sebelum membaca body /query/stream:

const response = await fetch("/query/stream", requestInit);
if (!response.ok) {
await readApiError(response);
}

// response.body now contains SSE frames.