API integration
Build the API
Conventions and sample code for an API that works well with the AI bot. You can connect an existing API as-is, but a bot-only endpoint is safer and returns cleaner answers.
#Response conventions
- JSON requests and responses (
Content-Type: application/json). - HTTP 2xx without
ok: falsein the body is a success. - Failures look like
{ "ok": false, "error": "code", "message": "a sentence for the customer" }. The AI explains the situation frommessage. - Return only the fields the AI needs to answer. A human-readable label next to the status code (
status_label) makes replies more accurate. - Dates are ISO 8601 with a timezone (
2026-09-20T10:12:00+09:00).
{
"ok": true,
"order": {
"order_no": "DM-10001",
"status": "shipped",
"status_label": "In transit",
"items": [{ "name": "Tumbler 473ml", "qty": 1 }],
"total": 24900,
"shipping": {
"carrier": "CJ Logistics",
"tracking_no": "689123456789",
"tracking_url": "https://trace.cjlogistics.com/…"
}
}
}#Server security checklist
- Check the key — verify the header key on every request. Return 401 if it does not match.
- Re-check ownership — on
/customers/{id}/orders/{no}, confirm on the server that the order really belongs to that customer. Do it again for actions (dry_run: false). - Minimum data — mask full addresses and phone numbers (for example, “Gangnam-gu, Seoul ***”).
- Idempotency — actions that may be called twice, such as cancel, should return the same result the second time, or “already processed”.
- IP allowlist — allow only Cliptalk server IPs when you can.
- Logs — record which customer ID did what, and never log keys.
#Example: Node.js (Express)
A minimal example with three endpoints: verify, order lookup, and cancel (dry_run). Copy the code and replace the database parts.
import express from 'express';
const app = express();
app.use(express.json());
// 1) check the key
app.use('/chatbot', (req, res, next) => {
if (req.get('x-api-key') !== process.env.CHATBOT_API_KEY) {
return res.status(401).json({ ok: false, error: 'unauthorized', message: 'Authentication failed' });
}
next();
});
// 2) identity check
app.post('/chatbot/verify', async (req, res) => {
const { order_no, phone_last4 } = req.body;
const order = await db.orders.findOne({ order_no });
if (!order || order.phone.slice(-4) !== phone_last4) {
return res.json({ ok: false, error: 'not_found',
message: 'No order matches that order number and phone number.' });
}
const c = await db.customers.findById(order.customer_id);
res.json({ ok: true, customer: { id: c.id, name: c.name } });
});
// 3) order lookup (ownership check)
app.get('/chatbot/customers/:cid/orders/:no', async (req, res) => {
const o = await db.orders.findOne({ order_no: req.params.no, customer_id: req.params.cid });
if (!o) return res.status(404).json({ ok: false, error: 'order_not_found', message: 'Order not found.' });
res.json({ ok: true, order: toChatbotView(o) });
});
// 4) cancel (dry_run)
app.post('/chatbot/customers/:cid/orders/:no/cancel', async (req, res) => {
const o = await db.orders.findOne({ order_no: req.params.no, customer_id: req.params.cid });
if (!o) return res.status(404).json({ ok: false, error: 'order_not_found', message: 'Order not found.' });
if (!['paid', 'preparing'].includes(o.status)) {
return res.json({ ok: false, error: 'not_cancellable', message: 'This order has already shipped and cannot be cancelled.' });
}
const summary = [`Order: ${o.order_no}`, `Refund: ${o.total.toLocaleString()} KRW`];
if (req.body.dry_run) return res.json({ ok: true, dry_run: true, summary });
await cancelOrder(o);
res.json({ ok: true, status: 'cancelled', message: `Order ${o.order_no} has been cancelled.` });
});
app.listen(3000);#Error codes Cliptalk adds
| error | Meaning |
|---|---|
http_<status> | The API returned a non-2xx status (example: http_404) |
bad_json | The response was not JSON |
timeout | Timed out |
connector_unreachable | DNS, connection, or SSL error |
blocked_address | The domain resolves to a private or local address |
missing_params | A required parameter has no value |
not_verified | An operation that needs the customer was called before identity check |
secret_missing | The referenced secret is not saved |
secret_origin_mismatch | The secret was saved for a different API address (re-enter it after changing the URL) |