# Record Status API — developer documentation

**Preliminary · version 0.9 · 14 October 2026 · My Content Tools LLC · mycontenttools.com**

> **Preliminary.** The Record Status API is not open yet. This is an early copy of the public documentation every API Consumer will receive, so your team can plan its work. Details may change before launch. No API keys are issued until the Public API Terms of Use are published at `mycontenttools.com/developers`.

Machine-readable companions: `openapi.json` (OpenAPI 3.1, every endpoint, header, field and error) and `test-vectors.json` (worked signing examples with the exact expected outputs). If you use an AI coding assistant, give it this file, `openapi.json` and `test-vectors.json`, and have it check its signing code against the test vectors before calling the API.

## 1. What the API tells you

Studios use Model Release Pro (phone) and My Content Tools (desktop) to record each scene: the performer's release and ID check. The Record Status API answers one question about one scene record:

**Is the studio's record for this performer in this scene complete?**

A record is complete when all four of these are on file: the performer's legal name, date of birth, a verified government ID, and the ID document itself.

- You get a yes/no answer plus the time it was checked.
- You never get personal data, documents, images or a reason why a record is incomplete.
- You only get answers for studios that chose to connect your platform. The studio can disconnect at any time.
- The answer is a status check. It does not by itself satisfy your platform's own record-keeping obligations.

## 2. How it works, start to finish

1. **Register** at `mycontenttools.com/developers`: company name, website, contact email, and acceptance of the Terms of Use.
2. **Approval.** Each account is reviewed and approved by hand. Until then, every request answers `403`.
3. **Get your credentials:** an API key and a signing secret, shown once. Store them server-side only. Test credentials are also available; they only ever see sample records, so you can build and test before any studio connects.
4. **A studio connects your platform.** Your site shows the studio a one-time connection code. The studio enters it in Model Release Pro, sees your registered company name, and confirms (section 3).
5. **The studio gives you the Record IDs for each scene**, one per performer, for example in your upload form (section 4).
6. **You ask for the status** of that record, one at a time or in batches (section 6).

## 3. Connecting a studio

Your server requests a connection code with your key (signed as in section 5, with an empty body):

```
POST /v1/connection-codes
```

```
200 OK
{
  "code": "MCT-7Q4KX-R2PHM",
  "expiresAt": "2026-10-14T15:19:06Z"
}
```

- Show the code to the studio. Each code works once and expires 15 minutes after it is created.
- The studio enters it in Model Release Pro under *Settings*. The app shows the company name you registered and asks the studio to confirm.
- Once confirmed, the studio's records answer to your key, including records made before the connection. If the studio disconnects, they answer `403` again.
- Wrong codes are limited per studio, so a studio that enters too many will have to wait before trying again.

## 4. Record IDs

