Skip to content

Offer content for review

POST
/content/management/v1/proposals
curl --request POST \
--url https://api.ebitex.io/content/management/v1/proposals \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "contract": "example", "folder": "example", "source": { "kind": "example", "ref": "example", "label": "example" }, "values": {} }'

Offers content for a person to review. A proposal is not content: it is stored on its own, spends no component allowance, and becomes a component only when somebody approves it.

Send contract (a contract’s external id), an optional folder (a slash-separated folder path, matched without regard to capitalisation), a source, and values.

values is a flat object of whatever names your own system uses. You do not need to know the contract’s field names: each key is matched to a field by name, ignoring case and punctuation, so hipScore finds hip-score. A field that holds a value typed in place can be filled from keys named after the field and the part, so author-name finds the name inside an author field. Keys that match nothing are kept and shown to the reviewer rather than dropped, so nothing a sender wrote is lost. Because that matching happens here, this endpoint needs no permission to read your content model.

source is { kind, ref, label } and is stored exactly as sent. It is never parsed, resolved, or called back to — it exists so a reviewer can see where something came from. ref is your own identifier for the submission, and (kind, ref) is unique: offering the same pair again returns the existing proposal rather than filing a second one, so retrying after a lost response is safe.

The destination folder must have a proposal workflow assigned to it, or to one of its ancestors. That is deliberate: without one there would be nobody to review what you send, and content offered from outside the organization must never reach the site without a person.

Key: content.management.proposals

Media typeapplication/json
object
contract
required
string
folder
required
null | string
source
required
object
kind
required
string
ref
required
string
label
required
null | string
values
required
object
Examplegenerated
{
"contract": "example",
"folder": "example",
"source": {
"kind": "example",
"ref": "example",
"label": "example"
},
"values": {}
}

{ id, status, alreadyExisted, match }. status is pending until somebody decides. alreadyExisted is true when this repeated an earlier offer. match is { matched, total, unmatchedKeys } — how many of your keys found a field, how many fields the contract has, and which keys matched nothing. Use it to check a new integration before real submissions start arriving.

missing_api_key or invalid_api_key: no key was sent, or it is unknown, revoked or expired.

payment_required: the organization’s subscription needs attention.

scope_denied: the key lacks this route’s scope. tier_required: the Content Management API needs the Pro plan or above. ip_denied: the caller’s address is outside the key’s allowed ranges. app_not_available: Content is not enabled for the organization.

unknown_contract: no contract with that external id exists in this environment. abstract_contract: that contract is abstract and cannot be instantiated. unknown_folder: no folder at that path exists in this environment. proposal_workflow_not_assigned: no proposal workflow governs that folder, so there would be nobody to review this.

Rate limit or quota exceeded; Retry-After says how long to wait. too_many_failed_authentications: too many bad keys from this address.