Logistivo CLI: 터미널과 ERP에서 실행하는 물류 운영

하나의 명령 카탈로그가 세 가지 표면에 공급됩니다: lg 터미널 클라이언트, 대시보드 안의 AI 어시스턴트, 그리고 외부 AI 어시스턴트가 연결하는 채널입니다. 기능은 한 번 작성되면 세 곳 모두에 나타나며, 이제 패널의 모든 쓰기 작업에 명령이 있으므로 여러분의 스크립트와 자체 AI 에이전트가 팀이 화면에서 하는 일을 수행할 수 있습니다. 결제, 법적 동의, 계정 해지는 의도적으로 브라우저에 남겨 둡니다. 이 페이지는 계약입니다. 아래 명령 표는 사람이 직접 작성한 것이 아니라, 요청이 있을 때마다 실시간 명령 레지스트리에서 생성됩니다.

Logistivo CLI란 무엇입니까?

Logistivo CLI(lg)는 Logistivo 계정의 업무 — 화물과 운행, 운송 요청, 입찰과 텐더, 차량과 운전기사, 문서, 거래처와 거래처 원장, 인보이스와 회계, 창고와 재고, 팀 구성원, 관세율표 — 를 터미널, 스크립트 또는 자체 AI 에이전트에서 실행하는 명령줄 클라이언트입니다. 명령은 클라이언트에 내장되어 있지 않습니다. 클라이언트는 서버에서 명령 카탈로그를 가져와 그것을 바탕으로 스스로를 그려 냅니다. 같은 명령은 아무것도 설치하지 않고 HTTP로도 호출할 수 있으며 — ERP 및 CRM 연동이 사용하는 경로가 바로 이것입니다 — 외부 AI 어시스턴트가 연결하는 MCP 채널을 통해서도 호출할 수 있습니다.

현재 운영 중인 것과 준비 중인 것

이 섹션은 의도적으로 페이지 맨 위에 있습니다. 연동 문서가 저지를 수 있는 가장 값비싼 실수는 아직 열리지 않은 엔드포인트를 열린 것처럼 설명하는 것입니다: 연동 개발자가 코드를 작성하고, 엔드포인트는 404 응답을 반환하며, 신뢰는 돌아오지 않습니다.

계약이 고정되어 있으므로, 오늘 작성한 연동 코드는 나머지 부분이 열리더라도 계속 동작합니다. 알지 못하는 필드는 무시해야 합니다. 이 규칙이 있기에 우리는 깨뜨리지 않고 추가할 수 있습니다.

무엇이 다른가: 클라이언트는 명령을 알지 못하고, 명령을 그려 냅니다

이 페이지의 나머지를 의미 있게 만드는 단 하나의 결정이 바로 이것입니다. 일반적인 CLI에서는 명령이 클라이언트 안에 있지만, Logistivo에서는 서버에 있습니다.

전통적인 방식에서는 서버가 기능을 얻으면 클라이언트는 새 릴리스가 필요하고 사용자는 그것을 설치해야 합니다. 그 사이에 양쪽은 서로 다른 것을 알게 되며 — 이것이 연동에서 가장 조용한 실패 유형입니다: 스크립트는 실행되고, 종료 코드는 0이며, 명령은 그저 서버가 할 수 있는 일을 설명하지 못합니다.

Logistivo에서 명령의 정의는 하나의 레코드입니다: 이름, 하는 일, JSON Schema 파라미터, 어떤 역할이 볼 수 있는지, 확인이 필요한지, 그리고 효과를 되돌릴 수 있는지. lg는 시작할 때 그 카탈로그를 가져와 도움말 텍스트, 플래그, 입력 검증을 그로부터 생성합니다. 서버에 명령이 추가되는 순간 lg는 그것을 알게 됩니다 — 클라이언트 업데이트 없이.

같은 카탈로그가 대시보드의 AI 어시스턴트와 외부 AI 어시스턴트가 연결하는 채널에도 공급됩니다. 기능은 한 번 작성되어 세 곳에 나타나며, 세 곳 모두 같은 역할 게이트, 같은 확인 게이트, 같은 감사 원장을 거칩니다. 별도의 CLI 명령 목록은 없습니다. 만약 있었다면 세 표면은 6개월 안에 조용히 서로 어긋났을 것입니다.

명명 규칙 — 명령 이름은 어떻게 늘어나는가

카탈로그는 커질 것입니다. 새 명령은 즉흥적으로 이름 붙지 않으며, 여섯 가지 규칙이 적용됩니다. 이 규칙을 여기에 공개하는 이유는 이것이 여러분의 연동 코드가 수년간 쓰게 될 이름들이고, 그 예측 가능성이 여러분에게 가치가 있기 때문입니다.

커버리지: 패널의 모든 것을 명령으로

많은 팀이 Logistivo를 자체 AI 에이전트나 자동화 하네스에 연결하며, 에이전트는 작업을 끝까지 마칠 수 있을 때만 쓸모가 있습니다. 그래서 규칙은 간단합니다: 사무실 사용자가 패널에서 바꿀 수 있는 것은 무엇이든 명령으로도 바꿀 수 있습니다 — 같은 역할 게이트, 같은 확인 게이트, 같은 감사 원장 뒤에서.

이것은 주장이 아니라 측정입니다. 패널 뒤의 모든 쓰기 엔드포인트를 명령 레지스트리와 대조하며, 어떤 명령이 그 엔드포인트를 자신의 HTTP 미러로 선언하거나 같은 공유 서비스를 호출할 때만 커버된 것으로 셉니다. 범위 내 쓰기 엔드포인트의 100%가 커버되며, 입증되지 않은 것은 하나도 없습니다. 제외된 모든 엔드포인트에는 서면 사유가 있습니다 — 신원 및 결제 단계, 연동 컨트롤 플레인, 운전기사 앱 자체의 엔드포인트, 클라이언트 텔레메트리, 호출자가 없는 죽은 사본 — 그리고 연동 개발자에게 중요한 것들은 아래에 나열되어 있습니다.

파일을 받는 명령도 같은 규칙을 따릅니다: 표의 33개 명령이 문서나 사진을 받으며, 모두 lg, HTTP, MCP에서 동작합니다("파일을 받는 명령" 참조).

관리 업무도 더 강한 키 뒤에서 커버됩니다: 표의 62개 명령 — 팀 구성원과 역할, 회사 설정, 은행 계좌, 저장된 카드, 연동 키 — 에는 manager 수준 연동 키가 필요합니다. 기본값인 employee 수준 키는 일상 업무를 실행하며, 이런 명령은 INSUFFICIENT_KEY_LEVEL 오류로 거부됩니다.

어필리에이트(추천 파트너) 계정은 자체 카탈로그를 갖습니다: 20개 도메인에 걸친 55개 명령 — 추천, 리드, CRM 메모와 작업, 영업 자료, 수익과 지급, 그리고 모든 역할이 가진 계정 및 팀 명령. 어필리에이트 전용 명령은 세 가지 패널 역할을 나열하는 아래의 공개 표에는 없습니다. 어필리에이트 계정은 GET /api/common/commands 엔드포인트와 lg commands 명령에서 그 명령들을 봅니다. 어필리에이트이면서 동시에 화주, 운송사 또는 관세사인 회사는 두 세트를 모두 봅니다.

에이전트는 lg users get(API 이름 get_current_user)으로 시작해야 합니다: 역할, 사용 중인 키의 접근 수준, 실효 권한, 그리고 대기 중인 법적 승인이나 인증되지 않은 전화번호가 현재 쓰기를 막고 있는지를 반환합니다 — 그래서 에이전트는 거부를 하나씩 겪는 대신 미리 예측할 수 있습니다.

커버리지는 명령 레지스트리와 패널의 쓰기 엔드포인트를 비교하는 보고서로 측정됩니다. 명령 없이 추가된 패널 엔드포인트는 그 보고서에 입증되지 않은 것으로 나타납니다. 명령으로 도달할 수 없는 패널 작업을 발견하면 지원팀에 알려 주십시오.

설치와 인증

lg는 의존성이 없는 단일 파일 Node 스크립트(Node 18+)이며, npm에 @lgstv/lg로 게시되어 있습니다. 패키지는 동일한 두 명령 lg와 logistivo를 설치합니다 — 여러분의 머신에서 lg가 이미 별칭(alias)으로 쓰이고 있다면 logistivo를 사용하십시오. 단일 파일이므로 lg.mjs를 고객의 ERP 서버에 복사해도 동작합니다. ERP/CRM 연동을 만드는 데 클라이언트가 필요하지는 않습니다 — 같은 명령을 HTTP로 사용할 수 있습니다.

1. 설치 — 필요한 것은 Node 18 이상뿐이며, 다른 의존성은 없습니다. 전역으로 설치하거나 npx로 설치 없이 실행하십시오.

