---
title: Authenticating with Splice (auth.md)
description: How an agent gets and uses credentials for Splice's MCP server and REST API.
url: https://splice.film.fun/auth.md
resource: https://splice.film.fun/api/mcp
authorization_server: https://splice.film.fun
---

# auth.md

You are an agent. Splice (https://splice.film.fun) accepts two credentials, both acting as one Splice user and spending that user's credits:

- an **OAuth 2.1 access token** (authorization code + PKCE, with dynamic client registration), for clients that can send the user to a browser once;
- an **agent key** (`sk_…`) the user created at https://splice.film.fun/dashboard/api-keys and gave you.

Splice does not support anonymous or email-only agent registration (the `agent_auth` profile): a human with a film.fun account always approves access. Follow the steps in order.

## Step 1 — Discover

Call the resource without credentials. The 401 tells you where the metadata is:

```http
POST /api/mcp HTTP/1.1
Host: splice.film.fun

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://splice.film.fun/.well-known/oauth-protected-resource"
```

The REST API does the same, pointing at `https://splice.film.fun/.well-known/oauth-protected-resource/api/agents`.

### 1a. Protected resource metadata (RFC 9728)

```http
GET /.well-known/oauth-protected-resource
```

```json
{
  "resource": "https://splice.film.fun/api/mcp",
  "authorization_servers": ["https://splice.film.fun"],
  "scopes_supported": ["splice:read", "splice:generate"],
  "bearer_methods_supported": ["header"],
  "resource_name": "Splice"
}
```

- `resource`: send this as the `resource` parameter when you authorize and exchange.
- `scopes_supported`: `splice:read` (read productions, compositions, characters, models and the balance) and `splice:generate` (generate and edit media, which spends credits).

### 1b. Authorization server metadata (RFC 8414)

```http
GET /.well-known/oauth-authorization-server
```

```json
{
  "issuer": "https://splice.film.fun",
  "authorization_endpoint": "https://splice.film.fun/oauth/authorize",
  "token_endpoint": "https://splice.film.fun/oauth/token",
  "registration_endpoint": "https://splice.film.fun/oauth/register",
  "revocation_endpoint": "https://splice.film.fun/oauth/revoke",
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}
```

## Step 2 — Pick a method

1. **The user gave you an agent key (`sk_…`)** → skip to [Step 6](#step-6--use-the-credential).
2. **You can open a browser for the user** (an MCP client, a desktop or CLI agent) → OAuth: Steps 3-5.
3. **Neither** → ask the user to create a key at https://splice.film.fun/dashboard/api-keys, or to connect you through a client that supports OAuth. There is no anonymous access.

## Step 3 — Register (public client)

Dynamic client registration (RFC 7591). Only public clients: no client secret.

```http
POST /oauth/register
Content-Type: application/json

{
  "client_name": "My Agent",
  "redirect_uris": ["http://127.0.0.1:33418/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
```

The response carries your `client_id`. Register once and reuse it. `redirect_uris` takes 1-10 URIs.

## Step 4 — User consent

Make a PKCE pair (`code_verifier`: 43-128 random characters; `code_challenge` = BASE64URL(SHA-256(verifier))), then send the user to:

```
https://splice.film.fun/oauth/authorize?response_type=code
  &client_id=<client_id>
  &redirect_uri=<a registered redirect_uri>
  &code_challenge=<code_challenge>&code_challenge_method=S256
  &scope=splice:read%20splice:generate
  &resource=https%3A%2F%2Fsplice.film.fun%2Fapi%2Fmcp
  &state=<random>
```

The user signs in with their film.fun account, sees your `client_name` and the scopes, and approves. Splice redirects to your `redirect_uri` with `code`, `state` and `iss`. Check that `state` matches. The code is single use and expires in 60 seconds.

## Step 5 — Exchange the code

```http
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&redirect_uri=<same redirect_uri>
&client_id=<client_id>&code_verifier=<code_verifier>&resource=https%3A%2F%2Fsplice.film.fun%2Fapi%2Fmcp
```

```json
{ "access_token": "sat_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "…", "scope": "splice:read splice:generate" }
```

Access tokens last 1 hour; refresh tokens 30 days. To refresh:

```http
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<client_id>
```

Refresh rotates **both** tokens. Store the new refresh token: presenting an old one again revokes the whole grant.

## Step 6 — Use the credential

```http
POST /api/mcp
Authorization: Bearer <access_token or sk_ key>
```

The same bearer works on the REST API (`https://splice.film.fun/api/agents/*`) within its scopes. A token without a tool's scope gets `403` with `WWW-Authenticate: Bearer error="insufficient_scope", scope="…"`: ask the user to approve the missing scope (repeat Step 4 with it).

If a previously working access token gets `401`: refresh once (Step 5). If that fails with `invalid_grant`, start again at Step 4.

Full API reference: https://splice.film.fun/developers.

## Errors

| Code | Where | What to do |
|---|---|---|
| `invalid_request` | register, authorize, token | Fix the parameters; `error_description` says which |
| `invalid_redirect_uri` | register | Send 1-10 valid redirect URIs |
| `invalid_client` (401) | token | Unknown `client_id`: register again (Step 3) |
| `invalid_grant` | token | Code expired, reused, wrong `redirect_uri` or PKCE mismatch; or a revoked/rotated refresh token. Restart at Step 4 |
| `unsupported_grant_type` | token | Use `authorization_code` or `refresh_token` |
| `invalid_target` | token | `resource` must be `https://splice.film.fun/api/mcp` |
| `access_denied` | authorize redirect | The user declined; don't retry without asking |
| `insufficient_scope` (403) | API | Get the user's approval for the scope |
| `rate_limited` (429) | any | Wait `Retry-After` seconds; the OAuth endpoints allow 20 requests a minute per IP |

## Revocation

- Revoke a token yourself (RFC 7009): `POST /oauth/revoke` with `token=<access or refresh token>` (form-encoded). It answers 200 even for unknown tokens.
- The user can revoke any OAuth connection or agent key at https://splice.film.fun/dashboard/api-keys ("Connected apps" and "API keys"). After that every call gets 401: start again at Step 2.
