Authenticate with an administrator key
Use an ML-DSA-65 administrator key when GET /status lists Core 0.3 in protocols. First login creates a private workspace; later logins return it. An Ed25519 key signs in only to an existing Core 0.1 workspace, which is read-only. No password, OAuth or account recovery.
- Request a challenge with the administrator public_key (Ed25519 32 bytes; ML-DSA-65 1952 bytes; unpadded base64url). The response has challenge_id, issuer, message and expires_at (five minutes).
- Rebuild the message below from your configured issuer, key fingerprint, returned UUID v4 challenge_id and canonical UTC expires_at, without a trailing newline. Accept only an expiry within seven minutes.
- Sign the exact UTF-8 message locally (Ed25519 for Core 0.1, ML-DSA-65 for Core 0.3). POST challenge_id, message and unpadded-base64url signature to /auth/verify. Challenges are single-use; never send the private key.
- Keep the administrator access_token in memory; send it only to owner routes as Authorization: Bearer <access_token>. It lasts 900 seconds; then sign in with a new challenge. Do not put administrator sessions or signing keys in CI.
iff-apostille/login/0.1
issuer:https://ifandonlyif.io/apostille
key_id:<administrator_key_id>
challenge:<challenge_id>
expires_at:<expires_at>
purpose:register_or_login
key_id is sha256: plus the lowercase SHA-256 hex of the raw public key. This message is not an artifact signature.
CI read-only credentials
API preview, with per-deployment availability. Before use, require enabled: true plus supply_chain_releases and supply_chain_ci_credentials in /status features, and confirm endpoint availability on the target service.
Use an opaque CI bearer for one exact workspace, product and policy label. It permits only release reads, current-policy reads and metadata evaluations in that scope. All other owner routes reject it. A current policy version and independent issuer/producer pins are still required.
Create with a Core 0.3 administrator session and a signed create_release_credential operation. Expiry is explicit, at most 90 days (30 recommended); scopes have no wildcards and cannot change. The token is returned once; retries return metadata only. The owner session can list or revoke credentials. Expiry/revocation is checked on every request; an in-flight request may finish.
Endpoint reference
Paths are relative to the REST base URL; GET also supports HEAD. Owner routes use the administrator session, with scoped CI access only on the three read/evaluation routes described below. Public routes need no token; public releases use public_id.
Field lists describe response shapes, not literal JSON. Version and size fields are decimal strings.
GET /status
Public
Read the issuer, the protocols it issues (protocols), the protocols it verifies (verification_protocols), availability and limits. The legacy protocol field is the Core 0.1 identifier. Planned provider names are not enabled identity checks.
- Request fields
- No request body
- Response fields / type
protocol (legacy: the Core 0.1 identifier), protocols[] (full identifiers the service issues; absent on a service that predates it), verification_protocols[] (full identifiers it verifies), issuer, enabled, limits {max_agents, max_daily_certificates}, features[] (supply_chain_releases advertises release API support; supply_chain_ci_credentials advertises scoped CI credentials; check enabled separately; ml_dsa_65_keys when Core 0.3 is enabled; core_0_1_issuance_retired), planned[]- Success HTTP status
200
GET /keys
Public
Read the issuer public key and fingerprint; ?protocol=<full identifier> selects one Core version’s directory. A directory is a lookup, not a trust root.
- Request fields
protocol (query, optional): full identifier, URL-encoded- Response fields / type
protocol, issuer, keys[] {key_id, public_key, algorithm}, trust- Success HTTP status
200
POST /auth/challenges
Public
Create a short-lived login challenge. A sixth unconsumed challenge for a key invalidates the oldest.
- Request fields
public_key: string (Ed25519, 43 characters; or ML-DSA-65, 2603 characters)- Response fields / type
challenge_id, message, expires_at, issuer- Success HTTP status
201
POST /auth/verify
Public
Consume a challenge and receive a 15-minute session. Expired, altered or replayed proofs are rejected.
- Request fields
challenge_id: string, message: string, signature: string (Ed25519, or ML-DSA-65 with 4412 characters)- Response fields / type
access_token, token_type, expires_in, workspace: Workspace- Success HTTP status
200
GET /me
Owner
Read your workspace, agent records and recent certificates. Not paginated in this alpha.
- Request fields
- No request body
- Response fields / type
workspace: Workspace, agents: Agent[], certificates: Certificate[]- Success HTTP status
200
PUT /workspace
Owner
Update the workspace name and public-profile visibility. Publishes no certificate; the profile is self-declared.
- Request fields
name: string, is_public: boolean- Response fields / type
Workspace- Success HTTP status
200
POST /agents
Owner
Register a valid delegation and acceptance. An identical authorized replay returns the original record. Core 0.3 only; a Core 0.1 request or workspace returns 410 core_0_1_retired.
- Request fields
name: string, delegation: Envelope, acceptance: Envelope- Response fields / type
Agent- Success HTTP status
201 / 200
POST /agents/{id}/revoke
Owner
Irreversibly revoke an agent for new issuance. Existing signed bytes are unchanged; offline verification cannot see revocation.
- Request fields
- No request body
- Response fields / type
Agent (revoked_at)- Success HTTP status
200
POST /submissions
Owner
Issue an origin certificate after validating the agent, statement and separate publication grant. Identical retries still need valid authorization. Core 0.3 only; a Core 0.1 request or workspace returns 410 core_0_1_retired.
- Request fields
statement: Envelope, grant: Envelope- Response fields / type
Certificate (id, workspace_id, agent_id, public_id, idempotency_key, body_hash, bundle, is_public, created_at)- Success HTTP status
201 / 200
GET /certificates/{id}/bundle
Owner
Download the signed Bundle itself. Other workspaces cannot fetch it.
- Request fields
- No request body
- Response fields / type
Bundle- Success HTTP status
200
POST /certificates/{id}/hide
Owner
Stop public serving of a certificate and any release depending on it. Cannot restore or erase downloaded copies; separate from release withdrawal.
- Request fields
- No request body
- Response fields / type
id, is_public: false- Success HTTP status
200
GET /public/certificates/{public_id}
Public
Read an explicitly public certificate by public_id (404 if private or hidden). Never serves the original file.
- Request fields
- No request body
- Response fields / type
public_id, bundle: Bundle, created_at- Success HTTP status
200
GET /public/organizations/{public_id}
Public
Read an explicitly public workspace profile by public_id. Marked self_declared; no keys or private fields.
- Request fields
- No request body
- Response fields / type
profile {public_id, name, created_at}, organization_binding: self_declared- Success HTTP status
200
GET /erc8004/config
Public
Read whether ERC-8004 binding is enabled, which registries are supported and which binding profiles are issued (profiles; profile is the legacy 0.1 identifier). Public.
- Request fields
- No request body
- Response fields / type
profile (legacy: the 0.1 identifier), profiles[] (binding profiles the service issues), enabled, networks[] {chain_id, registry_address}, max_binding_age_seconds, wallet_support- Success HTTP status
200
POST /agents/{id}/erc8004
Owner
Submit an administrator-signed binding request plus the EOA owner’s EIP-191 signature. Returns a private, immutable issuer_checked snapshot; no chain transaction is sent. Binding 0.3 for Core 0.3 workspaces; binding 0.1 returns 410 core_0_1_retired.
- Request fields
request: Envelope, owner_signature: string (0x + 130 lowercase hex characters)- Response fields / type
id, agent_id, document, created_at, expires_at- Success HTTP status
201 / 200
GET /agents/{id}/erc8004
Owner
Retrieve your latest private binding snapshot (404 for other workspaces). It records issuer-observed ownership at one finalized block, not current ownership or payment authority.
- Request fields
- No request body
- Response fields / type
id, agent_id, document, created_at, expires_at- Success HTTP status
200
POST /releases
Owner
API preview: needs enabled: true and supply_chain_releases in /status features. Register the canonical release manifest covered by a certificate, with a separate administrator-signed authorization. No binaries or source are uploaded. A Core 0.1 workspace or certificate returns 410 core_0_1_retired.
- Request fields
certificate_id: string, manifest_base64: string, visibility: private | public, authorization: {payload_base64, statement: Envelope}- Response fields / type
Release- Success HTTP status
201 / 200
GET /releases/{id}
Owner
Retrieve your release manifest, certificate bundle and current hosted status. Accepts the owner session or a CI credential for the same workspace and exact product. A valid signature does not grant current deployment permission.
- Request fields
- No request body
- Response fields / type
release: Release, bundle: Bundle, current_status: {state, withdrawn_at, reason, agent_state, as_of, basis, offline_proof: false, issuer_key_revocation: unknown}- Success HTTP status
200
GET /public/releases/{id}
Public
Retrieve public release evidence by public_id (404 if private or hidden). The status is a hosted observation, not an offline revocation proof.
- Request fields
id: release public_id- Response fields / type
public_id, product, version, manifest_base64, manifest_sha256, created_at, bundle: Bundle, current_status- Success HTTP status
200
POST /releases/{id}/status-events
Owner
Irreversibly withdraw a release with a fresh administrator-signed withdraw_release operation; release_id must match the path. A login signature cannot authorize it.
- Request fields
release_id: string (matches path), action: withdraw, reason: security_incident | superseded | maintainer_request, authorization: {payload_base64, statement: Envelope}- Response fields / type
event: ReleaseStatusEvent, state: withdrawn- Success HTTP status
201 / 200
POST /release-policies
Owner
Store an immutable policy version, starting at "1" and increasing by one. Pin the expected issuer, issuer and producer keys, product and Core protocols independently; an API key directory is not trust.
- Request fields
policy: Policy, authorization: {payload_base64, statement: Envelope}- Response fields / type
id, version: decimal string, policy: Policy, policy_sha256, authorization, created_at- Success HTTP status
201 / 200
GET /release-policies/{id}
Owner
Retrieve the current policy version. Accepts the owner session or a CI credential for this exact policy label and product in the same workspace. Earlier versions cannot be selected to bypass it.
- Request fields
- No request body
- Response fields / type
Current id, version: decimal string, policy: Policy, policy_sha256, authorization, created_at- Success HTTP status
200
POST /release-policy/evaluations
Owner
Evaluate metadata against the current policy, release, withdrawal and agent state. Accepts the owner session or a CI credential scoped to the same product and policy. allowed_by_policy does not inspect bytes (artifact_bytes_checked is false); valid_until is advisory, at most 30 seconds and no later than credential expiry. Verify and use the same actual file locally.
- Request fields
release_id: string, policy_id: string, policy_version: decimal string, artifact_name: string, artifact_sha256: string, artifact_size: decimal string- Response fields / type
decision: allowed_by_policy | blocked | unknown, reasons[], release_id, policy_id, policy_version, policy_sha256 (when found), evaluated_at, valid_until, current_status, artifact_name, artifact_sha256, artifact_size, artifact_bytes_checked: false- Success HTTP status
200
POST /release-credentials
Owner
Owner session plus Core 0.3 administrator-signed create_release_credential authorization over {name,product,policy_id,expires_at}. Policy must already exist for that product; expiry must be in the future, at most 90 days. HTTP 201 returns the token once; an identical authorized retry returns 200 metadata only. After a lost response, list/revoke and replace with a fresh nonce. Limits: 100 active and 1,000 retained credentials per workspace.
- Request fields
name: string, product: string, policy_id: string, expires_at: canonical UTC timestamp, authorization: {payload_base64, statement: Envelope}- Response fields / type
credential: {id, name, product, policy_id, created_at, expires_at, revoked_at (optional)}, token: string (201 only; absent on 200 retry)- Success HTTP status
201 / 200
GET /release-credentials
Owner
Owner session only; CI tokens are rejected. List credential metadata, including expired/revoked history. No raw token or token hash is returned, and lost tokens cannot be recovered.
- Request fields
- No request body
- Response fields / type
credentials[] {id, name, product, policy_id, created_at, expires_at, revoked_at (optional)}- Success HTTP status
200
POST /release-credentials/{id}/revoke
Owner
Owner session only; idempotently revoke the credential. No extra signed operation is needed because this only removes access. New requests fail; an in-flight request may finish. Offline evidence is unchanged.
- Request fields
- No request body
- Response fields / type
credential: {id, name, product, policy_id, created_at, expires_at, revoked_at}- Success HTTP status
200
Handle failures by code
Handler errors use the shape below; gateway errors may differ, so check the HTTP status and Retry-After. A failed response does not prove nothing committed.
{"error":true,"code":"authentication_required","message":"authentication required"}
HTTP 400
invalid_request, invalid_public_key, invalid_profile, invalid_agent, invalid_submission, submission_too_large, invalid_owner_signature, invalid_source_statement, delegation_exceeds_one_year, invalid_binding_request, binding_request_expired, erc8004_registry_unsupported, unsupported_protocol_version, unsupported_key_algorithm, invalid_release, invalid_release_manifest, release_certificate_mismatch, invalid_release_status_event, invalid_release_policy, invalid_release_evaluation, invalid_release_credential
Malformed JSON, invalid fields or signatures, or unacceptable binding request. unsupported_protocol_version: unknown/unsupported Core version, a version different from the workspace’s, or Core 0.2. unsupported_key_algorithm: ML-DSA-65 key while Core 0.3 is disabled.
HTTP 401
authentication_required, invalid_session, invalid_authentication_proof, invalid_ci_credential
Missing or expired session, invalid login proof, replayed challenge, or invalid/expired/revoked CI bearer. Disallowed owner routes reject CI tokens with invalid_session; the scoped routes use invalid_ci_credential.
HTTP 403
invalid_agent_authorization, invalid_publication_grant, source_authorization_inactive, invalid_owner_signature, agent_inactive, invalid_release_authorization, release_publication_denied
The administrator, agent or EOA owner proof does not authorize this action, or the release is not public.
HTTP 404
agent_not_found, certificate_not_found, profile_not_found, binding_not_found, release_not_found, release_policy_not_found, release_credential_not_found
No accessible agent, certificate, binding, release, policy, credential or public profile. Out-of-scope CI release/policy requests also return not found.
HTTP 409
agent_already_registered, agent_limit_reached, idempotency_conflict, binding_nonce_conflict, erc8004_owner_mismatch, agent_inactive, release_nonce_conflict, policy_version_conflict, release_already_withdrawn, release_conflict, release_credential_nonce_conflict, release_credential_conflict
Registration or nonce conflict, agent quota, inactive binding agent, owner mismatch, release collision, policy-version conflict or withdrawn release. Conflicts never authorize overwrite.
HTTP 410
core_0_1_retired
Core 0.1 is retired for issuance. core_0_1_retired: agent registration, certificate issuance, ERC-8004 binding 0.1, register_release for a Core 0.1 certificate, or a new Core 0.1 workspace (refused only after the login signature verifies). Existing Core 0.1 workspaces still sign in, read, edit profiles, revoke and manage release policies. CI credential creation also requires Core 0.3; a Core 0.1 workspace receives core_0_1_retired.
HTTP 422
erc8004_eoa_only
The ERC-8004 owner is a contract wallet; this alpha supports EOA ownership consent only.
HTTP 429
challenge_rate_limit, daily_submission_limit, daily_binding_limit, release_write_limit, release_rate_limit, release_credential_limit
IP, daily or credential quota reached. Honor Retry-After when supplied; reconcile the 100-active/1,000-history credential limits rather than retrying unchanged.
HTTP 500
session_unavailable, stored_agent_invalid, binding_unavailable
Operation failed. Check authenticated history before retrying a write.
HTTP 503
apostille_service_unavailable, challenge_unavailable, workspace_unavailable, agent_unavailable, agent_registration_unavailable, revocation_unavailable, issuance_unavailable, binding_unavailable, erc8004_unavailable, challenge_rate_limit_unavailable, core_0_3_unavailable, release_service_unavailable, release_rate_limit_unavailable, release_credential_unavailable
Hosted issuance, release or credential storage, the ERC-8004 reader or the rate-limit backend is unavailable. core_0_3_unavailable: Core 0.3 issuance is unavailable. Retry with bounded backoff.