REST API

# Use Mobile Joe over HTTP

Read app content, register devices, and toggle Feature Plan votes using an app API key.

[Open the dashboard](https://app.mobilejoe.dev/) · [Download OpenAPI](https://docs.mobilejoe.dev/api/openapi.yaml) · [Response schemas](https://docs.mobilejoe.dev/api/schemas.md)

## First request

Copy your app's API key from App Settings in the dashboard and replace YOUR\_API\_KEY below. The base URL is https://mbj-api.com/v1, not the dashboard host. An empty array [] means the request succeeded and no FAQs are published.

```sh
curl --include --request GET 'https://mbj-api.com/v1/faqs' \
  --header 'Authorization: Token token=YOUR_API_KEY'
```

## Authentication

Every operation requires Authorization: Token token=YOUR\_API\_KEY; Bearer YOUR\_API\_KEY is also accepted. A key selects one app, so IDs of another app return 404. Missing or invalid keys return 401; an account without a verified email or an active trial or subscription returns 403.

## Identity without an SDK

Feature Plan operations and device registration require Identity-Anonymous-ID: $MBJAnonymousID: followed by a random lowercase UUID without hyphens, persisted per installation. For a signed-in user, also send Identity-External-ID: $MBJExternalID: followed by the first 32 lowercase hexadecimal characters of SHA-256 over the UTF-8 bytes of your user ID; the server does not hash raw user IDs. The first request that pairs both identities links them and moves anonymous votes and devices to an existing account. Generate new anonymous and device identifiers on sign-out and before switching accounts, as the SDKs do. FAQs and Alerts do not use identity headers.

## Device registration

Persist a device identifier, $MBJDeviceID: followed by a lowercase UUID without hyphens, and send it to Register or update device. Repeating the registration updates the same device; omitted metadata headers clear their stored values.

## Retries and limits

Voting toggles: repeating a successful POST removes the vote, so after a timeout fetch the plan and inspect voted before retrying. Limits are 300 requests per minute, plus 30 per minute each for identify and voting, per API key and client IP. Back off after a 429; no Retry-After header is promised. Do not share identity-dependent responses between users.

## Operations

| Method | Path (relative to /v1) | Reference |
| --- | --- | --- |
| GET | `/feature_plans` | [List feature plans](https://docs.mobilejoe.dev/api/list-feature-plans.md) |
| GET | `/feature_plans/{id}` | [Get a feature plan](https://docs.mobilejoe.dev/api/get-feature-plan.md) |
| POST | `/feature_plans/{id}/vote` | [Toggle vote on a feature plan](https://docs.mobilejoe.dev/api/vote-feature-plan.md) |
| GET | `/alerts` | [List alerts](https://docs.mobilejoe.dev/api/list-alerts.md) |
| GET | `/faqs` | [List FAQs](https://docs.mobilejoe.dev/api/list-faqs.md) |
| POST | `/identify` | [Register or update device](https://docs.mobilejoe.dev/api/identify-device.md) |
| GET | `/faqs/{id}` | [Get an FAQ](https://docs.mobilejoe.dev/api/get-faq.md) |

## Headers

Each operation lists the headers it accepts; _Required_ applies to those operations only. Every request also needs the [Authorization](https://docs.mobilejoe.dev/api.md#authentication) header.

| Name / location | Required / type | Details |
| --- | --- | --- |
| `Identity-Anonymous-ID` / header | Yes / string | Required persisted anonymous transport identity, including when an external identity is present. See the introduction for generation and merging. Pattern: `^\$MBJAnonymousID:[a-z0-9]{32}$` Example: `"$MBJAnonymousID:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"` |
| `Identity-External-ID` / header | No / string | Optional external transport identity. Use the prefixed truncated SHA-256 value, not a raw SDK-facing user ID. See the introduction. Pattern: `^\$MBJExternalID:[a-z0-9]{32}$` Example: `"$MBJExternalID:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"` |
| `Preferred-Languages` / header | No / string | Comma-separated BCP 47 language tags in the user's priority order. Takes precedence over Language-Code. Empty, invalid, and duplicate entries are ignored; at most five valid entries are used. FAQs, alerts and feature plans resolve each entry like Language-Code before trying the next one; if no entry matches, they return the original content. An entry that matches the app's source language returns the original content. Identify stores the first entry as device metadata. Example: `"de-DE,en-GB"` |
| `Language-Code` / header | No / string | BCP 47 language tag, used when Preferred-Languages has no valid entry. FAQs, alerts and feature plans use the most specific localization (exact tag, language and script, language and region, then language). Values matching the app's source language, absent values, and unmatched values return the original content. No Accept-Language negotiation. Identify stores the normalized tag as device metadata. Example: `"en"` |
| `OS-Name` / header | No / string | Operating system. Alert filtering recognizes ios and android case-insensitively; unknown or absent values do not match filtered audiences. Example: `"ios"` |
| `App-Version` / header | No / string | App version string. Alert audience filters use exact string membership, not semantic version ranges. Example: `"1.5.0"` |
| `OS-Version` / header | No / string | OS version string. Alert audience filters use exact string membership, not semantic version ranges. Example: `"17.5"` |

## Errors

Errors return a JSON body with an `error` message, for example `{"error":"Unauthorized"}`.

| Status | Meaning | Body |
| --- | --- | --- |
| 400 | Missing or malformed request information | `{"error":"Invalid request"}` |
| 401 | Invalid or missing API token | `{"error":"Unauthorized"}` |
| 403 | The app owner does not currently have API access. | `{"error":"Forbidden"}` |
| 404 | Resource or page not found | `{"error":"Resource not found"}` |
| 422 | Invalid request data or headers | `{"error":"Invalid request"}` |
| 429 | Request limit exceeded. Back off before retrying; no Retry-After header is promised. | `{"error":"Rate limit exceeded"}` |
