API v1
API documentation
Manage your public profile and the content and advertisements your account is allowed to access. The API is designed for agents and uses one personal Bearer key instead of OAuth.
Automatic translation is not available through the API. Prepare every localized text before the request and send it through the manual translation fields or routes. Any auto_translate field is rejected with 422.
1. Create and protect your key
A key can be created in the profile only when the account email is verified and the profile status is trusted or verified. It is shown once, has no expiry, and works only on the site that issued it. Revoke it immediately if it may have been exposed.
Authorization: Bearer YOUR_API_KEY
Accept: application/json
2. Verify access and load references
curl -H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
"https://bali.anilau.com/api/v1/me"
curl -H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
"https://bali.anilau.com/api/v1/reference-data?lang=en"
Reference data returns configured languages, permitted content types, categories, active advertisement sections, all live locations localized for the requested language, location types, localized tags and their stable codes, advertisement types, price types and currencies, media types, and accessible block groups. The account response also reports profile access and separate location, block, and tag read, create, update, and delete permissions.
3. Your public profile
GET /api/v1/profile returns only the public profile owned by the current key. The profile contact email is separate from the account login email returned by /me. Username, account email, password, verification state, roles, profile status, social-login identifiers, and other users are not writable through this API.
curl -X PATCH "https://bali.anilau.com/api/v1/profile" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"f_name": "Profile owner",
"about": "<p>Public profile description.</p>",
"email": "public@example.com",
"contacts": {
"website": "https://example.com",
"telegram": "profile_owner"
}
}'
PATCH preserves omitted fields and omitted contact keys. Send null for an optional field or one contact to remove it; contacts: null clears the entire contact map. Supported contacts are website, Instagram, Facebook, Telegram, VK, WhatsApp, and LinkedIn. Handles and matching HTTP/HTTPS profile URLs are normalized into safe public links.
Use GET /profile/translations and PUT/DELETE /profile/translations/{lang} for manual translations of first name, last name, and description. A PUT fully replaces one non-source locale, but the overall language set may remain incomplete and the public site keeps its existing source-language fallback. Profile translation never uses the site's automatic translator.
Upload, replace, or remove the avatar through POST/DELETE /profile/avatar. Upload field image accepts GIF, JPEG, PNG, or WebP up to 10 MB and uses the same image processing pipeline as the profile form.
4. Locations
Locations are one shared hierarchy record with localized name and text. Their numeric ID, slug, parent, type, coordinates, options, URN, public path, and cover are common to every language. Management requires create location, edit location, or delete location in addition to the corresponding content:read or content:write token ability.
curl -X POST "https://bali.anilau.com/api/v1/locations" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lang": "en",
"type": "city",
"name": "Example city",
"slug": "example-city",
"text": "<p>A useful local guide.</p>",
"translations": {
"ru": {"name":"Пример города","text":"<p>Полезный местный путеводитель.</p>"}
}
}'
GET /locations supports lang, type, parent_id, slug, updated_after, and pagination up to 100. The language localizes the response and does not hide rows. GET /locations/tree returns the complete nested tree. New locations must include a ready manual name in every site language; when source text exists, every language also needs ready text.
Use PUT /locations/{id}/translations/{lang} to fully replace or repair a non-source name and text. Translations cannot be deleted. PATCH preserves omitted fields and requires coordinated translations when source name or text changes. Slug or parent changes recalculate descendant URNs. Only an unused leaf can be soft-deleted; its stored cover and metadata remain recoverable. Cover replacement and explicit removal physically delete the old file.
5. Localized blocks
Blocks are reusable localized HTML fragments addressed by an internal code. The management routes require an existing create block, edit block, or delete block permission in addition to the content token ability. Because block HTML can change public layouts, scripts, cookie notices, and footer content, these permissions must be treated as administrative access.
curl -X POST "https://bali.anilau.com/api/v1/blocks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lang": "en",
"code": "site-notice",
"name": "Site notice",
"group": "homepage",
"text": "<p>Important local notice.</p>",
"active": true,
"translations": {
"ru": {"text":"<p>Важное местное уведомление.</p>"},
"id": {"text":"<p>Pemberitahuan lokal penting.</p>"}
}
}'
Creation must include ready text for every language returned for the current site and is atomic. Lists accept lang, group, code, active, updated_after, and per_page up to 100. A source-text PATCH must include the corresponding ready manual text for every other language. Use PUT /blocks/{id}/translations/{lang} for a complete targeted correction.
Block images use POST/DELETE /blocks/{id}/image and accept GIF, JPEG, PNG, or WebP up to 5 MB. Attachments use POST/DELETE /blocks/{id}/file and accept PDF, Office documents, TXT, or RTF up to 10 MB. Explicit asset deletion removes the file physically. Deleting a block soft-deletes every live language with its code but preserves its stored assets.
6. Localized tags
Tags are linked across languages by the stable public code returned as code in JSON and stored independently on each site. Tag management reuses content:read and content:write, plus the account permissions create tag, edit tag, and delete tag. New tag sets must cover every language returned by this site's reference data.
curl -X POST "https://bali.anilau.com/api/v1/tags" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lang": "en",
"code": "remote-work",
"name": "Remote work",
"slug": "remote-work",
"text": "<p>Guides for remote workers.</p>",
"active": true,
"translations": {
"ru": {"name":"Удалённая работа","slug":"udalennaya-rabota","text":"<p>Материалы для удалённой работы.</p>"},
"it": {"name":"Lavoro remoto","slug":"lavoro-remoto"},
"uk": {"name":"Віддалена робота","slug":"viddalena-robota"},
"id": {"name":"Kerja jarak jauh","slug":"kerja-jarak-jauh"}
}
}'
Use GET/PATCH/DELETE /tags/{id} for one localized row and its complete code group, and GET /tags/{id}/translations plus PUT/DELETE /tags/{id}/translations/{lang} for ready manual translations. A repeated PUT preserves an omitted slug; a new non-Latin name falls back to code-lang.
POST /tags/{id}/translations/link with tag_id links one isolated legacy tag in another language; conflicts return 409. POST/DELETE /tags/{id}/cover replaces or physically removes the addressed locale's image. Deleting a translation or whole code group is a soft delete and keeps pivots and images for restoration.
7. Advertisement sections
Super-admin keys can list, create, read, patch, add a cover to, and delete advertisement sections through /api/v1/sections. Other accounts continue to receive active sections through reference-data, but cannot use the management routes.
curl -X POST "https://bali.anilau.com/api/v1/sections" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lang": "en",
"name": "Software development",
"slug": "software-development",
"title": "Software development services",
"text": "A complete description for this site.",
"translations": {
"ru": {"name":"Разработка ПО","title":"Услуги разработки ПО","text":"Полное описание раздела."},
"id": {"name":"Pengembangan perangkat lunak","title":"Layanan pengembangan perangkat lunak","text":"Deskripsi lengkap untuk situs ini."}
}
}'
Creation is atomic: every language returned by this site's reference data must be complete before the row is inserted. If a source name, title, teaser, or text changes later, send a ready manual value for the same field in every site language. Changing a slug or parent refreshes descendant section and advertisement URLs. Deletion is allowed only for an unused leaf section.
Each site has its own database and identifiers. A client publishing the same section to Bali, Ceylon, Mauritius, and Vietnam should keep the same slug, resolve parent identifiers independently, and provide site-specific descriptions plus every language supported by each site.
8. Content
Use /api/v1/content to list, read, create, and patch posts, pages, or categories allowed by the account. Lists accept lang, type, updated_after, and per_page up to 100.
curl -X POST "https://bali.anilau.com/api/v1/content" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"type": "post",
"lang": "en",
"name": "A practical island guide",
"teaser": "A detailed introduction long enough for article moderation.",
"text": "ARTICLE BODY OF AT LEAST 500 CHARACTERS",
"accept_rules": true,
"tag_codes": ["travel"],
"location_ids": [1]
}'
Ordinary key holders create moderated posts only. They may patch their own pending or rejected submissions; a rejected article returns to pending. Published articles, pages, and categories require the corresponding account permissions. Article and advertisement HTML supports safe semantic tables, including captions, column and row groups, headers, cells, numeric row/column spans, and header scope, plus HTTPS iframe video embeds from the configured provider allow-list. Protocol-relative embed URLs are normalized to HTTPS. Scripts, unapproved frames, event handlers, inline styles, srcdoc, and other unsafe attributes are removed. Use tag_codes to resolve the correct localized tags for the content language. The legacy tags field accepts only exact names of existing tags; unknown names return 422, and the two fields cannot be sent together.
9. Advertisements
Use /api/v1/ads to manage advertisements. Creation requires an active section, language, type, name, and at least one location. Supported type codes are offer, demand, promo, info (shown as Events), and place (Places and venues). Public contact is optional for place and info and required for other types. Place advertisements never expire and require a physical address; a map link is optional. Structured events use their final event date instead of ordinary expiration. Omitted PATCH fields and relations stay unchanged.
curl -X POST "https://bali.anilau.com/api/v1/ads" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"section_id": 1,
"lang": "en",
"type": "offer",
"name": "Local airport transfer",
"text": "Private local transfer with direct booking.",
"address": "WhatsApp +00 000 000 000",
"location_ids": [1]
}'
Only the owner may edit an advertisement, except for super-admin access. The create request may also include tag_codes, initial prices, and ready manual translations. For type=place, the allow-listed place object supports a physical address, map URL, optional weekly hours, price level, and amenity codes. For type=info, event requires a future start_date and attendance_mode, conditionally requires physical venue or online link fields, and supports optional end date, times, organizer, registration, map, and admission fields. Event data is shared by translations and uses the site timezone. Repeated sessions are separate advertisements. Online-only events receive Schema.org Event markup, although Google does not guarantee rich results for them. As with content, tags selects existing exact names only and cannot be combined with tag_codes.
10. Manual translations
List translations with GET /{resource}/{id}/translations. Use PUT /{resource}/{id}/translations/{lang} for a complete language payload. Content, advertisement, and tag APIs can remove a non-source language where their documented route exists; location translations cannot be deleted. Content translations are related Post records and keep the normal moderation and permission rules. Advertisement translations live in the advertisement JSON and may include price comments.
curl -X PUT "https://bali.anilau.com/api/v1/ads/123/translations/ru" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Готовый заголовок","title":"Готовый заголовок","teaser":"Готовое краткое описание","text":"Готовый текст объявления"}'
The API never generates translations and exposes no /translations/auto routes. Clients must prepare every localized name, title, teaser, HTML text, tag choice, and price comment before sending it. The auto_translate field is rejected at any nesting level with 422.
For PUT /content/{id}/translations/{lang}, omitting slug and parent_id preserves those values on an existing translation. A newly created translation whose localized name cannot form a Latin slug uses the predictable fallback source-slug-lang.
11. Prices
Advertisement prices are managed through GET/POST /api/v1/ads/{id}/prices and PATCH/DELETE /api/v1/ads/{id}/prices/{price}. Writable fields are type, value, a currency returned by reference data, discount, validity dates, comment, sort, and translated comments. PATCH preserves omitted fields. Send a JSON number when possible; the API also normalizes common grouped string forms such as 1 500 000 and 1 500 000 before validation.
curl -X POST "https://bali.anilau.com/api/v1/ads/123/prices" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"day","value":125,"currency":"usd","comment":"Breakfast included","translations":{"ru":{"comment":"Завтрак включён"}}}'
12. Covers and media galleries
Upload or replace a cover with a multipart POST to /api/v1/locations/{id}/cover, /api/v1/tags/{id}/cover, /api/v1/sections/{id}/cover, /api/v1/content/{id}/cover, or /api/v1/ads/{id}/cover. Use field image; GIF, JPEG, PNG, and WebP files up to 5 MB are accepted.
Gallery routes are GET/POST /{resource}/{id}/media and PATCH/DELETE /{resource}/{id}/media/{media}. A gallery item is a photo, uploaded file, or external video link with optional name, source, sort, group, and active state. Advertisement galleries are limited to six items. Deleting an item removes its stored file and relation. Content translations use gallery fallback through their shared bind and do not duplicate files.
13. Access and errors
401 means the key is missing or invalid; 403 means eligibility, ability, or operation access is missing; 404 hides an inaccessible entity; 409 reports an ambiguous or conflicting block/tag translation group; 422 contains validation errors; 429 is the API rate limit. Successful writes return data; lists also return Laravel pagination links and meta.
Nested prices, translations, and media inherit their parent policy. Never send a real key in chat, source control, logs, query strings, or screenshots. Revoke and recreate a key if it may have been exposed.