Skip to content

Authentication

Every request carries one credential in the Authorization header:

Authorization: Bearer <API key or access token>

Credentials are read only from that header. Query parameters such as ?api_key= and cookies are ignored, so a key never ends up in a URL or log.

A credential belongs to exactly one workspace, and every request acts on that workspace. There is no workspace parameter.

An API key is a long-lived secret for scripts, CI pipelines and integrations.

devsk_exampleexample00_EXAMPLE-not-a-real-key-EXAMPLE-000000000
└─┬─┘ └──────┬───────┘ └──────────────────┬──────────────────┘
prefix key id (public) secret
  • Create keys in the dashboard under Integrations → API keys. Workspace owners and members whose role allows managing API keys can do this.
  • The full key is shown once. DevSecure stores only a keyed hash and cannot show it again.
  • Scopes limit what a key can do. Pick the fewest you need; see Scopes. You can only grant scopes your own role has.
  • Expiry. Every key expires, after at most 730 days. Without a date, the maximum applies.
  • Rotate a key to get a new secret with the same id, name and scopes. The old secret stops working immediately.
  • Revoke a key to stop it at once.

A key follows the person who created it. If that person leaves the workspace, is disabled, or is limited to particular repositories or teams, the key stops working. If their role loses a permission, the key loses the matching scopes on its next request. GET /v1/credential always shows the scopes a key has right now.

Use an OAuth client when a service should exchange its credentials for short-lived tokens. Create one in the dashboard like an API key, choosing the OAuth client type. You get a client id (devsc_…) and a client secret (devss_…); the secret is shown once.

Request an access token with the client credentials grant (RFC 6749 §4.4):

Terminal window
curl -s https://api.devsecure.io/oauth/token \
-u "$DEVSECURE_CLIENT_ID:$DEVSECURE_CLIENT_SECRET" \
-d grant_type=client_credentials \
-d scope="issues:read scans:read"
{
"access_token": "devsat_exampleexample00_EXAMPLE-not-a-real-token-EXAMPLE-0000000",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "issues:read scans:read"
}
  • Send the body as application/x-www-form-urlencoded.
  • Authenticate with HTTP Basic, or with client_id and client_secret in the body. Use one method, not both.
  • scope is optional. Without it the token gets all of the client’s scopes; with it, the requested subset.
  • Tokens last one hour (expires_in) and never outlive the client. Request a new one when it expires; there are no refresh tokens.
  • Revoking the client revokes its tokens.

Token errors follow RFC 6749, not the API’s usual error format:

Status error When
400 invalid_request Missing or repeated parameters, wrong content type, two authentication methods
400 unsupported_grant_type Anything but client_credentials
400 invalid_scope A requested scope is unknown or not granted to the client
400 unauthorized_client The workspace’s plan does not include API access
401 invalid_client Wrong, unknown, expired or revoked client credentials
429 rate_limited Too many token requests; see Retry-After

Every authentication failure returns the same response, whatever the cause:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="devsecure"
{
"error": {
"code": "invalid_credentials",
"message": "Authentication failed.",
"request_id": "req_3f0c2a9d5b6e4f7a8c1d2e3f4a5b6c7d"
}
}

Repeated failures from one address are throttled with 429 rate_limited.

  • Keep keys in a secret manager or your CI’s secret store, never in code.
  • Give each integration its own key with only the scopes it needs.
  • Set an expiry and rotate keys on a schedule.
  • Revoke a key as soon as you suspect it leaked. The workspace audit log (GET /v1/audit-log) shows what each key did.