2. 승인 코드로 로그인하기 — lg login 명령은 짧은 승인 코드를 표시하고 브라우저에서 logistivo.com/connect 주소를 엽니다. SSH 환경이나 CI에서, 또는 --no-browser 플래그를 쓰면 주소만 출력하며, 그 주소는 휴대폰을 포함한 어떤 기기에서든 열 수 있습니다. 그곳에서 평소처럼 로그인하고, 페이지의 코드가 터미널의 코드와 같은지 확인한 뒤, '읽기 전용' 또는 '읽기 및 작업 수행' 접근을 선택하고 승인하십시오. 터미널에는 비밀 값을 전혀 입력하지 않습니다: 비밀번호도, 키도, 일회용 코드도 없습니다. 이후 세션은 그 컴퓨터에서 로그인 상태로 유지됩니다 — 터미널을 닫아도 로그아웃되지 않으며, lg가 액세스 토큰이 만료되기 전에 스스로 갱신합니다. lg logout 명령은 서버에서도 세션을 종료하며, 패널의 Settings › Integration keys › AI and CLI connections에서 세션을 제거할 수도 있습니다. lg login --password(이메일과 비밀번호)는 대체 수단으로 계속 제공됩니다. lg 0.11 이상이 필요합니다.

3. 서버와 CI용: 승인 코드가 아니라 키 — 서버나 CI 작업에는 브라우저에서 코드를 승인할 사람이 없습니다. 패널(Settings › Integration keys)에서 또는 lg keys create 명령으로 키를 발급하고 — 필요하면 좁은 범위만 가진 전용 팀 구성원을 대리하도록 — LOGISTIVO_TOKEN 변수에 넣거나 lg login --api-key 명령으로 저장하십시오. 이 변수가 설정되어 있는 동안 설정 파일은 결코 읽히지 않습니다. 자세한 내용: "API 키로 연결하기" 섹션.

4. 확인하고 카탈로그 둘러보기 — lg commands 명령은 여러분의 역할이 볼 수 있는 모든 명령을 도메인별로 묶어 나열합니다. lg help 은 그 도메인의 동사를 출력하고, lg help 는 한 명령의 전체 파라미터 목록을 타입 및 필수 여부와 함께 출력합니다. 이 텍스트는 어느 것도 클라이언트 안에 있지 않습니다 — 모두 카탈로그에서 옵니다. 목록은 역할에 따라 달라진다는 점을 기억하십시오: 한 도메인의 동사는 운송사와 화주에게 같지 않습니다(예를 들어 loads set-status는 운송사 카탈로그에만 있습니다). 동사가 보이지 않으면 설치를 확인하기 전에 역할부터 확인하십시오.

키를 버전 관리, CI 로그 또는 채팅 창에 절대 넣지 마십시오. 승인 코드나 연동 키로 로그인했다면 lg logout 명령은 서버에서도 해당 세션을 종료합니다. 비밀번호 세션(lg login --password)에서는 '로컬' 사본만 삭제하며 그 사실을 명시적으로 알려 줍니다. 감사 원장은 인수를 저장하지만, 이름에 token, password, IBAN, card 또는 OTP가 들어간 모든 필드와 명령이 비밀로 선언한 모든 파라미터를 ***로 마스킹합니다.

클라이언트 사용성

이 기능들은 `lg` 클라이언트의 일상적인 사용을 더 쉽게 만듭니다. 어느 것도 HTTP 계약을 바꾸지 않습니다 — HTTP로 직접 연동한다면 이 섹션은 건너뛰십시오.

API 키로 연결하기

ERP, CRM, 스크립트 또는 AI 에이전트는 패널에서 또는 lg keys create 명령으로 발급한 연동 키로 Logistivo에 연결합니다. 키는 실제 팀 구성원을 대리하여 동작합니다: 그 사람의 역할이 명령 목록을 결정하고, 키의 접근 수준이 작업의 무게를 결정하며, 그 사람이 팀을 떠나면 키도 함께 사라집니다. 합성 "서비스 계정"은 없으며 앞으로도 없을 것입니다 — 멀티테넌트 격리는 사용자에 묶여 있고, 소유자 없는 브리지는 수년간 조용히 계속 쓰기를 하는 종류의 브리지입니다.

1. 패널에서 키 발급하기 — Settings › Integration keys › New key. 이름을 지정하고("ERP 야간 동기화"), 스코프와 접근 수준을 고르고, 필요하면 수명을 줄이십시오. 키는 그 화면에서 한 번만 표시되며, 서버는 키를 저장하지 않습니다. 분실하면 Rotate로 새 키를 발급하십시오 — 이전 키는 즉시 무효가 됩니다.

2. 터미널에서 연결하기 — lg login --api-key 명령은 키를 검증하고, 신원과 스코프를 출력하며, 명령 카탈로그를 내려받습니다. lg whoami 명령은 여러분이 어떤 사용자, 회사, 역할, 스코프로 실행 중인지 서버에 묻습니다. lg logout 명령은 승인 코드 세션을 서버에서 종료하는 것과 마찬가지로 서버에서도 키를 폐기합니다(비밀번호 세션의 lg logout만은 그렇게 할 수 없습니다).

3. 또는 순수 HTTP — 키는 Authorization: Bearer 헤더로 전달되는 액세스 토큰이며 세 개의 명령 엔드포인트에 직접 사용할 수 있습니다. 서버나 CI에서는 LOGISTIVO_TOKEN 변수에 넣으십시오. 이 변수가 설정되어 있는 동안 설정 파일은 결코 읽히지 않습니다.

4. 또는 터미널에서 키 관리하기: lg keys — lg keys 명령은 패널 화면의 터미널 버전입니다: 같은 엔드포인트, 같은 게이트(팀 관리 권한, 그리고 키로 호출할 때는 manager 수준 키). create, rotate, transfer는 새 토큰을 한 번 반환합니다: --save-profile=NAME 플래그를 쓰면 토큰은 곧바로 로컬 프로필에 들어가고 절대 출력되지 않습니다. 그렇지 않으면 lg가 한 번 출력합니다. --json 모드에서는 --show-secret을 추가한 경우에만 토큰이 포함되며, 두 플래그 중 어느 것도 없는 --json 호출은 요청을 보내기 전에 거부되므로 토큰이 스크립트 출력에 조용히 들어갈 수 없습니다. rotate, transfer, revoke는 확인(--yes)을 요청합니다. 키는 정확한 이름이나 uuid로 지정하며, 추측으로 지정하는 일은 없습니다. 접근 수준은 교체되거나 이전된 키에 변경 없이 이어집니다.

스코프는 명령 카탈로그뿐 아니라 API 표면 전체에 강제됩니다: 읽기 전용 키는 읽기 요청(GET/HEAD/OPTIONS)을 통과시키고, 그 밖의 모든 쓰기 요청은 403 forbidden 응답을 받습니다. 의도적인 예외가 두 가지 있으며 둘 다 더 세밀한 게이트로 보호됩니다: 명령 실행 엔드포인트(쓰기를 하지 않는 명령도 POST 메서드로 호출되므로, 판단은 명령 자체의 read_only 플래그에서 옵니다)와 키가 자기 자신을 폐기하는 경우(lg logout, 읽기 전용 키에도 허용). 스코프, 도메인 권한 부여, 접근 수준은 세 개의 독립적인 축이며 모두 통과해야 합니다: 읽기 키는 manager 수준이라도 쓸 수 없고, employee 쓰기 키는 팀을 관리할 수 없으며, loads:write 도메인 권한 부여는 관리 작업을 열어 주지 않습니다. 필요를 충족하는 가장 낮은 권한의 구성원을 대리하도록 키를 발급하십시오: 애초에 어떤 레코드를 볼 수 있는지는 역할이 결정합니다.

처음 10분: 패널에서 첫 호출까지

연동에서 가장 값비싼 시간은 처음 10분입니다. 아래 네 단계는 그 경로를 순서대로 보여 줍니다. 같은 순서가 패널에 마법사(Settings › Integration › Setup wizard)로 있으며, 마법사는 여기에 설명된 엔드포인트를 정확히 그대로 호출합니다. 마법사에 접근할 수 없는 개발자도 이 섹션을 읽으면 같은 곳에 도달합니다.

네 단계 중 어느 것도 "보냈다"로 끝나지 않습니다. 키가 정말로 동작하는지는 요청 원장이 답하고, 웹훅이 정말로 도착하는지는 전달 원장이 답합니다. 둘 다 패널과 lg에서 열려 있습니다. 한 번도 측정되지 않은 설정은 그저 동작한다고 믿어지는 설정일 뿐입니다.

샌드박스: 운영 환경을 건드리지 않고 쓰기 연습하기

프리플라이트(dry_run)는 명령이 무엇을 '할지' 알려 줍니다. 하지만 멱등성이 정말로 재생되는지, 웹훅이 정말로 전달되는지, 오류 분기가 어떤 모습인지, 크레딧이 어디서 나오는지는 '실제' 쓰기에서만 볼 수 있습니다. 샌드박스는 바로 그것을 위한 것입니다: 여러분의 회사를 반영하는 별도의 테넌트 — 같은 코드, 같은 명령 카탈로그 — 이며, 실서비스 원장은 절대 건드리지 않습니다.

샌드박스는 요청 플래그가 아니라 '테넌트'입니다. 여러분의 회사를 위해 열린 별도의 Logistivo 회사로, 자체 사용자, 자체 패키지, 자체 크레딧 원장, 자체 웹훅 구독을 갖습니다. 환경은 키의 회사에서 '도출'되며 호출자가 지정할 수 없으므로, 잘못된 프로필을 고르더라도 요청은 서버 측에서 403 ENVIRONMENT_MISMATCH 오류로 중단됩니다. "샌드박스에 쓰는 줄 알았는데 운영에 썼다"는 일은 구조적으로 불가능합니다.

