# auth.md

**Service:** Thien Furniture — public read-only API (`https://tubepthien.vn/api/v1/`)
**Protocol convention:** auth.md (agentic registration: discover → register → claim → exchange → use → revoke)
**Last updated:** 2026-10-09 · **API version:** v1

You are an agent. Read this file in order; do not skip ahead.

**The short answer:** this service is **public and read-only**. It needs **no registration, no
credential, no API key and no OAuth flow**. Steps 2–5 of the usual auth.md recipe resolve to
"not applicable" and Step 6 is a plain `GET`. The steps are still written out in full so that you
can *verify* that for yourself instead of guessing — every check below is a real HTTP request you
can make.

Discovery summary you will obtain by following Step 1:

| Discovery artefact | URL | Result for this service |
| --- | --- | --- |
| Protected Resource Metadata (RFC 9728) | `https://tubepthien.vn/.well-known/oauth-protected-resource` | `200` — published, `authorization_servers: []` |
| Authorization server metadata (RFC 8414) | `https://tubepthien.vn/.well-known/oauth-authorization-server` | **not published** — there is no OAuth authorization server |
| `agent_auth` bootstrap block | (inside AS metadata) | **not applicable** — no `identity_endpoint`, no `claim_endpoint` |
| API catalog (RFC 9727) | `https://tubepthien.vn/.well-known/api-catalog` | `200` |
| OpenAPI 3.1 contract | `https://tubepthien.vn/openapi.json` | `200` |
| Human/agent docs | `https://tubepthien.vn/docs/` | `200` |

## Step 1 — Discover

### 1a. Fetch the Protected Resource Metadata

```http
GET /.well-known/oauth-protected-resource HTTP/1.1
Host: tubepthien.vn
```

Response (200):

```json
{
  "resource": "https://tubepthien.vn/api/v1/",
  "resource_name": "Thien Furniture — Public Read-Only API (tubepthien.vn)",
  "resource_documentation": "https://tubepthien.vn/docs/",
  "resource_policy_uri": "https://tubepthien.vn/deprecation-policy.md",
  "authorization_servers": [],
  "scopes_supported": [],
  "bearer_methods_supported": [],
  "x-note": "Public, read-only API. No authorization server, no scopes, no bearer token. Send a plain GET."
}
```

What each field tells you:

- `resource` — the canonical URL of the API you are calling. Use it as the `aud` if you ever mint
  an assertion for an audience (you will not need to here).
- `resource_name` / `resource_documentation` — display name and docs. Surface `resource_name` to the
  user when you report what you are about to call.
- `authorization_servers` — **empty**. There is no authorization server for this resource, so there
  is nothing to register with and nothing to exchange.
- `scopes_supported` / `bearer_methods_supported` — **empty** for the same reason. Do **not** send an
  `Authorization` header; the API ignores it and never asks for one.

### 1b. Authorization server metadata — intentionally absent

```http
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: tubepthien.vn
```

This document is deliberately **not published** (you will get `404`). There is no
`/.well-known/oauth-authorization-server` because there is no OAuth authorization server, no
`token_endpoint`, no `revocation_endpoint`, no `issuer`, and therefore no `agent_auth` block with an
`identity_endpoint` / `claim_endpoint`. Anything that claims otherwise for `tubepthien.vn` or
`api.tubepthien.vn` is not ours.

If your framework insists on an authorization-server document before it will call an API, treat the
`404` on `/.well-known/oauth-authorization-server` as the authoritative signal that this resource is
unauthenticated, and continue to Step 6.

## Step 2 — Pick a method

There is exactly one method:

1. **No credential.** Send `GET` and receive JSON.

The three registration methods of the auth.md convention are all *not enabled* on this service, by
design, because there is nothing to register:

- `identity_assertion` (ID-JAG) — **not enabled** (no authorization server to present an assertion to).
- `service_auth` (verified email) — **not enabled** (there is no account, no consent screen).
- `anonymous` — unnecessary (anonymous access *is* the model for this public API).

If you send the registration request anyway, you will get the usual profile error rather than
partial access:

```http
POST /api/v1/agent/identity HTTP/1.1
Content-Type: application/json

{ "type": "anonymous" }
```

```http
HTTP/1.1 405 Method Not Allowed
Allow: GET

{"error":{"code":"method_not_allowed","message":"This API accepts GET only","status":405,"documentation":"https://tubepthien.vn/docs/","request_id":"rl_9c0d1e2f"}}
```

