{"openapi":"3.0.0","info":{"title":"Stud API","version":"2.0.0","description":"Academic verification service API — authenticate users via their academic institution SSO.\n\n### Build it with AI\nThis API is AI-ready. Point your coding assistant at the [OpenAPI spec](https://api.studid.io/openapi.json) and [llms.txt](https://studid.io/llms.txt) — it can write the two-endpoint integration for you. See the [Integration Guide](https://studid.io/guide) for a complete walkthrough.","contact":{"name":"Studid","url":"https://studid.io/contact"}},"servers":[{"url":"https://api.studid.io","description":"Production"}],"tags":[{"name":"Auth","description":"Verification and SAML authentication"},{"name":"Entities","description":"Identity provider information"}],"components":{"schemas":{"EntityInfo":{"type":"object","properties":{"entityID":{"type":"string","description":"Unique SAML entity identifier for the identity provider"},"country":{"type":"string","description":"Country code of the institution"},"type":{"type":"string","enum":["test","live"],"description":"Whether the IdP is a test/development instance or a live production IdP"},"uiInfo":{"type":"object","properties":{"displayNames":{"type":"array","items":{"type":"object","properties":{"lang":{"type":"string","description":"Language code (e.g. en, de)"},"value":{"type":"string","description":"Display name in this language"}},"required":["lang","value"]},"description":"Display names of the institution in different languages"},"descriptions":{"type":"array","items":{"type":"object","properties":{"lang":{"type":"string"},"value":{"type":"string"}},"required":["lang","value"]},"description":"Descriptions in different languages"},"logos":{"type":"array","items":{"type":"object","properties":{"height":{"type":"number"},"width":{"type":"number"},"url":{"type":"string","description":"URL to the logo image"}},"required":["height","width","url"]},"description":"Logo images for the institution"},"privacyUrl":{"type":"string","description":"URL to the institution's privacy policy"}},"required":["displayNames"],"description":"UI and display information about the institution"},"organizations":{"type":"array","items":{"type":"object","properties":{"lang":{"type":"string","description":"Language code"},"name":{"type":"string","description":"Organization name"},"displayName":{"type":"string","description":"Display name of the organization"},"url":{"type":"string","description":"Organization website URL"}},"required":["lang","name"]},"description":"Organizations associated with this identity provider"},"scopes":{"type":"array","items":{"type":"string"},"description":"Security domains (scopes) served by this IdP"},"capabilities":{"$ref":"#/components/schemas/EntityCapabilities"},"federation":{"type":"string","nullable":true,"description":"ID of the federation this institution belongs to (e.g. DFN-AAI)"},"contacts":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"Contact type (technical, support, administrative, ...)"},"givenName":{"type":"string"},"surName":{"type":"string"},"emails":{"type":"array","items":{"type":"string"},"description":"Public contact email addresses"}},"required":["type","emails"]},"description":"Public contact persons published in the institution's SAML metadata"}},"required":["entityID","country","type","uiInfo"]},"EntityCapabilities":{"type":"object","properties":{"nameIdFormats":{"type":"array","items":{"type":"string"},"description":"NameID formats the IdP advertises in its metadata, as short names (persistent, transient, unspecified, email, ...)"},"supportsPersistent":{"type":"boolean","description":"Whether the IdP advertises support for the SAML 2.0 persistent NameID format"},"supportsTransient":{"type":"boolean","description":"Whether the IdP advertises support for the SAML 2.0 transient NameID format"},"entityCategories":{"type":"array","items":{"type":"string"},"description":"REFEDS/eduGAIN entity categories the IdP is committed to (research-and-scholarship, sirtfi, code-of-conduct, pseudonymous, hide-from-discovery, ...)"},"hasSingleLogoutService":{"type":"boolean","description":"Whether the IdP advertises a SingleLogoutService endpoint"},"protocolSupport":{"type":"array","items":{"type":"string"},"description":"SAML protocol versions supported by the IdP (e.g. urn:oasis:names:tc:SAML:2.0:protocol)"}},"required":["nameIdFormats","supportsPersistent","supportsTransient","entityCategories","hasSingleLogoutService","protocolSupport"],"description":"Advertised SAML capabilities of this IdP (NameID formats, entity categories, SLO). Describes what the IdP declares in its metadata — not a guarantee of what a session will return."},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error type"},"message":{"type":"string","description":"Error message"},"details":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"Validation issue code"},"path":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]},"description":"Path to the invalid field"},"message":{"type":"string","description":"Human-readable validation message"}}},"description":"Field-level validation issues (present on 400 Validation Error responses)"}},"required":["error"]},"RateLimitError":{"type":"object","properties":{"error":{"type":"string","description":"Rate limit error type"},"message":{"type":"string","description":"Rate limit error message"}},"required":["error","message"]},"CreateVerificationResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Verification ID (UUID)"},"link":{"type":"string","format":"uri","description":"URL to start the verification flow"}},"required":["id","link"]},"ServerError":{"type":"object","properties":{"error":{"type":"string","description":"Server error type"},"message":{"type":"string","description":"Server error message"}},"required":["error","message"]},"CheckVerificationResponse":{"type":"object","properties":{"id":{"type":"string","description":"Verification ID"},"created":{"type":"string","description":"Creation timestamp (ISO 8601)"},"session":{"$ref":"#/components/schemas/Session"}},"required":["id","created","session"]},"Session":{"type":"object","nullable":true,"properties":{"entityId":{"type":"string","description":"SAML entity ID of the identity provider (uniquely identifies the institution). This is always available and confirms which institution authenticated the user."},"affiliations":{"type":"array","items":{"type":"string"},"description":"eduPerson affiliation values describing the user's relationship to the institution. Standard values: faculty, student, staff, employee, member, alum, affiliate, library-walk-in. Often empty — most IdPs do not release affiliations to non-R&S services. See the Integration Guide for interpretation."},"authIdentifier":{"type":"string","nullable":true,"description":"The recommended identifier for authorization. Computed server-side: pairwiseId if the IdP supports it, subject-id / eduPersonTargetedID / persistent nameId otherwise, null if the IdP only issues a transient identifier (not usable for accounts). Use this field for user account linking."},"authIdentifierType":{"type":"string","nullable":true,"enum":["pairwise-id","persistent-nameid",null],"description":"The type of authIdentifier. pairwise-id is the modern per-SP standard and most privacy-preserving. persistent-nameid is an older standard but still persistent. null means no usable identifier — use entityId and per-session tracking instead."},"test":{"type":"boolean","description":"Whether this session was completed against a test identity provider. True for verifications created with mode: \"test\", and whenever the authenticating IdP is a test IdP (live verifications are blocked from using test IdPs). Reject sessions with test: true in production — they carry no academic verification."}},"required":["entityId","affiliations","authIdentifier","authIdentifierType","test"],"description":"Authentication session. null if no SAML login has completed yet."}},"parameters":{}},"paths":{"/v2/entities/{id}":{"get":{"tags":["Entities"],"operationId":"getEntity","summary":"Get entity info","description":"Returns information about an identity provider (institution) by its SAML entity ID. The entity ID can be found in the `entityId` field of a verification session. URL-encode the entity ID when passing it as a path parameter.\n\nExample: `GET /v2/entities/https%3A%2F%2Fidp.university.edu%2Fshibboleth`","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":512,"description":"The institution's SAML entity ID, URL-encoded.","example":"https%3A%2F%2Fidp.tu-berlin.de%2Fshibboleth"},"required":true,"description":"The institution's SAML entity ID, URL-encoded.","name":"id","in":"path"}],"responses":{"200":{"description":"Entity details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntityInfo"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Entity not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}}},"/v2/auth/verification":{"post":{"tags":["Auth"],"operationId":"createVerification","summary":"Create a verification session","description":"Initiates the SAML-based academic verification flow. Your backend calls this endpoint with a secret token, a redirect URL, and a service name. You receive a unique verification ID and a link to the Studid web app. Redirect the user to the link — they will select their institution and authenticate via SAML. After the user returns to your redirect URL, call `GET /verification/{id}` to check the result.\n\nKeep `secretToken` server-side; it authorizes the status check.\n\nSee the [Integration Guide](https://studid.io/guide) for a complete walkthrough.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"secretToken":{"type":"string","minLength":1,"maxLength":256,"description":"A secret your backend generates. Use the same value when checking the verification status. Keep it server-side — never expose it to the browser."},"redirectUrl":{"type":"string","maxLength":2048,"format":"uri","description":"Where the user should land after completing SAML login at their institution. Must be a valid URL."},"serviceName":{"type":"string","minLength":1,"maxLength":256,"description":"Human-readable name of your service, shown to the user during the flow."},"mode":{"type":"string","enum":["live","test"],"default":"live","description":"Verification mode. \"test\" restricts the flow to test identity providers (currently the DFN test IdP) and tags resulting sessions with test: true. \"live\" verifications may not authenticate at test IdPs. Defaults to \"live\"."}},"required":["secretToken","redirectUrl","serviceName"]}}}},"responses":{"201":{"description":"Verification session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVerificationResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}}},"/v2/auth/verification/{id}":{"get":{"tags":["Auth"],"operationId":"getVerificationStatus","summary":"Check verification status","description":"Returns the verification session status and the user's institutional affiliation data. Call this after the user has completed SAML authentication at their institution (step 3 of the [integration flow](https://studid.io/guide#step-by-step)). The verification ID is taken from the URL path; authenticate with the same secret token from step 1, preferably via the `Authorization: Bearer <secretToken>` header. If no SAML login has completed yet, `session` will be null — poll until you see data.\n\nSee the [Integration Guide](https://studid.io/guide) for details on the verification flow.","parameters":[{"schema":{"type":"string","format":"uuid","description":"The verification ID returned by `POST /v2/auth/verification`."},"required":true,"description":"The verification ID returned by `POST /v2/auth/verification`.","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Deprecated fallback. Prefer the `Authorization: Bearer <secretToken>` header, since query strings are recorded in access logs, proxies and browser history."},"required":false,"description":"Deprecated fallback. Prefer the `Authorization: Bearer <secretToken>` header, since query strings are recorded in access logs, proxies and browser history.","name":"secretToken","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Deprecated. The verification ID is read from the URL path; this query parameter is accepted for backwards compatibility and ignored when present."},"required":false,"description":"Deprecated. The verification ID is read from the URL path; this query parameter is accepted for backwards compatibility and ignored when present.","name":"id","in":"query"},{"schema":{"type":"string","description":"Preferred. Authenticate the status check with `Bearer <secretToken>`.","example":"Bearer your-secret-token"},"required":false,"description":"Preferred. Authenticate the status check with `Bearer <secretToken>`.","name":"authorization","in":"header"}],"responses":{"200":{"description":"Verification session details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckVerificationResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid secret token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Verification not found or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerError"}}}}}}}}}