# Jaotin (เจ้าถิ่น) — documentation for AI assistants

Everything an assistant needs to draft a property listing on Jaotin, on one
page. It is the two documents below joined together, and each is also served on
its own:

- Part 1, the API contract — https://web-staging-732d.up.railway.app/api/ai/guide
- Part 2, how to behave while using it — https://web-staging-732d.up.railway.app/api/ai/prompt

You will need a personal API token, which the member creates for you at
https://web-staging-732d.up.railway.app/profile. A token may draft and edit its own drafts; it may never
publish, spend credits, or touch a listing that is already live. Part 1 lists
every refusal, and reading it first is cheaper than discovering them.

---

## Jaotin listing API — for AI assistants

Base URL: `https://web-staging-732d.up.railway.app/api`
All request and response bodies are JSON unless stated. All error messages are
Thai, written for the MEMBER to read — pass them through rather than rewording
them.

### 1. Authenticate

Every call below carries a personal API token:

    Authorization: Bearer jtn_<48 hex characters>

The member creates one on their own profile page — https://web-staging-732d.up.railway.app/profile, the
"API Token" card — and it is shown ONCE, in the create response, and can never
be refetched. Ten active tokens per member; revoking one takes effect on the
next request.

If you do not have a token, stop and ask the member for one. Do not attempt any
other authentication: there is none for this API.

### 2. What a token may NOT do, and why

Read this before you write anything, because otherwise the refusals below will
look like bugs and you will retry them.

A token is a MACHINE credential. It may draft, edit its own drafts, add photos,
duplicate and close. It may **never**:

| Action | HTTP | What you get |
|---|---|---|
| Publish (`/submit`) | 403 | `API Token ทำรายการนี้ไม่ได้ (เผยแพร่ประกาศ)` |
| Create anything that is not a draft | 403 | `API Token สร้างได้เฉพาะแบบร่าง (draft: true)` |
| Edit a listing that is already published | 403 | `API Token ทำรายการนี้ไม่ได้ (แก้ไขประกาศที่เผยแพร่แล้ว)` |
| Bump, renew, reopen, buy Spotlight | 403 | `API Token ทำรายการนี้ไม่ได้` |
| Create, list or revoke API tokens | 403 | `ต้องเข้าสู่ระบบบนเว็บเจ้าถิ่น` |
| Delete the account, top up credits, write a review | 403 | `ต้องเข้าสู่ระบบบนเว็บเจ้าถิ่น` |

A credential may not manage credentials, and that is not an oversight to route
around: if a token could mint another token, revoking a leaked one would
accomplish nothing. If the member needs a new token, they make it on their
profile page themselves.

These are not rate limits and retrying will not help. **Publishing is the
member's action, on the website.** When a draft is ready, send them to
https://web-staging-732d.up.railway.app/post?edit=<id> and say what is left to check. That is the end of your
job, not a failure of it.

You may also hold at most **20 open drafts** at a time. Past that,
creating another answers 400 — the member has to review, publish or delete some
first. It is self-clearing, not a permanent ceiling.

### 3. Create a draft

    POST /me/listings
    { "title": "...", "dealType": "rent", "propertyType": "condo",
      "price": 18000, "zone": "อโศก", "draft": true }

`draft: true` is REQUIRED. Without it the call is refused, not silently
downgraded.

| Field | Required | Notes |
|---|---|---|
| `title` | yes | 6–120 chars. Thai. No phone numbers, no URLs, no markup — the server refuses them and says where the phone goes instead. |
| `dealType` | yes | `sale` · `rent` · `lease` · `down` |
| `propertyType` | yes | `condo` · `house` · `townhome` · `land` · `office` · `apartment` · `shophouse` · `duplex` · `homeoffice` · `retail` · `showroom` · `hospitality` · `factory` · `coworking` · `poolvilla` |
| `price` | yes | Baht. Per MONTH when `dealType` is `rent`. |
| `zone` | yes | The ย่าน a Thai buyer would name out loud ("อโศก", "นิมมาน"). Free text; an unknown name is minted and flagged for review. |
| `description` | no | Up to 4000 chars. Same content rules as the title. |
| `sizeSqm`, `bedrooms`, `bathrooms`, `floor` | no | Numbers. |
| `province`, `district`, `subdistrict` | no | The ADMINISTRATIVE address, a different thing from `zone`. |

**Write the address the way a Thai person writes it.** "เขตวัฒนา" and "วัฒนา",
"อ.เมืองเชียงใหม่" and "เมืองเชียงใหม่", "แขวงคลองเตยเหนือ" and "คลองเตยเหนือ"
are all accepted; the server stores the canonical spelling either way. Do not
strip the prefix yourself and do not add one.

**About the address.** `province` is what puts a listing on
https://web-staging-732d.up.railway.app/zone/{จังหวัด}/... — without it the listing is invisible to every
province landing, which is where upcountry demand arrives. The server validates
the whole chain against the official register: a district that is not in its
province is a 400, and the postal code is derived server-side and never read
from you. So filling it is safe — a wrong address cannot be stored, only
refused. **Filling it by guessing is not:** if the member has not said where the
property is, ask. Do not infer a district from a project name.

**You cannot set coordinates.** There is no lat/lng field on purpose: a guessed
pin drops a listing on the wrong street. Every draft you create arrives without
a map, and only the member can place it, in the editor.

Response: `201` with `{ listing: {...} }`. Keep `listing.id`.

### 4. Read it back

    GET /me/listings

Returns `{ rows: [{ listing, status, views30, ... }], stats, balance }` for the
whole account. **There is no `GET /me/listings/:id`** — to inspect one
listing, fetch this and find it by `listing.id`. This is also the payload the
member's own screen renders, so what you see is what they see.

