Content API
The HTTP contract for publishing ABFut articles and trading tips, including every in-app deeplink a tip can use. Generated from the running server code.
Generated from the running server code on
.
Numbers, enums and the deeplink list on this page are read straight out of the validator the server runs — not copied by hand. Machine-readable version: /api/contract.json.
Authentication
Every write call needs a bearer token in the header. There is no self-service signup: ask for a key and you get one scoped to just the endpoints you need.
Authorization: Bearer <key>
Content-Type: application/json
A key carries scopes. A key scoped to post:tip cannot publish articles, and vice versa — so a leaked key is limited to the one job it was issued for. Keys are stored as hashes, so they are shown once at creation and can be revoked individually without disturbing any other caller.
Authentication errors
| code | meaning |
|---|---|
401 thiếu Authorization: Bearer | the header is missing |
401 chứng thực không hợp lệ | wrong key, or it has been revoked |
401 khoá không có phạm vi "…" | valid key, but not allowed on this endpoint |
Endpoints
| endpoint | what it does | scope |
|---|---|---|
POST /postArticle | Publish an article — on the in-app wall at once, on the website at the next build. | post:article |
POST /postTip | Publish a TIPS post — shows up in the app's Tips tab immediately. | post:tip |
GET /newsFeed | Read published articles back. Public, no key needed. | no |
GET /sbcList?limit=50&sau= | The whole SBC table, paginated by docId (50 per page max). Pass the previous page's `tiep` as `sau` for the next one. | no |
GET /sbcMeta?ten= | Look up one SBC's cost and details by name. Omit `ten` to list the available names. | no |
endpoint: https://us-central1-ab-fut.cloudfunctions.net
postArticle — articles
An article lands on the in-app wall immediately. The website is static, so it appears there at the next build.
| field | required | limits |
|---|---|---|
title | yes | 10–120 characters |
body | yes | 80–6000 characters |
tags | no | ≤ 5 items, ≤ 24 characters each; EAFC27 is added by the server |
imageUrl | no | must be https://, ≤ 400 characters |
imageRatio | no | number, defaults to 1.91 |
authorName | no | ≤ 40 characters, defaults to ABFut News |
lang | no | en | vi | es | fr, defaults to en |
slugGoc | for translations | the slug of the English version |
curl -X POST https://us-central1-ab-fut.cloudfunctions.net/postArticle \
-H "Authorization: Bearer $ABFUT_KEY" \
-H 'Content-Type: application/json' \
-d '{
"title": "EA confirms TOTW 3 release time",
"body": "...",
"tags": ["Promo", "TOTW"],
"imageUrl": "https://..."
}'
Writing the body
The same string is both the website article and the in-app wall post. Write it for the wall and the website takes care of itself:
- 200–350 words. Longer than that reads as a wall of text on the wall.
- The first two or three sentences must stand alone — that is all a scrolling reader sees.
- End on one concrete action, not a sign-off.
Markup the website renders
| example | meaning |
|---|---|
| bold | bold |
a line containing only Heading | an <h2> |
• bullet | a list item |
| a YouTube link alone on its line | an embedded player |
Languages
The app and the website filter by language and do not fall back to English. An article that exists only in en is simply missing for everyone else.
Publish the English original first, keep the slug it returns, then send each translation with that slug as slugGoc. That pair is what ties the versions into one article and gets hreflang right; without it search engines read them as separate, duplicated pages.
Translations are exempt from the duplicate-topic rule — they are deliberately about the same thing.
-d '{ "title": "...", "body": "...", "lang": "vi",
"slugGoc": "<slug of the English version>" }'
What the server rejects
| code | meaning | what to do |
|---|---|---|
400 | missing field, too short, imageUrl not https | fix the payload |
409 | same topic already published within 7 days (the older post comes back in the response) | change the angle, or drop it |
429 | 10 articles already published in 24 hours | wait |
503 | recent posts could not be read | do not publish; retry later |
postTip — trading tips
A tip shows up in the app's Tips tab immediately; no website build needed. Posting the same (slug, lang) pair again overwrites rather than creating a second post.
| field | required | limits |
|---|---|---|
slug | yes | shared by every language of the same post |
lang | yes | en vi es fr |
loai | yes | leak promo thi_truong meo canh_bao |
doTinCay | yes | tin_don xac_nhan |
tieuDe | yes | ≤ 90 |
tomTat | yes | ≤ 160 — this is the line shown in the list |
than | yes | — |
hetHan | yes | ISO date, must be in the future |
anh | no | https:// |
chiPremium | no | true / false |
hanhDong | no | actions, at post level |
y | no | the entries array |
The y[] array is where the value is
than is prose a reader skims. Each entry in y[] is a card they can act on: a direction, a price range, one reason, and buttons.
| field | required | limits |
|---|---|---|
the.ten | yes | card name as futbin stores it |
viSao | yes | one sentence of reason |
huong | yes | mua ban giu tranh theo_doi |
doChacChan | yes | cao vua thap |
vungGia | no | { tu, den } — both > 0 and tu ≤ den |
hanhDong | no | Deeplinks |
{
"the": { "ten": "Hadj Moussa" },
"huong": "mua",
"doChacChan": "vua",
"viSao": "TOTW price dipped once packs dried up; floor around 9.5K.",
"vungGia": { "tu": 9500, "den": 11000 },
"hanhDong": [
{ "kieu": "xem_gia", "nhan": "Price", "uri": "abfut://player?id=70726" },
{ "kieu": "them_filter", "nhan": "Add to autobuy",
"uri": "abfut://filter?player=70726&buy=9500&sell=11000" }
]
}
Spell card names the way futbin stores them — Groß, not Gross. Get it right and the server resolves the eaId at publish time so the price button is ready; get it wrong and the app has to search by name when the reader taps, which is slower and can miss entirely.
A direction of tranh (avoid) is a valid entry. A post about a card being pumped that only carries mua entries is pointing readers at the danger.
Deeplinks
Tip buttons open the app through these URIs. The list is closed: a kieu that is not below, or a uri that does not match its shape, is rejected with 400 at publish time — and if one ever got through, the app would silently not draw the button.
Each action is an object:
{ "kieu": "...", "nhan": "button label", "uri": "..." }
| kieu | uri shape | conditions | example |
|---|---|---|---|
xem_gia | abfut://player?id=<eaId> | id > 0 | abfut://player?id=70726 |
them_filter | abfut://filter?player=<id>&buy=<price>&sell=<price> | player>0 | style>0 · buy>0 | bid>0 · sell>0 | abfut://filter?player=70726&buy=9500&sell=11000 |
mo_fodder | abfut://fodder?rating=<N> | 81 ≤ N ≤ 99 | abfut://fodder?rating=89 |
mo_market | abfut://market | — | abfut://market |
mo_sbc | abfut://sbc | abfut://sbc_solution | — | abfut://sbc |
mo_web | https://abfut.site/... | host = abfut.site | *.abfut.site | https://abfut.site/news/ |
mo_tab | abfut://player_tab?ten=<tab name> | ten ≠"" | abfut://player_tab?ten=TOTW |
xem_gia Opens the card page: price chart plus a button to add it straight to autobuy. id is the eaId, not the futbin id. If you do not have it, leave this action out — the server fills it in from y[].the.ten at publish time.
them_filter Adds a filter straight into autobuy — the strongest action here: one tap and the bot starts buying. So buy/sell must be real numbers from the source, and sell must clear buy by enough to survive EA's 5% tax; under ~8% the engine lowers or skips it. Use style=<id> instead of player=<id> when the advice is about a chem style, and bid instead of buy to bid rather than buy now.
mo_fodder Opens the fodder screen at that rating. If the post mentions fodder ratings, it needs one of these per rating — mention 89 and 90 and that is two separate buttons.
mo_market Opens the market price screen. For posts about overall price trends.
mo_sbc Opens the SBC screen or SBC solutions. For posts about a new SBC.
mo_web abfut.site only. futbin, YouTube and every other host is rejected. To point somewhere else, put the link in than as text.
mo_tab Opens one tab of the Players screen. ten is matched against the tab title, case-insensitively, trimmed. The tab set lives in Firestore (config/player_tabs) so it can change — the current list is in the table below. A name that matches no tab leaves the app on the tab it is already showing: the button reports no error, it simply does nothing. The server cannot catch this one, so it is on you to check.
Player screen tabs
Names accepted by mo_tab. Matched against the tab title case-insensitively. This set lives in Firestore, so it can change — this list is read at build time.
| name |
|---|
Market |
<50K |
Fastest |
Icon |
50-100K |
Most Expensive |
Meta |
TOTW |
newsFeed — reading back
Public, no key. Use it to check that a post went up and to avoid repeating a topic.
curl "https://us-central1-ab-fut.cloudfunctions.net/newsFeed?lang=vi&limit=20"
curl "https://us-central1-ab-fut.cloudfunctions.net/newsFeed?slug=<slug>"
| parameter | default | meaning |
|---|---|---|
lang | all | one of the languages above |
limit | 20 | max 50 |
slug | — | matches slug or slugGoc |
Returns { ok, lang, soBai, bai: [...] }. Edge-cached for 5 minutes, so something just published may not appear yet.
sbcMeta — SBC costs
Public, no key. Returns the crawled futbin figures for one SBC: cost on console and PC, whether it repeats, when it expires, the community vote.
curl --get "https://us-central1-ab-fut.cloudfunctions.net/sbcMeta" --data-urlencode "ten=83+ Upgrade"
curl "https://us-central1-ab-fut.cloudfunctions.net/sbcMeta" # list the names
The name is matched in three steps, strictest first: the exact document key, then ignoring case, then ignoring everything that is not a letter or digit (so 83 upgrade finds 83+ Upgrade).
A name that matches none of them returns 404 with a goiY list of close names — it never picks the closest one for you. These are coin costs: answering about a different SBC is worse than answering nothing. Call it with no ten to list every name available.
To label many SBCs at once — an overlay on EA's SBC screen, for example — use sbcList instead and match locally: one request for the whole table rather than one per name, and re-renders then cost nothing. Page through it by passing the previous response's tiep back as sau; tiep: null means that was the last page. Mirror the three matching steps above locally so a name does not resolve one way here and another way there.
curl "https://us-central1-ab-fut.cloudfunctions.net/sbcList?limit=50"
curl --get "https://us-central1-ab-fut.cloudfunctions.net/sbcList" -d limit=50 \
--data-urlencode "sau=<tiep>"
Rules
- Do not invent numbers. Every price, percentage and card name has to come from the source. A wrong price costs a reader real coins.
- Do not promise profit. No "guaranteed", no "double your coins". ABFut's own FAQ answers "Does ABFut guarantee profit? No" — writing the opposite contradicts it, and app stores scrutinise earnings claims closely.
- Expiry is mandatory on tips. Advice to "buy before Friday" still sitting in the feed on Monday is advice that is now wrong.
Guides and tactics pages live in the repository and have no API — draft the content and hand it over.