Never put a secret key in a website, mobile app or anything a user can download. Anyone with the secret key can act as your organisation within its scopes.
Keep secret keys in your server’s secret store or environment variables, not in source control.
Use one key per integration, with only the scopes it needs, so you can revoke one without breaking the others.
Give keys an expiry where you can (30, 90 or 365 days) and rotate them: create a new key, switch your integration over, then revoke the old one.
Watch Last used in Settings › API keys for keys nobody uses any more, and revoke them.
EMS stores only a one-way hash of each secret key: nobody at EMS can see it, and it can’t be shown again.
A missing, malformed, wrong, revoked or expired key, or a key whose organisation is suspended, gets the same response:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Basic realm="EMS API", charset="UTF-8"{ "error": "invalid or missing API key: use your public key as the username and secret key as the password (HTTP Basic auth)" }
Repeated failures from one IP address are limited: see Rate limits.
In Settings › API keys, choose Revoke next to the key. It stops working immediately.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"EMS Developers","description":"Connect your systems to EMS with the public API.","colors":{"primary":"#C83A1E","light":"#E35D43","dark":"#C83A1E"},"logo":{"light":"/logo/light.svg","dark":"/logo/dark.svg","href":"https://ems.aventia.africa"},"favicon":"/favicon.svg","api":{"openapi":"api-reference/openapi.json","playground":{"display":"simple"},"examples":{"languages":["curl","javascript","python","php","go"]}},"background":{"color":{"light":"#F7F1E8","dark":"#251B2F"}},"navbar":{"primary":{"type":"button","label":"Get API keys","href":"https://ems.aventia.africa/settings/api-keys"}},"footer":{"socials":{"website":"https://ems.aventia.africa"}},"fonts":{"heading":{"family":"Noto Serif","weight":700},"body":{"family":"Noto Sans"}},"integrations":{"telemetry":{"enabled":false}},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"hasAudioNarration":false,"customDomains":[],"gitSource":{"type":"github","owner":"local","repo":"local","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":"local","isBase":true},"buildId":"local","clientVersion":"0.0.4071"},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[],"googleFontsInlined":false,"isLoggedInCli":false,"mintExport":{}},"customCss":[],"page":{"pagePath":"authentication","slug":"/authentication","pageMetadata":{"title":"Authentication","description":"API key pairs and HTTP Basic auth.","href":"/authentication"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"API key pairs and HTTP Basic auth.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Table,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`Every request is authenticated with an `,_jsx(_components.strong,{children:`API key pair`}),` that belongs to your organisation:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{}),_jsx(_components.th,{children:`Looks like`}),_jsx(_components.th,{children:`Where it lives`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Public key`}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`ems_pk_live_`}),` + 24 characters`]}),_jsx(_components.td,{children:`Identifies the key. Safe to show in logs and dashboards.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Secret key`}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`ems_sk_live_`}),` + 48 characters`]}),_jsx(_components.td,{children:`Proves the request is yours. Server-side only.`})]})]})]}),`\n`,_jsxs(_components.p,{children:[`Send both with `,_jsx(_components.a,{href:`https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme`,children:`HTTP Basic auth`}),`: the public key as the `,_jsx(_components.strong,{children:`username`}),` and the secret key as the `,_jsx(_components.strong,{children:`password`}),`.`]}),`\n`,_jsx(CodeBlock,{numberOfLines:`3`,language:`http`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`http`,children:_jsxs(_components.code,{language:`http`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`GET`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` /api/public/v1/whoami `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`HTTP`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1.1`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`Host`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ems-api.aventia.africa`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`Authorization`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` Basic base64(ems_pk_live_…:ems_sk_live_…)`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Most HTTP clients do this for you (`,_jsx(_components.code,{children:`curl -u`}),`, `,_jsx(_components.code,{children:`requests`}),` `,_jsx(_components.code,{children:`auth=(…)`}),`, Node `,_jsx(_components.code,{children:`fetch`}),` with a Basic header).`]}),`\n`,_jsx(Heading,{level:`2`,id:`keeping-keys-safe`,children:`Keeping keys safe`}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`Never put a secret key in a website, mobile app or anything a user can download. Anyone with the secret key can act as your organisation within its scopes.`})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Keep secret keys in your server’s secret store or environment variables, not in source control.`}),`\n`,_jsxs(_components.li,{children:[`Use one key per integration, with only the `,_jsx(_components.a,{href:`/scopes`,children:`scopes`}),` it needs, so you can revoke one without breaking the others.`]}),`\n`,_jsx(_components.li,{children:`Give keys an expiry where you can (30, 90 or 365 days) and rotate them: create a new key, switch your integration over, then revoke the old one.`}),`\n`,_jsxs(_components.li,{children:[`Watch `,_jsx(_components.strong,{children:`Last used`}),` in `,_jsx(_components.strong,{children:`Settings › API keys`}),` for keys nobody uses any more, and revoke them.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`EMS stores only a one-way hash of each secret key: nobody at EMS can see it, and it can’t be shown again.`}),`\n`,_jsx(Heading,{level:`2`,id:`when-authentication-fails`,children:`When authentication fails`}),`\n`,_jsx(_components.p,{children:`A missing, malformed, wrong, revoked or expired key, or a key whose organisation is suspended, gets the same response:`}),`\n`,_jsx(CodeBlock,{numberOfLines:`4`,language:`http`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`http`,children:_jsxs(_components.code,{language:`http`,numberOfLines:`4`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`HTTP`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`/`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`1.1`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 401`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` Unauthorized`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`WWW-Authenticate`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` Basic realm=\"EMS API\", charset=\"UTF-8\"`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{ `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"error\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"invalid or missing API key: use your public key as the username and secret key as the password (HTTP Basic auth)\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Repeated failures from one IP address are limited: see `,_jsx(_components.a,{href:`/rate-limits`,children:`Rate limits`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`revoking-a-key`,children:`Revoking a key`}),`\n`,_jsxs(_components.p,{children:[`In `,_jsx(_components.strong,{children:`Settings › API keys`}),`, choose `,_jsx(_components.strong,{children:`Revoke`}),` next to the key. It stops working immediately.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Authentication","description":"API key pairs and HTTP Basic auth.","href":"/authentication"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Keeping keys safe","slug":"keeping-keys-safe","depth":2,"children":[]},{"title":"When authentication fails","slug":"when-authentication-fails","depth":2,"children":[]},{"title":"Revoking a key","slug":"revoking-a-key","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"localhost","siteName":"EMS Developers","title":"Authentication - EMS Developers","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"originalFileLocation":"authentication.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"API key pairs and HTTP Basic auth.\">\u003cmeta data-mint-head name=\"application-name\" content=\"EMS Developers\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/favicons/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicons/favicon-32x32.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicons/favicon-16x16.png\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicons/favicon.ico\">\u003cmeta data-mint-head property=\"og:title\" content=\"Authentication - EMS Developers\">\u003cmeta data-mint-head property=\"og:description\" content=\"API key pairs and HTTP Basic auth.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"EMS Developers\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://local./authentication\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Authentication - EMS Developers\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"API key pairs and HTTP Basic auth.\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/favicons/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"EMS Developers\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#C83A1E\">\u003clink data-mint-head rel=\"canonical\" href=\"https://local./authentication\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/authentication.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"EMS Developers\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"EMS Developers","nav":{"tabs":[{"tab":"Guides","groups":[{"group":"Get started","pages":[{"title":"EMS Public API","href":""},{"title":"Quickstart","description":"Create an API key and make your first request.","href":"/quickstart"}]},{"group":"Using the API","pages":[{"title":"Authentication","href":"/authentication"},{"title":"Scopes","description":"What each API key is allowed to do.","href":"/scopes"},{"title":"Pagination","href":"/pagination"},{"title":"Errors","href":"/errors"},{"title":"Rate limits","href":"/rate-limits"}]},{"group":"Guides","pages":[{"title":"Speakers","href":"/speakers"},{"title":"Uploading photos","href":"/uploading-photos"}]},{"group":"More","pages":[{"title":"Changelog","href":"/changelog"}]}]},{"tab":"API reference","pages":[{"title":"API reference","href":"/api-reference/whoami"}]}]},"basePath":"","title":"Authentication - EMS Developers","buildId":"660b7fc6c9f5","dynamics":[],"chunks":[]}