Create SAML connection
Create a SAML SSO connection for a provider. Provide exactly one IdP
source: idp_metadata_url, idp_metadata_xml, or an explicit idp
block. The Service Provider (SP) endpoints in the response are derived
from the app domain and the generated connection ID, and are immutable.
curl --request POST \
--url https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Acme Okta",
"behavior": {
"allow_email_account_merge": true,
"jit_provisioning": true,
"enforce_login": false,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": false,
"email_domain_allowlist": [
"acme.com"
]
},
"enabled": true,
"domain": "session.acme.com",
"idp_metadata_url": "https://acme.okta.com/app/abc/sso/saml/metadata",
"idp_metadata_xml": "<string>",
"mapping": {
"email": "email",
"first_name": "first_name",
"last_name": "last_name",
"groups": "groups",
"custom": {}
}
}
'import requests
url = "https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}"
payload = {
"name": "Acme Okta",
"behavior": {
"allow_email_account_merge": True,
"jit_provisioning": True,
"enforce_login": False,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": False,
"email_domain_allowlist": ["acme.com"]
},
"enabled": True,
"domain": "session.acme.com",
"idp_metadata_url": "https://acme.okta.com/app/abc/sso/saml/metadata",
"idp_metadata_xml": "<string>",
"mapping": {
"email": "email",
"first_name": "first_name",
"last_name": "last_name",
"groups": "groups",
"custom": {}
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Acme Okta',
behavior: {
allow_email_account_merge: true,
jit_provisioning: true,
enforce_login: false,
default_redirect_uri: 'https://app.acme.com/callback',
sync_profile_on_login: false,
email_domain_allowlist: ['acme.com']
},
enabled: true,
domain: 'session.acme.com',
idp_metadata_url: 'https://acme.okta.com/app/abc/sso/saml/metadata',
idp_metadata_xml: '<string>',
mapping: {
email: 'email',
first_name: 'first_name',
last_name: 'last_name',
groups: 'groups',
custom: {}
}
})
};
fetch('https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}', 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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}",
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' => 'Acme Okta',
'behavior' => [
'allow_email_account_merge' => true,
'jit_provisioning' => true,
'enforce_login' => false,
'default_redirect_uri' => 'https://app.acme.com/callback',
'sync_profile_on_login' => false,
'email_domain_allowlist' => [
'acme.com'
]
],
'enabled' => true,
'domain' => 'session.acme.com',
'idp_metadata_url' => 'https://acme.okta.com/app/abc/sso/saml/metadata',
'idp_metadata_xml' => '<string>',
'mapping' => [
'email' => 'email',
'first_name' => 'first_name',
'last_name' => 'last_name',
'groups' => 'groups',
'custom' => [
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}"
payload := strings.NewReader("{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}"
response = http.request(request)
puts response.read_body{
"connection": {
"id": "samlc_01jqebhswje1ka1z7ahr9rfsgt",
"provider_id": "okta",
"name": "Acme Okta",
"enabled": true,
"idp": {
"entity_id": "http://www.okta.com/exk1fcia6d6EMsf331d8",
"sso_url": "https://acme.okta.com/app/abc/exk.../sso/saml",
"slo_url": "<string>",
"certificates": [
"<string>"
]
},
"sp": {
"entity_id": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt",
"acs_url": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt/acs",
"slo_url": "<string>",
"metadata_url": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt/metadata",
"signing_certificate": "<string>"
},
"behavior": {
"allow_email_account_merge": true,
"jit_provisioning": true,
"enforce_login": false,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": false,
"email_domain_allowlist": [
"acme.com"
]
},
"mapping": {
"email": "email",
"given_name": "first_name",
"family_name": "last_name",
"groups": "groups",
"custom": {}
},
"created_at": "2025-03-15T10:30:00Z",
"updated_at": "2025-03-15T12:00:00Z"
}
}{
"code": "invalid_request",
"status": "bad_request",
"message": "The request body is invalid."
}{
"code": "app_not_found",
"status": "not_found",
"message": "The application was not found."
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
The id of the app the request refers to. An application's unique identifier.
"54e9ujn"
"fvua38g"
The SAML provider identifier.
"okta"
"google"
"jumpcloud"
Body
"Acme Okta"
Show child attributes
Show child attributes
true
App domain baked into the SP endpoints. Optional when the app has exactly one active domain; required for multi-domain apps.
"session.acme.com"
HTTPS URL to the IdP's metadata document. Provide exactly one IdP source.
"https://acme.okta.com/app/abc/sso/saml/metadata"
Raw or base64-encoded IdP metadata XML (for air-gapped IdPs). Provide exactly one IdP source.
Explicit IdP block. Provide this only when you are not using
idp_metadata_url or idp_metadata_xml. Certificates accept raw PEM
or base64-wrapped PEM.
Show child attributes
Show child attributes
Maps SAML assertion attributes to user profile fields. Defaults are applied per provider when omitted.
Show child attributes
Show child attributes
Response
Created
Show child attributes
Show child attributes
curl --request POST \
--url https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Acme Okta",
"behavior": {
"allow_email_account_merge": true,
"jit_provisioning": true,
"enforce_login": false,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": false,
"email_domain_allowlist": [
"acme.com"
]
},
"enabled": true,
"domain": "session.acme.com",
"idp_metadata_url": "https://acme.okta.com/app/abc/sso/saml/metadata",
"idp_metadata_xml": "<string>",
"mapping": {
"email": "email",
"first_name": "first_name",
"last_name": "last_name",
"groups": "groups",
"custom": {}
}
}
'import requests
url = "https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}"
payload = {
"name": "Acme Okta",
"behavior": {
"allow_email_account_merge": True,
"jit_provisioning": True,
"enforce_login": False,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": False,
"email_domain_allowlist": ["acme.com"]
},
"enabled": True,
"domain": "session.acme.com",
"idp_metadata_url": "https://acme.okta.com/app/abc/sso/saml/metadata",
"idp_metadata_xml": "<string>",
"mapping": {
"email": "email",
"first_name": "first_name",
"last_name": "last_name",
"groups": "groups",
"custom": {}
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'Acme Okta',
behavior: {
allow_email_account_merge: true,
jit_provisioning: true,
enforce_login: false,
default_redirect_uri: 'https://app.acme.com/callback',
sync_profile_on_login: false,
email_domain_allowlist: ['acme.com']
},
enabled: true,
domain: 'session.acme.com',
idp_metadata_url: 'https://acme.okta.com/app/abc/sso/saml/metadata',
idp_metadata_xml: '<string>',
mapping: {
email: 'email',
first_name: 'first_name',
last_name: 'last_name',
groups: 'groups',
custom: {}
}
})
};
fetch('https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}', 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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}",
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' => 'Acme Okta',
'behavior' => [
'allow_email_account_merge' => true,
'jit_provisioning' => true,
'enforce_login' => false,
'default_redirect_uri' => 'https://app.acme.com/callback',
'sync_profile_on_login' => false,
'email_domain_allowlist' => [
'acme.com'
]
],
'enabled' => true,
'domain' => 'session.acme.com',
'idp_metadata_url' => 'https://acme.okta.com/app/abc/sso/saml/metadata',
'idp_metadata_xml' => '<string>',
'mapping' => [
'email' => 'email',
'first_name' => 'first_name',
'last_name' => 'last_name',
'groups' => 'groups',
'custom' => [
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}"
payload := strings.NewReader("{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.prelude.dev/v2/session/apps/{appID}/config/login/saml/{providerID}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"Acme Okta\",\n \"behavior\": {\n \"allow_email_account_merge\": true,\n \"jit_provisioning\": true,\n \"enforce_login\": false,\n \"default_redirect_uri\": \"https://app.acme.com/callback\",\n \"sync_profile_on_login\": false,\n \"email_domain_allowlist\": [\n \"acme.com\"\n ]\n },\n \"enabled\": true,\n \"domain\": \"session.acme.com\",\n \"idp_metadata_url\": \"https://acme.okta.com/app/abc/sso/saml/metadata\",\n \"idp_metadata_xml\": \"<string>\",\n \"mapping\": {\n \"email\": \"email\",\n \"first_name\": \"first_name\",\n \"last_name\": \"last_name\",\n \"groups\": \"groups\",\n \"custom\": {}\n }\n}"
response = http.request(request)
puts response.read_body{
"connection": {
"id": "samlc_01jqebhswje1ka1z7ahr9rfsgt",
"provider_id": "okta",
"name": "Acme Okta",
"enabled": true,
"idp": {
"entity_id": "http://www.okta.com/exk1fcia6d6EMsf331d8",
"sso_url": "https://acme.okta.com/app/abc/exk.../sso/saml",
"slo_url": "<string>",
"certificates": [
"<string>"
]
},
"sp": {
"entity_id": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt",
"acs_url": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt/acs",
"slo_url": "<string>",
"metadata_url": "https://session.acme.com/v1/session/login/saml/okta/samlc_01jqebhswje1ka1z7ahr9rfsgt/metadata",
"signing_certificate": "<string>"
},
"behavior": {
"allow_email_account_merge": true,
"jit_provisioning": true,
"enforce_login": false,
"default_redirect_uri": "https://app.acme.com/callback",
"sync_profile_on_login": false,
"email_domain_allowlist": [
"acme.com"
]
},
"mapping": {
"email": "email",
"given_name": "first_name",
"family_name": "last_name",
"groups": "groups",
"custom": {}
},
"created_at": "2025-03-15T10:30:00Z",
"updated_at": "2025-03-15T12:00:00Z"
}
}{
"code": "invalid_request",
"status": "bad_request",
"message": "The request body is invalid."
}{
"code": "app_not_found",
"status": "not_found",
"message": "The application was not found."
}