Note that this is a `405 Method Not Allowed` on the API, **not** an `anonymous_not_enabled` profile
error: the whole registration surface is absent, not merely switched off.

## Step 3 — Register

Not required. There is no `POST /agent/identity`, no `registration_id`, no `identity_assertion`, no
scope set to consent to, and no user identity to assert. Nothing you can do through this API can
change the website: it is `GET`-only (every other method returns `405` with `Allow: GET`).

## Step 4 — Claim ceremony

Not applicable. There is no `claim_token`, no `user_code`, no `verification_uri` and no polling
grant, because Step 3 issued no anonymous registration to claim.

## Step 5 — Exchange the assertion for an access_token

Not applicable. There is no `token_endpoint` and no `identity_assertion` to exchange. Do not
implement a token-exchange step for this service.

## Step 6 — Use the API

Send `GET` with no `Authorization` header. Example:

```http
GET /api/v1/products?limit=2 HTTP/1.1
Host: tubepthien.vn
Accept: application/json
```

```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
RateLimit-Limit: 60
RateLimit-Remaining: 59
RateLimit-Reset: 41
RateLimit-Policy: "public-readonly-api";q=60;w=60
Link: <https://tubepthien.vn/openapi.json>; rel="service-desc"

{"data":[ … ],"meta":{"count":2,"limit":2,"offset":0,"total":42,"cursor":null,"next_cursor":"b2Zmc2V0OjI","has_more":true}}
```

To page, pass the opaque `meta.next_cursor` back as `?cursor=` (agent-friendly, preferred) or use
`?limit=&offset=`; stop when `meta.has_more` is `false`. See `https://tubepthien.vn/docs/` for the
endpoint index.

## Rate limits

**60 requests per minute per IP address.** Every response advertises the current quota with the IETF
`RateLimit`, `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`
fields, so you can self-throttle without probing. When you exceed the limit you get `429 Too Many
Requests` with a `Retry-After` header (seconds) — wait that long, then retry. The legacy
`X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` headers are still sent for older
clients and are deprecated (see `https://tubepthien.vn/deprecation-policy.md`).

Please cache responses (they carry `Cache-Control`) instead of polling.

## Errors

Every error is JSON and uses one typed object — `error.code` (stable, machine-readable),
`error.message` (human-readable), `error.status`, `error.documentation`, `error.request_id`. The
OpenAPI contract references that same schema from every documented `4xx`/`5xx` response of every
operation.

| Code | Status | Where | What to do |
| --- | --- | --- | --- |
| `bad_request` | 400 | any GET | A query parameter (`limit`, `offset`, `cursor`, `category`) or a path slug is malformed. Fix the input; re-read the parameter description in `/openapi.json`. |
| `not_found` | 404 | any GET | Unknown slug, or a path that matches no documented endpoint. Recover from `/openapi.json` or `/.well-known/api-catalog`. |
| `method_not_allowed` | 405 | any | You sent something other than `GET` (including any registration `POST`). The response carries `Allow: GET`. |
| `rate_limited` | 429 | any GET | More than 60 requests/minute from your IP. Honor `Retry-After` (seconds), then retry. |
| `internal_error` | 500 | any GET | Unexpected server error; the message is fixed and generic. Retry with exponential backoff; if it persists, report the `error.request_id` to the contact below. |

Retry policy:

- `5xx` → exponential backoff, retry the same request.
- `4xx` other than `429` → do not retry the same payload; fix the request.
- `429` → wait `Retry-After` seconds, then retry.

## Revocation

Nothing to revoke. There is no `access_token`, no `identity_assertion` and no registration, so the
credential layer (`revocation_endpoint`) and the registration layer (Security Event Token delivery at
`agent_auth.events_endpoint`) do not exist for this service.

## Contact

- **Legal entity:** CÔNG TY TNHH THIẾT KẾ XÂY DỰNG NỘI THẤT THIÊN — tax code 0316017419
- **Hotline:** 0767587878 · **Email:** info@tubepthien.vn
- **Sister brand:** `https://thiensg.vn/api/v1/` (same contract, no product catalogue)
- **Website:** https://tubepthien.vn

Questions about the API, or a request for a field that is not exposed, go to the hotline above.
