MODOO 밖의 프로그램(예: CP DIRECT 택배 예약 자동화)이 한 회사의 고객·거래를 읽고, 고객에게 메모를 남기는 데 쓰는 API 다.
https://modoo.com.au/api/v1Cache-Control: no-store 가 붙는다.createdAt 등)은 ISO 8601 UTC 문자열, 날짜(contractDate 등)는 yyyy-MM-dd 문자열이다.null(문자열 칸은 경우에 따라 "")로 준다. 응답 모양이 항상 같다.모든 요청에 API 키를 Bearer 토큰으로 넣는다.
Authorization: Bearer modoo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
키는 modoo_ 뒤에 43 글자가 붙은 문자열이다. 키가 없거나, 형식이 틀리거나, 없는 키이거나, 폐기된 키이면 이유와 상관없이 똑같이 401 unauthorized 를 준다.
키는 MODOO 앱 안에서 회사 관리자(대표·관리자 역할)가 직접 만든다. 운영자에게 따로 요청할 필요가 없다.
CP DIRECT 택배 예약같은 API 키 카드의 목록에서 해당 키의 폐기 를 누르고 확인한다. 즉시 효력이 생기며 되돌릴 수 없다. 폐기된 키는 기록으로 목록에 남는다.
403 이 아니라 404 not_found 를 준다. 그 id 가 어딘가에 존재한다는 사실도 알려 주지 않는다.오류 응답은 항상 이 모양이다.
{ "error": { "code": "bad_request", "message": "limit must be an integer between 1 and 200." } }
| HTTP | code | 뜻 |
|---|---|---|
| 400 | bad_request | 파라미터·본문이 잘못됨. message 에 이유가 적힌다. |
| 401 | unauthorized | 키가 없거나 쓸 수 없음. WWW-Authenticate: Bearer 헤더가 함께 온다. |
| 404 | not_found | 고객이 없거나 다른 회사 고객. |
| 413 | payload_too_large | 요청 본문이 16 KB 를 넘음. |
| 429 | rate_limited | 요청 한도 초과. Retry-After 헤더의 초만큼 기다린 뒤 다시 보낸다. |
| 500 | internal | 서버 오류. 잠시 뒤 다시 시도한다. |
429 를 받는다.401 을 준다. 틀린 키로 반복 호출하지 않도록 한다.429 를 받으면 Retry-After(초) 만큼 기다린 뒤 다시 보낸다./customers, /deals)은 서버가 회사 고객을 최대 30 초 들고 있다가 페이지 넘김에 다시 쓴다. 앱에서 방금 고친 고객이 목록에 최대 30 초 늦게 보일 수 있다. 고객 상세의 메모와 메모 쓰기는 항상 최신이다.GET /api/v1/me — 키 확인키가 동작하는지, 어느 회사의 키인지 확인한다. 연동을 설정한 직후 가장 먼저 부르면 된다.
curl -s https://modoo.com.au/api/v1/me \
-H "Authorization: Bearer $MODOO_API_KEY"
{
"data": {
"companyId": "3kd9Q2x…",
"companyName": "예시 상사",
"keyName": "CP DIRECT 택배 예약"
}
}
GET /api/v1/customers — 고객 목록앱의 고객 화면은 상태가 Won 인 고객만 보여 주지만, 이 API 는 모든 상태(리드 포함)를 다루고 status 로 거른다.
| 파라미터 | 기본값 | 설명 |
|---|---|---|
q | 없음 | 이름·회사·이메일·주소·연락처에서 대소문자 구분 없이 부분 일치로 찾는다. 검색어가 숫자와 공백·-·(·)·+·. 로만 되어 있으면 연락처는 숫자만 남겨 비교한다. 0491 570 156 로 찾아도 0491570156 이 걸린다. |
status | 전체 | 쉼표로 구분한 상태. New Lead, Contacted, Proposal, Won, Lost 중에서. 대소문자까지 정확히 적는다. |
limit | 50 | 1–200. 범위를 벗어나면 400. |
offset | 0 | 0 이상 정수. |
정렬: 등록 시각(createdAt) 최신순, 같으면 id 순. 등록 시각이 없는 고객은 맨 뒤.
curl -s -G https://modoo.com.au/api/v1/customers \
-H "Authorization: Bearer $MODOO_API_KEY" \
--data-urlencode "status=Won" \
--data-urlencode "q=0491 570 156" \
--data-urlencode "limit=20"
{
"data": [
{
"id": "Lx82hQ…",
"name": "Sumin Lee",
"company": "",
"contact": "0491 570 156",
"email": "sumin@example.com",
"address": "Unit 3, 12 Smith St, Sydney NSW 2000",
"addressParts": {
"streetNumber": "Unit 3/12",
"streetName": "Smith St",
"suburb": "Sydney",
"state": "NSW",
"postcode": "2000"
},
"status": "Won",
"temperature": "Hot",
"createdAt": "2026-03-01T04:12:55.120Z",
"statusUpdatedAt": "2026-03-05T01:00:00.000Z",
"dealCount": 1
}
],
"total": 1,
"limit": 20,
"offset": 0
}
total 은 조건에 맞는 전체 건수다. 다음 페이지는 offset 에 limit 을 더해 부르고, offset >= total 이면 끝이다.
GET /api/v1/customers/{customerId} — 고객 상세고객 정보에 거래(deals)와 메모(notes)를 붙여 준다.
curl -s https://modoo.com.au/api/v1/customers/Lx82hQ… \
-H "Authorization: Bearer $MODOO_API_KEY"
{
"data": {
"id": "Lx82hQ…",
"name": "Sumin Lee",
"company": "",
"contact": "0491 570 156",
"email": "sumin@example.com",
"address": "Unit 3, 12 Smith St, Sydney NSW 2000",
"addressParts": { "streetNumber": "Unit 3/12", "streetName": "Smith St", "suburb": "Sydney", "state": "NSW", "postcode": "2000" },
"status": "Won",
"temperature": "Hot",
"createdAt": "2026-03-01T04:12:55.120Z",
"statusUpdatedAt": "2026-03-05T01:00:00.000Z",
"dealCount": 1,
"deals": [
{
"id": "7f1c…",
"customerId": "Lx82hQ…",
"contractDate": "2026-09-12",
"salesType": "판매",
"contractAmount": 1290,
"items": [{ "productName": "Water Purifier A1", "quantity": 1, "unitPrice": 1290 }],
"oneTimeFee": null,
"paymentType": null,
"isIndefinite": null,
"rentalStartDate": null,
"rentalEndDate": null,
"rentalPeriod": null,
"rentalPeriodUnit": null,
"rentalPricePerUnit": null
}
],
"notes": [
{ "id": "c1…", "text": "설치 희망일 9/20 오전", "createdAt": "2026-09-12T02:00:00.000Z", "parentId": null, "source": null },
{ "id": "c2…", "text": "[배송] CouriersPlease 송장번호 CP123456789 (2026-09-14)", "createdAt": "2026-09-14T00:30:00.000Z", "parentId": null, "source": { "type": "api", "keyName": "CP DIRECT 택배 예약" } }
]
}
}
notes 는 최근 100 건만 담기며, 그 안에서 오래된 것부터 나열된다. 답글도 함께 들어 있고(100 건에 포함) parentId 가 원 메모의 id 다.notes 에는 직원들이 앱에서 남긴 내부 메모가 그대로 들어 있다. 고객에게 보여 주거나 외부 로그·채팅에 그대로 남기지 말고, 개인정보와 같은 수준으로 다룬다.source 가 { "type": "api" } 이면 API 로 남긴 메모다. 사람이 앱에서 쓴 메모는 null.GET /api/v1/deals — 거래(계약) 목록거래마다 짧은 고객 정보(customer)가 붙는다. 택배 예약에 필요한 받는 사람 정보는 여기서 한 번에 얻을 수 있다.
| 파라미터 | 기본값 | 설명 |
|---|---|---|
contractDateFrom | 없음 | yyyy-MM-dd. 이 날짜 포함 이후. 형식이 틀리거나 없는 날짜(2026-02-30)면 400. |
contractDateTo | 없음 | yyyy-MM-dd. 이 날짜 포함 이전. From 보다 앞이면 400. |
salesType | 전체 | 판매 또는 렌탈. URL 인코딩해서 보낸다. |
customerStatus | 전체 | 고객 상태, 쉼표 구분(status 와 같은 값). |
q | 없음 | 고객 필드(3.2 와 같음) 또는 품목 이름(items[].productName)에서 찾는다. |
limit / offset | 50 / 0 | 3.2 와 같음. |
contractDate 최신순 → customerId → 거래 id. 페이지를 넘겨도 순서가 흔들리지 않는다.yyyy-MM-dd 가 아닌 거래는 빠진다.curl -s -G https://modoo.com.au/api/v1/deals \
-H "Authorization: Bearer $MODOO_API_KEY" \
--data-urlencode "contractDateFrom=2026-09-14" \
--data-urlencode "contractDateTo=2026-09-14" \
--data-urlencode "salesType=판매" \
--data-urlencode "customerStatus=Won"
{
"data": [
{
"id": "7f1c…",
"customerId": "Lx82hQ…",
"contractDate": "2026-09-14",
"salesType": "판매",
"contractAmount": 1290,
"items": [{ "productName": "Water Purifier A1", "quantity": 1, "unitPrice": 1290 }],
"oneTimeFee": null,
"paymentType": null,
"isIndefinite": null,
"rentalStartDate": null,
"rentalEndDate": null,
"rentalPeriod": null,
"rentalPeriodUnit": null,
"rentalPricePerUnit": null,
"customer": {
"id": "Lx82hQ…",
"name": "Sumin Lee",
"company": "",
"contact": "0491 570 156",
"email": "sumin@example.com",
"address": "Unit 3, 12 Smith St, Sydney NSW 2000",
"addressParts": { "streetNumber": "Unit 3/12", "streetName": "Smith St", "suburb": "Sydney", "state": "NSW", "postcode": "2000" },
"status": "Won"
}
}
],
"total": 1,
"limit": 50,
"offset": 0
}
POST /api/v1/customers/{customerId}/notes — 메모 남기기본문:
{ "text": "[배송] CouriersPlease 송장번호 CP123456789 (2026-09-14)" }
text 는 앞뒤 공백을 자른 뒤 1–2000 자. 아니면 400. 본문이 JSON 이 아니어도 400.413.201 과 만들어진 메모를 준다.API 표시가 붙는다(마우스를 올리면 키 이름).curl -s -X POST https://modoo.com.au/api/v1/customers/Lx82hQ…/notes \
-H "Authorization: Bearer $MODOO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"[배송] CouriersPlease 송장번호 CP123456789 (2026-09-14)"}'
{
"data": {
"id": "c2…",
"text": "[배송] CouriersPlease 송장번호 CP123456789 (2026-09-14)",
"createdAt": "2026-09-14T00:30:00.000Z",
"parentId": null,
"source": { "type": "api", "keyName": "CP DIRECT 택배 예약" }
}
}
addressParts — 주소 나누기는 최선 노력이다MODOO 의 고객 주소는 자유 입력 한 칸(address)이다. API 는 호주 주소를 택배 칸에 맞게 나눠 addressParts 로 함께 주지만, 확실할 때만 나누고 조금이라도 애매하면 null 을 준다. 잘못 나눈 주소는 소포를 엉뚱한 곳으로 보내기 때문이다.
나누는 조건:
서버브 + 주 약어 + 네 자리 우편번호 이다. 주 약어는 NSW VIC QLD WA SA TAS ACT NT(대소문자 무관). 사이 쉼표와 맨 끝의 Australia 는 있어도 된다.VIC 2000 은 null).12, 12A, 12-14, 3/12, Unit 3, 12, Shop 5/100 (Unit·Shop·Suite·Apt·Apartment·Flat). 호실은 Unit 3/12 처럼 streetNumber 에 함께 담긴다.12 Smith St, Surry Hills NSW 2010), 쉼표가 없으면 거리 종류(St, Street, Rd, Ave, Dr, Ct, Pl, Cres, Hwy, Pde, Ln, Tce, Blvd, Cct, Cl, Way, Esp, Gr, Sq …)가 한 번만 나와 거기서 끊을 수 있다(100 George St Sydney NSW 2000).null 이 되는 예: PO Box 12, Sydney NSW 2000, Level 5, 12 Smith St, Sydney NSW 2000, 12 Broadway Ultimo NSW 2007(쉼표도 거리 종류도 없음), 12 Smith St St Kilda VIC 3182(거리 종류가 두 번), 12 Smith St North Sydney NSW 2060(North 가 거리 이름인지 서버브인지 모름), 주 이름을 풀어 쓴 주소.
대소문자: state 는 대문자로, streetNumber 의 호실 종류·번지 뒤 글자는 Unit 3/12A 처럼 맞춘다. streetName 과 suburb 는 입력된 대소문자 그대로 준다. 주소를 모두 소문자로 적은 고객이면 "smith st", "sydney" 로 온다. CP DIRECT 에 넣기 전에 대소문자를 맞출지는 연동 쪽에서 정한다.
addressParts 가 null 이면 원문 address 를 사람이 확인해 입력하는 흐름으로 넘긴다. 추측해서 채우지 않는다.
GET /api/v1/deals 의 한 건(거래 + customer)을 한 줄로 옮긴다고 할 때:
| CP DIRECT 칸 | API 필드 | 비고 |
|---|---|---|
| Reference Number | 거래 id 또는 customerId | 연동 쪽에서 정한다. CP DIRECT 칸 길이 제한을 확인할 것. |
| Receiver Name* | customer.name | 비어 있으면 customer.company. |
| Receiver Contact Number | customer.contact | |
| Receiver Email Address* | customer.email | 필수 칸. 비어 있으면 예약하지 말고 사람에게 넘긴다. |
| Receiver Business Name | customer.company | 대부분 비어 있다. |
| Receiver Street Number* | customer.addressParts.streetNumber | addressParts 가 null 이면 사람 확인. |
| Receiver Street Address* | customer.addressParts.streetName | |
| Receiver Postal Code* | customer.addressParts.postcode | |
| Receiver Suburb* | customer.addressParts.suburb | |
| Receiver State* | customer.addressParts.state | |
| Item Information | items | 예: items.map(i => i.productName + " x " + i.quantity).join(", ") |
MODOO 에 없는 값 — 연동(스킬) 쪽 설정으로 둔다:
YesNoGET /api/v1/deals?contractDateFrom=…&contractDateTo=…&customerStatus=Won 으로 대상 거래를 고른다.
예약하기 전에 GET /api/v1/customers/{customerId} 의 notes 를 보고, 같은 거래에 대한 [배송] 메모가 이미 있으면 건너뛴다. (notes 는 최근 100 건이다. 메모가 아주 많은 고객은 오래전 예약 메모가 빠질 수 있다.)
CP DIRECT 에서 예약한다.
예약이 끝나면 송장번호를 메모로 남긴다.
POST /api/v1/customers/{customerId}/notes
{ "text": "[배송] CouriersPlease 송장번호 XXXX (2026-09-14)" }
한 고객에게 거래가 여러 건이면 메모에 거래 id 나 품목을 함께 적어 두면 2 번에서 구별하기 쉽다.