curl --request POST \
--url https://app.testorim.com/api/projects \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"baseUrl": "https://example.com"
}
'{
"project": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"baseUrl": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"lastUsedAt": "2023-11-07T05:31:56Z",
"archivedAt": "2023-11-07T05:31:56Z",
"storageStateJson": {},
"storageStateCapturedAt": "2023-11-07T05:31:56Z",
"timezone": "<string>",
"locale": "<string>",
"geolocationJson": {
"latitude": 0,
"longitude": 0,
"accuracy": 5000
},
"appContextJson": {
"appType": "<string>",
"summary": "<string>",
"authModel": "<string>",
"keyFlows": [
"<string>"
],
"domainTerms": [
"<string>"
],
"testNotes": "<string>"
},
"appMapJson": {}
},
"created": false
}{
"project": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"baseUrl": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"lastUsedAt": "2023-11-07T05:31:56Z",
"archivedAt": "2023-11-07T05:31:56Z",
"storageStateJson": {},
"storageStateCapturedAt": "2023-11-07T05:31:56Z",
"timezone": "<string>",
"locale": "<string>",
"geolocationJson": {
"latitude": 0,
"longitude": 0,
"accuracy": 5000
},
"appContextJson": {
"appType": "<string>",
"summary": "<string>",
"authModel": "<string>",
"keyFlows": [
"<string>"
],
"domainTerms": [
"<string>"
],
"testNotes": "<string>"
},
"appMapJson": {}
},
"created": true
}{
"error": "Invalid request body",
"issues": [
{
"code": "<string>",
"path": [
"<string>"
],
"message": "<string>"
}
]
}{
"error": "Invalid or revoked API key"
}{
"error": "Viewers have read-only access to this workspace",
"code": "read_only_role"
}{
"error": "<string>",
"retryAfter": 123
}Create (or re-use) a project
Creates a project for the key’s organization.
This endpoint is idempotent-by-URL, and the status code tells you
which happened. The base URL is normalised
(https:// added when the scheme is missing, trailing slash on the
path removed) and then matched against existing non-archived
projects in the org:
- no match → the project is inserted and the response is
201withcreated: true; - match → the existing project’s
lastUsedAtis bumped and the response is200withcreated: false.
The normalised URL is checked by the SSRF guard before anything is
persisted, so a private or internal host is refused with 400 and
never stored.
When name is omitted it is derived from the hostname with a leading
www. stripped.
curl --request POST \
--url https://app.testorim.com/api/projects \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"baseUrl": "https://example.com"
}
'{
"project": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"baseUrl": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"lastUsedAt": "2023-11-07T05:31:56Z",
"archivedAt": "2023-11-07T05:31:56Z",
"storageStateJson": {},
"storageStateCapturedAt": "2023-11-07T05:31:56Z",
"timezone": "<string>",
"locale": "<string>",
"geolocationJson": {
"latitude": 0,
"longitude": 0,
"accuracy": 5000
},
"appContextJson": {
"appType": "<string>",
"summary": "<string>",
"authModel": "<string>",
"keyFlows": [
"<string>"
],
"domainTerms": [
"<string>"
],
"testNotes": "<string>"
},
"appMapJson": {}
},
"created": false
}{
"project": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"baseUrl": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"lastUsedAt": "2023-11-07T05:31:56Z",
"archivedAt": "2023-11-07T05:31:56Z",
"storageStateJson": {},
"storageStateCapturedAt": "2023-11-07T05:31:56Z",
"timezone": "<string>",
"locale": "<string>",
"geolocationJson": {
"latitude": 0,
"longitude": 0,
"accuracy": 5000
},
"appContextJson": {
"appType": "<string>",
"summary": "<string>",
"authModel": "<string>",
"keyFlows": [
"<string>"
],
"domainTerms": [
"<string>"
],
"testNotes": "<string>"
},
"appMapJson": {}
},
"created": true
}{
"error": "Invalid request body",
"issues": [
{
"code": "<string>",
"path": [
"<string>"
],
"message": "<string>"
}
]
}{
"error": "Invalid or revoked API key"
}{
"error": "Viewers have read-only access to this workspace",
"code": "read_only_role"
}{
"error": "<string>",
"retryAfter": 123
}Authorizations
Send Authorization: Bearer tst_live_….
Format (services/api-keys.ts): the literal prefix tst_live_
followed by 24 random bytes rendered as 32 base64url characters.
Only the SHA-256 hash is stored server-side. The shape check that
routes a token down the API-key path rather than the Clerk JWT path
requires the tst_live_ prefix and a total length of at least 25
characters.
Keys are minted in the dashboard at /settings/team. The same header
also accepts a Clerk session JWT, which is how the web app
authenticates, but the JWT path is out of scope for this document.
Body
The site under test. The scheme may be omitted, in which case https:// is prepended, and a trailing slash on the path is removed before storage and before the duplicate check. Private, loopback, link-local and cloud-metadata hosts are rejected.
3 - 500Display name. Defaults to the hostname with a leading www. stripped.
1 - 100