- A Record ID identifies one performer's record for one scene. A scene with two performers has two Record IDs. Ask for every performer's Record ID; the batch call takes up to 100 at once.
- Studios copy Record IDs from the record in Model Release Pro or My Content Tools.
- Record IDs are random strings. Treat them as opaque, case-sensitive text; do not parse them.
- An ID that is not a Record ID (for example a performer's own ID) answers `404`.

## 5. Authentication

Every request carries three headers:

| Header | Value |
|---|---|
| `x-mct-platform-key` | Your API key. |
| `x-mct-timestamp` | The current time in UTC, RFC 3339, e.g. `2026-10-14T15:04:05Z`. Must be within 5 minutes of our clock. |
| `x-mct-signature` | Lowercase hex HMAC-SHA256 of the string below, keyed with your signing secret. |

The string to sign is four lines joined by a single newline (`\n`), with no trailing newline:

```
METHOD
PATH
SHA256_HEX_OF_BODY
TIMESTAMP
```

- `METHOD`: uppercase, e.g. `GET` or `POST`.
- `PATH`: the request path without the host or query string, e.g. `/v1/attestations/AbC123`.
- `SHA256_HEX_OF_BODY`: lowercase hex SHA-256 of the exact body bytes you send. For a request with no body, hash the empty string.
- `TIMESTAMP`: exactly the value you sent in `x-mct-timestamp`.

Any failure (unknown key, account not approved, bad signature, stale timestamp) answers `403`. The API never says which check failed, so check your signing code against `test-vectors.json` first.

### Example: signing a request (Node.js 18+)

```js
import { createHash, createHmac } from 'node:crypto';

const BASE_URL = process.env.MCT_API_BASE_URL;   // issued with your key
const API_KEY = process.env.MCT_API_KEY;
const SIGNING_SECRET = process.env.MCT_SIGNING_SECRET;

export function signedHeaders(method, path, body = '') {
  const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
  const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
  const toSign = [method.toUpperCase(), path, bodyHash, timestamp].join('\n');
  const signature = createHmac('sha256', SIGNING_SECRET)
    .update(toSign, 'utf8').digest('hex');
  return {
    'x-mct-platform-key': API_KEY,
    'x-mct-timestamp': timestamp,
    'x-mct-signature': signature,
  };
}

export async function getRecordStatus(recordId) {
  const path = `/v1/attestations/${encodeURIComponent(recordId)}`;
  const res = await fetch(BASE_URL + path, { headers: signedHeaders('GET', path) });
  return { status: res.status, body: await res.json() };
}

export async function getRecordStatuses(recordIds) {
  const path = '/v1/attestations:batch';
  const body = JSON.stringify({ recordIds });
  const res = await fetch(BASE_URL + path, {
    method: 'POST',
    headers: { 'content-type': 'application/json', ...signedHeaders('POST', path, body) },
    body,
  });
  return { status: res.status, body: await res.json() };
}
```

Sign the exact body string you send. Re-serialising the JSON after signing will break the signature.

## 6. Endpoints

The base URL is issued with your key. All requests go over HTTPS, server to server. Browser calls are not supported.

### Get one record's status

```
GET /v1/attestations/{recordId}
```

```
200 OK
{
  "recordId": "AbC123",
  "attestedAt": "2026-10-14T15:04:06Z",
  "fieldsComplete": true,
  "faceMatch": null,
  "schemaVersion": "1"
}
```

### Get many records' statuses

```
POST /v1/attestations:batch
Content-Type: application/json

{ "recordIds": ["AbC123", "DeF456"] }
```

```
200 OK
{
  "attestations": [
    {
      "recordId": "AbC123",
      "attestedAt": "2026-10-14T15:04:06Z",
      "fieldsComplete": true,
      "faceMatch": null,
      "schemaVersion": "1"
    }
  ]
}
```

- Up to 100 IDs per batch. More than 100 answers `400`; the list is never cut short silently.
- Records that do not exist, or that you are not authorised to see, are **left out** of the array. A missing ID means "no answer for you", never "incomplete". To tell the cases apart, ask for that ID on its own.

## 7. Response fields

Every successful answer has exactly these five fields, and never more. Treat any other field as an error.

| Field | Type | Meaning |
|---|---|---|
| `recordId` | string | The Record ID you asked about. |
| `attestedAt` | string | When the record was checked, UTC, RFC 3339. |
| `fieldsComplete` | boolean | `true` when legal name, date of birth, verified government ID and the ID document are all on file. Otherwise `false`. |
| `faceMatch` | null | Reserved. Always `null` for API Consumers. |
| `schemaVersion` | string | Currently `"1"` (a string, not a number). |

## 8. Status codes

| Code | Body | Meaning |
|---|---|---|
| `200` | the answer | Success. |
| `400` | `{"error":"bad_request"}` | Malformed batch body, or more than 100 IDs. |
| `403` | `{"error":"forbidden"}` | Authentication failed, or the studio has not connected your platform for this record. |
| `404` | `{"error":"not_found"}` | No scene record with this ID, or an unknown path. |
| `405` | `{"error":"method_not_allowed"}` | Wrong method for the path. |
| `429` | `{"error":"rate_limited"}` | Over your rate limit. Wait for the seconds in the `Retry-After` header. |
| `5xx` | `{"error":"internal_error"}` | Our fault. Retry later with backoff. |

## 9. Limits and caching

- **Rate limit:** 30 requests per minute per key. In a batch, each ID counts as one request. Higher limits can be requested.
- **No caching:** every response carries `Cache-Control: no-store`. A record's status can change (a studio adds a missing document, or disconnects). Ask again when you need a current answer.

## 10. Keys

- Keep your API key and signing secret on your servers. Never put them in a web page or mobile app.
- You can rotate your credentials yourself. The old and new credentials both work for 24 hours so you can switch without downtime.
- Keys can be suspended for a breach of the Terms of Use, including sustained abuse of the rate limit.

## 11. Terms, cost and support

- **Terms of Use:** published at `mycontenttools.com/developers` and accepted at sign-up, before any key is issued.
- **Cost:** no charge at launch.
- **Support:** this documentation, plus email to `support@mycontenttools.com` on a best-effort basis. There is no service-level agreement. We cannot provide custom development, integration calls or debugging of your code.

---

Record Status API · Developer documentation · Preliminary v0.9 · 14 October 2026 · © My Content Tools LLC. Subject to change before launch.
