The Open API is for scripts, apps, and language models. With a personal Token you can query, upload images, publish, and delete your own posts. Read this page before calling anything. Do not invent paths or fields.

Overview

Create a Token after login under Apps → API. The full value is shown only once. Identity comes from the Token. Do not send a user id, cookie, or CSRF token. Apps → API

For language models: read https://taabb.com/api/open.md?lang=en or JSON https://taabb.com/api/open/spec.json?lang=en. Call only the four endpoints in this document. If there are images, upload them first, then put data.path into <img src> in the HTML.

You are calling Taabb Open API.
Base: https://taabb.com
HTML: GET https://taabb.com/open-api?lang=en
Full spec: GET https://taabb.com/api/open.md?lang=en
JSON spec: GET https://taabb.com/api/open/spec.json?lang=en
Auth: Authorization: Bearer <token>  (token is the user; never send uid)
Only these endpoints exist for you:
  GET/POST https://taabb.com/api/open/exists
  POST      https://taabb.com/api/open/upload
  POST      https://taabb.com/api/open/publish
  POST      https://taabb.com/api/open/delete
Envelope: {success, data, code}. Halt unless success===true.
Idempotency: provider + external_id. Duplicate publish is skipped, not updated.
Images: multipart upload first, then put data.path into <img src="upload/post/....jpg"> inside post_content. If upload times out, retry the same file (the server returns the existing path); do not guess a path or insert an image that never returned success.
post_type: 1=short note, 3=article (default). post_privacy: 0 public, 1 followers, 2 private.
No CSRF. No cookies. Do not invent endpoints.

Rules

  1. Read this spec completely before calling any endpoint.
  2. Call only the four endpoints listed below. Do not invent paths or fields.
  3. Do not call /api/open/tokens or other site APIs. Those need a browser login, not this Token.
  4. Never send uid, user_id, username, cookie, CSRF, or session. Identity is the Token only.
  5. Always inspect JSON field success. HTTP 200 can still mean failure when success is false.
  6. provider + external_id is the idempotency key. Re-publishing the same pair is skipped.
  7. Images must be uploaded via /api/open/upload first. Put data.path into <img src>. External image URLs are stripped, and the post will have no thumbnails.
  8. Do not log, echo, or store the Token in post_content or titles.

Authentication

  • Every exists / upload / publish / delete request must include the Token.
  • Do not send cookies, CSRF, uid, or user_id.
  • The Token starts with tab_. Send the full Token, not the prefix.
  • Preferred: Authorization: Bearer <token>
  • Fallback: X-Api-Token: <token>, or POST/JSON field api_token.
  • Missing or invalid Token → HTTP 403, success=false.

Response envelope

Every JSON response uses the same shape. Check success. Do not rely on the HTTP status alone.

{
  "success": true,
  "data": {},
  "code": 200
}
Case HTTP success Error
Auth failed 403 false data.message
Business failed 200 false data.message
OK 200 true read data

Idempotency

  • Key: provider (max 64) + external_id (max 191), unique per user.
  • Pick a stable provider slug for your integration, e.g. claude, gpt, my-bot.
  • external_id is your id for the source item. Publishing the same pair is skipped and does not overwrite.
  • If the post was already deleted, the mapping is cleared and exists is false.

Workflow

  1. To avoid duplicates: GET /api/open/exists first. If exists=true, stop or delete first.
  2. Images: POST /api/open/upload once per file. Save data.path from each response.
  3. Put those paths into HTML as <img src="upload/post/….jpg">, then send that HTML as post_content to publish.
  4. Delete: POST /api/open/delete with post_id, or the same provider + external_id.

How to put images into the post

Upload only stores the file. It does not attach the image to a post. You must insert the returned path into an <img> in post_content. After publish, the image appears in the body and a thumbnail is generated for list cards.

  1. POST /api/open/upload with multipart field upload_file (or file). One file per request.
  2. Read data.path, e.g. upload/post/202608/xxxx.jpg. Prefer path over a third-party URL.
  3. Insert <img src="upload/post/202608/xxxx.jpg"> into the HTML. Repeat for more images, in display order.
  4. Send the full HTML as post_content to POST /api/open/publish.

On a successful publish, the server finds every <img> whose src contains upload/post/, attaches those files to the post, and builds list thumbnails. Do not upload or send a separate thumbnail URL.

