# Majal - public API & agent discovery

Majal (مجال) at [majal.link](https://majal.link) hosts link-in-bio profiles and digital business cards for Arabic- and English-speaking creators and businesses. This page explains what an automated client (crawler, AI agent, API tooling) can fetch from the site and how to find it.

> **In short:** everything public is readable without credentials. There is no public write API and no self-service agent registration. Account actions are only available to the signed-in human owner through the web app.

## Discovery documents

| Resource | URL | Format |
| --- | --- | --- |
| API catalog ([RFC 9727](https://www.rfc-editor.org/rfc/rfc9727)) | `/.well-known/api-catalog` | `application/linkset+json` |
| OpenAPI 3.1 description | `/openapi.json` | `application/json` |
| This documentation | `/docs/api` (HTML) · `/docs/api.md` (Markdown) | `text/html` / `text/markdown` |
| MCP server (Streamable HTTP) | `/mcp` | JSON-RPC 2.0 |
| MCP Server Card | `/.well-known/mcp/server-card.json` | `application/json` |
| Agent registration & credential policy | `/auth.md` | `text/markdown` |
| Health status | `/health` | `application/json` |
| Sitemap | `/sitemap.xml` | `application/xml` |
| Crawler rules | `/robots.txt` | `text/plain` |

The homepage response also carries `Link` headers ([RFC 8288](https://www.rfc-editor.org/rfc/rfc8288)) with the `api-catalog`, `service-desc`, `service-doc` and `service-meta` relations pointing at the resources above, so a client that only knows `https://majal.link` can find everything from one request:

```http
GET / HTTP/1.1
Host: majal.link

HTTP/1.1 200 OK
Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json",
      </openapi.json>; rel="service-desc"; type="application/json",
      </docs/api>; rel="service-doc"; type="text/html",
      </auth.md>; rel="service-meta"; type="text/markdown"
```

## Reading pages as Markdown

Every HTML page on majal.link can also be read as Markdown. Send `Accept: text/markdown` and the same URL answers with `Content-Type: text/markdown; charset=utf-8` (and `Vary: Accept`). The body starts with a small front-matter block (`title`, `description`, `url`, `lang`) followed by the page content converted to Markdown, with scripts, styles and forms removed and links made absolute.

```http
GET /p/{page} HTTP/1.1
Host: majal.link
Accept: text/markdown

HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
Vary: Accept
```

Error pages negotiate the same way and keep their status code (for example `404`).

## Public resources

All of these are served as HTML (or Markdown, see above) and need no credentials.

| Resource | URL | Notes |
| --- | --- | --- |
| Profile page | `/p/{page}` | A creator's link-in-bio profile. Paid-plan owners may also have a short alias at `/{alias}` that redirects here. |
| Digital business card | `/c/{cardId}` | The card page. Cards configured as a plain redirect answer `302` to the owner's website. |
| Blog index | `/ar/blog`, `/en/blog` | `/blog` without a language segment follows the visitor's language preference. |
| Blog article | `/ar/blog/{slug}`, `/en/blog/{slug}` | Articles are also listed in the sitemap. |

Unknown profiles, cards and articles return `404` with an HTML error page.

## MCP server

Majal runs a public, **read-only** [Model Context Protocol](https://modelcontextprotocol.io) server at `https://majal.link/mcp` (Streamable HTTP, stateless, no authentication). Its Server Card is at `/.well-known/mcp/server-card.json`.

| Tool | Input | Returns |
| --- | --- | --- |
| `get_profile` | `page` | A public profile (`/p/{page}`) as Markdown |
| `get_digital_card` | `cardId` | A digital business card (`/c/{cardId}`) as Markdown |
| `list_blog_articles` | `lang?` | JSON list of articles with slug, title, date and URL |
| `get_blog_article` | `slug`, `lang?` | An article as Markdown |
| `read_page` | `path` | Any public majal.link page as Markdown |

Resources: the API documentation, `auth.md` and the OpenAPI document. Nothing on this server writes data or reaches account information.

```bash
curl -s https://majal.link/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

## WebMCP (in-browser tools)

The homepage registers tools with the browser's [WebMCP](https://webmachinelearning.github.io/webmcp/) API (`navigator.modelContext.registerTool`) when the browser supports it: `open_profile`, `open_digital_card`, `read_page`, `list_blog_articles`, `navigate` and `switch_language`. The script lives at `/global-scripts/webmcp.js` and does nothing in browsers without the API.

## Authentication

- **Reading public pages and the MCP server:** no authentication.
- **Everything else** (creating or editing profiles and cards, orders, analytics, account settings) is restricted to the account owner, who signs in through the web app at `/login` with an e-mail one-time code, Google or Facebook. The browser then holds a session cookie named `token`.
- The session cookie is **not** issued to automated clients and no endpoint described in `/openapi.json` requires it.
- Majal does not operate an OAuth authorization server and does not publish OAuth Protected Resource Metadata. Details, including how to request operator-provisioned access, are in [`/auth.md`](/auth.md).

## Rules for automated clients

1. Respect `/robots.txt`. The current policy allows crawling of all public pages.
2. Do not attempt the login, one-time-code, OAuth callback or payment flows. They create accounts, send e-mail and issue credentials.
3. Cache the discovery documents. They are served with `Cache-Control: public, max-age=3600`; `/health` is never cached.
4. Identify yourself with a descriptive `User-Agent` and keep request rates reasonable.
5. Questions and access requests: [contact@majal.link](mailto:contact@majal.link) or [majal.link/contact](https://majal.link/contact).

## Health endpoint

`GET /health` returns `200` with `{"status":"ok"}` when the application and its database are reachable, and `503` with `{"status":"degraded"}` otherwise. Each response also includes `service` (`"majal"`) and an ISO-8601 `time` stamp.
