Developers
One API behind the console, the CLI, and you.
JSON over HTTPS, deny-by-default permissions, and adsas ids only. API credentials for a workspace are issued by its owner; talk to us to set one up.
Basics
| Base URL | https://api.adsas.id/v1 |
|---|---|
| Format | JSON request and response bodies. Unknown fields are refused. |
| Pagination | List routes take limit (1 to 20) and cursor, and return next_cursor, empty on the last page. A cursor works only for the list and organization that produced it. |
| Retries | Journaled mutations (deactivate, add member, rotate secret) accept one Idempotency-Key header. |
| Tracing | Every response carries X-Request-Id. Send your own, 8 to 64 characters of [A-Za-z0-9_-], and it is kept and copied onto audit rows. |
| Callers | Server to server. The API rejects browser-origin and cross-site requests, so do not call it from front-end code. |
Authentication
Send Authorization: Bearer <token> on every route except GET /v1/health. Two kinds of bearer work:
- Machine credentials. An owner issues one with a role, a lifetime from 60 seconds to 7 days, and optional permission scopes. The token is shown once and only its SHA-256 hash is stored. A
servicerole has no permissions of its own and must carry an explicit scope. - OIDC access tokens. An access token from your workspace's issuer, whose issuer and subject an owner has bound to a principal. ID tokens are refused as bearers.
curl -sS https://api.adsas.id/v1/orgs/$ORG/credentials \
-H "Authorization: Bearer $OWNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"service","ttl_seconds":3600,
"scopes":["user:list","group:list"]}'
# 201: id, token, expires_at, role. The token is not shown again.
GET /v1/me returns the caller's principal id, kind, role, organization, products, and scope, and never echoes the bearer.
OIDC applications
Creating an OIDC application returns a client_id, a one-time client_secret, and the application's issuer. Issuers are per application on the public sign-in host:
| Issuer | https://id.adsas.id/application/o/<app-slug>/ |
|---|---|
| Discovery | https://id.adsas.id/application/o/<app-slug>/.well-known/openid-configuration |
The trailing slash is part of the issuer. Compare it exactly.
curl -sS https://api.adsas.id/v1/orgs/$ORG/apps \
-H "Authorization: Bearer $ADSAS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Wiki","slug":"wiki","mfa":"required",
"redirect_uris":["https://wiki.example.com/callback"]}'
Rotate a secret with POST /v1/orgs/{id}/apps/{appID}/rotate-secret. Replaying the same Idempotency-Key answers secret_available: false rather than revealing the secret twice.
SAML and the catalog
Send "protocol":"saml" with a saml object (acs_url, audience, and optionally sls_url, name_id_format, attributes, or sp_metadata), or create from a catalog entry with catalog_id and inputs. The response carries the IdP entity id, the SSO URL, and the IdP metadata document; there is no client secret.
| IdP entity id | https://id.adsas.id/application/saml/<app-slug>/metadata/ |
|---|---|
| SSO URL | https://id.adsas.id/application/saml/<app-slug>/ |
| Catalog | GET /v1/catalog, GET /v1/catalog/{id} |
Each SAML application gets its own signing keypair. Configure your service provider to pin both the IdP entity id and the certificate from the metadata.
SCIM 2.0
Inbound: your provisioner pushes to us
Create a source with POST /v1/orgs/{id}/scim-sources. Its token is returned once, stored as a hash, and revocable. The source token authenticates only that source's SCIM routes.
| SCIM base | https://api.adsas.id/scim/v2/<source-id>/ |
|---|---|
| Resources | Users, Groups, Schemas, ResourceTypes, ServiceProviderConfig |
| Methods | GET, POST, PUT, PATCH, DELETE on Users and Groups |
externalId is the link. Email changes never relink a user, and privileged attributes are refused.
Outbound: we provision into your apps
Attach a connector to an application with POST /v1/orgs/{id}/apps/{appID}/scim, assign groups, and sync. Syncs run as durable jobs you can watch at GET /v1/orgs/{id}/jobs. The destination's bearer token stays in the secret vault and is referenced, not pasted.
Audit API
| Route | Purpose |
|---|---|
GET /v1/orgs/{id}/audit | Events oldest first, with prev_hash and hash |
POST /v1/orgs/{id}/audit/verify | Recompute the chain; compare a held checkpoint |
GET /v1/orgs/{id}/audit/export | NDJSON, up to 5000 rows a page, ending in a checkpoint |
PUT /v1/orgs/{id}/audit/retention | 0 to keep forever, or 30 to 3650 days |
PUT /v1/orgs/{id}/audit/sink | Signed HTTPS webhook delivery |
Reading needs audit:read (owner, admin, auditor). Changing retention or the sink needs audit:configure (owner).
adsasctl
adsasctl is a client of the API and nothing more. The bearer comes from an owner-only file or the environment, never a flag. It refuses plain HTTP except on loopback, does not follow redirects, prints one-time secrets once, and stores nothing.
$ export ADSASCTL_SERVER=https://api.adsas.id
$ export ADSASCTL_TOKEN_FILE=~/.config/adsas/token # mode 0600
$ adsasctl me
$ adsasctl user create <org> -email ada@example.com -name Ada
$ adsasctl catalog list
$ adsasctl audit export <org> -out acme-audit.ndjson
$ adsasctl audit verify-file acme-audit.ndjson
List commands take -limit, -cursor, and -all; -json prints the raw response; adsasctl help lists every command.
Errors
Errors are JSON with a stable error code.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Bad, expired, or revoked bearer. Deliberately identical for every cause. |
| 403 | forbidden | Authenticated, but the role or scope lacks the permission. |
| 403 | product_required | The organization does not hold the product for this route. |
| 403 | context_denied, step_up_required | Refused by the organization's contextual access policy. |
| 404 | not_found | Missing, or belongs to another organization. |
| 429 | rate_limited, quota_exceeded, queue_full | Over a request budget, an organization cap, or the job queue. |
| 503 | audit_unavailable | The audit row could not be written, so the change did not commit. |