If the upload times out, the connection drops, or you get no JSON: do not put that image into the HTML, and do not guess a path. Retry POST /api/open/upload with the same file until success=true and you have data.path. Retrying the same file returns the existing path and does not store another copy. Use a client timeout of 60–120 seconds, one file per request. Keep paths from successful uploads; skip or retry the failed one before publishing.

Example post_content (two images between paragraphs)

<p>Today by the sea.</p>
<p><img src="upload/post/202608/one.jpg"></p>
<p>The lights at night were nice too.</p>
<p><img src="upload/post/202608/two.jpg"></p>
  • If you upload but omit <img> from post_content, the post has no images and no card thumbnail.
  • https images from other sites are stripped. No image, no thumbnail.
  • previewUrl usually works, but prefer data.path.

Example: push a Sina News article into notes

A script writes a Sina News article to the Token owner's account. Identity comes from the Token. Do not send uid, user_id, or a username.

How fields map

News item API field This example
Stable source slug you pick once provider sina-news
Stable article id, e.g. the docid in the URL external_id doc-inh8example01
Headline post_title Typhoon track shifts north; strong wind and rain on the east coast
Body HTML post_content Paragraphs plus <img src> for uploaded images
Original publish time post_time 2026-08-14 09:30:00
Author Do not send uid Authorization: Bearer only

Call order

  1. Call exists first. Reuse the same provider + external_id for the same article. If exists=true, it was already pushed; stop.
  2. Download cover images locally, then POST /api/open/upload. Do not put Sina https image URLs in <img>; they are stripped on publish.
  3. Then publish. Do not send uid. This example uses post_privacy=2 (private notes). Use 0 to make it public.

Replace TOKEN with your full Token. BASE below is this site. The JSON has no uid.

Copy-paste curl

TOKEN="tab_YOUR_TOKEN"
BASE="https://taabb.com"

# 1) Dedup. Same article → same provider + external_id. Do not send uid.
curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/open/exists?provider=sina-news&external_id=doc-inh8example01"

# If data.exists is true, stop. This article was already pushed.

# 2) Download the cover locally, then upload. Do not put the Sina https URL in <img>.
curl -sS -X POST "$BASE/api/open/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "upload_file=@./cover.jpg"
# Save data.path, e.g. upload/post/202608/abcd1234.jpg

# 3) Publish. Author is the Token owner. JSON has no uid.
curl -sS -X POST "$BASE/api/open/publish" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @- <<'EOF'
{
    "provider": "sina-news",
    "external_id": "doc-inh8example01",
    "post_title": "Typhoon track shifts north; strong wind and rain on the east coast",
    "post_type": 3,
    "post_privacy": 2,
    "post_time": "2026-08-14 09:30:00",
    "post_content": "<p>The typhoon track has shifted north. Strong wind and rain are expected along the east coast.</p><p><img src=\"upload/post/202608/abcd1234.jpg\"></p><p>Source: <a href=\"https://news.sina.com.cn/c/2026-08-14/doc-inh8example01.shtml\">Sina News</a></p>"
}
EOF

Enums

post_type

valueMeaning
1Short note. Title optional.
3Article / note. Default. Title recommended.

Use only 1 or 3.

post_privacy

valueMeaning
0Public. Default.
1Followers only.
2Private, owner only.

Use only 0, 1, or 2. Do not send 3.

exists — GET or POST /api/open/exists

Check whether this provider + external_id pair was already published.

  • URL:https://taabb.com/api/open/exists
  • Auth: required
  • Body:GET query | application/x-www-form-urlencoded | application/json

Parameters

nameintyperequirednotes
provider query|body string yes Source slug, max 64.
external_id query|body string yes Your item id, max 191.

Success data

{
    "exists": false,
    "post_id": 0
}
  • If exists=true, post_id is the existing post. If false, post_id is 0.
  • Does not update last-used time.

Example — GET

curl -sS -H "Authorization: Bearer tab_YOUR_TOKEN" "https://taabb.com/api/open/exists?provider=demo&external_id=abc-1"

Example — POST JSON

curl -sS -X POST "https://taabb.com/api/open/exists" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1"}'

upload — POST /api/open/upload

Upload one image. This only stores the file; it does not add it to a post yet.

  • URL:https://taabb.com/api/open/upload
  • Auth: required
  • Body:multipart/form-data

