{
"error": {
"type": "invalid_request_error",
"code": "meter_name_taken",
"message": "A meter named \"Image generations\" already exists in this store",
"request_id": "req_abc123def456",
"param": "events[3].customer_id",
"details": {
"archived_holder": true,
"archived_holder_id": "mtr_abc123"
}
}
}
Events
Ingest usage events
Send up to 100 usage events in one request.
Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not.
Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.
Your event_id is stored trimmed, and the event_ids we return are the stored values — so " abc " is recorded and echoed as "abc", which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.
The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested.
Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period’s totals.
202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.
How many events were accepted. Always the size of the submitted batch — validation is all-or-nothing, so a 202 means every event in the batch was accepted.
The event_id each accepted event was recorded under, in the order they were submitted — your own value where you supplied one, the generated value where you did not. This is the stored id after normalisation (surrounding whitespace is trimmed), so it is exactly what reference on GET /v1/events will match. Use it to correlate a submitted event with the row it became.
Advisory warnings about accepted events that will not produce billable usage as sent — no active meter consumes the event name, only archived meters match, or the timestamp falls in an already-finalized billing period. Warnings never change what was accepted: the events are stored either way. Absent when every event will aggregate normally.
Show child attributes
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Creem","colors":{"primary":"#FFBE98","light":"#FFBE98","dark":"#B09CFB"},"logo":{"light":"https://mintcdn.com/creem/-AIVGXbrCJu_8i70/logo/light.svg?fit=max\u0026auto=format\u0026n=-AIVGXbrCJu_8i70\u0026q=85\u0026s=686f06f43a3d7e4c0456e22de64c8bae","dark":"https://mintcdn.com/creem/-AIVGXbrCJu_8i70/logo/dark.svg?fit=max\u0026auto=format\u0026n=-AIVGXbrCJu_8i70\u0026q=85\u0026s=7c9135ca8b2365d6ec3728dc087f694d"},"favicon":"https://mintcdn.com/creem/-AIVGXbrCJu_8i70/favicon.svg?fit=max\u0026auto=format\u0026n=-AIVGXbrCJu_8i70\u0026q=85\u0026s=92df9cacbac6c8f17dbeb2d8b6befb93","navbar":{"links":[{"href":"https://creem.io/contact","label":"Support"},{"href":"https://docs.creem.io/llms.txt","label":"llms.txt"},{"href":"https://docs.creem.io/llms-full.txt","label":"llms-full.txt"}],"primary":{"type":"button","label":"Creem","href":"https://creem.io"}},"footer":{"socials":{"x":"https://x.com/creem_io","linkedin":"https://www.linkedin.com/company/creemio"}},"seo":{"metatags":{"twitter:site":"@creem_io","og:site_name":"Creem Documentation","keywords":"merchant of record, payment api, saas billing, subscription management, global payments, MoR, affiliate program, affiliate marketing, referral program"},"indexing":"all"},"integrations":{"posthog":{"apiKey":"phc_placeholder_gabriel_will_provide","apiHost":"https://us.posthog.com"}},"contextual":{"options":["copy","view","chatgpt","claude","perplexity"],"display":"header"},"styling":{"latex":true},"navigation":{"global":{"anchors":[{"anchor":"Community","icon":"discord","href":"https://discord.gg/q3GKZs92Av"},{"anchor":"Blog","icon":"newspaper","href":"https://creem.io/blog"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"creem","hasAudioNarration":false,"actualSubdomain":"creem","customDomains":["docs.creem.io"],"gitSource":{"type":"github","owner":"armitage-labs","repo":"creem","deployBranch":"main","contentDirectory":"packages/docs","isPrivate":false,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ae068cec9d0adb3afbc2c95","collectionId":"2fa55d35-6965-42a5-84f0-414da7321369","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ae068cec9d0adb3afbc2c95"},"entitlements":{},"buildId":"6ae068cec9d0adb3afbc2c95","clientVersion":"0.0.4021","searchFilterCounts":[{"language":"default","tag":"Documentation","count":100},{"language":"default","tag":"API Reference","count":65},{"language":"default","tag":"Features","count":44},{"language":"default","tag":"Code","count":21},{"language":"default","tag":"Customer Credits","count":19},{"language":"default","tag":"SDKs","count":19},{"language":"default","tag":"Guides","count":18},{"language":"default","tag":"Meters","count":9},{"language":"default","tag":"Get Started","count":9},{"language":"default","tag":"Webhooks","count":8}]},"assistantConfig":{},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"styles.css","content":"#topbar-cta-button{color:#000}"}],"page":{"pagePath":"api-reference/endpoint/ingest-usage-events","slug":"/api-reference/endpoint/ingest-usage-events","pageMetadata":{"title":"Ingest usage events","description":"Send up to 100 usage events in one request.\nReturns **202 Accepted** with the number of events accepted and the `event_id` of each, in submission order — your own value where you supplied one, a generated value where you did not.\nIngestion is **idempotent on (store, event_id)**: re-sending an event_id this store has already accepted records nothing and is not an error, so **retrying a whole batch after a timeout or a partial failure is always safe**. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same `event_ids` as the original. Supply your own `event_id` to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.\nYour `event_id` is **stored trimmed**, and the `event_ids` we return are the stored values — so `\" abc \"` is recorded and echoed as `\"abc\"`, which is what `reference=` on `GET /v1/events` matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the **same** event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An `event_id` that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.\nThe **whole batch is validated before anything is accepted**: if any event is invalid the request is rejected with 422 and `param` pointing at the offending event (e.g. `events[3].customer_id`), and no event in the batch is ingested.\nBilling attribution uses the `timestamp` you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals.\n**202, not 200, is deliberate.** It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.","openapi":"post /v1/events/ingest","href":"/api-reference/endpoint/ingest-usage-events"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n code: \"code\",\n p: \"p\",\n strong: \"strong\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsxs(_components.p, {\n children: [\"Send up to 100 usage events in one request.\\nReturns \", _jsx(_components.strong, {\n children: \"202 Accepted\"\n }), \" with the number of events accepted and the \", _jsx(_components.code, {\n children: \"event_id\"\n }), \" of each, in submission order — your own value where you supplied one, a generated value where you did not.\\nIngestion is \", _jsx(_components.strong, {\n children: \"idempotent on (store, event_id)\"\n }), \": re-sending an event_id this store has already accepted records nothing and is not an error, so \", _jsx(_components.strong, {\n children: \"retrying a whole batch after a timeout or a partial failure is always safe\"\n }), \". Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same \", _jsx(_components.code, {\n children: \"event_ids\"\n }), \" as the original. Supply your own \", _jsx(_components.code, {\n children: \"event_id\"\n }), \" to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.\\nYour \", _jsx(_components.code, {\n children: \"event_id\"\n }), \" is \", _jsx(_components.strong, {\n children: \"stored trimmed\"\n }), \", and the \", _jsx(_components.code, {\n children: \"event_ids\"\n }), \" we return are the stored values — so \", _jsx(_components.code, {\n children: \"\\\" abc \\\"\"\n }), \" is recorded and echoed as \", _jsx(_components.code, {\n children: \"\\\"abc\\\"\"\n }), \", which is what \", _jsx(_components.code, {\n children: \"reference=\"\n }), \" on \", _jsx(_components.code, {\n children: \"GET /v1/events\"\n }), \" matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the \", _jsx(_components.strong, {\n children: \"same\"\n }), \" event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An \", _jsx(_components.code, {\n children: \"event_id\"\n }), \" that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.\\nThe \", _jsx(_components.strong, {\n children: \"whole batch is validated before anything is accepted\"\n }), \": if any event is invalid the request is rejected with 422 and \", _jsx(_components.code, {\n children: \"param\"\n }), \" pointing at the offending event (e.g. \", _jsx(_components.code, {\n children: \"events[3].customer_id\"\n }), \"), and no event in the batch is ingested.\\nBilling attribution uses the \", _jsx(_components.code, {\n children: \"timestamp\"\n }), \" you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period’s totals.\\n\", _jsx(_components.strong, {\n children: \"202, not 200, is deliberate.\"\n }), \" It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\"]\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}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){return _jsx(_Fragment,{})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Ingest usage events","description":"Send up to 100 usage events in one request.\nReturns **202 Accepted** with the number of events accepted and the `event_id` of each, in submission order — your own value where you supplied one, a generated value where you did not.\nIngestion is **idempotent on (store, event_id)**: re-sending an event_id this store has already accepted records nothing and is not an error, so **retrying a whole batch after a timeout or a partial failure is always safe**. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same `event_ids` as the original. Supply your own `event_id` to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.\nYour `event_id` is **stored trimmed**, and the `event_ids` we return are the stored values — so `\" abc \"` is recorded and echoed as `\"abc\"`, which is what `reference=` on `GET /v1/events` matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the **same** event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An `event_id` that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.\nThe **whole batch is validated before anything is accepted**: if any event is invalid the request is rejected with 422 and `param` pointing at the offending event (e.g. `events[3].customer_id`), and no event in the batch is ingested.\nBilling attribution uses the `timestamp` you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals.\n**202, not 200, is deliberate.** It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.","openapi":"post /v1/events/ingest","href":"/api-reference/endpoint/ingest-usage-events"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[],"codeExamples":{}},"jsLinks":["/_mintlify/custom/creem/c0258fabb625b4ba/scripts/generate-api-docs.js"],"openApiReferenceData":{"operation":{"operationId":"ingestUsageEvents","x-speakeasy-name-override":"ingestEvents","x-speakeasy-group":"events","summary":"Ingest usage events","description":"Send up to 100 usage events in one request.\nReturns **202 Accepted** with the number of events accepted and the `event_id` of each, in submission order — your own value where you supplied one, a generated value where you did not.\nIngestion is **idempotent on (store, event_id)**: re-sending an event_id this store has already accepted records nothing and is not an error, so **retrying a whole batch after a timeout or a partial failure is always safe**. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same `event_ids` as the original. Supply your own `event_id` to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event.\nYour `event_id` is **stored trimmed**, and the `event_ids` we return are the stored values — so `\" abc \"` is recorded and echoed as `\"abc\"`, which is what `reference=` on `GET /v1/events` matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the **same** event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An `event_id` that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about.\nThe **whole batch is validated before anything is accepted**: if any event is invalid the request is rejected with 422 and `param` pointing at the offending event (e.g. `events[3].customer_id`), and no event in the batch is ingested.\nBilling attribution uses the `timestamp` you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals.\n**202, not 200, is deliberate.** It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.","parameters":[],"requestBody":"84e31238-0dc4-4479-af0d-8a3ad7c04d6d","responses":{"202":"b1a180f6-2f6f-445c-b740-a9f722a2f520","400":"93d40ee3-24b8-40bb-a211-a551252c8399","401":"612badb2-46b7-4a5d-b588-a2a3c58fb3b9","404":"30cedcef-f747-40fd-bb22-d64a32bb361f","422":"125ed45d-baf2-4bde-b6a2-8b507400e4c9"},"tags":["Events"],"security":["48f2c2ae-47cd-45e8-b51f-93809f728c3a"],"method":"post","type":"path","servers":["ce73d8da-bcf3-4183-ac11-826f6188634a","a3b3db05-e3f5-4845-baed-eca093fbbcd7"],"path":"/v1/events/ingest","baseUrlOptions":["https://api.creem.io","https://test-api.creem.io"],"title":"Ingest usage events","codeSamples":[],"requiredOnlyExamples":false,"requestExampleType":"generated","responseExampleType":"generated"},"dependencies":{"parameters":{},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"events":{"description":"The batch of usage events to ingest (1–100). The whole batch is validated before anything is written.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The usage event name; must match a meter to be aggregated","example":"image.generated","maxLength":128,"isRequired":true,"uniqueKey":"events.items.[INDEX].name","typeLabel":"string"},"customer_id":{"type":"string","description":"The Creem customer this usage is attributed to. Exactly one of `customer_id` / `external_customer_id` is required per event; the id must belong to an existing customer of your store or the whole batch is rejected. Trimmed; letters, digits, `_` and `-` only; at most 255 characters.","example":"cust_abc123","uniqueKey":"events.items.[INDEX].customer_id","typeLabel":"string"},"external_customer_id":{"type":"string","description":"Your own id for this customer, resolved to the Creem customer it was registered for via the customers API. Exactly one of `customer_id` / `external_customer_id` is required per event; an id no customer carries rejects the whole batch. Trimmed; letters, digits, `_` and `-` only; at most 255 characters.","example":"user-42","uniqueKey":"events.items.[INDEX].external_customer_id","typeLabel":"string"},"event_id":{"type":"string","description":"Your idempotency key for this event, unique within your store. Re-sending the same event_id is a no-op. Generated for you when omitted — supply your own if you want retries to be safe.","example":"order-1234","uniqueKey":"events.items.[INDEX].event_id","typeLabel":"string"},"timestamp":{"type":"string","description":"When the usage occurred (ISO 8601), on your clock. Defaults to the time of ingestion.","example":"2026-08-15T10:30:00.000Z","uniqueKey":"events.items.[INDEX].timestamp","typeLabel":"string"},"properties":{"type":"object","description":"The event's attributes — the ONE bag. Meter filter clauses and aggregation properties (e.g. \"tokens\") resolve their keys here, unprefixed. At most 50 keys, keys at most 40 characters, string and serialized-object values at most 500 characters.","additionalProperties":true,"example":{"tokens":1200,"tier":"pro"},"uniqueKey":"events.items.[INDEX].properties","typeLabel":"object"}},"required":["name"],"uniqueKey":"events.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"events","typeLabel":"object[]"}},"required":["events"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"events":[{"name":"image.generated","customer_id":"cust_abc123","external_customer_id":"user-42","event_id":"order-1234","timestamp":"2026-08-15T10:30:00.000Z","properties":{"tokens":1200,"tier":"pro"}}]}}},"exampleType":"generated"}},"exampleType":"generated"},"responses":{"b1a180f6-2f6f-445c-b740-a9f722a2f520":{"description":"Batch accepted for processing","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"type":"number","description":"How many events were accepted. Always the size of the submitted batch — validation is all-or-nothing, so a 202 means every event in the batch was accepted.","example":100,"isRequired":true,"uniqueKey":"accepted","typeLabel":"number"},"event_ids":{"description":"The `event_id` each accepted event was recorded under, in the order they were submitted — your own value where you supplied one, the generated value where you did not. This is the stored id after normalisation (surrounding whitespace is trimmed), so it is exactly what `reference` on GET /v1/events will match. Use it to correlate a submitted event with the row it became.","example":["order-1","order-2"],"type":"array","items":{"type":"string","uniqueKey":"event_ids.items.[INDEX]","typeLabel":"string"},"isRequired":true,"uniqueKey":"event_ids","typeLabel":"string[]"},"warnings":{"description":"Advisory warnings about accepted events that will not produce billable usage as sent — no active meter consumes the event name, only archived meters match, or the timestamp falls in an already-finalized billing period. Warnings never change what was accepted: the events are stored either way. Absent when every event will aggregate normally.","type":"array","items":{"type":"object","properties":{"index":{"type":"number","description":"Zero-based index of the event in the submitted batch","example":3,"isRequired":true,"uniqueKey":"warnings.items.[INDEX].index","typeLabel":"number"},"code":{"type":"string","description":"Warning code. `no_matching_meter`: no meter consumes this event name. `meter_archived`: only archived meters match — unarchive one to resume aggregation. `timestamp_outside_late_window`: the billing period the timestamp falls in is finalized, so the event will never change its totals.","enum":["no_matching_meter","meter_archived","timestamp_outside_late_window"],"example":"no_matching_meter","isRequired":true,"uniqueKey":"warnings.items.[INDEX].code","typeLabel":"enum\u003cstring>"},"message":{"type":"string","description":"Human-readable explanation","isRequired":true,"uniqueKey":"warnings.items.[INDEX].message","typeLabel":"string"}},"required":["index","code","message"],"uniqueKey":"warnings.items.[INDEX]","typeLabel":"object"},"uniqueKey":"warnings","typeLabel":"object[]"}},"required":["accepted","event_ids"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"accepted":100,"event_ids":["order-1","order-2"],"warnings":[{"index":3,"code":"no_matching_meter","message":"\u003cstring>"}]}}},"exampleType":"generated"}},"exampleType":"generated"},"93d40ee3-24b8-40bb-a211-a551252c8399":{"description":"Bad Request - Invalid input parameters","content":{"_mintlify/placeholder":{"schema":{"type":"object","description":""}}}},"612badb2-46b7-4a5d-b588-a2a3c58fb3b9":{"description":"Unauthorized - Invalid or missing API key","content":{"_mintlify/placeholder":{"schema":{"type":"object","description":""}}}},"30cedcef-f747-40fd-bb22-d64a32bb361f":{"description":"Not Found - Resource does not exist","content":{"_mintlify/placeholder":{"schema":{"type":"object","description":""}}}},"125ed45d-baf2-4bde-b6a2-8b507400e4c9":{"description":"An event in the batch is invalid — nothing was ingested; `param` identifies the event and field","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","description":"Error details","required":["type","code","message","request_id"],"properties":{"type":{"type":"string","description":"Error category","enum":["invalid_request_error","api_error","authentication_error","rate_limit_error"],"example":"invalid_request_error","isRequired":true,"uniqueKey":"error.type","typeLabel":"enum\u003cstring>"},"code":{"oneOf":[{"type":"string","description":"Machine-readable error code","example":"meter_name_taken","isRequired":true,"uniqueKey":"error.code.oneOf.0","typeLabel":"string"},{"type":"null","description":"Machine-readable error code","example":"meter_name_taken","isRequired":true,"uniqueKey":"error.code.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"error.code","typeLabel":"string | null"},"message":{"type":"string","description":"Human-readable error message","example":"A meter named \"Image generations\" already exists in this store","isRequired":true,"uniqueKey":"error.message","typeLabel":"string"},"request_id":{"type":"string","description":"Unique request identifier for support","example":"req_abc123def456","isRequired":true,"uniqueKey":"error.request_id","typeLabel":"string"},"param":{"oneOf":[{"type":"string","description":"The parameter related to the error, if applicable","example":"events[3].customer_id","uniqueKey":"error.param.oneOf.0","typeLabel":"string"},{"type":"null","description":"The parameter related to the error, if applicable","example":"events[3].customer_id","uniqueKey":"error.param.oneOf.1","typeLabel":"null"}],"uniqueKey":"error.param","typeLabel":"string | null"},"details":{"type":"object","description":"Structured, machine-readable context for errors that support an action, omitted entirely when there is none. Currently emitted for `meter_name_taken` when the colliding name is held by an ARCHIVED meter: `archived_holder` is true and `archived_holder_id` is that meter, which can be restored via POST /v1/meters/{id}/unarchive. Additive — treat unknown keys as ignorable.","example":{"archived_holder":true,"archived_holder_id":"mtr_abc123"},"additionalProperties":true,"uniqueKey":"error.details","typeLabel":"object"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"type":"invalid_request_error","code":"meter_name_taken","message":"A meter named \"Image generations\" already exists in this store","request_id":"req_abc123def456","param":"events[3].customer_id","details":{"archived_holder":true,"archived_holder_id":"mtr_abc123"}}}}},"exampleType":"generated"}},"exampleType":"generated"}},"security":{"48f2c2ae-47cd-45e8-b51f-93809f728c3a":{"ApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key for authentication. You can find your API key in the Creem dashboard under Settings > API Keys.","scopes":[]}}},"servers":{"ce73d8da-bcf3-4183-ac11-826f6188634a":{"url":"https://api.creem.io","description":"Production — live API for processing real transactions and data.","x-speakeasy-server-id":"prod"},"a3b3db05-e3f5-4845-baed-eca093fbbcd7":{"url":"https://test-api.creem.io","description":"Test — sandbox API for development and testing with no real charges.","x-speakeasy-server-id":"test"}},"processedSecurityOptions":[{"schemes":[{"scheme":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key for authentication. You can find your API key in the Creem dashboard under Settings > API Keys.","scopes":[]},"schema":{"title":"x-api-key","type":"string","description":"API key for authentication. You can find your API key in the Creem dashboard under Settings > API Keys.","isRequired":true,"typeLabel":"string","deprecated":false},"schemeName":"ApiKey","name":"x-api-key"}],"title":"ApiKey"}]},"originalFileLocation":"api-reference/openapi.json"},"subdomain":"creem","siteName":"Creem","title":"Ingest usage events - Creem","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-09-14T12:00:04.152Z","originalFileLocation":"api-reference/endpoint/ingest-usage-events.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=\"Send up to 100 usage events in one request. Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not. Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event. Your event_id is stored trimmed, and the event_ids we return are the stored values — so \u0026quot; abc \u0026quot; is recorded and echoed as \u0026quot;abc\u0026quot;, which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about. The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested. Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period\u0026#x27;s totals. 202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\">\u003cmeta data-mint-head name=\"keywords\" content=\"merchant of record, payment api, saas billing, subscription management, global payments, MoR, affiliate program, affiliate marketing, referral program\">\u003cmeta data-mint-head name=\"application-name\" content=\"Creem\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Ingest usage events - Creem\">\u003cmeta data-mint-head property=\"og:description\" content=\"Send up to 100 usage events in one request. Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not. Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event. Your event_id is stored trimmed, and the event_ids we return are the stored values — so \u0026quot; abc \u0026quot; is recorded and echoed as \u0026quot;abc\u0026quot;, which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about. The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested. Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period\u0026#x27;s totals. 202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Creem Documentation\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://creem.mintlify.app/_mintlify/api/og/v2?division=Events\u0026amp;title=Ingest+usage+events\u0026amp;description=Send+up+to+100+usage+events+in+one+request.%0AReturns+**202+Accepted**+with+the+number+of+events+accepted+and+the+%60event_id%60+of+each%2C+in+submission+order+%E2%80%94+your+o\u0026amp;theme=cafe6ce3ebe1df8d45ed0a88\u0026amp;signature=f29385365897759facd620901915494f2475dc1bb3a189f67daa713272bd2eae\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Ingest usage events - Creem\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Send up to 100 usage events in one request. Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not. Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event. Your event_id is stored trimmed, and the event_ids we return are the stored values — so \u0026quot; abc \u0026quot; is recorded and echoed as \u0026quot;abc\u0026quot;, which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about. The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested. Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period\u0026#x27;s totals. 202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://creem.mintlify.app/_mintlify/api/og/v2?division=Events\u0026amp;title=Ingest+usage+events\u0026amp;description=Send+up+to+100+usage+events+in+one+request.%0AReturns+**202+Accepted**+with+the+number+of+events+accepted+and+the+%60event_id%60+of+each%2C+in+submission+order+%E2%80%94+your+o\u0026amp;theme=cafe6ce3ebe1df8d45ed0a88\u0026amp;signature=f29385365897759facd620901915494f2475dc1bb3a189f67daa713272bd2eae\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/creem/nizlPYWnvgrMkN5E/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Creem\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#FFBE98\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/api-reference/endpoint/ingest-usage-events.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.creem.io/#organization\",\"name\":\"Creem\",\"url\":\"https://docs.creem.io\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/creem/-AIVGXbrCJu_8i70/logo/light.svg?fit=max\u0026auto=format\u0026n=-AIVGXbrCJu_8i70\u0026q=85\u0026s=686f06f43a3d7e4c0456e22de64c8bae\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.creem.io#website\",\"name\":\"Creem\",\"url\":\"https://docs.creem.io\",\"publisher\":{\"@id\":\"https://docs.creem.io/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events#webpage\",\"url\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events\",\"name\":\"Ingest usage events\",\"description\":\"Send up to 100 usage events in one request. Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not. Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event. Your event_id is stored trimmed, and the event_ids we return are the stored values — so \\\" abc \\\" is recorded and echoed as \\\"abc\\\", which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about. The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested. Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals. 202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\",\"dateModified\":\"2026-09-14T12:00:04.152Z\",\"isPartOf\":{\"@id\":\"https://docs.creem.io#website\"},\"breadcrumb\":{\"@id\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Features\",\"item\":\"https://docs.creem.io/features/checkout/checkout-api\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Ingest usage events\",\"item\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events\"}]},{\"@type\":[\"TechArticle\",\"APIReference\"],\"@id\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events#apireference\",\"headline\":\"Ingest usage events\",\"name\":\"Ingest usage events\",\"description\":\"Send up to 100 usage events in one request. Returns 202 Accepted with the number of events accepted and the event_id of each, in submission order — your own value where you supplied one, a generated value where you did not. Ingestion is idempotent on (store, event_id): re-sending an event_id this store has already accepted records nothing and is not an error, so retrying a whole batch after a timeout or a partial failure is always safe. Duplicates are deduplicated silently rather than reported — a replayed batch returns the same 202 and the same event_ids as the original. Supply your own event_id to get that guarantee; when you omit it we generate one, which makes the event unique and a retry a second event. Your event_id is stored trimmed, and the event_ids we return are the stored values — so \\\" abc \\\" is recorded and echoed as \\\"abc\\\", which is what reference= on GET /v1/events matches. Two entries in one batch whose ids differ only by surrounding whitespace are therefore the same event: the second is deduplicated against the first, both are reported accepted, and both carry the same id. An event_id that is entirely whitespace is rejected like an empty one (422) rather than being replaced with a generated id — an unusable key is worth telling you about. The whole batch is validated before anything is accepted: if any event is invalid the request is rejected with 422 and param pointing at the offending event (e.g. events[3].customer_id), and no event in the batch is ingested. Billing attribution uses the timestamp you supply (defaulting to the time of ingestion), bounded by the late-event window: once a billing period has been closed for longer than that window, an event arriving for it is still stored durably but is no longer folded into that period's totals. 202, not 200, is deliberate. It means the batch has been accepted for processing, not that every downstream effect has completed: aggregation into meter totals is already asynchronous today, and the transport behind this endpoint may become queue-backed. A queue producer cannot know how many rows a consumer will ultimately insert, so this response reports acceptance rather than an insert/duplicate split — the contract you code against stays identical when that lands.\",\"url\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events\",\"mainEntityOfPage\":{\"@id\":\"https://docs.creem.io/api-reference/endpoint/ingest-usage-events#webpage\"},\"dateModified\":\"2026-09-14T12:00:04.152Z\",\"publisher\":{\"@id\":\"https://docs.creem.io/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.creem.io#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Creem\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Creem","nav":{"global":{"anchors":[{"anchor":"Community","icon":"discord","href":"https://discord.gg/q3GKZs92Av"},{"anchor":"Blog","icon":"newspaper","href":"https://creem.io/blog"}]},"tabs":[{"tab":"Documentation","groups":[{"group":"Get Started","icon":"rocket","pages":[{"title":"Ship Software. Get Paid Globally.","href":"/getting-started/introduction"},{"title":"Quickstart","href":"/getting-started/quickstart"},{"title":"Test Mode","href":"/getting-started/test-mode"},{"group":"Account Reviews","pages":[{"title":"Account Reviews","href":"/merchant-of-record/account-reviews/account-reviews"},{"title":"How to Request a Re-Review","href":"/merchant-of-record/account-reviews/re-review-guide"},{"title":"If your account was rejected","href":"/merchant-of-record/account-reviews/rejected-accounts"},{"title":"AI Wrapper Compliance","href":"/merchant-of-record/account-reviews/ai-wrapper-compliance"}]},{"title":"Dashboard Overview","href":"/getting-started/dashboard-overview"},{"title":"Migrate to Creem","href":"/getting-started/migrate"}]},{"group":"AI","icon":"robot","pages":[{"title":"AI Integration","href":"/ai/introduction"},{"group":"For Humans","pages":[{"title":"Getting Started","href":"/ai/for-humans/getting-started"},{"title":"Creem CLI","href":"/ai/for-humans/cli"}]},{"group":"For Agents","pages":[{"title":"Skill Files","href":"/ai/for-agents/skill-files"},{"title":"MCP Server","href":"/ai/for-agents/mcp-server"},{"title":"Creem CLI","href":"/ai/for-agents/cli"},{"title":"Store Monitoring","href":"/ai/for-agents/store-monitoring"}]}]},{"group":"Features","icon":"sparkles","pages":[{"group":"Checkout","pages":[{"title":"Checkout API","href":"/features/checkout/checkout-api"},{"title":"Checkout Link","href":"/features/checkout/checkout-link"},{"title":"Embedded Checkout","href":"/features/checkout/embedded-checkout"},{"title":"Checkout Customization","href":"/features/checkout/checkout-customization"},{"title":"Checkout Custom Fields","href":"/features/checkout/checkout-custom-fields"},{"title":"Checkout Localization","href":"/features/checkout/checkout-localization"}]},{"title":"One Time Payments","href":"/features/one-time-payment"},{"title":"Pay What You Want","href":"/features/pay-what-you-want"},{"group":"Subscriptions","pages":[{"title":"Subscriptions","href":"/features/subscriptions/introduction"},{"title":"Managing Subscriptions","href":"/features/subscriptions/managing"},{"title":"Refunds and Cancellations","href":"/features/subscriptions/refunds-and-cancellations"}]},{"title":"Seat Based Billing","href":"/features/seat-based-billing"},{"title":"Revenue Splits","href":"/features/split-payments"},{"title":"Affiliate Program","sidebarTitle":"Affiliate Program","href":"/features/affiliate-program"},{"title":"Referral Program","sidebarTitle":"Referral Program","href":"/features/referral-program"},{"title":"Storefronts","href":"/features/storefronts"},{"title":"Custom Domains","href":"/features/custom-domains"},{"title":"Short Links","href":"/features/short-links"},{"title":"Discount Codes","href":"/features/discounts"},{"title":"Trials","href":"/features/trials"},{"title":"Customer Portal","href":"/features/customer-portal"},{"title":"Product Bundles","href":"/features/product-bundles"},{"title":"Moderation","href":"/features/moderation"},{"title":"Usage-Based Billing","href":"/features/usage-based-billing"},{"group":"Customer Credits","pages":[{"title":"Customer Credits","sidebarTitle":"Introduction","href":"/features/customer-credits/introduction"},{"title":"Accounts","href":"/features/customer-credits/accounts"},{"title":"Transactions","href":"/features/customer-credits/transactions"},{"title":"Auto-refill","description":"Keep metered customers running when their prepaid credits run out. Creem tells you and the customer once per exhaustion, and can re-purchase the credit top-up the customer opted into with their saved payment method.","href":"/features/customer-credits/auto-refill"}]},{"group":"Events","pages":[{"title":"Ingest usage events","openapi":"post /v1/events/ingest","href":"/api-reference/endpoint/ingest-usage-events"},{"title":"Preview usage events","description":"Dry-run of `POST /v1/events/ingest`: send the exact batch you would ingest and get a per-event report of what would happen — **nothing is stored**.\nFor each event: whether it passes validation (invalid events are reported in place instead of rejecting the batch, unlike ingest), the `event_id` it would be recorded under, whether it would deduplicate against an already-stored event or an earlier entry of the same batch (advisory — another writer can land between preview and ingest), the active meters that would consume it, and the same warnings ingest returns.\nUsage events are immutable once ingested, so preview is the way to gain first-run confidence: wire your integration against preview, check `valid` is true and every event matches the meters you expect, then switch the path to `ingest`.","openapi":"post /v1/events/preview","href":"/api-reference/endpoint/preview-usage-events"},{"title":"List usage events","openapi":"get /v1/events","href":"/api-reference/endpoint/list-usage-events"}]},{"group":"Meters","pages":[{"title":"Create a meter","openapi":"post /v1/meters","href":"/api-reference/endpoint/create-meter"},{"title":"List meters","openapi":"get /v1/meters","href":"/api-reference/endpoint/list-meters"},{"title":"Retrieve a meter","openapi":"get /v1/meters/{id}","href":"/api-reference/endpoint/get-meter"},{"title":"Update a meter","openapi":"patch /v1/meters/{id}","href":"/api-reference/endpoint/update-meter"},{"title":"Preview an unsaved meter definition","openapi":"post /v1/meters/preview","href":"/api-reference/endpoint/preview-meter"},{"title":"Preview an existing meter","openapi":"post /v1/meters/{id}/preview","href":"/api-reference/endpoint/preview-existing-meter"},{"title":"Get consumed units for a customer","openapi":"get /v1/meters/{id}/consumed","href":"/api-reference/endpoint/get-meter-consumed-units"},{"title":"Archive a meter","openapi":"post /v1/meters/{id}/archive","href":"/api-reference/endpoint/archive-meter"},{"title":"Unarchive a meter","openapi":"post /v1/meters/{id}/unarchive","href":"/api-reference/endpoint/unarchive-meter"}]},{"group":"Addons","pages":[{"title":"License Keys","href":"/features/addons/licenses"},{"title":"Private Notes","href":"/features/addons/private-notes"},{"title":"File Downloads","href":"/features/addons/file-downloads"},{"title":"Abandoned Cart Recovery","href":"/features/addons/abandoned-cart-recovery"}]}]},{"group":"Code","icon":"code","pages":[{"group":"SDKs","pages":[{"title":"TypeScript","href":"/code/sdks/typescript"},{"title":"Next.js adapter","href":"/code/sdks/nextjs"},{"group":"Better Auth","pages":[{"title":"Overview","href":"/code/sdks/better-auth"},{"title":"Quickstart","href":"/code/sdks/better-auth/quickstart"},{"title":"Configuration","href":"/code/sdks/better-auth/configuration"},{"title":"Client API","href":"/code/sdks/better-auth/client"},{"title":"Webhooks and access","href":"/code/sdks/better-auth/webhooks"},{"title":"Server API","href":"/code/sdks/better-auth/server"},{"title":"Migration","href":"/code/sdks/better-auth/migration"}]},{"group":"Convex","pages":[{"title":"Quickstart","href":"/code/sdks/convex/quickstart"},{"title":"Concepts","href":"/code/sdks/convex/concepts"},{"title":"Subscriptions","href":"/code/sdks/convex/subscriptions"},{"title":"One-Time Products \u0026 Credits","href":"/code/sdks/convex/one-time-and-credits"},{"title":"Entitlements \u0026 Account UI","href":"/code/sdks/convex/entitlements"},{"title":"Advanced","href":"/code/sdks/convex/advanced"},{"title":"Migration","href":"/code/sdks/convex/migration"},{"title":"Component Reference","href":"/code/sdks/convex/reference"}]},{"title":"Creem Next.js Template","href":"/code/sdks/templates"},{"title":"AI Agents","href":"/code/sdks/ai-agents"}]},{"title":"Creem CLI","href":"/code/cli"},{"title":"Webhooks","href":"/code/webhooks"}]},{"group":"Integrations","icon":"link","pages":[{"group":"Affiliate programs","pages":[{"title":"Affonso Affiliate Program Integration","sidebarTitle":"Affonso","href":"/integrations/affonso"},{"title":"LinkJolt Affiliate Program Integration","sidebarTitle":"LinkJolt","href":"/integrations/linkjolt"},{"title":"Referly Affiliate Program Integration","sidebarTitle":"Referly","href":"/integrations/referly"},{"title":"Refgrow Affiliate Program Integration","sidebarTitle":"Refgrow","href":"/integrations/refgrow"},{"title":"Rekomi Affiliate Program Integration","sidebarTitle":"Rekomi","href":"/integrations/rekomi"}]},{"title":"Evendeals","href":"/integrations/evendeals"},{"title":"Framer","href":"/integrations/framer"},{"title":"Strapi","href":"/integrations/strapi"}]},{"group":"Community","icon":"users","pages":[{"title":"Community Resources","href":"/code/community/community-resources"}]},{"group":"Merchant of Record","icon":"building","pages":[{"title":"What is a Merchant of Record","href":"/merchant-of-record/what-is"},{"title":"Supported Countries","href":"/merchant-of-record/supported-countries"},{"group":"Finance","pages":[{"title":"Payouts","href":"/merchant-of-record/finance/payouts"},{"title":"Payout Accounts","href":"/merchant-of-record/finance/payout-accounts"},{"title":"Refunds and Chargebacks","href":"/merchant-of-record/finance/refunds-and-chargebacks"},{"title":"Payments","href":"/merchant-of-record/finance/payment-methods"}]}]},{"group":"For Customers","icon":"circle-question","pages":[{"title":"Why did Creem charge me","href":"/for-customers/why-creem-charged-me"},{"title":"How to cancel a subscription","href":"/for-customers/how-to-cancel-subscription"},{"title":"How to update payment method","href":"/for-customers/how-to-update-payment-method"},{"title":"I can't log in to the Customer Portal","href":"/for-customers/cant-log-in-to-customer-portal"}]}]},{"tab":"Guides","pages":[{"title":"Guides","href":"/guides/introduction"}]},{"tab":"API Reference","pages":[{"title":"API Reference","href":"/api-reference/introduction"}]}]},"title":"Ingest usage events - Creem","buildId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","deploymentId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","dynamics":["c2aa737661","2b790eda92","b0f84f53ff","a9bb46feb3"],"chunks":["OpenApiReferenceProvider-26-lDe7V.js","utils-CWQmgYtH.js","cloneDeep-izs3Hzxb.js","_Uint8Array-BVIqJ90l.js","isObjectLike-C8vmzPEN.js","isArray-Dcu1dQ5t.js","utils-4-Aw3rSD.js","toString-BX-iyh8G.js","isSymbol-CSH9U2Gm.js","addUniqueKeysToSchema-C2zjXYA5.js","snippetPresets-DFmpFA7C.js","style-to-object-BY3JEAdk.js","v4-DIgz8zy6.js","ApiExamples-FJpQavWq.js","isEqual-BfaiBbcl.js","usePlaygroundInputsStore-D_GvPF7k.js","shallow-Bs2ZB0NO.js","getApiPlaygroundDisplay-DCcqJJk-.js","CodeSnippet-DzDxawVe.js","CodeSnippets-aUr7fU-7.js","Page-CT9uE8Ip.js","getPastedJson-Y1fKsj_I.js","icon-DI4zTsDr.js","MethodPill-C0nDlmSQ.js","DuplicateIcon-NUXbx2nQ.js","createIcon-BdTHrPAi.js","DynamicMarkdown-CtpgKJwj.js","SectionHeading-B3x35tOt.js","axios-vUo_bRtp.js","Markdown-3OmurJMP.js","clsx-DMKzROTp.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","html-void-elements-mCSPHezU.js","dist-1ms6tsT-.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js","AssistantPageLink-Ke9xIMFx.js"]}