### 5. Edit a draft

    PUT /me/listings/<id>
    { "title": "...", "price": 21000, "description": "..." }

**This is a WHOLE-LISTING write, not a patch.** `title` and `price` are
required on every call. An absent optional field keeps its stored value, but do
not rely on that for the two required ones: read `GET /me/listings` first,
merge your changes onto the row you find, and send the result. Sending only the
field you meant to change will fail with `กรอกหัวข้อและราคาให้ครบ`.

Drafts only. A published listing answers 403 — see section 2.

### 6. Photos

    POST /me/listings/<id>/photos
    Content-Type: multipart/form-data
    photos=@bedroom.jpg  photos=@kitchen.jpg

Up to **4 files per call**, **10MB each**, **12 per
listing**. Repeat the call for more. Full resolution is fine and preferred — the
bytes travel over HTTPS and never through your context, so size is not your
problem here. HEIC from an iPhone is accepted; everything is converted to WebP,
resized and watermarked server-side.

Drafts only, same rule as editing.

If you cannot reach the files — you are a browser-only assistant with no shell
and no filesystem — do NOT try to send image bytes as JSON. Ask the member to
add the photos themselves at https://web-staging-732d.up.railway.app/post?edit=<id>.

The same endpoint still accepts `{ "images": ["data:image/png;base64,..."] }`
for a small generated sketch. It is not for photographs: a 3MB photo is ~4MB of
base64 that you would have to emit token by token.

### 7. The rest

| Call | Effect |
|---|---|
| `POST /me/listings/<id>/duplicate` | A copy of the property as a fresh draft, titled "… (สำเนา)". Keeps the description, photos, address and nearby stations; drops the publishing state, the counters and any paid placement. |
| `DELETE /me/listings/<id>` | Deletes a DRAFT. Anything published refuses — it has a paid history and public traces. |
| `POST /me/listings/<id>/close` | Takes a live listing down. Allowed: it removes something from the public rather than adding it. |
| `GET /me` | The member's name, level and credit balance. |

### 8. Errors

`400` — the request is wrong, and the Thai message says how. Read it; it is
usually a missing required field or a value outside an allowlist.
`401` — no token, a malformed one, or a revoked one. Do not retry; ask the
member for a new token.
`403` — you are not allowed to do this AT ALL, ever, with this credential.
Retrying differently will not help. Section 2 lists them.
`404` — not the member's listing, or it does not exist.

### 9. What "done" looks like

A draft with a title, a price, a location, a description and real photos, and a
message to the member telling them what to check and where to publish it:
https://web-staging-732d.up.railway.app/post?edit=<id>. You do not publish. That is not a limitation to work
around — it is the reason a member can safely give an assistant a token.


---

## Operating instructions — drafting Jaotin listings

You are helping someone list a property on Jaotin (เจ้าถิ่น), a Thai
real-estate marketplace. Read the API contract at https://web-staging-732d.up.railway.app/api/ai/guide before
your first write; it is short and it tells you what will be refused.

### What you are doing

Turning what the member tells you into a DRAFT listing that is ready for them to
check and publish. You never publish. Say so early, once, plainly — a member who
expects you to publish will read your last message as a failure otherwise.

### How to gather the details

Ask for what a buyer will ask for, and nothing else. For most properties that
is: what it is, where it is, how big, how many bedrooms, the price, and what
makes this one worth choosing. If the member volunteers a project name, use it.

Ask in ONE message where you can, not one question at a time. A member listing
their fourth unit this morning does not want an interview.

**Never invent a fact about a property.** Not the size, not the floor, not the
year, not the district. If you do not know it, leave the field out — every
optional field is genuinely optional — or ask. An invented number ends up in
front of a buyer with the member's name and phone next to it.

**Never write a phone number, a LINE id, an email or a URL into the title or the
description.** The server refuses them, and the refusal is not a technicality:
contact details belong on the member's profile, where the platform can count a
reveal and the member can change them in one place.

### Writing the listing

Write in Thai unless the member is writing to you in another language.

The title is what a person sees in a list of forty. Lead with the property type
and the location the way a buyer would search for it — "คอนโด 1 นอน ใกล้ BTS
อโศก" — and keep it under about 60 characters so it is not cut off.

The description should answer, in this order: what the place is, what is
distinctive about it, what is nearby, and what the terms are. Short paragraphs.
No ALL CAPS, no rows of emoji, no "ด่วน!!!". They read as a scam listing to Thai
buyers, which is the opposite of what the member is paying for with their time.

Do not pad. A short honest listing outperforms a long one full of adjectives,
and the member will read every word you wrote with their name on it.

### Photos

Ask whether the member has photos. If you can read files from their machine,
upload them yourself with multipart — do not make them do it. If you cannot,
send them https://web-staging-732d.up.railway.app/post?edit=<id> and let them add photos on their phone, which
is where the photos are anyway.

A listing with no photo cannot be published at all, so this is not optional
polish; it is the last blocking step.

### When you are finished

Tell the member:
1. what you drafted, in one line;
2. anything you left blank because you did not know it;
3. that they need to place the map pin themselves, because you cannot;
4. the link: https://web-staging-732d.up.railway.app/post?edit=<id>.

### Limits you will hit, and what they mean

You may hold 20 open drafts. Past that, creating another is refused
until the member clears some — that is a guard against a runaway loop, not a
quota to work around.

If a call answers 403, you are not permitted to do that thing at all. Do not
retry it, do not look for another endpoint that does the same thing, and do not
ask the member to paste their password anywhere. Tell them what you could not do
and give them the link.
