Authentication
PropSocket authenticates every API request with a bearer token. There are no OAuth dances, no request signing on the read path, and no per-request nonce — just a key in a header. The key determines which Organization's data you see and which mode you're in — live ortest.
The header
Send your key in the Authorization header on every request, prefixed withBearer :
curl https://api.propsocket.io/v1/properties \
-H "Authorization: Bearer ps_test_YOUR_TEST_KEY"import os, requests
resp = requests.get(
"https://api.propsocket.io/v1/properties",
headers={"Authorization": f"Bearer {os.environ['PROPSOCKET_API_KEY']}"},
)const resp = await fetch("https://api.propsocket.io/v1/properties", {
headers: { Authorization: `Bearer ${process.env.PROPSOCKET_API_KEY}` },
});package main
import (
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.propsocket.io/v1/properties", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PROPSOCKET_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}require "net/http"
require "uri"
uri = URI("https://api.propsocket.io/v1/properties")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('PROPSOCKET_API_KEY')}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code<?php
$ch = curl_init("https://api.propsocket.io/v1/properties");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("PROPSOCKET_API_KEY")],
]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://api.propsocket.io/v1/properties"))
.header("Authorization", "Bearer " + System.getenv("PROPSOCKET_API_KEY"))
.build();
HttpResponse<String> resp = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("PROPSOCKET_API_KEY"));
var resp = await client.GetAsync("https://api.propsocket.io/v1/properties");
Console.WriteLine((int)resp.StatusCode);Miss the Bearer prefix, send an empty header, or pass a malformed token and you'll get a 401. There is no query-parameter or cookie fallback — the header is the only way in.
Key formats
Keys carry a mode prefix so you can tell at a glance whether writes will commit. Both prefixes authenticate against the same host, https://api.propsocket.io/v1:
| Prefix | Mode | Behavior |
|---|---|---|
ps_test_ | Test mode | Real reads; writes validate, return a mocked success, and are never delivered. |
ps_live_ | Live | Real reads; writes commit to your integrations. |
Both key types read the same real connected data; the prefix only changes whether writes take effect. See Isolated Test Mode for the full behavior.
The full key is shown once, at creation, in the dashboard. PropSocket stores only the prefix and a hash — we can't recover a lost key, only issue a new one. Treat the value like a password: keep it in a secret manager, never commit it. A grep forps_live_ or ps_test_ across your repos is a cheap pre-commit guard.
Keys are per-Organization, not per-user
A key authenticates the Organization that owns it — not an individual person. Every request made with a key is scoped to that Organization's data; you cannot read another Organization's records with it, full stop. Two consequences worth internalizing:
- All keys for an Organization share one rate-limit pool. Issuing a second key doesn't double your throughput — it splits the same 150 requests/minute. Seerate limits.
- Share carefully. Because keys aren't tied to a user, a leaked key is a leaked Organization. Rotate immediately if one escapes (below), and prefer one key per consuming service so you can rotate them independently.
Rotation
Rotate a key from Settings → API keys in the dashboard: create the replacement, deploy it to the consumer, then revoke the old one. Both keys are valid during the overlap, so you cut over with zero downtime — there's no forced grace window on the API side, the overlap is however long you leave the old key active before revoking it.
Revocation takes effect immediately. The next request with a revoked key returns 401. Rotate on a schedule, and rotate immediately on any suspected leak.
Need the click-by-click dashboard walkthrough? See the help-center guide ongenerating and rotating API keys.
401 versus 403
These two get conflated constantly. In PropSocket they mean distinct things:
- 401 Unauthorized — we don't know who you are. The key is missing, malformed, revoked, or expired. Fix the credential.
- 403 Forbidden — we know who you are, but you're explicitly denied. This is reserved for permission-level denials, not data ownership.
One subtlety to plan for: if you request a resource that belongs to adifferent Organization, you get 404, not 403. PropSocket filters other tenants' records out before the lookup happens, so to your key they simply don't exist. This is a deliberate information-leak defense — a 403 would confirm the ID is real. Theerrors reference documents every status code with example bodies.
A 401 looks like this — note the request_id for support tickets:
{
"error": {
"code": "unauthorized",
"message": "Authentication credentials were not provided.",
"status": 401,
"request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}
}Next
Wire your first authenticated call in the quickstart, then readthe error model so you can tell a credential problem from a rate-limit or validation problem at a glance.