adsas.idAfter Dark Systems

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 URLhttps://api.adsas.id/v1
FormatJSON request and response bodies. Unknown fields are refused.
PaginationList 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.
RetriesJournaled mutations (deactivate, add member, rotate secret) accept one Idempotency-Key header.
TracingEvery 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.
CallersServer 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 service role 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.
Issue a scoped service credential
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:

Issuerhttps://id.adsas.id/application/o/<app-slug>/
Discoveryhttps://id.adsas.id/application/o/<app-slug>/.well-known/openid-configuration

The trailing slash is part of the issuer. Compare it exactly.

Create an OIDC app
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 idhttps://id.adsas.id/application/saml/<app-slug>/metadata/
SSO URLhttps://id.adsas.id/application/saml/<app-slug>/
CatalogGET /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 basehttps://api.adsas.id/scim/v2/<source-id>/
ResourcesUsers, Groups, Schemas, ResourceTypes, ServiceProviderConfig
MethodsGET, 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

RoutePurpose
GET /v1/orgs/{id}/auditEvents oldest first, with prev_hash and hash
POST /v1/orgs/{id}/audit/verifyRecompute the chain; compare a held checkpoint
GET /v1/orgs/{id}/audit/exportNDJSON, up to 5000 rows a page, ending in a checkpoint
PUT /v1/orgs/{id}/audit/retention0 to keep forever, or 30 to 3650 days
PUT /v1/orgs/{id}/audit/sinkSigned 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.

shell
$ 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.

StatusCodeMeaning
401unauthorizedBad, expired, or revoked bearer. Deliberately identical for every cause.
403forbiddenAuthenticated, but the role or scope lacks the permission.
403product_requiredThe organization does not hold the product for this route.
403context_denied, step_up_requiredRefused by the organization's contextual access policy.
404not_foundMissing, or belongs to another organization.
429rate_limited, quota_exceeded, queue_fullOver a request budget, an organization cap, or the job queue.
503audit_unavailableThe audit row could not be written, so the change did not commit.