표면은 동일합니다 — 측정 결과: 샌드박스 키가 그려 내는 카탈로그는 실서비스 키가 그려 내는 것과 같습니다(화주 키의 경우 양쪽 모두 448개 명령). 샌드박스에서 동작하는 브리지는 운영 환경에서도 동작합니다. 배운 것을 두 번 배울 필요가 없습니다.

샌드박스는 비어 있는 상태로 열리지 않습니다: 생성된(복사된 것이 '아닌') 시드 데이터 — 거래처, 양방향 화물, 운송 요청 — 가 들어 있습니다. 이름은 명백히 지어낸 것이고 이메일 주소는 @sandbox.invalid 도메인에 있습니다. 실제 테넌트의 행을 읽거나, 복제하거나, 템플릿으로 사용하는 일은 결코 없습니다.

초기화 버튼은 의도적으로 '없으며', 어떤 레코드, 파일, 문서도 결코 삭제되지 않습니다. 깨끗하게 다시 시작하고 싶다면, 갱신이 새 세대를 열고 이전 세대는 은퇴한 상태로 남습니다. 수백 개의 리허설 행이 쌓이는 비용은 잘못된 테넌트에 연결된 삭제 루틴에 비하면 아무것도 아닙니다.

명령 모델

문법은 하나이며 예외가 없습니다. 명령을 어떻게 호출하는지 알기 위해 필요한 것은 카탈로그가 그 명령에 대해 말하는 내용뿐입니다.

lg loads list --status=in_transport --limit=20 --json

플래그 이름은 스키마의 파라미터 이름이며, lg는 두 가지 표기를 모두 받습니다: --load_code와 --load-code는 같은 필드에 도달합니다(lg help는 kebab-case 형태를 출력합니다). 따라서 플래그를 보면 그것이 어느 JSON 필드로 들어가는지 알 수 있고, CLI에서 HTTP로 옮길 때 이름을 변환할 필요가 없습니다. 값 없이 쓴 boolean 파라미터는 true입니다(--only_open). 그 반대는 --no-only_open입니다. 대시로 시작하는 값을 가진 인수는 --flag=value 형태로 쓰십시오. 그렇지 않으면 빠뜨린 플래그가 다음 플래그를 조용히 자기 값으로 삼켜 버립니다. 파일을 받는 명령에서는 로컬 경로 앞에 @를 붙이십시오(--attachment_uuid=@./cmr.pdf) — "파일을 받는 명령"을 참조하십시오.

실제 예시

아래의 모든 명령은 현재 카탈로그에 존재하며, 여기에 있는 이름이나 파라미터는 하나도 지어낸 것이 아닙니다. 출력은 축약되어 있습니다. 출력은 `data.result` 수준을 보여 줍니다. HTTP에서는 같은 객체가 `{success, data:{…}}` 봉투(envelope) 안에 담겨 옵니다(HTTP 명령 API 참조).

클라이언트 없이: HTTP 명령 API

