가맹점 Backend가 호출하는 OmniID 외부 API 스펙입니다.
Base URL#
| 환경 | Base URL |
|---|---|
| test | https://api.test.omniid.ai.kr |
| production | https://api.omniid.ai.kr |
모든 구간은 HTTPS입니다. HTTP 요청은 HTTPS로 redirect됩니다. Hosted UI 주소는 직접 조립하지 말고 세션 생성 응답의 popup_url을 그대로 사용하십시오. test/production credential은 분리 발급되며 merchant_id, client_id, client_secret을 환경 간에 혼용하지 않습니다 (연동 가이드 §1).
Authentication#
가맹점 Backend는 OAuth2 client credentials 방식으로 access token을 발급받고, OmniID API 호출 시 Bearer token을 전달합니다.
POST/oauth/token#
Access token을 발급합니다.
Method
POST
Content-Type
application/x-www-form-urlencoded
Request
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | yes | client_credentials 고정 |
client_id | string | yes | OmniID가 가맹점에 발급한 client id |
client_secret | string | yes | OmniID가 가맹점에 발급한 client secret |
scope | string | no | 공백으로 구분한 scope 목록. 생략 시 client 기본 scope 사용 |
Example:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=client_merchant_123&
client_secret=secret_xxx&
scope=enrollment:create verification:create result:read
Response 200
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 600,
"scope": "enrollment:create verification:create result:read"
}
Token Claims
Access token은 short-lived JWT로 발급합니다.
| Claim | Description |
|---|---|
iss | token issuer |
sub | merchant_id |
aud | omniid-api |
exp | 만료 시각 |
iat | 발급 시각 |
jti | token id |
merchant_id | OmniID가 발급한 가맹점 ID |
client_id | token 발급에 사용한 OAuth client ID |
scope | 허용 scope |
Error Codes
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | 필수 field 누락 또는 형식 오류 |
| 400 | unsupported_grant_type | client_credentials 외 grant type |
| 401 | invalid_client | client 인증 실패 |
| 400 | invalid_scope | 요청 scope가 허용 범위를 벗어남 |
| 429 | rate_limited | token endpoint rate limit 초과 |
Security Notes
client_secret은 브라우저, 모바일 앱, 바이오 인증앱에 노출 금지client_secret과 access token 원문은 로그에 남기기 금지- refresh token은 MVP에서 발급하지 않습니다.
- access token 기본 만료 시간은 10분입니다.
- 현재 test/production token endpoint 설정은
client_id별 60초에 30회입니다.
Common Headers#
OmniID API 호출에는 다음 header를 사용합니다.
Authorization: Bearer {access_token}
Content-Type: application/json
Idempotency-Key: {uuid} # state-changing request 권장
응답 추적 (X-Request-Id)#
HTTPS API endpoint가 반환하는 모든 application 응답(성공·실패)에 X-Request-Id 응답
헤더를 반환합니다. 이 값은 OmniID가 요청마다 생성합니다. 가맹점은 CS 문의 시 이 값 또는
session_id를 제출합니다. 에러 body의 request_id는 응답 헤더와 동일합니다. 성공 응답
body에는 별도 request_id 필드를 추가하지 않습니다(헤더가 canonical). HTTP→HTTPS 301
redirect나 reverse proxy 자체 오류는 application 응답에 포함되지 않습니다.
Common Error Response#
{
"error": {
"code": "invalid_token",
"message": "Access token is invalid.",
"request_id": "req_..."
}
}
외부 응답에는 provider 내부 payload나 바이오 payload를 포함하지 않습니다.
Common Request Errors#
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | JSON 등 request body를 해석할 수 없음 |
| 413 | payload_too_large | request body 크기 제한 초과 |
JSON body 제한은 1 MiB(1,048,576 bytes), application/x-www-form-urlencoded body 제한은
64 KiB(65,536 bytes)입니다. 두 오류 모두 공통 오류 형식과 X-Request-Id 응답 헤더를
사용합니다.
Scope#
| Scope | Usage |
|---|---|
enrollment:create | 등록 세션 생성 |
verification:create | 확인 세션 생성 |
result:read | 등록/확인 결과 조회 |
session:cancel | 세션 취소 |
POST/v1/enrollment-sessions#
등록 세션을 생성합니다.
Authentication#
Bearer token with enrollment:create
Request#
{
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"return_url": "https://merchant.example.com/bio/complete",
"provider_id": "pointlink",
"type": "fingerprint",
"finger": "LI",
"locale": "ko-KR",
"metadata": {
"order_ref": "optional-non-sensitive-ref"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | yes | 1~128자. token의 merchant_id와 일치해야 함 |
merchant_user_ref | string | yes | 1~256자. 가맹점 로그인 사용자를 나타내는 비식별 불투명 참조값 |
return_url | string | no | 최대 2048자의 전체 HTTPS URL(HTTP는 거부). PC popup postMessage target origin 고정용이며 모바일 자동 복귀 계약이나 최종 결과 판단에 사용하지 않음 |
provider_id | string | no | 1~64자. MVP 기본값 pointlink |
type | string | no | 1~32자. MVP 기본값 fingerprint |
finger | string | no | PointLink finger code. 예: LI, LM, RI, RM. 등록에서는 앱에 전달되지 않는 참고값입니다. PointLink 딥링크의 finger는 match 전용이며, 등록 시 앱이 보고한 손가락이 가맹점 Bio API 저장 요청으로 전달됩니다. 가맹점은 등록 결과 조회의 result.finger를 저장해 확인 세션에 사용합니다. |
locale | string | no | 최대 16자. Hosted UI 표시 locale |
metadata | object | no | JSON 직렬화 기준 최대 4096자. 비민감 trace용 메타데이터만 허용 |
merchant_user_ref에는 이메일, 전화번호, 이름, 주민등록번호 등 개인정보 원문을 넣지 않습니다. 가맹점 DB의 무작위 내부 회원 ID(UUID 등)를 권장합니다. 개인정보에서 값을 유도해야 한다면 가맹점만 보관하는 비밀키와 용도 구분값을 사용한 HMAC을 적용하고, 전화번호처럼 경우의 수가 작은 값의 단순 SHA-256 해시는 사용하지 않습니다. metadata에도 features, enc_key, 생체 원본, 주민번호, 카드번호 같은 민감정보를 넣지 않으며, 해당 의미를 가진 최상위 key도 허용되지 않습니다.
Response 201#
{
"enrollment_session_id": "enr_01J...",
"session_id": "enr_01J...",
"mode": "enroll",
"status": "created",
"popup_url": "https://verify.omniid.ai.kr/s/enr_01J...?token=hosted_...",
"expires_at": "2026-07-02T13:10:00Z",
"provider_id": "pointlink"
}
Error Codes#
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | 필수 field 누락 또는 형식 오류 |
| 400 | unsupported_provider | 지원하지 않는 provider. MVP는 pointlink만 지원 |
| 400 | unsupported_bio_type | 지원하지 않는 bio type. MVP는 fingerprint만 지원 |
| 401 | invalid_token | token 없음, 위조, 만료 |
| 403 | insufficient_scope | enrollment:create scope 없음 |
| 403 | merchant_mismatch | 요청 merchant_id와 token의 merchant_id 불일치 |
| 409 | idempotency_conflict | 동일 Idempotency-Key로 다른 body 요청 |
Idempotency#
Idempotency-Key header 사용을 권장합니다. 같은 key와 같은 request body는 같은 생성 결과를 반환할 수 있습니다.
Security Notes#
- 생성된 세션은
created상태와expires_at을 가집니다. - 기본 만료 시간은 10분이고 hard max는 30분입니다.
- Hosted UI token은 OAuth access token과 별개이며, 브라우저에서 OmniID Hosted 화면 접근에만 사용합니다.
POST/v1/verification-sessions#
확인 세션을 생성합니다.
Authentication#
Bearer token with verification:create
Request#
{
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"return_url": "https://merchant.example.com/bio/complete",
"provider_id": "pointlink",
"type": "fingerprint",
"finger": "LI",
"locale": "ko-KR",
"metadata": {
"purpose": "login-step-up"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | yes | 1~128자. token의 merchant_id와 일치해야 함 |
merchant_user_ref | string | yes | 1~256자. 가맹점 로그인 사용자를 나타내는 비식별 불투명 참조값 |
return_url | string | no | 최대 2048자의 전체 HTTPS URL(HTTP는 거부). PC popup postMessage target origin 고정용이며 모바일 자동 복귀 계약이나 최종 결과 판단에 사용하지 않음 |
provider_id | string | no | 1~64자. MVP 기본값 pointlink |
type | string | no | 1~32자. MVP 기본값 fingerprint |
finger | string | yes | 확인할 등록 손가락. 등록 결과 조회의 result.finger 값을 사용합니다. PointLink 딥링크의 finger는 match 전용·서버 지정이므로(가이드 v1.0) 생략 불가 — 생략 시 invalid_request (2026-07-08 필수화) |
locale | string | no | 최대 16자. Hosted UI 표시 locale |
metadata | object | no | JSON 직렬화 기준 최대 4096자. 비민감 trace용 메타데이터만 허용 |
Response 201#
{
"verification_session_id": "ver_01J...",
"session_id": "ver_01J...",
"mode": "verify",
"status": "created",
"popup_url": "https://verify.omniid.ai.kr/s/ver_01J...?token=hosted_...",
"expires_at": "2026-07-02T13:10:00Z",
"provider_id": "pointlink"
}
Error Codes#
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | 필수 field 누락 또는 형식 오류 |
| 400 | unsupported_provider | 지원하지 않는 provider. MVP는 pointlink만 지원 |
| 400 | unsupported_bio_type | 지원하지 않는 bio type. MVP는 fingerprint만 지원 |
| 401 | invalid_token | token 없음, 위조, 만료 |
| 403 | insufficient_scope | verification:create scope 없음 |
| 403 | merchant_mismatch | 요청 merchant_id와 token의 merchant_id 불일치 |
| 409 | idempotency_conflict | 동일 Idempotency-Key로 다른 body 요청 |
GET/v1/sessions/{session_id}/result#
등록/확인 세션의 결과를 조회합니다.
가맹점 Frontend의 완료 화면은 최종 성공 근거가 아닙니다. 가맹점 Backend는 이 API로 최종 상태를 확인해야 합니다.
Authentication#
Bearer token with result:read
Query Parameters#
| Field | Type | Required | Description |
|---|---|---|---|
merchant_user_ref | string | yes | 현재 로그인 사용자와 세션 사용자 일치 확인용. 세션 생성 시 사용한 비식별 참조값과 동일해야 함 |
Request Example#
GET /v1/sessions/ver_01J.../result?merchant_user_ref=user_abc
Authorization: Bearer eyJ...
Response 200 - Enrollment Succeeded#
{
"session_id": "enr_01J...",
"mode": "enroll",
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"status": "succeeded",
"provider_id": "pointlink",
"result": {
"enrolled": true,
"finger": "LI",
"completed_at": "2026-07-02T13:03:10Z"
},
"expires_at": "2026-07-02T13:10:00Z"
}
result.finger는 가맹점 Bio API의 저장 응답에 finger가 있으면 그 값, 없으면 앱 callback의
손가락 값입니다. 세션 생성 시 지정한 참고용 finger와 다를 수 있으므로, 가맹점은 확인 세션에
필요한 손가락을 이 값으로 판단해야 합니다. Bio API가 응답에 finger를 보낼 때는 실제 저장한 요청의
finger와 동일한 값을 반환해야 합니다.
Response 200 - Verification Succeeded#
{
"session_id": "ver_01J...",
"mode": "verify",
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"status": "succeeded",
"provider_id": "pointlink",
"result": {
"matched": true,
"finger": "LI",
"completed_at": "2026-07-02T13:03:10Z"
},
"expires_at": "2026-07-02T13:10:00Z"
}
Response 200 - Verification Not Matched#
확인 처리가 정상 완료됐지만 바이오가 일치하지 않은 경우입니다. 시스템 처리 실패가 아니므로 session status는 succeeded이고, 가맹점은 result.matched=false를 기준으로 서비스 처리를 결정합니다.
{
"session_id": "ver_01J...",
"mode": "verify",
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"status": "succeeded",
"provider_id": "pointlink",
"result": {
"matched": false,
"finger": "LI",
"completed_at": "2026-07-02T13:03:10Z"
},
"expires_at": "2026-07-02T13:10:00Z"
}
Response 200 - Processing#
{
"session_id": "ver_01J...",
"mode": "verify",
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"status": "processing",
"provider_id": "pointlink",
"result": null,
"expires_at": "2026-07-02T13:10:00Z"
}
Response 200 - Failed / Expired#
status가 failed 또는 expired이면 최상위 failure_code 필드가 포함되고 result는 null입니다.
{
"session_id": "ver_01J...",
"mode": "verify",
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"status": "failed",
"provider_id": "pointlink",
"failure_code": "provider_timeout",
"result": null,
"expires_at": "2026-07-02T13:10:00Z"
}
failure_code는 비민감 코드만 노출합니다. 값 목록과 각 코드의 재시도 판단·안내 방향은 상태 및 오류 처리가 canonical입니다. expired의 failure_code는 session_expired로 고정됩니다. 재시도는 항상 새 세션 생성입니다.
Final Status Values#
succeeded
failed
expired
canceled
Non-final status values may be returned while the flow is active:
created
opened
app_launched
callback_received
processing
Error Codes#
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | merchant_user_ref 누락 또는 형식 오류 |
| 401 | invalid_token | token 없음, 위조, 만료 |
| 403 | insufficient_scope | result:read scope 없음 |
| 403 | merchant_mismatch | token의 merchant_id와 세션 merchant_id 불일치 |
| 403 | merchant_user_ref_mismatch | query의 merchant_user_ref와 세션 사용자 불일치 |
| 404 | session_not_found | 세션 없음 또는 접근 불가 |
Security Notes#
- 결과 응답에는
features,enc_key, 바이오 원본, 바이오 템플릿을 포함하지 않습니다. - provider 내부 오류는 표준 error code로 변환합니다.
score,decision_threshold는 결과 응답에 노출하지 않습니다. 매칭 유사도는 내부 운영/튜닝 목적으로만 저장하며, 가맹점은matched만으로 판정합니다.
POST/v1/sessions/{session_id}/cancel#
세션을 사용자 취소 상태로 전환합니다.
가맹점 Backend와 Hosted UI 모두 세션을 취소할 수 있습니다. 현재 허용된 상태 전이에 따라
opened 또는 app_launched 상태에서만 canceled로 전이합니다.
Authentication#
Bearer token with session:cancel
Request#
{
"merchant_id": "merchant_123",
"merchant_user_ref": "user_abc",
"reason": "user_cancel"
}
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | yes | 1~128자. token 및 세션의 merchant_id와 일치해야 함 |
merchant_user_ref | string | yes | 1~256자. 세션 생성 시 사용한 비식별 참조값과 일치해야 함 |
reason | string | no | 최대 128자의 비민감 취소 사유 코드. 생략 시 user_cancel |
Response 200#
{
"session_id": "ver_01J...",
"status": "canceled"
}
Error Codes#
| HTTP | Code | Description |
|---|---|---|
| 401 | invalid_token | token 없음, 위조, 만료 |
| 403 | insufficient_scope | session:cancel scope 없음 |
| 403 | merchant_mismatch | token의 merchant_id와 요청/세션 불일치 |
| 403 | merchant_user_ref_mismatch | 요청의 merchant_user_ref와 세션 사용자 불일치 |
| 404 | session_not_found | 세션 없음 또는 접근 불가 |
| 409 | session_finalized | 이미 최종 상태인 세션 |
| 409 | invalid_session_transition | 현재 상태에서 취소 전이가 허용되지 않음 |
| 400 | invalid_request | 요청 body 형식 오류 |
Hosted UI URL#
세션 생성 응답의 popup_url은 가맹점 Frontend가 PC popup 또는 모바일 현재 창/새 화면으로 여는 OmniID Hosted 화면 URL입니다. 모바일 앱 완료 후 복귀는 manual return을 기본으로 하며, return_url 자동 redirect에 의존하지 않습니다.
https://verify.omniid.ai.kr/s/{session_id}?token={hosted_token}
Hosted token은 다음 용도에만 사용합니다.
- OmniID Hosted 화면 진입
- QR 또는 앱 링크 표시
- 진행 상태 polling
- 성공/실패/만료/취소 화면 표시
Hosted token은 가맹점 API 인증 수단이 아니며, 최종 성공 처리는 결과 조회 API로만 확정합니다.