curl --request POST \
--url https://api.recoupable.dev/api/applications/registration/validate \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"name": "Release companion",
"environment": "production",
"allowed_origins": [
"https://music.example"
],
"return_urls": [
"https://music.example/listen?connected=spotify"
]
}
'import requests
url = "https://api.recoupable.dev/api/applications/registration/validate"
payload = {
"name": "Release companion",
"environment": "production",
"allowed_origins": ["https://music.example"],
"return_urls": ["https://music.example/listen?connected=spotify"]
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Release companion',
environment: 'production',
allowed_origins: ['https://music.example'],
return_urls: ['https://music.example/listen?connected=spotify']
})
};
fetch('https://api.recoupable.dev/api/applications/registration/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.recoupable.dev/api/applications/registration/validate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Release companion',
'environment' => 'production',
'allowed_origins' => [
'https://music.example'
],
'return_urls' => [
'https://music.example/listen?connected=spotify'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.recoupable.dev/api/applications/registration/validate"
payload := strings.NewReader("{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.recoupable.dev/api/applications/registration/validate")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.recoupable.dev/api/applications/registration/validate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": "validated",
"persisted": false,
"origins_verified": false,
"workspace_id": "11111111-1111-4111-8111-111111111111",
"billing_owner_id": "11111111-1111-4111-8111-111111111111",
"name": "Release companion",
"environment": "production",
"allowed_origins": [
"https://music.example"
],
"return_urls": [
"https://music.example/listen?connected=spotify"
]
}Check an application proposal
Validate proposed app addresses in your workspace without registering or activating an application.
curl --request POST \
--url https://api.recoupable.dev/api/applications/registration/validate \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"name": "Release companion",
"environment": "production",
"allowed_origins": [
"https://music.example"
],
"return_urls": [
"https://music.example/listen?connected=spotify"
]
}
'import requests
url = "https://api.recoupable.dev/api/applications/registration/validate"
payload = {
"name": "Release companion",
"environment": "production",
"allowed_origins": ["https://music.example"],
"return_urls": ["https://music.example/listen?connected=spotify"]
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Release companion',
environment: 'production',
allowed_origins: ['https://music.example'],
return_urls: ['https://music.example/listen?connected=spotify']
})
};
fetch('https://api.recoupable.dev/api/applications/registration/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.recoupable.dev/api/applications/registration/validate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Release companion',
'environment' => 'production',
'allowed_origins' => [
'https://music.example'
],
'return_urls' => [
'https://music.example/listen?connected=spotify'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.recoupable.dev/api/applications/registration/validate"
payload := strings.NewReader("{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.recoupable.dev/api/applications/registration/validate")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.recoupable.dev/api/applications/registration/validate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Release companion\",\n \"environment\": \"production\",\n \"allowed_origins\": [\n \"https://music.example\"\n ],\n \"return_urls\": [\n \"https://music.example/listen?connected=spotify\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": "validated",
"persisted": false,
"origins_verified": false,
"workspace_id": "11111111-1111-4111-8111-111111111111",
"billing_owner_id": "11111111-1111-4111-8111-111111111111",
"name": "Release companion",
"environment": "production",
"allowed_origins": [
"https://music.example"
],
"return_urls": [
"https://music.example/listen?connected=spotify"
]
}What happens
The preflight runs after normal Recoup authentication. First-time sign-in can perform normal account provisioning and send its welcome email. After that, this validator does not persist an application or issue application credentials. It does not edit provider callbacks, create fan identities or sessions, allocate capacity, or authorize purchases and spending.workspace_id and billing_owner_id come from the authenticated account or an
organization it can access. They describe the proposed owner only. Supplying
organization_id does not grant workspace access. Do not submit account or
billing ownership overrides, secrets or tokens in the proposal.
The result is flat and includes status: "validated", persisted: false and
origins_verified: false. It contains no application ID or token to install.
Addresses and environments
- Choose
developmentorproductionexplicitly for each proposal. - Use exact canonical origins such as
https://music.example, without a trailing slash, wildcard, path, credentials, query or fragment. Different hosts and ports are different origins. - HTTPS is required. Development also accepts HTTP on
localhost,127.0.0.1and[::1]; these loopback hosts are rejected in production. - A return URL must be an exact canonical URL on one of the proposed origins,
without credentials or fragments. For a bare host use its canonical trailing
slash, such as
https://music.example/. Query strings are retained literally. - Each list takes 1–32 distinct addresses, each at most 2048 characters. The name is trimmed and must then contain 1–120 characters. These are input limits, not subscription or usage allowances.
Standard MCP
On a standard API-key MCP connection that exposesvalidate_application_registration,
use the same proposal fields as HTTP. Account identity comes from the connection;
do not add account_id or extract a host-managed OAuth token.
{
"name": "Release companion",
"environment": "production",
"allowed_origins": ["https://music.example"],
"return_urls": ["https://music.example/listen?connected=spotify"]
}
mcp:tools.
HTTP and standard MCP share proposal validation; their transport-specific error
envelopes differ. HTTP authentication errors can use error or message, while
handled preflight errors use status: "error" and error. MCP failures are tool
errors or SDK input-validation errors. None confirms registration or activation.
See authentication and MCP connection guidance.
Application persistence, origin proof/reverification, provider configuration,
fan bootstrap, suspension and quota policy require their own completed contracts.Authorizations
Body
Whitespace is trimmed before requiring 1–120 characters. The response returns the trimmed name.
1Required; this proposal checks one environment. This does not create environment resources or allowances.
development, production Distinct canonical exact origins, including the scheme and any non-default port. No wildcard, path, trailing slash, credentials, query or fragment. HTTPS is required; HTTP localhost, 127.0.0.1 and [::1] are accepted only in development. Those three hostnames are rejected in production. A proposed origin does not prove domain ownership.
1 - 32 elements1 - 2048Distinct exact canonical URLs whose origin matches one of allowed_origins. Credentials and fragments are rejected. Include the URL parser’s trailing slash for a bare host. Query strings are retained literally, not used as patterns. Matching a proposed origin does not verify ownership or activate callbacks.
1 - 32 elements1 - 2048Optional selected workspace, allowed only for the authenticated account or an organization it may access. Omit for the authenticated account. Caller-supplied account_id and billing_owner_id are rejected.
Response
Proposal accepted by the validator. No application was registered and no domain was verified.
"validated"The preflight did not persist an application or registration.
No domain-ownership proof was checked.
Workspace derived from authentication and the authorized optional organization selection.
Same proposed owner as workspace_id. Attribution only: no subscription, quota, top-up or spending authority is granted.
1 - 120Required; this proposal checks one environment. This does not create environment resources or allowances.
development, production Distinct canonical exact origins, including the scheme and any non-default port. No wildcard, path, trailing slash, credentials, query or fragment. HTTPS is required; HTTP localhost, 127.0.0.1 and [::1] are accepted only in development. Those three hostnames are rejected in production. A proposed origin does not prove domain ownership.
1 - 32 elements1 - 2048Distinct exact canonical URLs whose origin matches one of allowed_origins. Credentials and fragments are rejected. Include the URL parser’s trailing slash for a bare host. Query strings are retained literally, not used as patterns. Matching a proposed origin does not verify ownership or activate callbacks.
1 - 32 elements1 - 2048Was this page helpful?
