Endpoint CORS Configuration
Configure Cross-Origin Resource Sharing (CORS) on a per-endpoint basis to enable direct browser-to-API access for web applications.
Overview
By default, browsers block JavaScript from making requests to a different origin than the page was loaded from. CORS (Cross-Origin Resource Sharing) is a mechanism that allows your inference endpoints to accept requests from web applications hosted on different domains.
Xerotier.ai supports per-endpoint CORS configuration, meaning each inference endpoint can have its own CORS policy. This is useful when:
- Your web application calls inference endpoints directly from the browser
- You have multiple frontend applications on different domains that need endpoint access
- You want to restrict which origins can access specific endpoints
CORS configuration is applied on a per-endpoint basis and enforced on every request to the endpoint.
Configuration
CORS settings are configured when creating or updating an endpoint. The following fields control CORS behavior:
| Field | Type | Default | Description |
|---|---|---|---|
| allowedOrigins | string[] | [] |
List of origins permitted to make requests. Use * for any origin. |
| allowedMethods | string[] | ["GET", "POST", "OPTIONS"] |
HTTP methods the client is allowed to use |
| allowedHeaders | string[] | ["Content-Type", "Authorization"] |
Request headers the client is allowed to send |
| maxAge | integer | 86400 |
How long (in seconds) browsers should cache the preflight response |
| allowCredentials | boolean | false |
Whether the browser should send credentials (cookies, auth headers) with requests |
How It Works
Preflight Requests
When a browser makes a cross-origin request that is not "simple" (e.g., uses custom headers or non-standard methods), it first sends an OPTIONS preflight request. Xerotier handles this automatically:
- Browser sends
OPTIONSrequest withOriginandAccess-Control-Request-Methodheaders - The origin is checked against the endpoint's
allowedOriginslist - If the origin is allowed, appropriate CORS headers are returned
- Browser proceeds with the actual request
Simple Requests
For simple requests (GET/POST with standard headers), the browser sends the request directly. The Access-Control-Allow-Origin header is added to the response if the origin is allowed.
Origin Validation
The Origin header is validated against the configured allowedOrigins list:
- If
allowedOriginscontains*, all origins are allowed - Otherwise, the origin must exactly match one of the listed origins
- If the origin is not allowed, no CORS headers are added and the browser blocks the response
Examples
Single Origin
Allow requests only from your production web application:
{
"cors": {
"allowedOrigins": ["https://app.example.com"],
"allowedMethods": ["GET", "POST", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAge": 86400,
"allowCredentials": false
}
}
Multiple Origins
Allow requests from both production and staging environments:
{
"cors": {
"allowedOrigins": [
"https://app.example.com",
"https://staging.example.com",
"http://localhost:3000"
],
"allowedMethods": ["GET", "POST", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAge": 3600,
"allowCredentials": false
}
}
Wildcard Origin
Allow requests from any origin (use with caution):
{
"cors": {
"allowedOrigins": ["*"],
"allowedMethods": ["GET", "POST", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAge": 86400,
"allowCredentials": false
}
}
Setting CORS via API
Configure CORS on an endpoint using the endpoint update API:
curl -X PATCH https://api.xerotier.ai/proj_ABC123/my-endpoint/v1/endpoints/my-endpoint \
-H "Authorization: Bearer xero_my-project_abc123" \
-H "Content-Type: application/json" \
-d '{
"cors": {
"allowedOrigins": ["https://app.example.com"],
"allowedMethods": ["GET", "POST", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAge": 86400,
"allowCredentials": false
}
}'
import requests
headers = {
"Authorization": "Bearer xero_my-project_abc123",
"Content-Type": "application/json"
}
response = requests.patch(
"https://api.xerotier.ai/proj_ABC123/my-endpoint/v1/endpoints/my-endpoint",
headers=headers,
json={
"cors": {
"allowedOrigins": ["https://app.example.com"],
"allowedMethods": ["GET", "POST", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"],
"maxAge": 86400,
"allowCredentials": False
}
}
)
print(response.json())
JavaScript Fetch Example
Once CORS is configured on your endpoint, you can make direct browser requests:
const response = await fetch(
"https://api.xerotier.ai/v1/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer xero_my-project_abc123"
},
body: JSON.stringify({
model: "my-model",
messages: [
{ role: "user", content: "Hello!" }
]
})
}
);
const data = await response.json();
console.log(data.choices[0].message.content);
Security Considerations
- Avoid wildcard origins in production: Using
*forallowedOriginsmeans any website can make requests to your endpoint. This is acceptable for public APIs but not recommended for endpoints with sensitive data. - Wildcard and credentials are mutually exclusive: Browsers will reject responses that set
Access-Control-Allow-Origin: *withAccess-Control-Allow-Credentials: true. If you need credentials, list specific origins. - API keys in browser code: When calling endpoints from browser JavaScript, your API key is visible to anyone who inspects the page source. Consider using scoped API keys with minimal permissions and endpoint restrictions.
- Restrict allowed headers: Only allow the headers your application actually needs. The defaults (
Content-TypeandAuthorization) are sufficient for most inference use cases. - Use short maxAge during development: A shorter
maxAge(e.g., 300 seconds) during development means CORS policy changes take effect faster in browsers.
Important: CORS is a browser-enforced security mechanism. Server-to-server requests (curl, backend services, SDKs) are not affected by CORS settings and will always work regardless of configuration.
Troubleshooting
Common CORS Errors
| Error | Cause | Fix |
|---|---|---|
| "No 'Access-Control-Allow-Origin' header" | Origin not in allowedOrigins list | Add your origin to the endpoint's allowedOrigins |
| "Method not allowed by CORS" | HTTP method not in allowedMethods | Add the method (e.g., POST) to allowedMethods |
| "Request header not allowed" | Custom header not in allowedHeaders | Add the header name to allowedHeaders |
| "Credentials flag is true but origin is wildcard" | allowCredentials=true with allowedOrigins=["*"] | Use specific origins instead of wildcard when credentials are needed |
Diagnostic Steps
- Open browser DevTools and check the Network tab for the failed request
- Look for the OPTIONS preflight request -- check its response headers
- Verify the
Originheader in your request matches an entry inallowedOriginsexactly (protocol, domain, and port must match) - Test with curl to confirm the endpoint works without CORS:
curl -H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-X OPTIONS \
"https://api.xerotier.ai/v1/chat/completions" -v
- Check the response for
Access-Control-Allow-OriginandAccess-Control-Allow-Methodsheaders
Simple vs Preflighted Requests
Browsers classify cross-origin requests into two categories:
- Simple requests: GET or POST with standard Content-Types (text/plain, multipart/form-data, application/x-www-form-urlencoded) and no custom headers. These skip the preflight step.
- Preflighted requests: Requests with custom headers (like
Authorization), non-standard Content-Types (likeapplication/json), or non-simple methods. These trigger an OPTIONS preflight first.
Most inference API calls use application/json and Authorization headers, so they are preflighted. Make sure your CORS configuration includes OPTIONS in allowedMethods.