Parameters

nameintyperequirednotes
upload_file file file yes Preferred field name.
file file file no Used if upload_file is absent.

Success data

{
    "message": "上传成功",
    "path": "upload/post/202608/example.jpg",
    "previewUrl": "https://taabb.com/upload/post/202608/example.jpg",
    "width": 1200,
    "height": 800
}
  • Multipart only. JSON file upload is not supported.
  • One file per request. Repeat for more images.
  • Put data.path into <img src> in the HTML you later publish.
  • On timeout or a dropped connection, retry the same file. The server returns the existing path and does not store another copy. Do not insert an image that never returned success.

Example — multipart

curl -sS -X POST "https://taabb.com/api/open/upload" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -F "upload_file=@/path/to/photo.jpg"

publish — POST /api/open/publish

Create a post for the token owner. The same provider+external_id is skipped, not overwritten.

  • URL:https://taabb.com/api/open/publish
  • Auth: required
  • Body:application/x-www-form-urlencoded | application/json

Parameters

nameintyperequirednotes
provider body string yes provider
external_id body string yes external_id
post_content body string (HTML) yes HTML
post_title body string no max 200
post_type body integer no 1 | 3, default 3
post_privacy body integer no 0 | 1 | 2, default 0
post_time body string no datetime

Success data

{
    "message": "发布成功",
    "skipped": false,
    "exists": false,
    "post_id": 12345
}
  • If it already exists: success=true, skipped=true.
  • Do not send uid. The author is the Token owner.
  • <img> tags whose src contains upload/post/ are attached and thumbnailed automatically.

Example — Publish with images

curl -sS -X POST "https://taabb.com/api/open/upload" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -F "upload_file=@./one.jpg"
# 记下返回的 data.path,例如 upload/post/202608/one.jpg

curl -sS -X POST "https://taabb.com/api/open/publish" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  --data-urlencode "provider=demo" \
  --data-urlencode "external_id=abc-1" \
  --data-urlencode "post_title=标题" \
  --data-urlencode "post_content=<p>正文</p><p><img src=\"upload/post/202608/one.jpg\"></p>"

Example — JSON

curl -sS -X POST "https://taabb.com/api/open/publish" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1","post_title":"标题","post_type":3,"post_privacy":2,"post_content":"<p>正文</p><p><img src=\"upload/post/202608/one.jpg\"></p>"}'

delete — POST /api/open/delete

Delete a post owned by the token user. Send post_id, or provider + external_id.

  • URL:https://taabb.com/api/open/delete
  • Auth: required
  • Body:application/x-www-form-urlencoded | application/json

Parameters

nameintyperequirednotes
post_id body integer no post_id
provider body string no provider
external_id body string no external_id

Success data

{
    "message": "删除成功",
    "post_id": 12345
}
  • Token last-used time updates only after a real publish (not skip) or a successful delete. exists / upload / skipped publish do not count.

Example — JSON

curl -sS -X POST "https://taabb.com/api/open/delete" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1"}'

HTML rules

  • post_content is HTML. Empty after sanitizing fails.
  • Allowed: common text tags, links, lists, <img> for uploaded files, checkboxes.
  • Stripped: script, iframe, style, textarea, object, embed.
  • JSON body max 1MB. Prefer application/x-www-form-urlencoded for large HTML.
  • post_title max 200, plain text. post_time e.g. 2026-08-14 15:00:00. Empty = now.