lg는 편의 도구일 뿐, 유일한 문은 아닙니다. 카탈로그와 실행 모두 세 개의 HTTP 엔드포인트를 거칩니다(파일에는 네 번째인 업로드 엔드포인트가 있습니다 — "파일을 받는 명령" 참조). ERP 또는 CRM 연동은 여기에 직접 연결합니다. 신원은 Authorization: Bearer 헤더로 전달되는 개인 액세스 토큰입니다. 모든 응답은 같은 봉투로 돌아옵니다: `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. 계약 전체는 `data` 안에 있습니다 — `contract_version`, `command`, `status`, 그리고 명령의 출력인 `result`; 실패 시에는 `error_code`, `error_key`, `error`. 이 페이지의 응답 예시는 공간을 아끼기 위해 봉투가 아니라 `data` 객체를 보여 줍니다. 여러분의 코드에서는 `data`를 꺼내는 것을 잊지 마십시오. `lg --json`도 봉투 전체를 출력하며, 그래서 jq 경로가 `.data.result…`로 시작합니다. 유일한 예외는 `/api/public/status`입니다: 이 엔드포인트는 감시 도구가 파싱 없이 경보를 낼 수 있도록 봉투가 없습니다.

200 + ok:false 형태로 확인을 요청하는 것은 금지되어 있으며 결코 일어나지 않습니다. 이유는 간단합니다: 클라이언트는 200 응답을 성공으로 처리하므로, 200 상태로 반환된 확인 요청은 흐름을 조용히 끊어 버리고 아무도 알아차리지 못할 것입니다. 확인은 항상 409 응답입니다.

파일을 받는 명령

표의 33개 명령이 문서나 사진을 받습니다: 화물에 CMR 첨부, 인보이스에서 HS 코드 읽기, 차량 등록증으로 차량 생성, 사진으로 팔레트 추정, 회사 문서 업로드, 인보이스와 패킹 리스트 교차 검증. 브라우저에서는 파일을 양식에 끌어다 놓지만, 그 밖의 모든 곳에서는 두 단계가 필요합니다 — 파일을 업로드한 다음, 그 id를 명령에 전달합니다.

파라미터 이름이 attachment_uuid 또는 attachment_uuids 형태로 끝나면 그 파라미터는 파일을 받습니다 — 목록 안의 photos[].attachment_uuid, 수표의 front_attachment_uuid 같은 것입니다. 이를 기억할 필요는 없습니다: 모든 카탈로그 행에는 정확한 경로를 담은 file_params 값이 있고, 스키마는 그런 필드를 표시하며(format: uuid, x-logistivo-file: true), 카탈로그 응답에는 업로드 주소, 허용되는 형식, 크기 제한, 유효 시간을 담은 attachments 블록이 있습니다. lg help 도 이를 보여 줍니다.

업로드는 쓰기이므로 읽기 스코프 키로는 업로드할 수 없습니다. 쓰기 스코프 키라면 어느 접근 수준이든 업로드할 수 있습니다. 업로드는 키가 아니라 사용자에게 속합니다: 같은 사용자의 두 키는 같은 업로드를 보며, 누구도 다른 사람의 업로드를 사용할 수 없습니다. 알 수 없거나, 만료되었거나, 여러분의 것이 아닌 id는 그냥 찾을 수 없는 것으로 처리되며, 그 이유는 공개되지 않습니다.

대시보드 채팅 첨부 파일과 명령 업로드는 별개의 공간입니다: 어시스턴트 채팅에 첨부한 파일은 id로 명령에 전달할 수 없으며, 명령 업로드는 채팅 스레드에 절대 나타나지 않습니다.

업로드는 명령이 아니며 그 자체로는 비즈니스 효과가 없습니다: 명령이 그 id를 사용하기 전까지 여러분의 데이터에는 아무 일도 일어나지 않습니다. 업로드가 확인을 요청하지 않는 이유도 그 때문입니다 — 파일을 소비하는 명령이 게이트를 가집니다.

확인 게이트와 되돌릴 수 없는 명령

카탈로그의 모든 명령에는 두 개의 플래그가 있습니다: confirmation_needed(실행 전에 명시적 승인이 필요한가)와 irreversible(그 효과를 되돌릴 수 있는가). 이 둘은 별개의 질문이며, 둘 다 클라이언트의 선의에 맡겨지지 않고 서버에서 강제됩니다.

게이트는 비즈니스 규칙이 아니라 책임에 관한 것입니다. 입찰을 넣는 것은 운송 계약으로 가는 첫걸음이고, 인보이스 발행은 회계 분개를 만들며, 재고 이동은 원장을 바꿉니다. 이들 중 어느 것도 실수로 두 번 실행할 수 있는 일이어서는 안 됩니다.

게이트는 '실제로 실행되는' 명령에서 읽습니다. 한 명령이 다른 명령을 감쌀 때 — 채팅의 범용 실행기가 그렇듯이 — 게이트는 감싸는 명령이 아니라 실제로 실행될 명령의 플래그에서 옵니다. 플래그가 고정되어 있다면 두 가지 실패 중 하나는 피할 수 없었을 것입니다: 인보이스 명령이 확인 없이 실행되거나, 국가 조회마다 확인 카드가 뜨거나.

read_only 플래그와 '게이트 없음'은 같은 것이 아닙니다. 서로 다른 세 가지 상태가 있습니다: invoices preview는 게이트가 없고 아무것도 쓰지 않습니다. loads create는 게이트가 있고 씁니다. invoices create는 게이트가 없지만 씁니다 — 초안이 생겨납니다. 하나의 테스트로 판정됩니다: 명령이 반환되었을 때 데이터베이스의 행이 바뀌었는가?

일부 작업은 애초에 명령이 아니기 때문에 이 게이트 뒤에 있지 않습니다: 패키지 구매, 3D Secure로 카드 추가, 법적 문서 승인, 계정 해지는 당사자 자신의 브라우저에서 이루어집니다. 이런 경우 lg handoff-links generate 명령은 logistivo.com 링크를 반환합니다 — 무료이고, 아무것도 쓰지 않으며, 비밀을 담지 않습니다 — 그리고 당사자가 그곳에서 단계를 마칩니다. 어떤 플래그를 어떻게 설정하더라도 결제나 동의가 API 호출로 바뀌지는 않습니다.

연동이 모든 호출에서 게이트를 자동으로 통과시키는 것은 기술적으로 가능하지만 권장되지 않습니다. 올바른 패턴은 되돌릴 수 있다고 알고 있는 명령에 대해서만 게이트를 자동화하고, 되돌릴 수 없는 명령은 사람의 대기열로 보내는 것입니다.

프리플라이트: 쓰기 명령이 무엇을 할지 실행하지 않고 확인하기

ERP 브리지를 연결할 때 가장 값비싼 단계는 요청 본문이 올바른지를 명령을 '실제로' 실행해 보고서야 알아내는 것입니다: 실서비스 원장에 생긴 인보이스 초안, 소진된 크레딧, 수작업 정리. dry_run 플래그가 이를 끝냅니다 — 요청에 필드 하나를 추가하면 명령은 실행되지 않고, 무슨 일이 일어날지를 읽을 수 있습니다.

현재 레지스트리에는 519개 명령이 있으며, 그중 233개는 읽기만 하고 아무것도 쓰지 않습니다. 나머지 286개 쓰기 명령 중 251개는 게이트가 있습니다: confirm 없이 호출하면 이미 409 응답을 반환하고 무엇을 할지 설명합니다. 빈틈은 나머지 35개였습니다 — 인보이스 초안, 수출 서류 초안, 라인 항목: 바로 브리지가 가장 먼저 연결하는 체인입니다. 이 명령들은 호출하는 것이 곧 실행하는 것이었습니다.

게이트가 있는 명령에도 두 번째 빈틈이 있었습니다: 스키마 검증은 게이트 '이후'에 실행됩니다. 그래서 인수가 빠진 호출은 먼저 확인 카드를 받고, 그것을 확인한 뒤에야 "이 필드가 없습니다"라는 말을 들었습니다. 프리플라이트는 두 가지를 한 번의 왕복으로 답합니다.

그리고 가장 흔하면서도 가장 늦게 발견되는 브리지 버그는 이것입니다: 스키마에 없는 필드를 보내면(recipient_contact_id 필드가 기대되는 곳에 customer_id 필드), 서버는 그것을 조용히 버리고, 명령은 200 응답을 반환하며, 레코드는 엉뚱한 곳에 저장됩니다. 프리플라이트 응답은 그런 필드를 arguments.ignored 아래에 이름으로 나열합니다. 오류 메시지는 결코 그렇게 하지 않을 것입니다.

프리플라이트는 아무것도 쓰지 않으며, 이는 측정 이전에 구조적인 사실입니다: 프리플라이트 경로는 명령의 핸들러에 절대 진입하지 않으므로, 나중에 추가되는 명령을 포함한 모든 명령에 적용됩니다. 출시 당시에는 측정도 했습니다: 당시 카탈로그의 모든 쓰기 명령을 프리플라이트하는 동안 발생한 모든 SQL 문을 수집했습니다(두 가지 인수 세트, 두 가지 역할, 178 건의 프리플라이트). INSERT/UPDATE/DELETE 구문은 단 하나도, 대기열 작업도 단 하나도, 이메일도 단 한 통도 없었으며, 31개 테이블의 행 수와 크레딧 잔액은 변하지 않았습니다.

종료 코드는 실제 호출이 낼 것과 같습니다 — 실행될 경우 0, 확인 게이트 4, 멱등성 충돌 2, 그 밖의 모든 경우 1 — 따라서 "lg … --dry-run || exit"는 CI 단계에서 의미 있는 게이트입니다.

멱등성: 재시도해도 중복이 생기지 않는 이유

ERP는 여러분의 오류 메시지를 읽지 않고, 화면을 보지 않으며, 타임아웃 후에는 같은 요청을 다시 보냅니다. 이 섹션은 바로 그 동작 때문에 존재합니다.

idempotency_key 값을 담은 호출은 최대 한 번만 실행됩니다. 두 번째 호출은 명령을 다시 실행하지 않고, 기록된 결과를 replayed 상태와 함께 반환합니다. 네트워크가 끊기거나, 요청이 타임아웃되거나, 대기열이 같은 작업을 두 번 가져가더라도, 중복 인보이스 대신 첫 번째 결과를 받습니다.

모든 쓰기 명령에 키를 사용하십시오. 카탈로그의 read_only 필드가 그 구분을 공짜로 알려 줍니다: read_only: false인 모든 명령에는 멱등성 키를 쓸 가치가 있습니다.

재시도 전략은 지수 백오프를 사용하고 모든 시도에서 '같은' 키를 보내야 합니다. 시도마다 새 키를 생성하는 것은 멱등성을 전혀 사용하지 않는 것과 같습니다. 같은 키를 병렬로 보내지 마십시오: 최대 1회 실행은 순차적인 재시도에 대해 보장되며, 서로 경쟁하는 두 요청에 대해서는 보장되지 않습니다.

증분 동기화와 페이지네이션

ERP/CRM 브리지는 매시간 전부가 아니라 바뀐 것만 가져와야 합니다. 모든 목록 명령 — loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search — 은 두 개의 공통 파라미터를 받고 하나의 공통 필드를 반환합니다.

offset이 없는 이유: 실시간으로 바뀌는 테이블에서 offset은 어긋납니다. 두 페이지 사이에 삽입된 행 때문에 레코드를 건너뛰거나 두 번 세게 되며, 그것도 조용히 일어납니다. 커서는 앞으로만 진행하므로, 순회 도중 삽입되거나 갱신된 레코드는 목록의 끝에 놓입니다: 아무것도 잃지 않고, 아무것도 중복되지 않습니다.

전체 초기 적재도 같은 메커니즘입니다: updated_since=1970-01-01T00:00:00Z로 시작하여 next_cursor 값이 null이 될 때까지 따라가십시오. 별도의 “내보내기” 엔드포인트는 필요하지 않으므로 존재하지 않습니다.

lg로도 같습니다: lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json, 그다음 lg loads list --cursor= --json.

다음 동기화에서는 updated_since 값을 여러분이 본 가장 큰 updated_at 값으로 설정하십시오. 타임스탬프는 1초 정밀도이므로 경계에 있는 레코드가 두 번 도착할 수 있습니다 — 여러분 쪽에서 external_ref 또는 code 기준으로 upsert한다면 무해합니다. updated_since 또는 cursor 없이 실행하면 명령은 채팅 모드로 동작합니다: 사람을 위한 정렬, 최대 20행, next_cursor 값은 null, 그리고 truncated 필드가 “더 있음”을 알립니다. 동기화에는 이 모드를 사용하지 마십시오.

이 섹션의 기계 판독용 오류 키: INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. 모두 HTTP 422 응답으로, 본문에 error_code: "failed" 및 result.error_key 값을 담아 응답합니다. 텍스트가 아니라 키로 분기하십시오.

여러분 자신의 키: external_ref

우리의 자동 증가 id(invoice_id, contact_id)를 ERP의 비즈니스 키로 삼지 마십시오. 이 id들은 불투명하며 여러분의 회사에만 한정됩니다. 화물, 운송 요청, 거래처, 인보이스, 제품, 창고는 대신 여러분의 키를 담습니다.

발신 웹훅: 무언가 발생하면 여러분에게 POST 요청을 보냅니다

지금까지의 모든 것은 풀(pull) 방식이었습니다: 여러분이 묻고, 우리가 답합니다. ERP 브리지는 화물 상태가 바뀌었는지, 인보이스가 발행되었는지, 입찰이 도착했는지 알기 위해 1분마다 물어봐야 했습니다. 이제는 그렇지 않습니다: https 주소를 등록하고 관심 있는 이벤트를 고르면, 이벤트가 발생할 때 서명된 POST 요청이 도착합니다.

푸시는 풀을 대체한 것이 아니라 그 '옆에' 추가되었습니다. 증분 동기화(updated_since + cursor)는 그대로이며 여전히 올바른 도구입니다: 첫 전체 적재, 이력 백필, "놓친 것이 있는가"에 대한 답은 모두 그쪽에 있습니다. 웹훅은 지연 시간을 분 단위에서 초 단위로 줄입니다. 건강한 브리지는 둘 다 사용합니다 — 웹훅은 트리거로, 동기화는 안전망으로.

페이로드는 레코드의 사본이 아니라 '신호'입니다. 식별자(id, 여러분 자신의 external_ref, status, 시간)를 담고 있으며, 상세 내용은 여러분의 키로 GET 요청을 보내 가져옵니다. 그 호출은 이미 여러분 자신의 권한 게이트를 거칩니다. 따라서 노출 표면은 식별자 수준에 머뭅니다. 두 필드는 의도적으로 빠져 있습니다: 입찰한 회사의 신원(블라인드 입찰은 패널에서 별칭으로 표시되며, 신원을 웹훅에 넣으면 두 번째 채널을 통해 그 비공개성을 뚫게 됩니다)과 예약 연락처 정보(개인 데이터를 추가 채널로 옮길 필요가 없습니다).

이벤트 이름은 '고정'되어 있습니다. 이름의 의미가 바뀌면 새 이름을 열고, 옛 이름은 지원 중단 기간 동안 유지됩니다. 페이로드에 새 필드를 추가해도 계약 버전은 올라가지 '않습니다' — 소비자는 알지 못하는 필드를 무시해야 합니다.

이벤트 카탈로그

아래 13개 이벤트는 모두 '실제로' 발송되는 이벤트 클래스로 뒷받침됩니다. 카탈로그에 "있으면 좋을" 이름은 없습니다: 발표되었지만 한 번도 발생하지 않는 이벤트는 연동 개발자가 영원히 기다리게 되는 이벤트이며, 그 실패는 조용합니다. 이 표 역시 사람이 작성한 것이 아니라, 페이지를 불러올 때마다 실시간 카탈로그에서 그려집니다.

invoice.payment_recorded 이벤트는 의도적으로 '빠져' 있습니다: 그 이벤트 클래스는 코드에 존재하지만 어디에서도 발송되지 않습니다. 결제 이벤트는 그 클래스가 실제로 발생하는 날에 추가될 것이며, 그 전에는 추가되지 않습니다. 이벤트를 미리 발표하는 것은 아예 추가하지 않는 것보다 나쁩니다.

서명 검증

모든 요청에는 X-Logistivo-Signature 헤더가 있습니다: t=,v1=. 서명 대상 문자열은 "."이고 알고리즘은 HMAC-SHA256입니다. v1 접두사는 체계를 버전 관리할 수 있게 해 줍니다: 언젠가 두 번째 체계가 생기면 헤더는 t=…,v1=…,v2=…를 담고, 여러분은 아는 것을 고르면 됩니다.

의존성이 없는 참조 수신기 두 개를 게시합니다. 하나는 Node용, 하나는 순수 PHP용입니다. 둘 다 위의 네 가지 규칙을 적용하며 --selftest로 스스로를 테스트합니다: 유효한 서명, 변조된 본문, 잘못된 비밀 키, 허용 범위를 벗어난 타임스탬프, 형식이 잘못된 헤더, 재직렬화 함정, 그리고 두 종류의 반복(같은 전달 / 같은 사실). 검증 코드는 우리가 직접 사용하는 코드와 정확히 대응합니다 — 우리는 우리가 게시하는 계약을 직접 소비합니다.

전달, 재시도, 음소거

여러분의 수신기가 항상 살아 있지는 않을 것입니다. 이 경로는 그것을 전제로 합니다: 일시적인 실패는 재시도 일정에 들어가고, 영구적인 실패는 구독을 음소거하며 — 음소거의 '사유'는 항상 기록됩니다.

음소거 사유는 기계가 읽을 수 있고 안정적이며(delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user), 패널과 `lg webhooks list` 양쪽에 표시됩니다. 사유 없는 음소거는 "왜 아무것도 도착하지 않는가"를 답할 수 없게 만들 것입니다. 다시 활성화하면 카운터와 타임스탬프가 초기화됩니다.

주소는 여러분이 쓰지만, 요청은 '우리' 서버가 보냅니다. 게이트가 없다면 클라우드 메타데이터 주소를 입력한 테넌트가 우리 자신의 네트워크 안에서 요청을 보내게 만들 수 있습니다. 그래서 주소는 등록할 때와 '모든' 전달 시에 검사됩니다:

상태와 헬스: 조용히 멈춘 브리지를 여러분보다 먼저 알아차리는 사람이 없어야 합니다

연동 브리지는 요란하게 죽는 일이 드뭅니다. 보통은 새벽 시간에 조용해집니다: 토큰이 만료되거나, 키가 대리하는 사람이 팀을 떠나거나, 스코프가 좁혀지거나, 수신 엔드포인트가 403 응답을 반환하기 시작합니다. 가장 먼저 알아차리는 사람은 거의 언제나 고객입니다. lg status 명령은 그 순서를 뒤집기 위해 존재합니다.

명령 하나가 두 질문에 동시에 답합니다. 장애 중에는 두 질문이 같은 순간에 나오기 때문입니다: 플랫폼이 살아 있는가, 그리고 내 브리지는 건강한가. 플랫폼 상태 줄은 토큰 없이도 동작합니다 — 장애 중에는 토큰 자체가 첫 번째 용의자이며, 상태 질문을 토큰에 묶으면 정작 답이 필요할 때 답을 얻지 못하게 됩니다.

헬스 요약은 새로운 원장을 두지 않습니다. 기존의 세 가지 소스를 한 문장으로 접습니다: 일일 사용량 카운터(얼마나 호출했고 얼마나 실패했는가), 요청 원장(어떤 요청이 언제, 왜 거부되었는가), 발신 웹훅 전달 원장(이벤트가 수신기에 도달했는가). 오류율은 항상 일일 카운터에서 읽습니다. 상세 원장에는 상한이 있으며, 상한에 도달하면 그 사실을 분명히 밝힙니다.

같은 요약이 패널에도 표시됩니다: Settings › Integration › Keys 아래의 모든 행에는 배지가 있고, 그 아래에 사유가 표시됩니다. 임계값은 클라이언트 측에서 계산되지 않습니다. 서버가 지정한 수준을 패널과 lg status 명령이 그대로 표시하므로, 두 화면이 같은 키에 대해 서로 다른 말을 할 수 없습니다.

감시 도구에는 토큰이 필요 없습니다. 이 엔드포인트는 상태 단어만 반환합니다. 버전, 호스트 이름, 대기열 이름, 작업 수, 오류 텍스트, 고객 데이터를 담지 않으며 — 담을 수도 없습니다.

헬스 요약은 요청당 일정한 수의 쿼리로 만들어집니다. 쿼리 수는 키의 개수에 따라 늘어나지 않습니다. 상세 정보가 필요하면 lg logs 명령으로 해당 키의 최근 요청을, lg webhooks list 명령으로 발신 브리지를 확인하십시오.

오류와 종료 코드

기계가 분기 기준으로 삼는 것은 안정적인 문자열입니다. error 필드의 텍스트는 사람을 대상으로 하고, 현지화되며, 예고 없이 바뀝니다 — 메시지 텍스트로 분기하는 연동은 지원되지 않으며, 그것이 깨지는 것은 버그가 아닙니다.

두 가지 분류 체계, 두 개의 필드. error_code 필드는 전송 계층(위 표)이며 가장 먼저 분기에 사용하는 값입니다. error_key 필드는 failed 안의 안정적인 비즈니스 규칙 키로 — INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK — 카탈로그(error_keys)와 아래에 사전으로 게시됩니다. 아직 키를 내보내지 않는 명령은 error_key: null을 반환합니다. 사전에 없는 키는 일반적인 실패로 취급하십시오.

안정성 약속

연동 계약에서 가장 값비싼 질문은 “오늘 작성한 코드가 내일도 실행될까?”입니다. 숫자 하나가 이에 답합니다: 카탈로그의 contract_version 값과 모든 응답의 X-Contract-Version 헤더. 이 숫자는 정확히 한 가지 질문에 답합니다 — 이전에 작성한 코드가 변경 없이 여전히 실행되는가? 숫자가 증가하지 않았다면 그렇습니다. 증가했다면 아닙니다: 여러분이 의존하던 무언가가 제거되었거나 좁혀졌습니다.

공지는 아무것도 제거하지 않기 때문입니다: 기존 코드는 공지 다음 날에도 이전과 정확히 똑같이 실행됩니다. 공지 시점에 버전을 올리면 하나의 변경에 두 개의 신호를 내보내게 되고, 여러분이 첫 번째 신호를 무시하도록 길들여 — 정말 중요한 신호를 약하게 만듭니다. 공지는 이미 여러분이 하는 호출에 실려 옵니다: 카탈로그 행의 deprecated_at 및 replaced_by 값, 그 명령의 응답에 붙는 X-Deprecated 헤더와 deprecated 블록, 그리고 lg를 사용한다면 한 줄짜리 stderr 경고. 무언가가 사라진다는 것을 알기 위해 아무것도 할 필요가 없습니다. 이미 여러분의 로그에 있습니다.

공지와 제거 사이에는 최소 두 개의 역년 분기가 지나갑니다. 기간 내내 옛 명령은 여전히 응답하며, 호출할 때마다 경고합니다. CLI 이름이 폐기되면 그 후속 명령이 그 이름을 별칭으로 넘겨받습니다: 기간 동안 옛 이름은 옛 명령에 도달하고, 제거되는 날 같은 호출은 새 명령에 도달합니다 — 여러분이 클라이언트 릴리스를 내지 않아도 됩니다.

preview_invoice → get_invoice (2026년 9월 3일 공지). get_invoice 명령은 같은 미리 보기 블록을 반환하며 라인 항목, 세금 내역, 기록된 결제, 잔액, 그리고 여러분 자신의 ERP 키(external_ref)에 의한 조회를 추가로 제공합니다. preview_invoice 명령은 계속 동작하며, lg invoices preview도 마찬가지입니다 — 둘 다 경고를 냅니다.

명령 식별자 표

아래 표는 사람이 작성한 것이 아닙니다: 이 페이지가 제공될 때마다 실시간 명령 레지스트리에서 생성됩니다. 서버에 추가된 명령은 여기에 저절로 나타납니다. "게이트" 열은 명령이 확인을 요청하는지와 그 효과를 되돌릴 수 있는지를, "역할" 열은 어떤 계정 유형의 카탈로그에서 그 명령을 찾을 수 있는지를 알려 줍니다. 배지는 manager 수준 키가 필요한 명령, 파일을 받는 명령, lg에서만 실행되는 명령을 표시합니다.

API 이름(list_loads)은 고정되어 바뀌지 않으며, HTTP 호출에 사용하는 이름입니다. CLI 식별자(loads list)는 버전 관리 정책을 따릅니다: 한 번 공지되면 버전 증가와 마이그레이션 기간을 거쳐서만 바뀌며, 그 기간 동안 카탈로그는 옛 쌍을 별칭으로 제공합니다. 여기의 설명은 각 명령 자체의 레코드에서 가져와 축약한 것입니다 — 전체 텍스트, 파라미터 타입, enum 값은 카탈로그 엔드포인트를 읽거나 lg help 를 실행하여 확인하십시오. 어필리에이트 전용 명령은 여기에 나열되지 않습니다. 어필리에이트 계정은 자체 카탈로그에서 그 명령들을 봅니다.

ERP 및 CRM 연동 — 신원: 서비스 계정이 아니라 실제 사용자

ERP나 CRM은 패널에서 발급한 개인 액세스 토큰을 가진 실제 회사 사용자로서 Logistivo에 연결합니다. 회사도 없고, 범위 제한에서 면제되며, 아무도 책임지지 않는 합성 서비스 계정은 결코 만들어지지 않습니다.

이것은 선호가 아니라 아키텍처입니다. 멀티테넌트 격리는 전적으로 사용자의 회사에 기반합니다. 회사가 없는 신원에서는 격리 범위가 작동하지 않게 되며, 그 토큰에서 잘못된 쿼리 하나가 자신의 것이 아닌 데이터를 읽게 됩니다. 두 번째 이유는 감사입니다: 원장의 사용자 열은 "누가 이것을 실행했는가"를 한 사람에게 묶습니다. 서비스 계정은 그 열을 무의미하게 만들고, 인보이스를 발행하는 연동에 소유자가 없게 만듭니다. 세 번째는 폐기입니다: 토큰이 한 사람에게 속하면 그 사람이 떠날 때 연동은 조용해집니다. 소유자 없는 브리지는 수년간 조용히 계속 쓰기를 하는 브리지입니다.

ERP 및 CRM 연동 — 버전 관리와 지원 중단

카탈로그 응답에는 정수 contract_version 값이 있고, 명령 행에는 since, deprecated_at, replaced_by 값이 있습니다. 계약 버전은 호환성을 깨는 변경에서만 올라갑니다.

ERP 및 CRM 연동 — 안정적인 식별자: 자동 증가 id를 ERP의 키로 넘기지 마십시오

내부 자동 증가 id는 불투명하며 테넌트 범위로 한정됩니다: 다른 회사의 유효한 id는 여러분에게 데이터가 아니라 "찾을 수 없음"을 반환합니다. 하지만 불투명하다고 해서 비즈니스 키가 되는 것은 아닙니다. ERP가 자체 레코드에 저장하는 것은 비즈니스 키여야 합니다.

ERP 및 CRM 연동 — 페이지네이션, 필터링, 증분 동기화

목록 명령은 limit을 받고 count를 반환합니다. 야간 동기화를 위해 계약은 다음을 약속합니다. 이를 채택한 목록 명령은 JSON Schema에 cursor 및 updated_since 파라미터를 명시하며, 그 전까지는 작은 창(상한 20행, 커서 없음)으로 취급하십시오.

ERP 및 CRM 연동 — 속도 제한

제한 키는 IP가 아니라 토큰입니다: ERP는 하나의 NAT 뒤에서 들어오므로, IP 기반 제한은 회사 전체를 한 명의 사용자로 셀 것입니다. 두 개의 키를 가진 두 연동은 서로의 할당량을 결코 잠식하지 않습니다 — 이것이 "키 하나에 연동 하나"의 두 번째 이유입니다.

ERP 및 CRM 연동 — 우리가 하지 않을 것들

이것은 종종 계약에서 가장 유용한 부분입니다: 무엇이 결코 오지 않을지 알면 그것을 피해 설계할 수 있습니다.

기계 판독용 카탈로그

이 페이지는 사람을 위해 작성되었습니다. AI 어시스턴트나 자동화된 클라이언트를 위해서는 같은 정보가 구조화된, 인증 없는 사본으로 존재합니다.

GET /api/public/cli/catalog 엔드포인트는 인증이 필요 없으며 명령의 공개 식별 정보를 반환합니다: 고정된 API 이름, CLI 도메인/동사 식별자, 설명, JSON Schema 파라미터, read_only, confirmation_needed, irreversible 플래그, credit_cost, 그리고 접근 필드 key_level, permission, file_params, secret_parameters, cli_only. 개인 데이터, 테넌트 데이터, 샘플 레코드는 반환하지 않습니다 — 표면이 무엇인지만 반환합니다.

외부 에이전트가 이를 사용하는 올바른 방법: 카탈로그를 읽고, 어떤 작업이 가능한지 '자신의 말로' 설명하며, 실행이 필요할 때는 사용자가 자신의 환경에서 자신의 토큰으로 실행하도록 안내하는 것입니다. 에이전트는 사용자의 토큰을 결코 요청하거나, 생성하거나, 전달하지 않습니다.

회원 가입과 운송 요청을 위해 Logistivo를 AI 어시스턴트에 연결하는 것은 자체 페이지를 가진 별도의 채널입니다:

기계 판독용 정의: OpenAPI 3.1과 Postman

위의 카탈로그는 Logistivo 고유의 형식입니다. 같은 정보는 여러분의 도구가 이미 이해하는 두 가지 업계 표준 형식으로도 게시됩니다: OpenAPI 3.1 정의와 Postman 컬렉션. 어느 것도 사람이 작성하지 않습니다 — 둘 다 요청이 있을 때마다 실시간 명령 레지스트리에서 생성되므로 낡을 수 없습니다.

정의는 두 번째 현실이 아니라 카탈로그 '그 자체'입니다. 모든 명령은 POST /api/common/commands/ 오퍼레이션으로 나타납니다. 본문 스키마는 명령 자체의 JSON Schema(어시스턴트가 보는 것과 바이트 단위로 동일)이고, 응답 스키마는 공통 봉투이며, 게이트 플래그(read_only, confirmation_needed, irreversible 및 크레딧 배지)는 x-logistivo 확장에, 비즈니스 오류 키는 x-error-keys 아래에 있습니다. 폐기된 명령은 deprecated: true로 표시되며 6개월의 기간 동안 계속 호출할 수 있습니다.

명령 표면과 함께, 연동 계약을 담은 REST 엔드포인트도 정의에 포함되어 있습니다: 증분 동기화(updated_since + cursor + limit)를 지원하는 10개 목록 엔드포인트와 여러분 자신의 키(external_ref)를 받는 7개 쓰기 엔드포인트. Logistivo의 REST 표면은 이보다 훨씬 크지만, 나머지는 '의도적으로' 제외했습니다. 그 페이로드들은 제품 자체의 계약이며, 추측으로 작성한 스키마는 첫 호출에서 여러분이 생성한 클라이언트를 깨뜨릴 것입니다 — 그리고 여러분은 버그를 우리 코드가 아니라 여러분의 코드에서 찾게 될 것입니다.

정의는 요청한 사람의 '역할'에 맞춰 생성됩니다: 다른 역할의 명령은 문서에 절대 들어가지 않습니다. 정의는 사전이 아니라 계약이며, 그 안의 모든 것은 해당 토큰으로 호출할 수 있어야 하기 때문입니다. 인증 없는 버전은 세 역할의 합집합이며, 그곳에서는 각 오퍼레이션이 어떤 역할에 보이는지를 x-logistivo.roles 아래에 명시합니다.

우리 말만 믿지 말고 검증하십시오: npx @redocly/cli lint logistivo-openapi.json 은 경고 0건으로 통과합니다.

코드 생성: 정의로부터 만드는 타입 지정 클라이언트

정의를 손에 넣었으니 이제 그것으로 동작하는 코드를 생성하십시오. `lg codegen`은 OpenAPI 문서를 읽고 의존성이 없는 단일 파일 클라이언트를 작성합니다 — TypeScript(타입 선언과 얇은 호출 래퍼) 또는 PHP(PHPDoc 배열 형태를 갖춘 클라이언트 클래스). `npm install`도, composer도 필요 없습니다: TypeScript 쪽은 `fetch`, PHP 쪽은 `ext-curl`을 사용합니다.

생성은 우리 서버가 아니라 '여러분의' 머신에서 일어납니다. 이것은 취향의 문제가 아닙니다: 정의를 저장소에 한 번 고정해 두면(`lg spec --out=...`) 같은 명령이 네트워크도 토큰도 없이 모든 빌드에서 실행되므로, 여러분의 CI는 우리의 가동 시간에 결코 의존하지 않습니다. 그리고 생성된 코드는 여러분의 ERP에서 '실행되는' 코드이므로, API에서 가져오는 대신 저장소에 보관하면 모든 변경이 `git diff`에 나타납니다.

생성된 클라이언트는 Logistivo의 세 경로를 모두 올바르게 다루며, 어느 것이 어느 것인지를 추측이 아니라 문서에서 배웁니다: 모든 오퍼레이션은 `x-logistivo.surface`에 자신의 경로를 선언합니다. 명령 경로는 `{success, data:{…}}` 봉투를 풀어냅니다. REST 동기화 목록은 봉투에 싸여 있지 '않으며' 그대로 반환됩니다(봉투를 풀면 `next_cursor`가 사라집니다). REST 쓰기는 엔드포인트 자체의 제품 본문을 반환합니다. 범용 생성기는 이 구분을 알지 못하지만 — 우리 생성기는 압니다.

타입은 추측이 아닙니다: 각 명령의 인수 인터페이스는 어시스턴트가 보는 바로 그 JSON Schema에서 생성됩니다. 필수는 필수로 남고, enum은 유니언 타입이 되며, 중첩 객체는 중첩 타입이 됩니다. 실무에서는: 서버가 요구하는 필드를 빠뜨린 호출은 운영 환경에서 422 응답으로 실패하는 것이 아니라 — 컴파일 시점에 실패합니다.

파일에는 생성 타임스탬프가 기록되지 않습니다: 같은 정의와 같은 플래그는 같은 바이트를 만듭니다. 따라서 다시 생성해도 `git diff`에 잡음이 생기지 않으며, 실제로 바뀐 단 하나 — 계약 — 가 계속 눈에 보입니다.

정직한 범위

우리는 이 섹션을 줄이기보다 늘리고 싶습니다. 연동 계약의 가치는 그것이 약속하는 것보다, 하지 않는 것을 얼마나 분명히 밝히는가에 더 있습니다.

자주 묻는 질문

Logistivo CLI는 어디에 사용합니까?

Logistivo 계정의 업무를 터미널, 스크립트 또는 자체 AI 에이전트에서 실행합니다: 화물과 운행, 운송 요청, 입찰과 텐더, 차량, 운전기사와 그 경비, 문서, 거래처와 거래처 원장, 인보이스와 회계, 창고와 재고, 팀 구성원, HS 관세율과 반덤핑 관세. 패널의 모든 쓰기 작업에는 명령이 있으며, 결제, 법적 동의, 계정 해지는 의도적으로 브라우저에 남습니다. 같은 명령을 ERP/CRM 연동을 위해 HTTP로, 그리고 MCP 채널을 통해서도 호출할 수 있습니다.

Logistivo CLI에서 명령 이름은 어떻게 정해집니까?

형태는 다음과 같습니다: lg --parameter=value. 도메인은 복수형 엔터티(loads, demands, invoices, export-documents)이고, 동사는 의도적인 결정에 의해서만 늘어나는 닫힌 어휘(list, get, create, update, search, set-status, generate 등)에서 가져옵니다. 한 엔터티의 모든 동사는 하나의 도메인에 있으며, (도메인, 동사) 쌍은 카탈로그 전체에서 고유합니다.

CLI에서 어떻게 인증합니까?

대화형으로는 lg login 명령이 승인 코드를 표시하며, Logistivo에 로그인한 뒤 브라우저에서 그 코드를 확인합니다 — 터미널에는 비밀번호나 키를 입력하지 않으며, 세션은 lg logout을 실행할 때까지 로그인 상태로 유지되고 스스로 갱신됩니다. lg login --password(이메일과 비밀번호)는 대체 수단으로 계속 제공됩니다. 서버와 CI에서는 패널에서 또는 lg keys create 명령으로 발급한 연동 키를 사용하십시오: lg login --api-key 명령으로 로컬 프로필에 저장하거나, LOGISTIVO_TOKEN 환경 변수에 넣습니다. HTTP에서는 토큰이 Authorization: Bearer 헤더로 전달됩니다.

ERP 연동을 위한 서비스 계정을 만들 수 있습니까?

아니요. 연동은 패널에서 만든 실제 회사 사용자의 토큰으로 연결합니다. 회사가 없는 합성 신원은 멀티테넌트 격리를 무력화하고, 감사 원장의 "누가 이것을 실행했는가" 열을 무의미하게 만들며, 소유자가 떠나도 조용해지지 않는 브리지를 남깁니다. 올바른 패턴은 좁은 역할과 지정된 소유자를 가진 전용 사용자입니다.

Logistivo에 테스트(샌드박스) 환경이 있습니까?

예, 셀프서비스로 제공됩니다: 여러분의 회사를 위해 별도의 샌드박스 테넌트가 열립니다 — 같은 코드, 같은 명령 카탈로그, 자체 크레딧 원장. 환경은 키의 회사에서 도출되며 요청으로 지정할 수 없습니다. 잘못된 프로필로 한 호출은 서버 측에서 403 ENVIRONMENT_MISMATCH 오류로 중단됩니다. 샌드박스에서 연 운송 요청은 실제 운송사에 절대 도달하지 않고, 샌드박스 이벤트는 실서비스 웹훅 구독으로 절대 전달되지 않으며, 실서비스 잔액은 변하지 않습니다. 엔드포인트: POST /api/common/integration-sandbox (열기), POST /api/common/integration-sandbox/keys (키), GET /api/common/integration-sandbox/go-live (운영 전환 체크리스트). lg 쪽에서는 lg --sandbox와 lg --live가 1회 실행용 프로필 선택자입니다.

스크립트에서 lg --json 출력을 어떻게 읽습니까?

출력은 HTTP 봉투 전체입니다: { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. 비즈니스 페이로드는 .data.result 아래에 있습니다. 목록 명령에서 행은 .data.result., 개수는 .data.result.count, 다음 페이지는 .data.result.next_cursor 필드입니다. 따라서 올바른 jq 경로는 lg loads list --json | jq -r '.data.result.loads[].load_code'와 같은 형태입니다. 봉투를 건너뛰는 경로는 오류를 내지 않고 조용히 null을 반환합니다 — 감시 스크립트에서 이는 멈추지 않고 울리는 경보가 됩니다. 유일한 예외는 /api/public/status 엔드포인트이며, 그 본문은 의도적으로 봉투가 없습니다.

같은 요청을 두 번 보내면 중복 레코드가 생깁니까?

idempotency_key 값을 보냈다면 생기지 않습니다. 같은 키로 두 번째 호출을 하면 명령이 다시 실행되지 않고, 첫 번째 결과가 replayed 상태와 함께 반환됩니다. 키는 UUID여야 하며 재시도 간에 '같은' 값으로 유지되어야 합니다 — 시도마다 새 키를 생성하는 것은 멱등성을 사용하지 않는 것과 같습니다.

--yes는 무엇을 합니까?

확인 게이트를 통과시킵니다. 게이트가 있는 명령을 --yes 없이 호출하면 아무것도 바뀌지 않습니다: 서버는 하려는 일을 한 문장으로 요약하고, 되돌릴 수 없는지 알려 준 뒤 멈춥니다(HTTP 409, CLI 종료 코드 4). 명령을 --yes와 함께 반복하면 작업이 수행됩니다.

스크립트에서 실패한 명령을 어떻게 감지합니까?

메시지 텍스트가 아니라 종료 코드를 읽으십시오: 0 성공, 1 비즈니스 규칙 거부, 2 명령 또는 인수 오류, 3 인증 또는 역할 오류, 4 확인 필요. HTTP에서 이에 해당하는 것은 200, 422, 404, 403, 409 상태이며, 본문의 error_code 필드는 안정적입니다. 오류 '메시지'는 현지화되며 예고 없이 바뀝니다.

사용 가능한 명령을 프로그래밍 방식으로 어떻게 찾습니까?

인증 없는 탐색에는 GET /api/public/cli/catalog 엔드포인트를, 여러분의 계정이 실제로 볼 수 있는 목록에는 GET /api/common/commands 엔드포인트를 사용하십시오. 둘 다 각 명령의 JSON Schema 파라미터를 반환합니다. 명령 목록을 하드코딩하지 말고 이것으로부터 클라이언트를 그려 내십시오.

새 명령이 추가되면 클라이언트를 업데이트해야 합니까?

아니요. 클라이언트는 카탈로그에서 명령 목록을 가져오므로, 서버에 추가된 명령은 새 클라이언트 릴리스 없이 나타납니다. 업데이트는 계약 버전을 올리는 호환성 파괴 변경에만 필요하며, 그 경우에도 옛 형태는 최소 6개월 동안 유지됩니다.

CLI가 기존 REST API를 대체합니까?

아니요. 웹 및 모바일 앱을 구동하는 REST 표면은 그대로 유지됩니다. 명령 카탈로그는 리소스 CRUD를 대체하지 않습니다. 그것은 동사 계층 — 역할 게이트와 확인 게이트를 거치고 감사되는, 작업을 수행하는 단일한 방법 — 입니다.

Logistivo API 키는 어떻게 받으며 무엇을 할 수 있습니까?

팀 관리 권한을 가진 관리자가 패널의 Settings › Integration keys에서, 또는 터미널에서 lg keys create 명령으로 발급합니다. 키는 실제 팀 구성원을 대리하여 동작하며 세 가지 독립적인 설정을 가집니다: 스코프(read는 카탈로그와 쓰기를 하지 않는 명령을 열고, write는 모든 명령을 엽니다 — 확인 게이트는 그대로 유지됩니다), 선택적 도메인 권한 부여, 그리고 접근 수준 — 일상 업무용 기본값인 employee, 또는 팀·설정·키·금전 업무용 manager. 키는 한 번만 표시되며 서버에 저장되지 않습니다. 교체, 이전, 폐기할 수 있습니다. 키가 대리하는 구성원이 떠나면 키도 닫힙니다. 서비스 계정은 없습니다.

멱등성 키를 다른 명령에 재사용하면 어떻게 됩니까?

서버는 409 idempotency_conflict 응답으로 답하고 그 키가 처음 사용된 명령의 이름을 알려 줍니다. 아무것도 실행되지 않고 아무것도 재생되지 않습니다. 키는 여러분의 회사 범위로 한정되고, 하나의 명령에 묶이며, 24시간 동안 잠깁니다 — 그 이후에는 같은 키가 새로운 실행을 시작합니다.

속도 제한은 연동에 어떻게 적용됩니까?

토큰 단위, 분 단위로 세 개의 버킷에 적용됩니다: 카탈로그 읽기 120, 읽기 명령 120, 쓰기 명령 30. 429 응답에는 항상 Retry-After 헤더와 retry_after, limit, bucket을 담은 본문이 포함됩니다. 그만큼 기다린 뒤 재시도하십시오. 카탈로그는 현재 수치를 rate_limits 아래에 게시합니다.

비즈니스 거부와 전송 오류를 어떻게 구분합니까?

먼저 error_code 값으로 분기하십시오: failed(422)는 비즈니스 거부이며 INSUFFICIENT_CREDITS 또는 NOT_FOUND 같은 안정적인 error_key 값을 담습니다. server_error(5xx)와 네트워크 오류는 같은 멱등성 키로 재시도합니다. rate_limited(429)는 기다려서 넘깁니다. 오류 텍스트로는 절대 분기하지 마십시오.

Logistivo는 웹훅을 보냅니까? 어떤 이벤트에 대해서입니까?

예. https 주소를 등록하고 관심 있는 이벤트를 고르면, 이벤트가 발생할 때 서명된 POST 요청이 도착합니다. 현재 13개 이벤트를 구독할 수 있습니다: 화물 생성/상태 변경/운전기사 배정, 운송 요청 생성 및 상태 변경, 입찰 등록/수정/수락, 인보이스 초안/발행/취소, 창고 예약 생성/상태 변경. 모든 이름은 실제로 발송되는 이벤트 클래스로 뒷받침됩니다 — 발표만 하고 한 번도 발생하지 않는 것은 없습니다. 그런 이벤트는 연동 개발자가 영원히 기다리는 조용한 실패이기 때문입니다. 구독은 Settings › Integration › Webhooks에서, 또는 터미널에서 lg webhooks create 명령으로 만듭니다.

Logistivo 웹훅 서명은 어떻게 검증합니까?

모든 요청에는 X-Logistivo-Signature 헤더가 있습니다: t=,v1=. 서명 대상 문자열은 ".", 알고리즘은 HMAC-SHA256, 키는 구독 비밀 키입니다. 네 가지 규칙: (1) '원시' 바이트를 검증하십시오 — 본문을 디코딩한 뒤 다시 직렬화하면(JSON.stringify / json_encode) 같은 데이터가 다른 바이트가 되어 서명이 더 이상 일치하지 않습니다. 이것이 가장 흔한 실수입니다. (2) 타임스탬프를 확인하고, 서명이 올바르더라도 |now − t| > 300 s인 요청은 모두 버리십시오. (3) hash_equals / timingSafeEqual로 상수 시간 비교를 하십시오. (4) 전달 id가 아니라 event_id 값으로 중복을 제거하십시오 — event_id 값은 재전송 시 유지되지만 전달 id는 바뀝니다. 동작하는 Node 및 PHP 수신기가 저장소의 examples/webhook-receiver/ 디렉터리에 있으며 --selftest로 스스로를 테스트합니다.

웹훅을 전달할 수 없으면 어떻게 됩니까? 이벤트를 잃게 됩니까?

아니요. 수신기가 2xx를 반환하지 않으면 전달은 재시도 일정에 들어갑니다: 60 · 300 · 1800 · 7200 · 21600초, 총 6회 시도, 대략 8.6시간의 기간. 410 Gone을 제외한 '모든' 실패는 4xx를 포함해 재시도됩니다 — 비즈니스 이벤트를 잃는 것은 요청 몇 번이 더 드는 것보다 비쌉니다. 전달이 연속으로 10번 소진되면 구독이 음소거되고 사유가 기록되므로, "왜 아무것도 도착하지 않는가"에 계속 답할 수 있습니다. 전달 원장은 모든 시도(상태 코드, 소요 시간, 오류)를 보관하며, 패널에서 또는 lg webhooks replay 명령으로 단일 전달을 다시 보낼 수 있습니다. 증분 동기화(updated_since + cursor)는 여전히 안전망으로 존재합니다.

제 AI 에이전트가 사용자가 패널에서 하는 모든 일을 할 수 있습니까?

거의 모든 일을 할 수 있습니다. 패널의 모든 쓰기 작업에는 명령이 있으며 — 패널의 쓰기 엔드포인트와 대조해 측정한 결과, 범위 내 엔드포인트의 100%가 커버됩니다 — 같은 명령이 lg, HTTP, MCP 채널에 열려 있습니다. 의도적인 예외는 신원과 금전 관련 단계입니다: 로그인, 패키지 구매, 3D Secure로 카드 추가, 법적 문서 승인, 계정 해지는 당사자의 브라우저에 남으며, lg handoff-links generate 명령이 이를 위한 링크를 반환합니다. 연동 키, 웹훅, 샌드박스는 비밀 값을 한 번만 반환하기 때문에 lg 내장 명령으로 관리합니다.

CLI나 API에서 PDF나 사진 같은 파일을 명령에 전달할 수 있습니까?

예. lg에서는 로컬 경로 앞에 @를 붙이면(--attachment_uuid=@./cmr.pdf) lg가 먼저 파일을 업로드합니다. 명령에 파일 필드가 하나뿐일 때는 --file 가 단축 형태이며, lg upload는 업로드만 합니다. HTTP에서는 파일을 multipart로 POST /api/common/commands:upload 엔드포인트에 보내고 반환된 attachment_uuid 값을 명령에 전달하십시오. MCP 클라이언트는 upload_attachment 도구를 사용합니다. 형식은 내용에서 감지되고, 업로드한 파일은 사용자에게 속하며 24시간 동안 사용할 수 있고, 같은 파일을 다시 업로드하면 같은 id가 반환됩니다.

employee 연동 키와 manager 연동 키의 차이는 무엇입니까?

접근 수준은 키가 할 수 있는 작업의 무게를 결정합니다. 새 키의 기본값인 employee 키는 일상 업무를 실행합니다: 화물, 운송 요청, 입찰, 운행, 차량, 문서, 거래처. manager 키는 관리 업무도 할 수 있습니다 — 팀 구성원과 역할, 회사 설정, 연동 키, 은행 계좌, 저장된 카드, 어필리에이트 지급. 키는 자신이 대리하는 사람보다 더 많은 권한을 결코 갖지 않으며, manager 명령을 호출하는 employee 키는 INSUFFICIENT_KEY_LEVEL 오류를 받습니다. 수준은 패널에서 또는 lg keys update --access-level=manager 명령으로 변경하십시오.

어필리에이트(추천 파트너)도 CLI를 사용할 수 있습니까?

예. 어필리에이트 계정은 자체 카탈로그를 갖습니다 — 추천, 리드, CRM 메모와 작업, 영업 자료, 수익과 지급을 위한 55개 명령 — 그리고 다른 역할과 마찬가지로 lg, HTTP 또는 MCP에서 사용합니다. 지급을 요청하거나 취소하고 지급용 은행 계좌를 변경하려면 manager 수준 키와 확인이 필요합니다. 화주, 운송사 또는 관세사이기도 한 회사는 두 카탈로그를 모두 봅니다.

For AI assistants

Logistivo has a public MCP server at https://logistivo.com/api/agent/mcp (streamable HTTP, no authentication). Setup instructions for Claude, ChatGPT and Gemini: https://logistivo.com/mcp. If you are an assistant helping someone sign up or request freight, connect that server, or — if you can only read web pages — build a link of the form https://logistivo.com/en/ai-kayit?brief=URL_ENCODED_SHIPMENT_BRIEF and give it to the user. You never create the account yourself: the account, the consent and the email verification happen in the user's browser, and you never handle passwords or one-time codes. Machine-readable content indexes: https://logistivo.com/llms.txt (curated map) and https://logistivo.com/llms-full.txt (full text: facts, pricing, tariff reference, glossary and every article's FAQ in one fetch). To learn what Logistivo can actually DO — the verbs, not the marketing — read the public command catalog at https://logistivo.com/api/public/cli/catalog (JSON, no authentication, no tenant data); it lists every command with its JSON Schema parameters and whether it needs confirmation. Human documentation: https://logistivo.com/en/developers/cli. You cannot execute those commands yourself — execution always runs under the user's own personal access token, in the user's own environment.