Hosted MCP server
The API serves a Model Context Protocol (MCP) server, so an agent can sign media, read jobs and assets, and check what a file’s Content Credentials declare without writing REST calls. Each tool maps onto a REST operation: the same service runs, with the same scopes, refusals, billing and per-key rate limit.
| What | Value |
|---|---|
| Endpoint | POST https://api.originalpictures.com/mcp |
| Transport | Streamable HTTP, stateless: no session, JSON responses, GET and DELETE answer 405 |
| Protocol revisions | MCP 2025-11-25, and the older 2025-06-18, 2025-03-26 and 2024-11-05 a client asks for |
| Credential | An API key as a bearer token (OAuth sign-in is not available yet) |
Authenticate
Section titled “Authenticate”Send an API key exactly as for the REST API (Authentication):
Authorization: Bearer opk_<lookup>.<secret>Create a dedicated key for each MCP client, holding only the scopes the client needs. A key
lists only the tools its scopes reach; sign:write and verify:write enable the billable tools,
so leave them out of a key meant for reading. The key’s own restrictions apply to every request,
including its rate limit and IP allowlist. When a hosted agent platform (OpenAI, xAI) connects on
your behalf, the requests come from that platform’s network, not yours.
- A missing or invalid key, or a console session, is refused with
401and aWWW-Authenticatechallenge naming the endpoint’s protected-resource metadata. That document names no authorization server yet; an API key is the only accepted credential. - A key that holds no scope any tool uses (a publishable disclosure key, for example) is refused
with
403. - Every HTTP request to
/mcpcounts as one request against the key’s rate limit, whatever it carries (initialize,tools/list, onetools/call). The REST operation a tool calls does not count a second time.
| Tool | Scope | REST operation |
|---|---|---|
get_current_user |
identity:read |
GET /v1/me |
list_assets, get_asset, list_receipts, get_signing_receipt |
assets:read |
GET /v1/assets, /v1/receipts and their single reads |
create_download |
assets:read |
POST /v1/assets/{asset_id}/download |
list_jobs, get_job, list_recipes |
jobs:read |
GET /v1/jobs, /v1/jobs/{job_id}, /v1/recipes |
get_recipe |
recipes:read |
GET /v1/recipes/{recipe_id} |
get_usage, get_pricing |
billing:read |
GET /v1/usage, GET /v1/billing/pricing |
list_verifications, get_verification_history |
verifications:read |
GET /v1/verifications, /v1/verification-history/… |
get_verification |
recovery:read |
GET /v1/verifications/{verification_id} |
upload_file, create_upload, complete_upload |
uploads:write |
POST /v1/uploads, POST /v1/uploads/{id}/complete |
sign |
sign:write |
POST /v1/sign (billable) |
create_verification |
verify:write |
POST /v1/verifications (billable) |
get_disclosure_policy |
any admitted key | GET /v1/disclosures/policies |
Every tool has a title and explicit readOnlyHint, destructiveHint, idempotentHint and
openWorldHint annotations. No tool deletes anything. A refusal comes back as a tool error naming
the HTTP status and reason code, never the key or a storage location.
Sign a file from an agent
Section titled “Sign a file from an agent”An agent connected to the hosted server has no local file the API can read, so it uploads first:
upload_filewith the file’s bytes as base64 (at most 2 MiB) and itsmedia_type. It returns anasset_idand anobject_id. For a larger file,create_uploadreturns a presignedPUTrequest: send the bytes there yourself, then callcomplete_upload.signwith theasset_id,watermarkandfingerprintflags, anidempotency_key, and the confirmation stringsign:<asset_id>. It returns the job (job_…), which runs asynchronously.get_jobuntil the job iscompleted; its result names the signed output’sasset_id.create_downloadwith thatasset_idreturns a time-limited link to the signed file. It is a bearer link: anyone holding it can download the file until it expires.
create_verification takes an object_id (and verify:<object_id> as its confirmation). A
verification reports what the file’s Content Credentials declare about its origin. It is not a
judgement of whether the content is true.
Spend safety
Section titled “Spend safety”signandcreate_verificationneed an exact confirmation string and an idempotency key, so a retry returns the first result instead of billing twice.- Their descriptions carry a cost preview, also in
_meta["com.originalpictures/billing"]:create_verificationstarts one billable operation;signstarts one for the signature plus one each for the watermark and fingerprints when chosen (audio and video watermarks and fingerprints are priced per second).get_pricingreturns the rates. - Both accept an optional
max_billable_operationsargument. A client can also send the headerX-OP-Max-Billable-Operations: <0-1000>with every request. A call that would start more billable operations than the lower of the two is refused before anything is sent;0makes the connection read-only for billable work. - Set a rate limit on the key (Restrict a key) to bound how fast an agent can spend.
Connect a client
Section titled “Connect a client”In every example, keep the key in an environment variable or the client’s secret store, never in a file you commit.
Claude Code
Section titled “Claude Code”claude mcp add --transport http originalpictures https://api.originalpictures.com/mcp \ --header "Authorization: Bearer $ORIGINALPICTURES_API_KEY"Cursor
Section titled “Cursor”~/.cursor/mcp.json (or .cursor/mcp.json in a project):
{ "mcpServers": { "originalpictures": { "url": "https://api.originalpictures.com/mcp", "headers": { "Authorization": "Bearer ${env:ORIGINALPICTURES_API_KEY}" } } }}VS Code
Section titled “VS Code”.vscode/mcp.json; VS Code asks for the key once and stores it as a secret:
{ "inputs": [ { "type": "promptString", "id": "originalpictures-api-key", "description": "Original Pictures API key (opk_...)", "password": true } ], "servers": { "originalpictures": { "type": "http", "url": "https://api.originalpictures.com/mcp", "headers": { "Authorization": "Bearer ${input:originalpictures-api-key}" } } }}Microsoft Copilot Studio
Section titled “Microsoft Copilot Studio”In the agent, open Tools, Add a tool, New tool, Model Context Protocol. Enter the
endpoint URL, choose API key authentication sent as a header named Authorization, and
when you create the connection enter the value Bearer opk_….
Perplexity
Section titled “Perplexity”On a plan that offers custom connectors, add a custom remote MCP connector with the endpoint
URL and API-key authentication, sent as the Authorization header with the value Bearer opk_….
xAI API
Section titled “xAI API”A remote MCP tool in a Responses request:
{ "model": "<a Grok model>", "input": "List my five most recent sign jobs.", "tools": [ { "type": "mcp", "server_label": "originalpictures", "server_url": "https://api.originalpictures.com/mcp", "headers": { "Authorization": "Bearer opk_..." }, "allowed_tools": ["list_jobs", "get_job"] } ]}OpenAI Agents SDK
Section titled “OpenAI Agents SDK”A hosted MCP tool: OpenAI’s platform calls the endpoint for the agent.
import asyncioimport os
from agents import Agent, HostedMCPTool, Runner
agent = Agent( name="Provenance assistant", tools=[ HostedMCPTool( tool_config={ "type": "mcp", "server_label": "originalpictures", "server_url": "https://api.originalpictures.com/mcp", "headers": {"Authorization": f"Bearer {os.environ['ORIGINALPICTURES_API_KEY']}"}, "allowed_tools": ["list_jobs", "get_job", "get_asset"], "require_approval": "never", } ) ],)
result = asyncio.run(Runner.run(agent, "List my five most recent sign jobs."))print(result.final_output)Use a read-only key for an agent that should not sign, or keep require_approval on for the
billable tools.
Smithery
Section titled “Smithery”Smithery can list a remote server by its URL and pass the API key as the client’s configuration. The Original Pictures listing is not published yet; until it is, connect directly with one of the configurations above.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Request body | 4 MiB (413 above it) |
upload_file file size |
2 MiB; use create_upload for larger files |
| Tool deadline | 20 s by default, deadline_seconds up to 55 s |
| Tool result | 256 KiB of JSON; list tools page at most 100 items |
| Server-sent events | Not offered: every answer is a single JSON response |