Do not call

  • GET|POST /api/open/tokens
  • POST /api/open/tokens/delete
  • Any /api/* path not listed in endpoints
  • Browser publish /post/add
  • uid, specify_users, post_pass, diary_book_id, topics

Token last-used time updates only after a real publish (not skip) or a successful delete. exists / upload / skipped publish do not count.

Source spec

Same as the Markdown below. Public, no login, no secrets. https://taabb.com/api/open.md?lang=en

# Taabb Open API

SPEC_VERSION: 1.0
LANG: en
AUDIENCE: language-model agents and HTTP clients
BASE_URL: https://taabb.com
CANONICAL_HTML: https://taabb.com/open-api?lang=en
CANONICAL_MARKDOWN: https://taabb.com/api/open.md?lang=en
CANONICAL_JSON: https://taabb.com/api/open/spec.json?lang=en

This API publishes to the token owner's Taabb account. Read this file completely. Do not guess missing fields.

## 0. Agent brief

```
You are calling Taabb Open API.
Base: https://taabb.com
HTML: GET https://taabb.com/open-api?lang=en
Full spec: GET https://taabb.com/api/open.md?lang=en
JSON spec: GET https://taabb.com/api/open/spec.json?lang=en
Auth: Authorization: Bearer <token>  (token is the user; never send uid)
Only these endpoints exist for you:
  GET/POST https://taabb.com/api/open/exists
  POST      https://taabb.com/api/open/upload
  POST      https://taabb.com/api/open/publish
  POST      https://taabb.com/api/open/delete
Envelope: {success, data, code}. Halt unless success===true.
Idempotency: provider + external_id. Duplicate publish is skipped, not updated.
Images: multipart upload first, then put data.path into <img src="upload/post/....jpg"> inside post_content. If upload times out, retry the same file (the server returns the existing path); do not guess a path or insert an image that never returned success.
post_type: 1=short note, 3=article (default). post_privacy: 0 public, 1 followers, 2 private.
No CSRF. No cookies. Do not invent endpoints.
```

## 1. Rules

1. Read this spec completely before calling any endpoint.
2. Call only the four endpoints listed below. Do not invent paths or fields.
3. Do not call /api/open/tokens or other site APIs. Those need a browser login, not this Token.
4. Never send uid, user_id, username, cookie, CSRF, or session. Identity is the Token only.
5. Always inspect JSON field success. HTTP 200 can still mean failure when success is false.
6. provider + external_id is the idempotency key. Re-publishing the same pair is skipped.
7. Images must be uploaded via /api/open/upload first. Put data.path into <img src>. External image URLs are stripped, and the post will have no thumbnails.
8. Do not log, echo, or store the Token in post_content or titles.

## 2. Authentication

- Every exists / upload / publish / delete request must include the Token.
- Do not send cookies, CSRF, uid, or user_id.
- The Token starts with tab_. Send the full Token, not the prefix.
- Preferred: Authorization: Bearer <token>
- Fallback: X-Api-Token: <token>, or POST/JSON field api_token.
- Missing or invalid Token → HTTP 403, success=false.

## 3. Response envelope

Every JSON response uses the same shape. Check success. Do not rely on the HTTP status alone.

```json
{
  "success": true,
  "data": {},
  "code": 200
}
```

## 4. Idempotency

- Key: provider (max 64) + external_id (max 191), unique per user.
- Pick a stable provider slug for your integration, e.g. claude, gpt, my-bot.
- external_id is your id for the source item. Publishing the same pair is skipped and does not overwrite.
- If the post was already deleted, the mapping is cleared and exists is false.

## 5. Workflow

- To avoid duplicates: GET /api/open/exists first. If exists=true, stop or delete first.
- Images: POST /api/open/upload once per file. Save data.path from each response.
- Put those paths into HTML as <img src="upload/post/….jpg">, then send that HTML as post_content to publish.
- Delete: POST /api/open/delete with post_id, or the same provider + external_id.

## 6. How to put images into the post

Upload only stores the file. It does not attach the image to a post. You must insert the returned path into an <img> in post_content. After publish, the image appears in the body and a thumbnail is generated for list cards.

1. POST /api/open/upload with multipart field upload_file (or file). One file per request.
2. Read data.path, e.g. upload/post/202608/xxxx.jpg. Prefer path over a third-party URL.
3. Insert <img src="upload/post/202608/xxxx.jpg"> into the HTML. Repeat for more images, in display order.
4. Send the full HTML as post_content to POST /api/open/publish.

On a successful publish, the server finds every <img> whose src contains upload/post/, attaches those files to the post, and builds list thumbnails. Do not upload or send a separate thumbnail URL.

If the upload times out, the connection drops, or you get no JSON: do not put that image into the HTML, and do not guess a path. Retry POST /api/open/upload with the same file until success=true and you have data.path. Retrying the same file returns the existing path and does not store another copy. Use a client timeout of 60–120 seconds, one file per request. Keep paths from successful uploads; skip or retry the failed one before publishing.

Example post_content (two images between paragraphs):

```html
<p>Today by the sea.</p>
<p><img src="upload/post/202608/one.jpg"></p>
<p>The lights at night were nice too.</p>
<p><img src="upload/post/202608/two.jpg"></p>
```

- If you upload but omit <img> from post_content, the post has no images and no card thumbnail.
- https images from other sites are stripped. No image, no thumbnail.
- previewUrl usually works, but prefer data.path.

## 7. Example: push a Sina News article into notes

A script writes a Sina News article to the Token owner's account. Identity comes from the Token. Do not send uid, user_id, or a username.

### How fields map

| News item | API field | This example |
| --- | --- | --- |
| Stable source slug you pick once | `provider` | sina-news |
| Stable article id, e.g. the docid in the URL | `external_id` | doc-inh8example01 |
| Headline | `post_title` | Typhoon track shifts north; strong wind and rain on the east coast |
| Body HTML | `post_content` | Paragraphs plus <img src> for uploaded images |
| Original publish time | `post_time` | 2026-08-14 09:30:00 |
| Author | `Do not send uid` | Authorization: Bearer only |

### Call order

1. Call exists first. Reuse the same provider + external_id for the same article. If exists=true, it was already pushed; stop.
2. Download cover images locally, then POST /api/open/upload. Do not put Sina https image URLs in <img>; they are stripped on publish.
3. Then publish. Do not send uid. This example uses post_privacy=2 (private notes). Use 0 to make it public.

Replace TOKEN with your full Token. BASE below is this site. The JSON has no uid.

```bash
TOKEN="tab_YOUR_TOKEN"
BASE="https://taabb.com"

# 1) Dedup. Same article → same provider + external_id. Do not send uid.
curl -sS -H "Authorization: Bearer $TOKEN" \
  "$BASE/api/open/exists?provider=sina-news&external_id=doc-inh8example01"

# If data.exists is true, stop. This article was already pushed.

# 2) Download the cover locally, then upload. Do not put the Sina https URL in <img>.
curl -sS -X POST "$BASE/api/open/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "upload_file=@./cover.jpg"
# Save data.path, e.g. upload/post/202608/abcd1234.jpg

# 3) Publish. Author is the Token owner. JSON has no uid.
curl -sS -X POST "$BASE/api/open/publish" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @- <<'EOF'
{
    "provider": "sina-news",
    "external_id": "doc-inh8example01",
    "post_title": "Typhoon track shifts north; strong wind and rain on the east coast",
    "post_type": 3,
    "post_privacy": 2,
    "post_time": "2026-08-14 09:30:00",
    "post_content": "<p>The typhoon track has shifted north. Strong wind and rain are expected along the east coast.</p><p><img src=\"upload/post/202608/abcd1234.jpg\"></p><p>Source: <a href=\"https://news.sina.com.cn/c/2026-08-14/doc-inh8example01.shtml\">Sina News</a></p>"
}
EOF
```

## 8. Enums

### post_type

| value | Meaning |
| --- | --- |
| 1 | Short note. Title optional. |
| 3 | Article / note. Default. Title recommended. |

Use only 1 or 3.

### post_privacy

| value | Meaning |
| --- | --- |
| 0 | Public. Default. |
| 1 | Followers only. |
| 2 | Private, owner only. |

Use only 0, 1, or 2. Do not send 3.

## 9. Endpoints

| method | path |
| --- | --- |
| GET or POST | `/api/open/exists` |
| POST | `/api/open/upload` |
| POST | `/api/open/publish` |
| POST | `/api/open/delete` |

### exists — GET or POST /api/open/exists

Check whether this provider + external_id pair was already published.

- URL: `https://taabb.com/api/open/exists`
- Auth: required
- Body: GET query | application/x-www-form-urlencoded | application/json

| name | in | type | required | notes |
| --- | --- | --- | --- | --- |
| `provider` | query|body | string | yes | Source slug, max 64. |
| `external_id` | query|body | string | yes | Your item id, max 191. |

```json
{
    "exists": false,
    "post_id": 0
}
```

- If exists=true, post_id is the existing post. If false, post_id is 0.
- Does not update last-used time.

Example — GET:

```bash
curl -sS -H "Authorization: Bearer tab_YOUR_TOKEN" "https://taabb.com/api/open/exists?provider=demo&external_id=abc-1"
```

Example — POST JSON:

```bash
curl -sS -X POST "https://taabb.com/api/open/exists" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1"}'
```

### upload — POST /api/open/upload

Upload one image. This only stores the file; it does not add it to a post yet.

- URL: `https://taabb.com/api/open/upload`
- Auth: required
- Body: multipart/form-data

| name | in | type | required | notes |
| --- | --- | --- | --- | --- |
| `upload_file` | file | file | yes | Preferred field name. |
| `file` | file | file | no | Used if upload_file is absent. |

```json
{
    "message": "上传成功",
    "path": "upload/post/202608/example.jpg",
    "previewUrl": "https://taabb.com/upload/post/202608/example.jpg",
    "width": 1200,
    "height": 800
}
```

- Multipart only. JSON file upload is not supported.
- One file per request. Repeat for more images.
- Put data.path into <img src> in the HTML you later publish.
- On timeout or a dropped connection, retry the same file. The server returns the existing path and does not store another copy. Do not insert an image that never returned success.

Example — multipart:

```bash
curl -sS -X POST "https://taabb.com/api/open/upload" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -F "upload_file=@/path/to/photo.jpg"
```

### publish — POST /api/open/publish

Create a post for the token owner. The same provider+external_id is skipped, not overwritten.

- URL: `https://taabb.com/api/open/publish`
- Auth: required
- Body: application/x-www-form-urlencoded | application/json

| name | in | type | required | notes |
| --- | --- | --- | --- | --- |
| `provider` | body | string | yes | provider |
| `external_id` | body | string | yes | external_id |
| `post_content` | body | string (HTML) | yes | HTML |
| `post_title` | body | string | no | max 200 |
| `post_type` | body | integer | no | 1 \| 3, default 3 |
| `post_privacy` | body | integer | no | 0 \| 1 \| 2, default 0 |
| `post_time` | body | string | no | datetime |

```json
{
    "message": "发布成功",
    "skipped": false,
    "exists": false,
    "post_id": 12345
}
```

- If it already exists: success=true, skipped=true.
- Do not send uid. The author is the Token owner.
- <img> tags whose src contains upload/post/ are attached and thumbnailed automatically.

Example — Publish with images:

```bash
curl -sS -X POST "https://taabb.com/api/open/upload" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -F "upload_file=@./one.jpg"
# 记下返回的 data.path,例如 upload/post/202608/one.jpg

curl -sS -X POST "https://taabb.com/api/open/publish" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  --data-urlencode "provider=demo" \
  --data-urlencode "external_id=abc-1" \
  --data-urlencode "post_title=标题" \
  --data-urlencode "post_content=<p>正文</p><p><img src=\"upload/post/202608/one.jpg\"></p>"
```

Example — JSON:

```bash
curl -sS -X POST "https://taabb.com/api/open/publish" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1","post_title":"标题","post_type":3,"post_privacy":2,"post_content":"<p>正文</p><p><img src=\"upload/post/202608/one.jpg\"></p>"}'
```

### delete — POST /api/open/delete

Delete a post owned by the token user. Send post_id, or provider + external_id.

- URL: `https://taabb.com/api/open/delete`
- Auth: required
- Body: application/x-www-form-urlencoded | application/json

| name | in | type | required | notes |
| --- | --- | --- | --- | --- |
| `post_id` | body | integer | no | post_id |
| `provider` | body | string | no | provider |
| `external_id` | body | string | no | external_id |

```json
{
    "message": "删除成功",
    "post_id": 12345
}
```

- Token last-used time updates only after a real publish (not skip) or a successful delete. exists / upload / skipped publish do not count.

Example — JSON:

```bash
curl -sS -X POST "https://taabb.com/api/open/delete" \
  -H "Authorization: Bearer tab_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"demo","external_id":"abc-1"}'
```

## 10. HTML rules

- post_content is HTML. Empty after sanitizing fails.
- Allowed: common text tags, links, lists, <img> for uploaded files, checkboxes.
- Stripped: script, iframe, style, textarea, object, embed.
- JSON body max 1MB. Prefer application/x-www-form-urlencoded for large HTML.
- post_title max 200, plain text. post_time e.g. 2026-08-14 15:00:00. Empty = now.

## 11. Do not call

- `GET|POST /api/open/tokens`
- `POST /api/open/tokens/delete`
- `Any /api/* path not listed in endpoints`
- `Browser publish /post/add`
- `uid, specify_users, post_pass, diary_book_id, topics`

Token last-used time updates only after a real publish (not skip) or a successful delete. exists / upload / skipped publish do not count.